Clean Code: Small and Clean Functions

In this post, I would like to share the importance and method to write clean functions, the second section of the Clean Code.

Indeed, functions have been an essential component of every modern programming language to enhance reusability and improve coding efficiency. Another important aspect of functions in the developer’s perspective is improving readability by separating codes into segments and allowing readers to skim through functions.


Principle: One Thing Per Function

To make your function clean and readable, you should try to keep your functions small, and the most essential principle is to do only ONE thing in each function.

I have seen lots of codes with thousands of lines of code and bugs everywhere. I try to help to debug but you can imagine how difficult it is, especially when I encounter a Segmentation Fault! I try to use GDB and its backtrace to find the point of invalid memory access. However, if the codes are written in a single large function with thousands of lines of codes incorporating dozens of nested IF-ELSE and WHILE loops, it is undebuggable! Although I know which line of code causes the Segmentation Fault, I cannot understand the whole workflow in a short time. It is terrible!

Consider the following example of scanning character:

int main(){
    do something
    while ((nextChar = getChar() != NULL) {
        if (nextChar == 'a') {
            do something
        }
        if (nextChar == 'b') {
            do something
        }
        if (nextChar == 'c') {
            do something
        }
        do something
    }
    do something
}
typedef void (*CharacterHandler)(param);

...
void aHandler(param) {do something}
void bHandler(param) {do something}
void cHandler(param) {do something}
...

CharacterHandler characterHandlers[] = {..., aHandler, bHandler, cHandler, ...};

void processOneCharacter(char currentCharacter, param) {
    characterHandlers[currentCharacter](param);
}

int main(){
    do something
    while ((nextChar = getChar() != NULL) {
        processOneCharacter(nextChar, param);
        do something
    }
    do something
}

It seems that the second one has more lines and functions, but which one would you prefer?

Obviously, the second one is more clean and readable because it encapsulates utility into functions and keeps functions small. By using an array of functions, it gets rid of the usage of IF-ELSE statements to significantly shorten the codes. Moreover, by encapsulating character processing into a function processOneCharacter, further extensions like error-detection are simplier; that is, you only need to add one more check inside this function instead of damaging a thousand-lines WHILE loop.

Small Functions Enhance Debugging

If you have a Segmentation Fault inside one handler function, you can clearly debug from GDB that it enters the function processOneCharacter, passes several checks, and finally enters the handler function. You may need to add a breakpoint to the function processOneCharacter, and very possibly, you could solve your bug here.

Small Functions Help Naming

Another advantage of using smaller functions is that you will not have trouble with function naming. Consider you have a significant function called main. Nobody understands what it actually does. But if you separate the procedure into functions like getInput, checkInput, and getAndCheckInput, where getAndCheckInput will invoke getInput and then checkInput, it would make developement easier.

Fewer Arguments, Easier Managing

With a small function that has a single principle, it is possible to contain fewer arguments. The reason to have fewer arguments is to free your memory from memorizing the order of arguments. Consider you have a function with ten arguments; you will definitely find a mess when calling this function, especially when you have nested calls that pass their outputs directly as the input of your heavy function.

Although code hint and your compiler are smart enough to detect type errors, misusing arguments with the same types is still popular. For example, a function like assertEquals(expected, actual) has arguments of the same type. Then, one may exchange the position of expected and actual but still pass the compiler test.

The best practice of functions is using monadic forms, that is, using a single argument. Usually, it is enough, especially if using OOP. For OOP implementations, each function has an invisible argument of the object itself already, and it only requires one more argument for the action like getter and setter handlers.

If the language does not support OOP, like Go, I suggest it is acceptable to have the first argument as the object itself and allow one more argument. As long as we follow this convention, we will have no trouble in coding.

PS: Go even forces you to write context as the first argument. That’s a productive option!


Read more about the Clean Code series:

To buy the book:

3 thoughts on “Clean Code: Small and Clean Functions

Leave a Reply

Your email address will not be published.