13 ms·
I have always liked much longer functions then the average programmer. The main reason is that we read sequentially. It much easier to follow something sequenti
by quelsolaar 6y ago
I have always liked much longer functions then the average programmer. The main reason is that we read sequentially. It much easier to follow something sequential, then something jumping around. A lot of bugs happen when you see a function call that you think you know what it does but doesn't.
There is a reason regular front-to-back books are more popular then choose-your-own-adventure books. You know where you are, and you dont spend all day flipping back and forth.
I also find that when you start splitting up your functions down to 5 lines, it becomes almost impossible to differentiate what the different functions do clearly, and that makes good naming almost impossible.
EDIT: Also, I dont understand why people complain about functions being longer then 5 lines making code hard to navigate, and then turn around and write header only files with 10000 lines of code....
- hvidgaard 6y ago> There is a reason regular front-to-back books are more popular then choose-your-own-adventure books. You know where you are, and you dont spend all day flipping back and forth. I don't think that has anything to do with it. It's a roleplaying adventure where you need to keep track of things on a piece of paper and use dies for encounters. People that want to read don't want to participate, just consume. Active vs passive. Regardsless, splitting 4-5 lines of code out into a function and giving that a proper name can enhance readability significantly. And modern IDE tools make it simple to read that without disrupting the flow. Visual Studio and IDEA for instance has a peak definition that makes it trivial. If that function does not do what it say, that is a different problem.
- srtjstjsj 6y agoInstead of cramming the description into a NonPunctuatedFunctionName, just lay down some scoping brackets and write a comment. IDEs are food at folding code. If the function doesn't need to be called many times, it doesn't need to exist.
- hvidgaard 6y agoCode folding is not good for readability, and creates a significant overhead when reading the code. Even to the point where some static analysis tools default to highlight the use of "regions" as problematic.
- will4274 6y agoJumping up and down in the file while hitting f12 is not good for readability and creates a significant overhead when reading the code. C# finally introduced local functions in a recent version, which will hopefully remind other enterprise-y languages and IDEs that humans are programmed to read lines on a page in order.
- hvidgaard 6y agoIn VS you can press Alt+F12 to peak the function, no need to jump back and forth. Regardless, programming in that way requires that the functions adhere to SRP and a clear name can be written.
- quelsolaar 6y agoI think it can be good to have functions that are only called in one place if they do really well defined things. load_settings, or similar is probably one called on one place, but if it makes sense to have it as a separate function. I prefer clear naming over comments, because comments doesn't "travel" everywhere the named function/type/variable does.
- canttestthis 6y agoLong functions are fine when you read sequentially, but when enclosed in a loop you have to scroll back up to recover context about the loop and this is disruptive. Carmack says it in the essay too > Enclosing pages of code inside a conditional or loop statement does have readability and awareness drawbacks
- srtjstjsj 6y agoCode folding solves this problem.
- roel_v 6y agoI've seldom found use for editor based code folding, and I've been trying to use it long enough to remember being excited when vim first got it (20+ years ago?). I mean I'm sure there are people who find it useful, but to me it's one of those 'nice on paper' things. I think it's mostly that you don't know how much is hidden away, and you don't get a feel for the balance of say the if then and the else part of a decision. I've been working on refactoring a 3000 line single function Fortran 95 program lately, with 6, 7, 8 levels deep loops and if/then etc; you'd think this sort of work would be the prime candidate for folding. In my experience it hasn't helped my understanding at all though. 'jump to matching brace' and 'search under cursor' along with keeping an eye on the scroll bar work much better for me.
- juliendorra 6y agoAnecdotally it (psychologically) helps some of my beginners students in Processing (young designers), because they feel less overwhelmed by their code when it has started to grow over their comfort threshold. As beginners their brain isn’t used to selectively focus and block out the rest, so folding functions helps them focus. It’s also a good way for me to end the moment when I show them how to extract some code out of the Processing main loop function into a separate function, “now that we invented our own command we don’t need to look at it all the time … and we could also put it in a library…”.
- danidiaz 6y agoOn the opposite end, reading long functions can be hard when they mix levels of abstraction, or their lack of abstraction buries the overall logic with details. If I'm reading a novel, it would be jarring to read "and then he took 45 steps of average length to reach the kitchen" instead of "he went to the kitchen".
- fxtentacle 6y agoThat's where editor folding comes in.
- slx26 6y agoI guess the real problem is that writing good short functions is hard, so some people might find it better to just write long functions. By good short functions I mean functions that, as you say, don't mix levels of abstraction, use proper names, and follow a consistent pattern so their behavior can be safely assumed most of the time. And sometimes you can't have such a clear view of the problem because it already got too messy at another stage of the process, and you want to murder the system designer plus maybe some of your coworkers too. Of course, for certain tasks long functions are still reasonable, but they tend to be the exception rather than the rule, and it takes quite some practice to realize. Best practical advice: do more design and planning on paper.
- crpatino 6y agoA program composed of one thousand functions with average length of 1K LOC is jarring and hard to understand. The same program refactored into a quarter of a million functions of 5-8 LOC each is jarring in a very different way. At the end of day we are talking of a million LOC, and no level of granularity is going to protect your brain against that amount of information. What you need at those levels is modules. Dozens of short functions (though 5 LOC is overkill IMHO) that work together to solve a single, well constrained problem. Whatever its other shortcomings, Object Oriented Programming gives you that out of the box, though you have to design your classes instead of just banging out code as you go. And then you can create packages, - modules that use number of tightly coupled classes to solve a single, well constrained problem.
- rectang 6y agoI don't like long functions because they're harder to write unit tests for. I make too many mistakes, and I need the unit tests to verify the code and bring down my error rate.
- arcturus17 6y agoI agree with this... If a part of a function is a hotspot of unavoidable complexity then it's good to split it out and test it. But then some people will say that you're testing the internals of the caller and that you shouldn't do that. If we follow that logic to the absurd, there should only be one test for the entire application, one for main(), or whatever your entry point is. I've never seen an answer to this problem coded as an intelligible, easy-to-follow guideline. Empirically and intuitively I think my "hotspot" approach kinda works in maximizing reliability.
- mhh__ 6y agoIf your function has to be read by jumping around, your names are bad or you should be using a different abstraction. Don't break stuff up for the sake of it, but DRY.
- arcturus17 6y agoI sometimes find myself refactoring helpers by folding them back into the caller when I'm pretty sure that the helper won't be reused and that the caller won't end up being too long. But I don't think I'd like writing long functions as a system or philosophy; I think there is a threshold where a function becomes fat enough that it hurts readability. Code is an art form.
- slx26 6y agoAll good art is self-punishment.
- imtringued 6y agoFunction length shouldn't be based on philosophy. Decide on a case by case basis. Start small and keep adding functionality until you find a way to divide the function along some sensible boundary. Generally you can say that long functions contain more functionality and the more functionality you have the higher the probability that functionality can be extracted into it's own function. As you said, organizing code is an art form. There are no hard and fast rules, only your own judgement and experience.
- jtolmar 6y agoI feel similarly, and usually only separate out functions when there's opportunity for code reuse, which usually ends up with one or two very large functions per module. That's been fine in most of my career (fancy algorithms) or hobbies (games), but lately I've been writing a web backend, in which this approach can get very untenable very fast. Some of those stylistic differences are probably well-suited to what other people are working on.
- ascar 6y agoI hope you don't take this as too pedantic as we all make spelling errors, but you consistently wrote "then" instead of "than".
- rimliu 6y agoLong functions usually do several different things. If you split them into functions by the function (ha), and name those functions accordingly, you will have short and readable functions.
- ratww 6y agoThis is advocated in some popular books, but IMO it's the worst reason to break up a large function. You end up with the same amount of code, but now you have to jump around. In most cases it also forces you to turn local variables into instance variables, globals, or pass state in parameters in order to carry state between the new functions, which further decreases readability. You might as well add an "index" at the top of the large function and get the same readability benefits from doing it. What should be done instead is trying to find code that can be abstracted without hurting readability. Most of the time in large functions there are mixed abstraction levels, such as business logic mixed with I/O, generic error handling that could be somewhere else, or object transformation that exists because of different libraries working differently. When you automatically refactor into smaller functions you carry those problems into the small functions and you get code that is probably harder to follow, generates worse stack traces and only looks good in a shallow inspection.
- UK-Al05 6y agoYou shouldn't have to jump around, because the function names tells you exactly what it does according to reasonable expectations. You can just skip over it, if your not interested in its work. If your function does an unexpected side effect, thats a problem with the code. Not with the idea of writing functions that do a specific thing. I can't help but think people who complain about this, are just bad at creating functions at the correct level of abstraction. It's also why i'm a functional fan, because it makes it more difficult for functions to do unexpected things.
- ubercow13 6y ago>I can't help but think people who complain about this, are just bad at creating functions at the correct level of abstraction. They are complaining due to difficulty of reading other peoples' code, not their own.
- temac 6y agoFunctions are about code reuse, abstracting variables and easy handling of their lifetime, and most importantly invariants and specifying reasonably sized pieces of code (even if you "specify" only in your own head). And then maybe testing because once you actually have a spec, why not test it. If you need none of that, you don't really need to split. As a bonus small functions tend to be more naturally and easily programmed with less mutable variables, which is then easier to read and maintain. Especially if you don't need reuse (or if you only need it in a very limited fashion) nor unitary testing, you can handle things using different techniques than splitting into functions. Like simply having a succession of blocks in a bigger function, with inline notes about invariants between each blocks, etc. If done in a disciplined way I don't see much avantage about that approach than about just using functions anyway, esp with a good IDE or code editor. Of course nothing is absolute but if it does not fit on a screen maybe at least consider splitting it (and improving the doc, etc.; or at least just improve the doc). Blindly requiring e.g. 5 lines max strictly everywhere is insane though, never do that. There can be excesses on both sides. As a more personal rule of thumb, I avoid splitting things too much esp. if the result takes more lines than inline versions, except if not splitting results in a high number of duplications (small in that case, but still duplications). A moderate increase of LOC when splitting is OK but when trying to split like crazy you can fall to N fold increases and at some point this is sometimes counter productive. On the other hand some very moderate amount of code duplication is ok, at least if the duplicated parts are not too far away and can be found easily, and not in an unreasonable quantity, modulated by the size of duplications. For extremely tiny duplications even in huge quantity, replacing by a function call does not necessarily makes sens, if e.g. that's (i + base) % mod repeated 30 times in the same file maybe it would give no advantage to replace that with rotbufindex(i, base, mod), the call is not more easy to reuse than the inlined formula.
- CuriouslyC 6y agoI have a hard time imagining a large monolithic function where I wouldn't want to test any parts, ever. Also, I find that most people can only keep a certain amount of code in their head at the same time, so even if you have a monolithic function, I think you'd still want nested helper functions so that the more complicated portions of the code that are hard to read and understand stay manageable.
- SloopJon 6y agoIt's hard to discuss in the abstract, especially with bad names like MinorFunction1() and MinorFunction2(). In style C, you may have to scan over lower levels of abstraction than you care about: MajorFunction() { // fast inverse square root float x2 = number * 0.5F; float y = number; // several more lines of code // find decimal point char *c = input; while (*c && *c != '.') ++c; } A goal of style A or B is for the names of the minor functions to make the shape more obvious, ideally self documenting: MajorFunction() { y = Q_rsqrt(number); char *c = strchr(input, '.'); } This is the kind of advice you'll find in, say, Code Complete. Although I generally accept the premise, there is a readability cost to indirection. The bigger and more complex MinorFunction() is, the more likely I'm going to have to jump into it and remind myself what it does. There are two concepts that underlie DRY: coupling and cohesion. There are good expositions on this in old writings on structured programming and design (e.g., Yourdon and Constantine). If MinorFunction() is cohesive, and MajorFunction() is appropriately coupled to MinorFunction(), then style A/B is likely to be superior to style C. One of Carmack's points is that "very little of our real code" ends up that way.
- dustingetz 6y agoalgebra does not have this problem. a key idea of functional programming is that it is essentially raw math. it is the for loops and array mutations that have caused the code to not be math and therefore needing 100s of lines to express simple dataflows.
- dkarl 6y agoI think you've put your finger on the vital issue: catering to readability. A function call is great if you can give it a name that allows readers to read past it without navigating to the implementation. If most readers are going to be interrupted by needing to navigate to the function implementation to understand it, it might be better to inline it.
- megameter 6y agoI see it as a tradeoff of where you want your dependencies. In analysis using the structured program theorem, you only have three tools, really: Sequence, selection, iteration. If you copy-paste, you lean on the sequence more; if you add a loop or a cursor structure, you're iterating; and if you add more names for things, you're selecting on the name. All three create dependency risks, but not in equal measure. What a really long function mostly indicates is that the inherent dependencies are mostly sequential. This maps with the domains where they appear most naturally: game loops are notoriously single-threaded, embedded code often needs to operate to hard real time constraints. I think the appeal in splitting out more names has something to do with linguistic comfort zones: Rather than examine long sequences, assume the functions used by the one you are looking at are trustworthy. Then you are "improving" the code each time you factor it out because you can read more names, and because each function is small there is little concern about sequencing errors. It's intuitive, but shows its flaws as soon as you use another lens like the structured program analysis; adding the new function makes it harder to examine the sequence across the function boundaries, causing the "flea-jump" code you describe. What I've found works is to let the large functions accumulate and mature, then derive a new dependency - a function, class, or other abstraction - that will simplify maintenance. Not every problem is solved with a new function, sometimes it really takes a compiler to get the desired improvement.
- natoally 6y agoI never understood the dogmatic need to wrap lines at 80 or 120. Are people still printing out and stapling their code?