Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Ridiculously long functions are a maintainability problem but so is a ton of really small functions that do not provide a logical separation of concerns.

OO code can provide modularity which can greatly improve the ability to make changes without breaking other code. On the other hand, when applied poorly it can have they opposite effect.

It's not the concepts, it's how they are applied.



A swarm of small functions is a worse maintainability problem --- it's not obvious how they interact to solve a particular problem, and the amount of plumbing you need to ship state between these different functions is frequently brutal. Sometimes it's easier to stick things in local variables and just have a long function.

Languages that make it easy to define "local" functions that operate on implicitly captured state can help --- e.g., C++, Lisp, sometimes Java --- but only where it makes sense. I don't believe in splitting functions solely because they're too long.


> Languages that make it easy to define "local" functions that operate on implicitly captured state can help

You don't even need that. In many languages, you can have {}-delimited blocks which cause variables inside of them to go out of scope when control flow exits them. I've used that to great effect in Perl to keep intrinsically large functions maintainable.


>- it's not obvious how they interact to solve a particular problem, and the amount of plumbing you need to ship state between these different functions is frequently brutal

this is a problem with the architecture, not a problem with small functions.


Not to mention that the more small functions you have, the worse your locality of reference (in terms of programmer cache, not CPU cache). In absurdum, software composed of 1-2 line functions which are then composed into higher and higher level 1-2 line functions is no better than software composed of one giant function with internal gotos for flow control.

Agreed that you should never split functions due purely to length, but a super long function smells bad because it suggests poor separation of concerns (if a function's super long then it's probably doing a lot more than one thing). Sometimes this is a problem, sometimes (like the case of Carmack's big main loop function) there's just a lot of small things to do sequentially and one big function is as good a way to represent that as any other.


Small functions (less dependencies) are easier to test


Here's another bit of heresy: testing isn't everything. Very fine-grained test suites frequently break when the structure of code under test changes even when the new code still does its job. In the limit, it's the equivalent of just breaking if SHA256(old_code) != SHA256(new_code).

Very short functions, I've found, encourage this kind of over-testing and just add friction to code changes without actually improving system reliability.

You're better off testing at major functional boundaries, and if you do that, the length of functions matters less than the interface major modules provide to each other.


> You're better off testing at major functional boundaries, and if you do that, the length of functions matters less than the interface major modules provide to each other.

Otherwise known as integration testing, which is far more useful than unit testing because bugs, especially regression bugs, more often occur in the system, than in specific functions.

You can have as many unit tests as you want, but until you have integration test, you have zero coverage for the really complex part of your code.

Unit tests are great for algorithms though.


Small functions are generally uninteresting to test. As smallness approaches "one liner", you're just verifying the compiler. Yes, 1+1 == 2.


It depends on how small, and how swarm-y. If the function is hilariously simple, and improves readability (like a premade getter for an alist or cons based structure in lisp), just do it. If it's pretty big, and you'll never reuse it... No. If you must, keep it local.


Even if you don't reuse a function it still encapsulates certain things. By looking at the name you know what it does (self-documenting-code). The interface tells you what variables it depends on. And finally it logically segregates your code.

I think the real reason people don't like small function is simply code navigations - which honestly is a poor excuse. That's an editor/IDE problem


Yes, but encapsulation isn't always a good thing: it lessens your awareness of what's happening within the encapsulated environment. This can lead to bugs.


Agreed, but really long functions are an indication that the code could benefit from some judicious refactoring.


And yet invariably it's refactoring into a ton of small functions. Round and round we go...


Why? I've been recommending the middle ground all along.


The 90's and 00's fetishization of OO as magic incantation that makes your code better did generate a lot of anti-patterns, some built into the languages. Multiple/deeply-multilevel inheritance, singletons, constructors with side effects, IO objects, and all the more trivial code-bloating boilerplate like "put everything in a class" and getters and setters.


In regards to the length of functions, to me it comes down to whether it is preferable to have the entire content of the function visible to the programmer at the time any changes need to be made, or if there are multiple things going on that can be assessed independently of each other. The idea of setting a hard rule that a function can only be as long as your screen height ignores the context is what is being done within the function, and encourages the programmer to make breaks in places where it may not make sense to do so.


Was it Atwood who said, modifying someone else's quote: "The two hardest problems in programming are cache invalidation, naming things, and off-by-one errors?"

Aside from separation of concerns when you make many, many functions, you have to come up with So Many Names.


A lot of those names will end up being the equivalent of nasty old assembly comments.

  add 10   ; add 10 to accumulator.
becomes

  int AddTen(int x) { return(x+10);}




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: