8 ms·
Functions Should Be Short And Sweet, But Why?
- noir_lord 13y ago> Functions Should Be Short And Sweet, But Why? In my case the why (as well as many of the arguments here) is because I'm not a "superstar/rockstar ninja coder" and by keeping functions short, clear and with a descriptive name in 6 months when I open the file to fix the change request I won't have to spend 2 hours building a mental model of the system to fix the bug.
- mercurial 13y agoIf your rockstar coder makes functions five screen longs as a matter of habit, you may consider changing bands.
- sdegutis 13y agoOn the flip side, I've seen people take this to the extreme. They often separate lines of code that intrinsically belong together, necessary parts of an algorithm get spread around for no other reason than to try and keep functions small. It leads to code that's really hard to work with and somehow more brittle than when it started. I'm not saying this happens all the time. But I'm more concerned about it, because I see it more often than the inverse, and it's more damaging to the codebase. At least long spaghetti methods can be refactored into something nice. This kind of mess has to be refactored into a single area first, which is much harder.
- cclogg 13y agoYeah I do hate it when trying to follow through code and I'm having to do searches to find where the next method calls out to in a massive chain of tiny methods. But both sides have their point heh. I wonder if it's a limitation of our IDEs using plain text and files. Maybe the next generation of programming languages will be nodes or visual blocks that chain together or something? Who knows haha.
- Geee 13y agoCheck out NoFlo http://noflojs.org/ http://noflojs.org/
- fuzzix 13y ago> Maybe the next generation of programming languages will be nodes or visual blocks that chain together or something The problem is, you still need to have control at a "line of code" level of granularity (though not quite a line of assembler mnemonic) if you want to have general purpose programming power. You could probably, with some work, make a graph tool for something like ifttt[0] and do powerful things relatively simply. It's not quite programming though. I've used state machine UML code generation systems in which you "program" by moving blocks around (something akin to a flow chart). Even when the system's purpose is something extremely limited, e.g. handling a phone's menu transitions, there's still a mind-bending amount of complexity to handle. I believe ImageJ[1], a freely downloadable image analysis tool, is among the tools with this sort of UML functionality built in if you want to play with it. [0] https://ifttt.com/ https://ifttt.com/ [1] http://rsbweb.nih.gov/ij/ http://rsbweb.nih.gov/ij/
- nightski 13y agoIf this were true expression based languages would not work. Which simply is not the case. Of course if you live in a world of statement based languages I can see where this would seem the case.
- fuzzix 13y agoNo matter how powerful and expressive your language is, it still breaks down into lines of code to achieve something. I'm not trying to claim some complete, graphically represented program will have as many operations (or boxes) as the equivalent C code. It won't, however, require less cognitive overhead than crafting the program in the language of your choice without making most of your choices for you (and in the process becoming much less powerful, not general purpose at all). edit When a computer is smart enough to derive intent, then a picture will be worth a thousand words. Until then...
- shangxiao 13y agoYes code cleanliness would be something like a spectrum where there are instances of extremes on both sides.
- collyw 13y agoI am glad to see i am not the only one that finds tons of small methods / functions, with no obvious path through them makes code difficult to read.
- tunesmith 13y agoI think some of the perception of how extreme it is has to do with what IDE the coder is using. For instance, in IntelliJ I can have the mouse on a method name and hit a keystroke to go to that method definition, and then another keystroke to go "back" to where I was. That's really helpful for jumping around and following a logical train of thought that goes through several small, one-concept methods - even across files. But, if I were having to deal with an IDE or editor where I was having to string-search and scroll, it'd be maddening and I'd wish for longer, more self-contained (multiple-concept) methods.
- gbog 13y agoThere is another way, we do not need to be either in a big spaghetti blurb or infinite micro onions spread all over the place in multiple files. With some work and some luck: - all functions are short enough but all of them do something more than just passing things around. - there is never any ambiguity on where a function's implementation is to be found. (In Python, I prefer "import x.y.z" then "x.y.z.myfunc()" than using "from x.y.z import myfunc", for this reason) - like-minded functions are grouped together. - functions are nice enough (read: testable and tested enough) so that there is rarely a need to check its code to use it.
- henrik_w 13y agoI have the same experience. I coded C++ in Emacs for many years, then switched to Java in IntelliJ IDEA. I definitely write shorter methods now in Java, than I did in C++ (and it's for the better in my opinion). Navigation in general really is so much better in an IDE. More details in "Programmer Productivity: Emacs versus IntelliJ IDEA" http://henrikwarne.com/2012/06/17/programmer-productivity-emacs-versus-intellij-idea/ http://henrikwarne.com/2012/06/17/programmer-productivity-em...
- mironathetin 13y ago>>On the flip side, I've seen people take this to the extreme.<< indeed, I have seen functions that contain only one line of code. Even if that is intended for reuse, it makes no sense, because the function call is one line too. Readability goes down the drain this way. If a reader tries to understand a logic and thinking has to be interrupted by searches and trying to find the right code window, split-ups don't do good things. Polymorphism, if applied wrong, is a notorious tool to hide the logic from the readers too. If the code decides at runtime, which implementation is used, comments become really important. Especially when this decision is made completely elsewhere in the code. Think strategy pattern, for example. The underlying problem is IMHO developers blindly following rules. This can be seen a lot. If you ask them 'why' and you get a textbook answer, be careful.
- chongli 13y agoDepends on the language. In Haskell function calls are extremely lightweight, syntactically speaking, and so it's actually quite common to write one line functions. A one-line function may consist of a composition of a dozen or more other functions and be given a short, one-word name. This makes perfect sense for reuse. Or it can be something as simple as this: odds = filter odd While that might not make sense if you only call odds once, you might end up calling it a dozen times! You may also want to limit the scope of this function definition by using a let or where clause. This can aid readability if it allows you to avoid repetition.
- NigelTufnel 13y agoI've tried to learn Haskell for a several times and the composition-heavy/flipping-so-currying-will-work/no-variables style always made the programs difficult for me to understand. Probably the classic case of Imperative-Guy-Struggling-To-Get-FP.
- aaronem 13y agoSpeaking as an "imperative guy" who's lately been learning Common Lisp, I tend to suspect the confusion you're encountering is not so much a property of the functional style in general, as of Haskell's style in particular. While I'm admittedly not best placed to comment on the matter, it seems to me that what Haskell offers is not so much greater power than a Lisp, as greater concision, as well as a language structure which doesn't allow for the sort of "escapes" into imperative behavior that a Lisp programmer can employ. In fact, now I think about it, I'd say that Lisp and Haskell are in this way roughly analogous to Perl and Java. I'm sure this claim will infuriate partisans of all four languages, so let me hasten to point out that the only sense in which I mean it is that Lisp and Perl allow the programmer considerable stylistic freedom ("TIMTOWTDI"), while Haskell and Java go to great lengths to enforce that style which their respective implementers consider the One True Way. Both these traits have benefits, of course, or one would've probably outcompeted the other by now; of particular note here, though, is that the One True Way style requires a lot more front-loading on the part of the developer than the TIMTOWTDI style does -- in the latter, you can more or less "fake it 'til you make it", while in the former, trying to "fake it" results in a nasty dressing down from the compiler, and you can't accomplish anything until you've become at least a neophyte, preferably a proper acolyte, of the One True Way. All of which is a very long-winded way of saying that if what you're interested in is not so much Haskell specifically as the functional style in general, you might be well advised to start with a Common Lisp or a Scheme, where you can study the functional style without needing first to understand any large and rather dry bodies of theory. Granted, you'll probably screw it up a lot right at the start, at least if my own experience is any indication. But Haskell's a big elephant to swallow all at once, and starting in a Lisp or a Scheme will let you nibble around the edges at your own pace, rather than being required to choke down nine-tenths of the entire carcass just to get to Hello World.
- mattmanser 13y agoBest way I've seen code like this described is Holographic code: http://www.johndcook.com/blog/2012/01/09/holographic-source-code/ http://www.johndcook.com/blog/2012/01/09/holographic-source-... It's as much a nightmare to work with as overly coupled code is.
- agumonkey 13y agoFunny, I've seen explanations like that for why natural language is full of redundancies. I used to want full DRY/zipped code but I understand the issues better.
- e12e 13y agoI think it (at least in part) depends a bit on the language. When I tried to solve some simple Java exercises with classic Literate Programming (noweb[1]) -- I found that it helped enormously with simple top-down design -- but I ended up with pieces of code that were a lot smaller than the natural java abstractions -- it felt natural to factor out parts of loops etc... mostly this was me overusing the power that comes with literate programming -- but if done reasonably it could also be rather effective. It allows one to start essentially with pseudo-code -- but that pseudo code often ends up dealing with stuff like arrays, that with (classic, pre-iterator) java implies explicit counter-variables (like "i") to keep track of an array pointer. So you suddenly get a problem reusing blocks of code, for an inner loop that indexes by "j". At the same time it made for wonderful compact and readable code (where "code" means the Literate Programming document, not the actual java noweb emitted). [1] http://www.cs.tufts.edu/~nr/noweb/ http://www.cs.tufts.edu/~nr/noweb/
- lucaspiller 13y agoI've been doing this in client side Javascript for the last year or so. Rather than having 20 lines of nested jQuery callbacks on a page, I make each callback a function and group the event bindings together: e.g.: var showPreview = function() { ... }; var calculate = function() { ... }; $('a.preview').click(showPreview); $('a.calculate').click(calculate); Even nicer is to split this out into a separate CoffeeScript file. Then just call `new SuperAwesomeCalculator()` on the page. I think what made me do this was Erlang. After a point it gets annoying to type NewNewNewNewNewParams, so it's easier just to split it out into a separate function :)
- cdelsolar 13y agoI recently started messing around with Clojure, and I love the language. I noticed that you are pretty much basically forced to write lots of short functions if you want to stay idiomatic and readable. It is pretty good in a way.
- lstamour 13y agoThis reminds me strongly of some concepts I've heard of in TDD Ruby circles though I'm certain they're not unique to that. The bit on writing your function names first is like the practice of "write the test with the API you want to use, then make it pass with an implementation that does just that." It's a kind of planning-by-wishful-thinking that just seems to work in certain scenarios. But I think for it to be successful, you need to read enough best-practices code in your target language to understand better approaches. That said, using your phantom code first will give you a better API design. The trick, as in this article, is to keep writing ideal implementations like turtles all the way down. If your logic is complicated enough that you'll need notes or comments, descriptive function names are wonderful. Particularly in languages like Objective-C with named parameters.
- cheapsteak 13y agoI've thought about it as the programming equivalent of writing outlines before starting on an essay. It requires more forethought, but you can recognize early on if a line of thought / solution is a dead end, and also makes restructuring and fixing it much easier
- henrik_w 13y agoI often end up factoring out things into common methods in the end. That's when I see what parts are common, something I don't always recognize in the beginning (or even in the middle of it).
- chipsy 13y agoI think it's a Goldilocks problem. There's an optimal average size for any group of related functions, but the skill of planning it is not in nailing it on the first try, but in determining whether the refactoring will benefit more when the initial estimate is padded higher or lower. The main reason for going small at first, I think, is that it creates more names. Names especially help with very simple calculations that appear repeatedly and turn into visual noise(true of a lot of UI code). The naming process can point you towards an instant design change as you discover what you are "actually" doing in the code. A big function acts as a bigger black box, thus it can hide these issues. Sometimes you want the black box, but it's the exceptional case.
- henrik_w 13y agoI completely agree - I made the same points in "7 Ways More Methods Can Improve Your Program" http://henrikwarne.com/2013/08/31/7-ways-more-methods-can-improve-your-program/ http://henrikwarne.com/2013/08/31/7-ways-more-methods-can-im...
- dschiptsov 13y agoThis is why decent schools in old days taught students Lamda Calculus and Scheme or even Haskell. It helps do develop a strong aversion to verbose spaghetti code very early.)
- rtpg 13y agoI think most design patterns and 'best practices' exist because, without them being explicit, people will tend to do the opposite. Obviously, there are always use cases where going against the pattern might make sense. But you should be able to justify it. Would you rather have mostly short functions with a couple long ones or the opposite?
- chj 13y agoAll the reasons boil down to one: human brains can not hold too much working memory.
- taeric 13y agoIs it a form of irony when programs wind up overwhelming this memory with too many functions? :)
- AnimalMuppet 13y agoWhere do you spend your time? I spend some of my time trying to find the right function, and that gets harder if there are more functions. But I spend more of my time trying to understand and/or modify that function, and that gets easier with smaller functions. So more, smaller functions winds up being a net win (up to some limit, of course...)
- taeric 13y agoThis depends on how connected they are. If every function is a "leaf" function, then yes this is straight forward. However, that isn't possible as you have to have some functions that call other functions. At this point, you then need to worry not just about the function you are touching, but all functions that touch it. It isn't unheard of to spend the time trying to find all of the subtleties of all functions that rely on the one you are looking at. To that end, I would think the more segmented the call trees the better. But this is often hard to see when looking at a single function/data structure. This also seems to imply that having duplication of a function is not as evil as it would seem, since it can free up many of the dependency lines you have to consider. All of this is to say that I do not disagree with the premise; though I do think it is less clear cut.
- cheshire_cat 13y agoMy (quite oldschool) professor taught me about this top-down design. This kind of "write as if you have all the methods you want/need" style is sometimes called "Wishful Programming" http://blog.thesoftwarecraft.com/2013/11/wishful-programming.html http://blog.thesoftwarecraft.com/2013/11/wishful-programming... Bart Bakker references "Structure and Interpretation of Computer Programs" as an early source and I'm somewhat positive that I read about an similar approach in "How to design programs 2nd ed", called "programming with a wish list". Edit: I found the mention in HtDP but as I understand it it's not exactly the same as wishful programming: http://www.ccs.neu.edu/home/matthias/HtDP2e/part_one.html http://www.ccs.neu.edu/home/matthias/HtDP2e/part_one.html under "3.4 From Functions to Programs"
- agumonkey 13y agoI remember doing something similar in college using Eclipse/Java (but any static analysis friendly language with on-the-fly type checker would do) and it was pure fun. You just write the code as you'd want it to be, the IDE will flag missing classes/interfaces and generate them on the go if you tell them, and if down the road you use one interface differently the IDE will extend or ~reparameterize it for you. You end up with an abstract system/algorithm that works by type checked interfaces you can now implement.
- NicoJuicy 13y agoFor everyone that didn't knew this, http://amzn.to/1hhI37v http://amzn.to/1hhI37v You should read it.. PS. It's the book Code Clean ;)
- yason 13y agoFunctions should be short and sweet, to an extent, because by thinking in terms of functions you're building a new language in which you write your program. If you're writing big functions by habit then you're just laying more code after existing code to build a program. If you split your code in functions and, instead, start talking in the language you've just invented then you're composing a program. And I think compilers should do the building while I refrain myself to composing. Of course, one-line functions don't have inherent value but sometimes they're needed. For example, to present a higher-level concept that just effectively happens to be a simple assignment or an inc. Short functions themselves don't have an inherent value either: if you manage to write your function with less tokens, possibly due to composing it out of other functions, then that's all good but isn't the real value of doing it. Shortness for the sake of shortness only matters until you're down to a screenful or so. The real value of defining and writing functions is thinking of the proper words for your language. As soon as you're making your own language, then you're just using functions as vehicles to represent words in that language. Some words might take 30 lines to code, some words might take 3 lines. It doesn't matter as you've got your language together in a way that is concise and beautiful which is the real point.
- jboy 13y agoThis is an excellent answer, and (in my opinion, at least) really gets to the heart of the matter. I'm not unduly obsessed with classes or design patterns in object-oriented languages, but to me the key benefit of functions, classes and design patterns is about defining a shared language of named abstractions. These abstractions take care of details, so you can think at a higher level. The names are a common vocabulary shared between the designer/implementer and user/client of the code; and as yason says, if you choose your abstractions at an appropriate level, you can "talk about" (or describe/define) the problem+solution in terms that are best suited to the problem+solution. And to note one more benefit of short functions: When you're talking about functions (operations) and classes (encapsulated state), you rely upon these abstractions to behave in a certain way. Shorter functions and simpler classes generally have fewer moving parts, thus making it easier to verify the correctness of the implementation of the abstraction concept.
- sergiosgc 13y ago
- kingrolo 13y agoI worked on a massive codebase once many years ago (C#/.NET) which had been built by a relatively competent outsourced team. Somewhere along the way some consultant on the UK side of the business had insisted that each function could be no more than 5 lines long. It was horrible to work with - it took so long to find the place you actually needed to make changes, and it seems like they'd just started inventing layer names in order to facilitate it. So yeah, small functions good, but you can take it to extremes.
- sigmaml 13y agoI worked on a similar project; the US consultant insisted that each method should be less than 25 lines. The code involved several decision trees. This rigid limit made the code quite inconvenient to follow and enhance. After much sweat and swearing on both sides, the limit was increased to 40 lines!
- lostcolony 13y agoI've run into that. Compounding that issue, I've run into cases where people pulled out code into functions, realized that in two places the behavior was ~slightly~ different, and they addressed it by adding a variable, 'shouldThing' or something, that serves just as a boolean flag as to which bit(s) of logic you should fire. So now rather than just have to focus on the code that needs changing, I've got to go to an entirely different section/file, and wade through code that doesn't even relate to what I'm trying to fix.
- VexXtreme 13y agoI just had to deal with extending an absolutely massive C++ function the other day and it was a nightmare. The function was 2500 lines long and littered with several dozen exit points - all of which execute under slightly different conditions. Throw in a couple several hundred lines long switch-case statements and you can see what I'm getting at. Figuring out the right place to put a breakpoint that will actually fire due to a maze of conditions and returns took me a couple hours. Of course, I've seen many cases where people take this to the opposite extreme and start breaking up functionality that logically belongs together and can't be reused in other contexts. It makes it pretty apparent that writing decent readable code is hard (for many people anyway).
- virmundi 13y ago"start breaking up functionality that logically belongs together and can't be reused in other contexts" I know you said you're in the C++ world, so I don't think what I'm doing fits yours, but I've responded to the same behavior in the Java world in a way that tends to confuse and mystify my co-workers. In Java I've started to declare classes within methods. These classes can compute complex processes, but are nicely tucked away in the only logical place they make sense, that method. My reason for this is that the method really belongs to the containing class. I can't break it down or abstract it out. I could use composition, but the new method-class isn't usable by anything else. The sub steps in the method aren't usable by any other method within the housing class. I could put them there, but they would just muddy the outer class. So now the code looks something like this. public class Outer { void doSomethingComplex(final params) { class ComplexThing { private void step1() {...} private void step2() {...} public void goToTown() {...} } new ComplexThing().goToTown(); } } ComplexThing gets access to the outer params. This cuts down on constructor complexity and cements the intricate bond between the two entities, method and inner class. The logic is broken down nicely. Finally, nothing is needlessly shared. This might make the outer class a bit larger, but I find this structure pleasant. It's also a useful pattern for writing tests. Each test method gets a class (could have inheritance) that has a setup, test, tear down. All of that is encapsulated in one place.
- JoeAltmaier 13y agoI know that code. Mine was a serial protocol with a maze of flags and conditions. I drew a table on a large sheet of paper, and recopied the code as a state machine. When I was done, it was clear where the problem lay: the state machine was woefully incomplete. I rewrote the thing AS a state machine with all state-event pairs handled. Voila! It worked. Had to go back and diddle with some events (better way to handle parity error during checksum calculation etc) but at least I could find exactly the place in the code to do that.
- avdd_ 13y agoSo why does the main function in this example violate the principle ... i.e. where is the dispatch table?
- mildtrepidation 13y agoFunctions should be exactly as long as they need to be to accomplish their purpose. The purpose is the important part: A function should have a concise and easily-understood purpose (or very small and logically inseparable set of purposes) and, tangentially, a name that describes it well. You can have a fairly long function that's readable and maintainable; those last two are the important part, and, to a point, they hinge more on how much work is being done than how much code it takes. Of course, if you're just plain writing more code than is needed, that's not a problem with the function itself.
- JoeAltmaier 13y agoYes, the idea of writing straightforward code is critical. If you can do it in less lines, even at expense of speed or space, you benefit many-fold. Its easier to understand, read, explain to someone else, remember, debug, compile. The slowest processor in the system is the wetware between your ears. Funny we rarely talk of optimizing for speed or space in that engine!