loading...

How to write cleaner functions.

singh1114 profile image Ranvir Singh Originally published at ranvir.xyz Updated on ・4 min read

This post was originally published on my blog. Please read the post, How to read cleaner functions.

I bet you have, every once in awhile, gone through a small piece of code trying to figure out that why was the given function written in a way it is written. Almost all companies have this service which just works and nobody wants to touch it and most of the time it is due to the bad way in which the code was written.

In this post, we are going to talk about writing clean functions and eventually reducing the technical overhead.

A few days ago I went through a book called, Clean code. Although I was not able to complete the full book, I was able to go through a few of the chapters one of which was,

How to write clean functions.

Here are the learnings of the chapter.

Functions should not be large.

Functions should not be bigger than a few lines. Keeping a rule of thumb is to disallow a function bigger than 100 lines of code. Generally, functions should be even smaller than 10 lines.

Creating blocks and proper indentation is very helpful.

Proper indentation and usage of blocks will take to long-distance while programming in a production system. Although this part is very much imposed in Python but keeping a style guide for a company is a good way to go.

const function = makeChanges() {
const a = 10;
 const b = 20;
  return a + b;
  }
const function = makeChanges() {
  const a = 10;
  const b = 20;
  return a + b;
}

You can feel the difference in the above examples.

Every function should do only one thing.

Generally speaking, a function should only do one thing which should be self-explanatory from the name of the function. You should never stop yourself from writing longer names for your function if it self-explains itself.

We should not be able to divide a function into sections.

Another way to put in that a function should generally be doing only one thing.

One level of abstraction in all functions: Single level of a loop or if/switch statements.

The level of abstraction is something that a lot of people miss out on. Simply speaking the level of abstraction is the count of nested if statements/ loops that you use inside a function.

The number should be generally kept at a lower value.

The code must be read from top to bottom.

I know this is hard for a number of people. Initially, even I used to follow the opposite rule. But after reading the book, I was able to reason this.

While reviewing the code people tend to start from the top slowly move toward the end. So it makes sense to start everything from the top and move down as you keep writing it.

Switch statements should be avoided.

It's good to avoid switch statements as much as you can. Better to use dict( Python), Map/ Object (Javascript).

const monthObj = {
  jan: function_1,
  feb: function_2,
  mar: function_3
};

const month = 'jan';

monthObj[month]();
month_obj = {
  'jan': function_1,
  'feb': function_2,
  'mar': function_3
};

month = 'jan';

monthObj[month]();

Although sometimes it's hard to change switch statements to something like this. Always prefer readability over speed if the differences in the speed are not that large.

Use descriptive names.

We have already discussed this. We should always choose better names for the functions. Descriptive names will go a long way rather than some random docstring for the function.

A lesser number of function arguments should be there.

The number of arguments should be lower. And if you are writing the class method, better to make it an instance variable.

Note: Try to avoid flag arguments. This means function does more than one thing.

Monadic function

Function with one argument. Always try to write Monadic functions whenever possible.

Dyadic function

Function with two arguments

Triads

Functions with three arguments. Try not to write as much as possible. It's better to wrap those arguments in their own class if the number of arguments starts increasing a given number.

For example:

converting,

makeCircle(x, y, radius)

to

makeCircle(center, radius)

where a center is an object of the class Centre, makes a lot of sense.

Naming Conventions:

For monads: Choose a combination of verb + noun:

Example: writeField(name), explains that name is written to a field.

Also, function name assertEquals(expected, actual) can be changed to assertExpectedEqualsActual(expected, actual), which makes more sense.

Have no side effects

Side effects mean changing the value of the class argument/ Global argument/ passed argument. All these types of changes must be avoided.

For example: Say you have a function called checkUserPassword(userName, password) and in the same function you are initializing the session for the user, then the function can only be used in certain instances( when the user is trying to log in, not at a time when the user wants to change the password). This coupling is also called temporal coupling. A good idea is to change the name of the function and specify that the function has temporal coupling.

Example: checkPasswordAndInitializeSession(userName, Password)

Functions should either do something or answer something but should never do both.

This is called Command Query Separation.

Prefer raising exceptions rather than returning error codes.

Extract the bodies of the try-catch statements into functions.

Example:

try:
   trythisfunc()

except:
   logError() # this function do only error logging.

DRY: Don’t repeat yourself.

Note: One great idea is to write long and bad function first, then writing tests for every line of code. Then refining the code and still not allowing the tests to fail.

I hope you liked some of the points over there. Also, there might be a few things that you don't agree upon. Do share them in the comment section of this post.

Discussion

pic
Editor guide
Collapse
patricnox profile image
PatricNox

Interesting article you wrote there!

However,
I don't really agree with the switch case argument.

Arguably, it's way more clean looking when it comes to input handling. I tend to use it for preprocessing variables before transmitting them to a output or similar, it's nice to have them scoped.

Besides, 5 if conditions looks quite bad. Refactored to s switch case looks way better and much easier to read.

I'd probably also would love for the article to provide more proof of concepts to the arguments written.

it's very nice anywho. ✔️

Collapse
rektdeckard profile image
Tobias Fried

Kotlin when statement/expressions FTW

Collapse
genspirit profile image
Genspirit

Personally, I'm a fan of just using a Map instead of a switch. For me switch statements just seem overly verbose and less intuitive.

Collapse
olayemii profile image
OLayemii

Totally agree, switch is cleaner..

Collapse
singh1114 profile image
Ranvir Singh Author

I have to totally agree with @genspirit up here. Even I always prefer Map, dicts against switch statements. I have updated the article accordingly. Do give a look and let me know.