Build Functions In The Most Maintainable Way— Clean Code Part 3

Hussein Reda
7 min readOct 19, 2020

as in the previous part, we discussed the rules of choosing names for variables, functions, class ...etc, let us take another step to something more important which is Functions, Function is the basic building block and the core of any system.

But first Let’s give an example to show the concepts that will be discussed. If you have a haystack and needle inside it, and you have to extract the needle from it, which case will need fewer efforts and time?

case 1 vs case 2

Definitely, case number two will be the easier one, why? as it is small, rolled, and arranged which makes the needle is crystal clear.

Dividing the haystack into small arranged Pieces and separate them into collections, every collection is for a specific purpose make searching for a needle inside it, is easier, this example explains in brief, the benefits of divided and arranged the haystack, it works also for functions it makes it easier to find bugs and make code more Readable and Maintainable, let’s dive into this rules.

Functions should be Small

The first rule of functions is that they should be small, the smaller the function the more clear they are, and easy to read and understand.

Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.

Blocks and Indenting

the blocks of (if, else, while, and for …etc) statements, should be one line, this line should be a function call.

this rule not just helps you to keep function small, but it also makes it easy to read and understand the function, because inside each block there is only one line for calling a function that has a descriptive name, and this avoid have nested blocks that make code hard to read.

level of nessting inside a function should not be greater than one or two.

Do One Thing

functions should do one thing. they should do it well. they should do it only.

The problem is it hard to know what “one thing” is.

So, another way to know that a function is doing more than “one thing” is if you can extract another function from it with a descriptive name, and functions that do one thing cannot be divided into sections.

If you read a long function you will realize that more than one thing is done there. For example in the same function, you open the DB connection, execute a query, convert the result to another type and handle special cases. Each of these things should be done in a separate place.

If you need to do more than one thing then you should split it into separated functions.

“So much complexity in software comes from trying to make one thing do two things.” — Ryan Singer

One Level Of Abstraction Per Function

In order to make sure our functions are doing “one thing,” we need to make sure that the statements within our function are all at the same level of abstraction.

Reading Code from Top to Bottom (Stepdown Rule)

We want the code to read like a top-down narrative, We want every function to be followed by those at the next level of abstraction so that we can read the program.

SWITCH STATEMENTS

It’s also hard to make a switch statement that does one thing. By their nature, switch statements always do N things.

Using it violates the Single Responsibility Principle (SRP) because there is more than one reason for it to change. additional to violates the Open-Closed Principle (OCP) because it must change whenever new cases are added.

So the problem function has a switch statement:

  1. do more one thing.
  2. has more than one reason to change violates SRP.
  3. not open for extended violates OCP.
  4. complex and long function structure.

you can solve the first two problems with extract every case code block to function and just returning calling this function.

But the class that has a function has a switch inside it, is still not open for extended, which means for every time you need to add a case you need to modify this class, and the more cases you put the longer function will be.

To solve this problem we need to use polymorphism.

polymorphism

Polymorphism is the ability of an object to take on many forms, in simple words instead of using switch statement you will use Polymorphism by creating a superclass that has a function for what we need to do in switch and overwrite this function in subclasses, for example, if switch statement manages payment methods like this:

double calculatePay(Employee e) {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
}
}

to use Polymorphism instead:

  1. crate the interface
public class Payment{double pay(Employee){// default case
}
}

2. create a class for every case and overwrite the pay method.

public class CommissionedPayment extends Payment{@overwrite
double pay(Employee e){
}
}
public class HourlyPayment extends Payment{@overwrite
double pay(Employee e){
}
}
public class SalariedPayment extends Payment{@overwrite
double pay(Employee e){
}
}

3. using it

Employee employee = new Employee();Payment commissionedPayment = new CommissionedPayment();
commissionedPaymen.pay(employee);
Payment hourlyPayment = new HourlyPayment();
hourlyPayment.pay(employee);
Payment salariedPayment = new SalariedPayment();
salariedPayment.pay(employee);

And now if we need to add another payment method, we just add another class so the system is now open for extended, and for every subclass, there is a different implementation for pay method which is a use-case for Polymorphism.

Use Descriptive Names

Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.

FUNCTION ARGUMENTS

Functions Arguments categories:

  • Zero Arguments (niladic), the ideal number of arguments.
  • One Argument (monadic).
  • Two Arguments (dyadic).
  • Three Arguments (triadic).
  • More than three (polyadic), shouldn’t be used, It shouldn’t be used unless necessary.

From a testing point of view, more arguments mean more effort to write test scripts to ensure that all the various combinations of arguments work correctly.

Flag Arguments

Passing boolean Flag into a function as an argument is totally unacceptable, that’s mean that this function does more than one thing based on the value of this flag, the correct way is declaring two separated functions one for each purpose, and instead of passing the flag to function and check for its value inside it and write code for two different things, you check for value and call the two functions that you built.

Dyadic and Triadic Functions

the issue with them, the order of parameters when you call the function you, maybe miss order arguments, you may declare the function like that functionName(ar1, ar2, ar3) but you call it like that functionName(ar1, ar3, ar2), the ordering of arguments doesn't have meaning to make you predict which parameters you must pass first, in some modern programming language this issue solved by using Named argument, which provides you a way to send the value for a specific name and not depend on the order you passing the arguments with.

or you can use an old solution which encodes the arguments name to function name to avoid remember the ordering of the arguments for example writeTextIntoPath(text, path) instead of write(text, path)

Argument Objects

When a function needs more than two or three arguments( polyadic), it is good practice to wrap these arguments into a class and send an instance of it.

Don’t use inputs as outputs

Using an output argument instead of return value for a transformation is confusing. If a function is going to transform its input argument, the transformation should appear as the return value. and Don’t change global variable return new one.

Have no side effects

The function must not change any unexpected things, it just does what it has to do only (do one thing), the side effects coming from a function promise to do one thing, but it also does other hidden things.

Error Handling

Error handling is one thing, a function that handles errors should do nothing else. If the keyword try exists in a function, it should be the very first word in the function, and that there should be nothing after the catch/finally blocks.

DON’T REPEAT YOURSELF (DRY)

it all about removes duplication code, Wherever you find some code you repeat and using it in different places, you must refactor your code and extract this repeated code to function and use this function instead, This makes changing the code done in one place and also reduces the size of the code.

Single-Entry -> Single-Exit Rule.

a function should have one entry and one exit. that means there should only be one return statement in a function, no break or continue statements in a loop, and never ever any goto statements, It is only in large functions this rule provide benefits, so if you keep your functions small, then the multiple return, break, or continue statement does no harm and can sometimes even be more expressive.

In the end, don’t worry to start writing your functions long, complicated, having lots of indenting, nested loops, long argument lists, duplicated code and names are arbitrary, You can refine your code over and over again until there is no way to make code better than it is, with time you will have code-sense.

--

--