Photo by Daniel K Cheung on Unsplash

Should Functions Be Small?

Gregory Leman
CodeX
Published in
6 min readMay 11, 2022

--

Clean Code tells us:

“The first rule of functions is that they should be small.”

Curly was really talking about code.

Pretty much every developer is familiar with the Do One Thing (DOT) guideline, otherwise known as Curly’s Law. This guideline tells us that functions should only do a single thing. Clean Code explains it a bit more with:

“Functions should do something, or answer something, but not both.”

That’s a good distinction because if we’re both taking an action and answering something we’re by definition doing more than one thing.

Eventually we have to piece together those functions and do something. A higher-level function calling multiple lower-level functions will end up doing more than one thing by definition. Uncle Bob clarifies this again in Clean Code with the Same Level of Abstraction Principle (SLAP):

“The idea behind this principle is that all the code inside a method should be at the same level of abstraction. This way it is easier to read and understand the code. If code is expressed with different levels of abstraction within the same method, this will could result in confusion, and difficulty understanding.”

The DOT guideline really means that the “one thing” we are doing is something at the same level. Consider a simple function that would update a phone number on a user record. First, the function would have to find the record. Then it would update the record, and save finally the record. It would be reasonable for it to return the updated user record. It could be calling functions for each of those activities, and those functions might be calling other functions that get down to the actual hardware where the data is written.

The three steps of Find-Update-Save are all at the same level and could be contained in a single function where the “one thing” that function does is UpdateUser(). Consider the horrors of reading the code where UpdateUser() starts with low level disk reads. Or if UpdateUser() also performed UpdateCompany() and UpdateAccount() at the same time.

None of this tells us how large a function can be as long as it’s just doing one thing. “Small” is a subjective term.

The PowerPoint Rule

When I first got into middle management, I did a presentation to a group of VPs. One of the VPs took me aside after the meeting and said “Great presentation, but let me offer some suggestions.” They were:

  • Use Large Fonts. The higher up you go the worse your eyes get.
  • Say Less. The talking part is for the explanation, what’s on the screen should be the minimum.
  • Three. You should never have more than three bullets on a slide. That’s the most information an executive can keep in their head.

Follow those rules if you want to have effective presentations. I think the same thing applies to a function:

  • A function should fit on a single screen, with a reasonably sized font. Uncle Bob recommends no more than 20 lines.
  • It should read simply. Anything that is explanatory is a lower-level function that is named well. The result should be code that reads like a story.
  • Three. Keep the function to about three blocks of code. Not lines, but about three “paragraphs”. Those paragraphs should only be about three steps long as well. Notice I never said anything about lines of code.

These rules are about readability and cognitive load. But there are other reasons to keep our functions small:

  • Don’t Repeat Yourself (DRY). Longer functions tend to have repeating code that is used in slightly different ways. When we have shorter functions it’s easier to remove the repetitions in actions.
  • Debugging via Unit Tests. Yes, yes, I know, we don’t unit test functions, we unit test behaviors. But when we’re debugging why something isn’t working the way we expect, it can be extremely useful to write unit tests that make assertions about how the building blocks work. The smaller our blocks, the finer the grain we can debug. Of course, we can also move up the abstraction ladder and make assertions about what is going on as we move up.
  • Refactor to reduce complexity. Smaller functions make it easier to delete code.
  • Reorganization. Smaller functions make it easier to move things between modules/classes. If a function is doing a bunch of things, we might have to break it down before we can move a part of it.

What About the Other Side?

There is a line of thinking that takes the opposite approach. The claim is that shorter functions lead to higher defect density and make it harder to debug. I’m not buying it:

  • The studies the article relies on for the higher defect density claims are pre-2000. Know what wasn’t around pre-2000 either? Clean Code, TDD, and the more powerful languages and libraries we have today.
  • It could be that back in the olden days (which I lived through, get off my lawn) longer routines got more attention since they were the core of the system. It’s not so much length of the routines, but the amount of developer attention that drives the number of defects.
  • If you’re following a code-debug cycle then shorter functions may indeed be a problem. But we’re refactoring all along, we’re using TDD, and we’re writing code to be readable. Readable code is inherently easier to maintain. Anyone that disagrees with this has never inherited a mess.
  • Readability was not valued as highly in the early days. In Fortran, your variable type was determined by the starting letter of the variable name. Max variable length was 8 characters back when I was writing Fortran. Shorter functions probably increased the cognitive load, now they have the opposite effect. COBOL was claimed to be self documenting, which is easily refuted by anyone that has worked with it.
  • Any time one approach claims to have fewer defects than another, you have to answer “compared to what?” If you don’t find a defect, did it still exist? Did you build the same thing twice and compare the defect rates? How did you remove the knowledge from your head you got from the first implementation? How did you make sure the pools of developers doing the work were at the same talent level? The studies were examining codebases that had already been developed and measuring function length vs reported defects. It could have easily been that shorter functions just hadn’t matured enough to cover the corner cases that show up as defects.

This is NOT the Single Responsibility Principle

One of my pet peeves is that many writers confuse the SRP (A function or method should only have one reason to change) with the DOT. The SRP is about people — the actors that will be doing things with our system. If one of the actors using our code needs a change, it should not require that we change code used by other actors. The shipping clerk should not be affected by changes to the security camera monitoring system, even though the clerk might be a subject of that monitoring system.

Happily, following the DOT helps us with SRP. Being able to move methods around easily because we’ve got lots of atomic building blocks helps us separate the behaviors into the correct modules.

Writing Short Functions Breaks My Train of Thought

Then go ahead and write a long function and refactor. Make it work then make it better. Most IDEs these days come with great refactoring tools to break out methods and move them between classes. Keep refactoring until it is absolutely clear what your code intends.

As Uncle Bob says in one of his videos, he likes to reward himself for having shorter code when he can remove the curly braces from his IF statements. Myself, I like braces, but to each their own.

How Short is Too Short?

When the code you extract is more readable than the function name you replace it with, you’ve gone too far. That’s why there’s not a hard and fast rule on how many lines of code we should have in a function, merely some guidelines.

People often ask me how long to cook something. The answer is always “Until it is done.” Some thing with code. Keep cleaning the code until it is clean.

--

--