I usually notice functions get hard to read when they hit 80 lines or so long (generalization, of course), and try to keep mine under say 50 unless it's one of a few core functions in an application.
Interested in hearing others' experiences.
I usually notice functions get hard to read when they hit 80 lines or so long (generalization, of course), and try to keep mine under say 50 unless it's one of a few core functions in an application.
Interested in hearing others' experiences.
I have seen code where a function was shorter than 10 lines. ;-) In that same code base, I have seen a function that was ~750 lines. And for that particular function, it actually made sense. It involved a lot of local state, and breaking it up would have involved passing lots of parameters to the new, shorter functions. (The average function in that code base was around 50-60 lines, I guess.)
Using a fixed number, like "10 lines" seems arbitrary to me. Sometimes it makes sense, sometimes it does not. It also ignores other factors, such as comments. Or readability. Sometimes, I 100+-line function can be marvelously clear, other times, a 5-liner can be so dense it makes my eyes bleed.
I have grown wary of "Always do this"/"Never do that"-advice.
"Try to keep your functions short. If they grow long, look if it makes sense to break them up into multiple shorter functions" is sound advice. But to do so without questioning or thinking is problematic.
If I was refactoring it, and 750 lines is a massive red flag that it is in need of refactoring; then the local state indicates that it becomes not a function or a bunch of functions, but a class with state and private methods. Is that hard to spot?
So while that function was very, very long, it was relatively simple to understand and did not contain a lot of logic. Also, the fact that it was a huge switch would have made breaking it up slightly non-trivial.
Though I would still try things like :
- making each case in the switch as small as possible, preferably a 1-liner.
- making a separate object that does the "parameter mangling".
- grouping all the bits of state into an object to hold the "message loop state".
- Or most radically but best: these "map", "lookup", "strategy" style patterns that reduce it to a table of (message id, handler fn) pairs http://stackoverflow.com/a/126453
When I was hired there, that application had been in development for ~15 years, and a lot of what I did was maintenance. So making coming in as the new guy, then starting to change things, I had to tread rather carefully.
FWIW, I got each of the three developers to individually admit to me that if we had the time and the money, we would basically throw the old code out and rewrite it from scratch, using the lessons they had learnt since. But time and money being what they were, that was not likely to happen. (I was really proud I got them to admit that, though - one of the other developers was very defensive of his work, and getting him to admit that was quite an achievement; I actually gained some valuable people skills in doing so.)
I was glad, though, that the code was relatively readable and organized in a sane manner. There was some cruft that had accumulated over the years, but overall it was surprisingly sane. That was one of the first times I had to read somebody else's code, and it was good code for that purpose - sparsely commented, yet very clear.
Also, keep in mind that this was C. Different languages offer different ways of decomposing / structuring code.
But I prefer Linux take on function size:
> The maximum length of a function is inversely proportional to the complexity and indentation level of that function. So, if you have a conceptually simple function that is just one long (but simple) case-statement, where you have to do lots of small things for a lot of different cases, it's OK to have a longer function.
Also, it strongly depends on the code you are writing. Things like reading/writing data often end up as pretty long functions, while glue code between model and view is usually just a couple of lines.
Following this line of thought I've become a lot more precious about vertical whitespace & I've adopted BSD style curly braces.
But, for instance, the Python PEP8 spec saying 80 characters max, I think is kind of ridiculous these days. Who is programming on their phone, tablet, or 90s computer that can't handle 120 characters these days?
Edit: I just mention 120 as PyCharm's PEP8 linter 'erroneously' checks that max rather than 80
When trying to understand code I haven't written, I have had the experience that trying to trace logic and debug through a bunch of little functions is more cognitive overhead than just seeing everything that happens within one larger function.
I get the idea that small functions are supposed to be wonderful, and clear, if everything is named really well and everything works as the name implies.
But for me the downside is it ends up feeling like detective work. I have to jump around and remind myself exactly how each little function works just to figure out what exactly is happening.
My dream IDE would let me see "expand" every function call ... like with little dropdown arrows .. so I could see it as if it were inline, and could trace execution linearly in one solid block.
It depends on the language. In Node.js code, it's not uncommon to not only see production code that follows this rule, but also puts no more than 1 or 2 functions in each file. Once you get used to it, and as long as you're quick with your editor/IDE's open shortcuts, I've found it a pretty nice way to organize code.
I personally find C code hard to follow sometimes, not because I don't understand C (I have more experience with C than I do JavaScript, if you count years), but because C tends to feature multiple functions per file, each with hundreds of lines of code per function. At least for me, that's hard to follow, and I hate having to move hundreds of lines up and down a file, even using Vim shortcuts.
Edit: Huh? Why a downvote?
For a rule made with the goal of improving understanding, it's great that the rule is not actively detrimental to understanding.
But we still need to know how often it actually succeeds vs. having marginal effect.
We also need to know what it costs in terms of labor.
Without those two bits of info we have absolutely no idea if it was a good rule.
Extremely low benefit -- I wouldn't even notice, and most actually confusing things are bigger scale, like the confusing DAL "abstraction" that I was dragooned into copying from the wider C# community.
I've seen code where many functions were like this, and very few were over 30 lines. It was easy to read. And it was tested and generally working well. Naming becomes important, but that's part of the point of extracting functions for readability.
blabla match {
case 1 => func1
case 2 => func2
}
instead of blabla match {
case 1 =>
println("1")
1 + 2
case 2 => biggerFunc
}