8 ms·
>> ...a careless approach to a main function can result in needless complexity. Compare two versions of the same main function: before and after. One version is
by payne92 9y ago
>> ...a careless approach to a main function can result in needless complexity. Compare two versions of the same main function: before and after. One version is over 400 lines. The other is about 40 lines. That’s an order of magnitude. One will be easy to change.
A potentially contrarian view: a long-ish straight linear function without duplicated code is often much easier to understand than a smaller function broken into subroutine callouts (because you don't have to chase down the implementation of the subroutines to figure out what's going on).
- acangiano 9y agoGood naming helps. If you strive to make it obvious what each subroutine does, you can treat them as small black boxes (until you need to look into a specific function). Obviously, there needs to be some balance and not go crazy in either direction.
- jeremiep 9y agoHaving a lot of single-use functions, even if properly named, still splits the context in that many pieces. For some cases that results in more confusing code.
- krylon 9y agoI agree with you. Sometimes a single long-ish function is preferrable to a flock of shorter ones. Still, I think the GP has a point - clear naming is very important, regardless of one's attitude towards function size. ;-)
- vram22 9y agoI partly agree with acangiano and partly with you. On the whole, I tend to agree more with him, with this proviso: don't just name the functions well, name their parameters well too. That can make the overall program easier to understand, and we can still get the benefit of modularity and black boxes, while not creating more confusion. YMMV, of course.
- jeremiep 9y agoI agree about modularity and encapsulation being important. Usually I try to weight the overhead of having more function prototypes to track, indirections in the main function and whatnot versus the overhead of having it all inlined. A lot of things become more evident as you inline one-use functions, because the whole context is now local.
- vram22 9y agoYes, it's a tradeoff. Interesting, your comment reminded me of inline functions in C++ and C99; not the same thing (or at least goal) as what we were discussing, but closely related: https://en.wikipedia.org/wiki/Inline_function https://en.wikipedia.org/wiki/Inline_function
- haberman 9y agoWhile I tend to agree with this, I think a counterargument is: long functions mean more local variables that are in scope. This makes it harder to understand the interaction between different variables at different scopes. A separate function introduces a separate namespace, and all the data flow in and out has to be explicit, through function parameters/returns.
- vram22 9y agoYes, agreed about the counter-argument. See my nearby comment here: https://news.ycombinator.com/item?id=16064412 https://news.ycombinator.com/item?id=16064412 I think the important thing is that modularity (breaking up longer functions into shorter ones) helps, but only if not done blindly. One has to spend some time doing it, since there are many possible ways it can be done, and some of them will be better than others. So it can pay off in the long term, but only if one is willing to put in the effort and time, and be ready to throw away some not-so-good attempts. I've done this sometimes and seen that it does help.
- deleted 9y ago[deleted]
- Waterluvian 9y agoI would love an IDE that unrolls subroutine calls inline as a view mode.
- dec0dedab0de 9y agoThat is really interesting. They would have to be made to stand out somehow, but it might be helpful. I can only imagine what some library heavy code would look like.
- WaxProlix 9y agoPeek definition does this, sort of. Definitely exists in VS and VS Code, and probably in the JetBrains things.
- deleted 9y ago
- riku_iki 9y agoIf you want to find specific place in large function, you will need to read all code line by line. If case of smaller functions, you can locate branch of interest much faster if naming is good enough.
- jeremiep 9y agoIt really depends on the context of the problem being solved. Some problems become more complex when modularized like this. Splitting functions is basically adding indirections and spreading the context all over the place. For reusable tasks that works wonders, but for single-use functions that usually obfuscates the intent more than anything. I don't want to follow fifteen 10-lines one-use functions when I could read one 150-line function (which would most likely be a lot less due to less boilerplate).
- mvaliente2001 9y agoIMO the advantage of small functions is that they act as a form of documentation. In the given example (short main) I can see that it loads some configuration without having to worry about how it does it. With the long main, I'm forced to see the details. Also, if we're talking about OOP, small methods are better. With small methods it's easier to change behavior by subclassing. In several occasions, I've been forced to make a subclass with a copy of a big method with only one line changed.
- sinhpham 9y agoI find myself increasingly doing this in Rust: let lines = { let ret = vec![]; // Open file // Read into ret ret }; let processed_data = { // Open another file // Use lines // Construct ret ret }; .... You get the best of both worlds this way: scoped, meaningful names like with sub-functions and the continuity of a long one. Everything is an expression is an underrated feature of Rust.
- sbov 9y agoIn at least Java, and probably more languages, you can also use braces to limit variable scope. E.g.: List<String> lines; { int whatever; lines = new ArrayList<>(); // modify lines } List<Object> processed_data; { int whatever; processed_data = new ArrayList<>(); // modify processed_data, using lines }
- maxxxxx 9y agoI do this a lot in unit tests where a pattern is repeated.
- fishywang 9y agoIn Go something similar to this is actually more common because of `defer`, e.g.: if err := func() error { f, err := os.Open(filename) if err != nil { return err } defer f.Close() // work with f return nil }(); err != nil { return err }
- Stoids 9y agoA viewpoint shared by the go-kit faq: (https://gokit.io/faq/#dependency-injection-mdash-why-is-func-main-always-so-big https://gokit.io/faq/#dependency-injection-mdash-why-is-func...), which I tend to agree with.
- logicchains 9y ago>because you don't have to chase down the implementation of the subroutines to figure out what's going on If the subroutines are sensibly named, you shouldn't need to. I'd argue they're also easier to follow than blocks of code within a long function as well-written small functions will depend only on their inputs, whereas code in a large function could depend on any of the params to that large function.
- guru4consulting 9y agoI have to disagree with this. If the function is larger, then the mental cognitive overload to understand (and follow the flow) is much higher. If a 1000 line method is divided into 10 methods with each 100 lines, and if those 10 methods are named appropriately, then it would be much easier to understand the flow. It also gives a high-level picture of what the function does, and if needed, I can always jump into particular sub-routine.