back to homepage

Chapter 3: Functions


The Goal

The question for me is this: Can I recommend this book to my colleagues or to a beginner? Such a book can have a few controversial or outdated ideas as long as it’s “mostly good” and I don’t have to pair it with a lot of comments and additional reading to recommend it.

I really don’t like this chapter but one chapter shouldn’t necessarily tarnish the whole book. However, I think the author sets up a fundamental axiom in this chapter, from which bad rules and practices will probably cascade through large chunks of the book.

Code should read like prose

We want the code to read like a top-down narrative. — page 37

Master programmers think of systems as stories to be told rather than programs to be written. — page 49

This idea is hard to argue with (I even nodded while reading this) but that’s because it’s vague. These statements are still in the same category as “code should be readable” and “code should be clean.” All of these rely on heavy interpretation.

The root problem is that the author implements this idea almost literally.

A function is basically a named code block. If you want, you could convert your code into text by moving chunks of code to a separate function and use it’s name to describe the code as a sentence. But this will only really work if you divide your code into small enough chunks to be able to describe it’s content with a reasonably long function name. Classes are essentially just another tool in your belt to build Subject.verb(object) type sentences.

The smaller and more focused a function is, the easier it is to chose a descriptive name. — page 39

I don’t think (anymore) that programs should read like text. English is a way better tool to communicate to other humans than Java. No matter how you might define “readable code,” another human will have to read your code. Every rule about effective communication via text still apply to code, unfortunately.

Text is linear but programs are really not. Call trees will always be opaque. Your one sentence function hides ten of your other nicely named functions, which might hide even more code. It’s still a labyrinth that I have to search recursively, accumulating tabs and splits. You will most likely just scatter all the relevant code, that your colleague wants to look at and which could have fit into one screen, into multiple files and out of order.

Code doesn’t exist in a vacuum. Our screen sizes, IDE features and developer tools absolutely influence code design, even though these factors are completely detached from the business logic. OOP would not be as popular without code suggestions.

An ape doing a memory retention test in a lab.

I’m not that ape. Code that leaves the screen is code that I have to keep in my head and that space is limited and volatile. Every function implies that this code will be called from multiple places, even if it’s just part of a sequence (except if your IDE shows inlay reference counts). More indirection also causes modern IDE features like “list references” to be a lot less useful.

What fits into a screen matters. Consider the screen and IDE in your code design.

One central critique of OOP is that you will write a lot of code that actually doesn’t do anything except wiring the code, that does something, together. You could argue that this wiring is what makes code flexible but in my experience, I have to touch the code “in between” at least as often as the code that actually does something.

An abstractions is a powerful but dangerous tool. It usually simplifies code in one dimension by introducing complexity in another. I think it’s good practice to use abstractions sparingly in your code base. Quality over quantity should be the goal. Prose-like code is not a good, standalone reason for refactoring and the following concept will, by definition, introduce abstractions (or rather indirections) everywhere in large quantity and low quality.

Small!

Functions should hardly ever be 20 lines long. — page 34

This is a rule that only serves moving documentation into code and is arguably the most important one to achieve this. It’s so important in fact, that most of the rules in this chapter only serve keeping functions small.

I don’t think functions should only do “one thing.” It’s very convenient to have closely related code close together. Sometimes two or three “things” are by nature tightly coupled. Forcing them apart will result in unnecessary abstraction from which nothing is to be gained (but it’s fun to do and I feel very smart when I pull it off). Since your function is most likely a tree of functions, calling this function will do more than one thing anyway. You just described those things in more abstract terms. I don’t care that it’s like “climbing up and down from abstract concepts to implementation detail,” because I rather have a simpler call tree or none at all. Just show me the code that does something useful.

All of these sub-sections in this chapter pretty much only serve to indicate when to split code off into a separate function. None of these concepts are bad places to split up code, the opposite in fact, but they are not reasons to split up code.

Now. The boring answer to functions lengths is actually:

A function should be as long as it needs to be and this will depend on many factors. — guy on the internet

The least important rule as the highest rule

Starting off with avoiding documentation by moving it into code is just backwards. “Code should read like prose” should be a low priority goal. I’ll avoid writing an assay about code design at this point but there are a lot of good rules and practices about writing good code. Implement those first and if you happen to stumble upon an opportunity to make your code more prose-like, without compromising something else, then go for it.

Focus on writing good code instead of “readable” code. Trust me, good code is usually also readable code. And if it’s not readable, than there’s probably a justified reason for it. You can totally write a 600 line function with 10 arguments that is easy to understand for your colleagues. However, inlining as much code as you can to squeeze business logic into screen size chunks should not be the goal either. There are many good reasons to shrink functions.

I suspect many of the following rules and practices will mainly serve to implement the design guides listed above. My biggest fear is that the concepts in this book just stack up to uphold each other and most of the rules can basically be traced back to enable small functions and prose-like code.

Of course many (if not most) practices will certainly stand on their own but they can still be introduced in a way that will cause the reader to associate these practices with the wrong goal.

Little side notes

I don’t think we need to discuss “Prefer Exceptions to Returning Error Codes.” I’m not against exceptions but they add hidden control flow and many programming languages choose to not even include that feature. Monadic return values are pretty common nowadays.

I also think it’s a little ironic that the “Output Arguments” section, in which the author suggests to utilize object state to avoid function arguments, comes right after the “Have No Side Effects” section.

However, much of the need for output arguments disappears in OO languages because this is intended to act as an output argument. — page 45

Lastly, I don’t want to be disrespectful but the SetupTeardownIncluder refactoring is atrocious and pretty much demonstrates all of my points. If you find someone who finds this “readable” than you have found a liar. Reducing code duplication and adding early returns would have already gone a long way to make this code more readable. I genuinely struggled as much reading this class than reading the original function, even though I knew what the code was doing. All the improvement is undone by converting a sequence to a jumbled call tree.

I do understand that printing good examples in book form is hard and this code might be focused too much on illustrating the concepts of the chapter. However it is not clear at all that this example should be taken literally or not.