Remix.run Logo
keyle 3 hours ago

I was enjoying what I was reading until the "Limit function length" part which made me jolt out of my chair.

This is a common misconception.

   Limit function length: Keep functions concise, ideally under 70 lines. Shorter functions are easier to understand, test, and debug. They promote single responsibility, where each function does one thing well, leading to a more modular and maintainable codebase.
Say, you have a process that is single threaded and does a lot of stuff that has to happen step by step.

New dev comes in; and starts splitting everything it does in 12 functions, because, _a function, should do one thing!_ Even better, they start putting stuff in various files because the files are getting too long.

Now you have 12 functions, scattered over multiple packages, and the order of things is all confused, you have to debug through to see where it goes. They're used exactly once, and they're only used as part of a long process. You've just increased the cognitive load of dealing with your product by a factor of 12. It's downright malignant.

Code should be split so that state is isolated, and business processes (intellectual property) is also self contained and testable. But don't buy into this "70 lines" rule. It makes no sense. 70 lines of python isn't the same as 70 lines of C, for starters. If code is sequential, and always running in that order and it reads like a long script; that's because it is!

Focus on separating pure code from stateful code, that's the key to large maintainable software! And choose composability over inheritance. These things weren't clear to me the first 10 years, but after 30 years, I've made those conclusions. I hope other old-timers can chime in on this.

The length of functions in terms of line count has absolutely nothing to do with "a more modular and maintainable codebase", as explained in the manifesto.

Just like "I committed 3,000 lines of code yesterday" has nothing to do with productivity. And a red car doesn't go faster.

igogq425 21 minutes ago | parent | next [-]

This is a balancing act between conflicting requirements. It is understandable that you don't want to jump back and forth between countless small subfunctions in order to meticulously trace a computation. But conceptually, the overall process still breaks down into subprocesses. Wouldn't it make sense to move these sub-processes into separate functions and name them accordingly? I have a colleague who has produced code blocks that are 6000 lines long. It is then almost impossible to get a quick overview of what the code actually does. So why not increase high-level readability by making the conceptual structure visible in this way?

A ReverseList function, for example, is useful not only because it can be used in many different places, but also because the same code would be more disruptive than helpful for understanding the overall process if it were inline. Of course, I understand that code does not always break down into such neat semantic building blocks.

> Focus on separating pure code from stateful code, that's the key to large maintainable software! And choose composability over inheritance.

100%!

stouset 3 hours ago | parent | prev | next [-]

“Ideally under 70 lines” is not “always under 70 lines under pain of death”.

It’s a guideline. There are exceptions. Most randomly-selected 100-line functions in the wild would probably benefit from being four 25-line functions. But many wouldn’t. Maturity is knowing when the guideline doesn’t apply. But if you find yourself constantly writing a lot of long functions, it’s a good signal something is off.

Sure, language matters. Domain matters too. Pick a number other than 70 if you’re using a verbose language like golang. Pick a number less if you’re using something more concise.

People need to stop freaking out over reasonable, well-intentioned guidelines as if they’re inviolable rules. 150 is way too many for almost all functions in mainstream languages. 20 would need to be violated way too often to be a useful rule of thumb.

Kwpolska 2 hours ago | parent | next [-]

As with all guidelines, some people will turn it into a hard rule, and build a linter to enforce it. Then they will split long functions into shorter ones, but with a lot of arguments. And then their other linter that limits argument count will kick in.

VBprogrammer 41 minutes ago | parent | next [-]

And someone else will use the idea that this is a misconception to justify putting hundreds of lines in one function.

Some junior dev will then come along with his specific user story and, not wanting to tear the whole thing up, will insert his couple of lines. Repeat over a 10 year lifecycle and it becomes completely unmanageable.

I remember trying to tease apart hundreds of lines of functions in a language which didn't have the IDE support we have these days. It was always painful.

Even the most script like functions I've ever worked with benefit from comments like:

  # Validate that the payment was successfully sent.
These subheadings can just as easily become functions. If you limit the context in each function it becomes a genuine improvement.
n4r9 an hour ago | parent | prev [-]

Sounds like an opportunity to guide that person.

wouldbecouldbe an hour ago | parent [-]

Good luck Arguing with stubborn devs who know it all

eviks 33 minutes ago | parent | prev [-]

> It’s a guideline ... Pick a number

It's using a parameter of secondary importance as a primary, so it's wrong with any number. The comment even has a helpful analogy to LOCs. People need to stop freaking out over reasonable, well-intentioned criticism of guidelines as if they were arguing something else like misundertstanding of that strictness of rules.

zem 2 hours ago | parent | prev | next [-]

> Say, you have a process that is single threaded and does a lot of stuff that has to happen step by step. > New dev comes in; and starts splitting everything it does in 12 functions, because, _a function, should do one thing

I would almost certainly split it up, not because "a function should only do one thing" but because invariably you get a run of several steps that can be chunked into one logical operation, and replacing those steps with the descriptive name reduces the cognitive load of reading and maintaining the original function.

rhubarbtree 3 hours ago | parent | prev | next [-]

Just chiming in here to say, absolutely you should keep functions small and doing one thing. Any junior reading this should go and read the pragmatic programmer.

Of course a function can be refactored in a wrongheaded way as you’ve suggested, but that’s true of any coding - there is taste.

The ideal of refactoring such a function you describe would be to make it more readable, not less. The whole point of modules is so you don’t have to hold in your head the detail they contain.

Long functions are in general a very bad idea. They don’t fit on a single screen, so to understand them you end up scrolling up and down. It’s hard to follow the state, because more things happen and there is more state as the function needs more parameters and intermediate variables. They’re far more likely to lead to complecting (see Rich Hickey) and intertwining different processes. Most importantly, for an inexperienced dev it increases the chance of a big ball of mud, eg a huge switch statement with inline code rather than a series of higher level abstractions that can be considered in isolation.

I don’t think years worked is an indicator of anything, but I’ve been coding for nearly 40 years FWIW.

xorcist 2 hours ago | parent [-]

> They don’t fit on a single screen, so to understand them you end up scrolling up and down.

Split them up into multiple functions and there is more scrolling, and now also jumping around because the functions could be anywhere.

> It’s hard to follow the state, because more things happen

It's easier to follow state, because state is encapsulated in one function, not constantly passed around to multiple.

> a huge switch statement with inline code

Huge switch statements are a common reason for large functions. Python has this architecture and largely won because the interpreter is surprisingly easy to understand and extend.

CraigJPerry 2 hours ago | parent | prev | next [-]

What would be a good example of the kinds of things a 100 line function would be doing?

I don't see that in my world so i'm naively trying to inline functions in codebases i'm familiar with and not really valuing the result i can dream up.

For one, my tests would be quite annoying, large and with too much setup for my taste. But i don't think i'd like to have to scroll a function, especially if i had to make changes to the start and end of the function in one commit.

I'm curious of the kinds of "long script" flavoured procedures, what are they doing typically?

I ask because some of the other stuff you mentioned i really strongly agree with like "Focus on separating pure code from stateful code" - this is such an under valued concept, and it's an absolute game changer for building robust software. Can i extract a pure function for this and separately have function to coordinate side effects - but that's incompatible with too long functions, those side effectfull functions would be so hard to test.

brodo an hour ago | parent [-]

Containing the giant switch statement in a byte code interpreter or a tokeniser.

frje1400 2 hours ago | parent | prev | next [-]

I think that you are describing an ideal scenario that does not reflect what I see in reality. In the "enterprise applications" that I work on, long functions evolve poorly. Meaning, even if a long function follows the ideal of "single thread, step by step" when it's first written, when devs add new code, they will typically add their next 5 lintes to the same function because it's already there. Then after 5 years you have a monster.

alfons_foobar 3 hours ago | parent | prev | next [-]

Agree that "splitting for splittings' sake" (only to stay below an arbitrary line count) does indeed not make sense.

On the other hand I often see functions like you describe - something has to be executed step-by-step (and the functionality is only used there) - where I _whish_ it was split up into separate functions, so we could have meaningful tests for each step, not only for the "whole thing".

cjfd an hour ago | parent | prev | next [-]

I have been programming professionally for 17 years and I think this guideline is fine. I have difficulty imagining a function of 70 lines that would not be better off being split into multiple functions. It is true that if a function is just a list of stuff longer functions can be allowed then when it does multiple different things but 70 lines is really pushing that.

vanschelven an hour ago | parent | prev | next [-]

And here's John Carmack on the subject of 1000s of lines of code in a single function: http://number-none.com/blow/blog/programming/2014/09/26/carm...

an hour ago | parent [-]
[deleted]
psychoslave 2 hours ago | parent | prev | next [-]

Funny this is the assertion in the I would most agree to use as general design principle to apply as thoroughly as possible, with tightened variable scope on equal position. Though no general principle should be followed blindly of course.

That's not the the function length per se. A function that is 1000 lines of mere basic assignments or holding a single giant switch can sometime be an apt option with careful consideration of tradeoffs as origin of the design. Number of line doesn't tell much of the function complexity and cognitive load of will imply to grasp what it does, though it can be a first proxy metric.

But most of the time giant functions found in the wild grow up organically with 5 levels of intertwined control control moving down and up, accumulating variables instead of const without consideration to scope span. In that case every time a change is needed, the cognitive load to grasp everything that need to be considered to change this finding is extremely huge. All the more as this giant function most likely won't have an test suit companion, because good engineering practices are more followed at equal level on several points.

teiferer 2 hours ago | parent | prev [-]

> They're used exactly once

To me, that's key here. That things are scattered over multiple files is a minor issue. Any competent IDE can more or less hide that and smoothen the experience. But if you have factored some code into a function, suddenly other places may call it. You have inadvertently created an API and any change you make needs to double check that other callers either don't exist or have their assumptions not suddenly violated. That's no issue if the code is right therr. No other users, and the API is the direct context of the lines of code right around it. (Yes you can limit visibility to other modules etc but that doesn't fully solve the issue of higher cognitive load.)

exceptione an hour ago | parent [-]

That is an important consideration which you should weigh against the clarity gained by breaking up your code in units of logic.

That is precisely the reason why for a function the scope of its container should be constrained. A class with a Singe Responsibility (SRP) would not suffer from several private methods. Breaking up your methods in a God Class brings both a decrease and an increase of mental load.

Also, in functional programming languages one can nest functions, and OOP languages oftentimes can nest classes.

Haskell does function hiding the most elegant imo, by means of `where`:

  foo x = 
    if odd x then
        short x
    else
        reallyLongFunc x
    where 
        short y = y*2
        reallyLongFunc z = z + 2