— Clean code — 2 min read
Functions are one of the fundamental building blocks of our programs.
It's our responsibility as developers to keep them concise, easy to understand, and reason about.
Let's explore 7 language-agnostic tips that will help us to do so.
Don't be afraid to use long names as well. It's better than comments, abbreviations, etc.
Naming is not an easy thing to do and it's OK if it takes more time to think about how to name something!
I've written a separate post for naming which you can find useful as well.
💡 If another function can be extracted with a name that is not merely a restatement of its implementation, it would mean that your function is doing too much.
You don't know when and where this external state can be changed. Therefore, you don't know what to expect from invoking your function.
1// ❌ Avoid2// cartInformation is external state for the function.3// External state can be changed from outside and can lead to hard to find bugs.45const cartInformation = getCartInformation();67const calculateTotalPrice = () => {8 return cartInformation.items.reduce((acc, item) => item.price + acc, 0);9};
Passing a variable as an argument to the function is a very simple, yet powerful solution in such situations to avoid external state usage.
1// ✅ Prefer2// Pass it as argument and avoid external state.3const calculateTotalPrice = (cartInformation) => {4 return cartInformation.items.reduce((acc, item) => item.price + acc, 0);5};
Meaning given the same input, the function will always behave in the same way and return the same output.
Avoid mutating input arguments but use the returned value from your function.
1// ❌ Avoid2const isAvailableForSale = (product) => {3 product.availableForSale = product.numberOfItemsLeft > 0;4};56// Usage7const product = getProduct('iPad Pro');8isAvailableForSale(product);910if (product.availableForSale) {11 // do something12}1314// ✅ Prefer15const isAvailableForSale = (product) => product.numberOfItemsLeft > 0;1617// Usage18const product = getProduct('iPad Pro');1920if (isAvailableForSale(product)) {21 // do something22}
Some of the benefits of using pure functions:
More arguments are making the function harder to understand and to test as well because of all of the different arguments' combinations.
Try to look for logical groupings of arguments and combine them in an object/class and then pass them as a single argument to the function.
This is a way to try and reduce the number of arguments that a function needs instead of passing separately each argument.
Configuration options can be a good example.
We start by adding just one argument to configure something, then another is introduced, etc. but some of them can be skipped.
1// ❌ From2// true, null, false, 42, 'config' - doesn't make sense until we go and read the function itself.3someFunc(true, null, false, 42, 'config');45// ✅ To6// By grouping the arguments in a config object, we will get better naming and readability as well and we will know what "true" means, etc.7someFunc(config);
It also helps for:
If we do so, it's easier to spread around even more (like the broken windows theory) and most probably means that we have violated the single responsibility principle.
✅ Prefer separate functions - one doing state change, another one answering a question about the object.
Mixing the two can create confusion and ambiguity.
What we've covered:
As one of the fundamental building blocks in our software, it's essential to have and keep our functions as clean as possible.
Thank you for reading this to the end 🙌 . If you enjoyed it and learned something new, support me by clicking the share button below to reach more people and/or give me a follow on Twitter where we can catch up. I am sharing some other tips, articles, and things I learn there.
If you didn't like the article or you have an idea for improvement, please reach out to me on Twitter and drop me a DM with feedback so I can improve and provide better content in the future 💪.