Coding and cognitive functions
When I was doing programming as a hobbyist, I thought that writing source code is to communicate to the computer. Then lately working as software developer, I realised that coding is not just communicating to the computer, it is also communicating with other software developers.
When we are working on a big project, working alone is impractical. Then we need to work as a team. Consequently, our coding style needs to be conformed to ease the code review. Because at the end, the code is belong to the team, not an individual. That is why, practicing “clean code” is very important.
Practicing “clean code” makes the code easy to read. Easy to read means easy to understand, so that easy to be re-used, updated, tested, hacked, managed, etc. To make the code clean, code duplication is always unwanted. Duplication of the code means if there are any changes, all the duplicated codes need to be changed. As a result, code duplication causes difficulty to manage.
However, when we want to develop a system quickly, we may not clean up our code. As a result, those dirty codes will be accumulated and create technical debt. When the debt is large, are you going to pay? That is why, refactoring is important as to reduce the technical debt. It reduces the duplicated code and creates smaller functions/methods so that easy to be read.
Opinions
Refactoring
The followings are my opinions towards clean code practice. During the development process using clean code, doesn’t mean that we have to clean up the code in every step. Assuming our source code is the final product, we always need to draft, test, and finally build the final product (in terms of the source code itself). Thus, it is impractical to write the clean code directly without drafting or prototyping.
You may respond, that is why refactoring is important in order to clear the technical debt. I agree that refactoring and removing the technical debt are both important. However, there is another issue, it is unrealistic for a fresh graduate to use all of his salary to pay his student loan. So does a business man needs to use business loan to operate his business, instead of just clearing the loan, else he doesn’t need to apply the loan at the first hand. In short, “balance” is important.
Now, let me explain early refactoring and late refactoring. Let’s say we create a solution in terms of mathematical function,
$latex f(x) = 2x^2 + 8x$
When you see a function like this, you will happy to factor it to
$latex f(x) = 2x (x + 4)$
It looks nice and smaller. And we can name them as
$latex g(x) = 2x$
$latex h(x) = x+4$
$latex f(x) = g(x) \cdot h(x)$
Now, the problem is, we may found that the f(x) doesn’t work as desired. We need to add something to it. As a result, the f(x) should look like this,
$latex f(x) = 2x^2 + 8x + 8$
So, if we are doing the factoring after we add 8, then we can simplify the f(x) as
$latex f(x) = 2(x+2)^2$ — (1)
However, if we already factored the function into f(x), g(x), and h(x), we can do something like,
$latex f(x) = g(x) \cdot h(x) + 8$ — (2)
As a result, it becomes more complex than $latex 2(x+2)^2$. I personally don’t think that formula (2) is easy to read as formula (1). Because you need to refer to g(x) and h(x).
Yet, please bear in mind, this doesn’t mean that we always need to do the factoring late. Like I mentioned earlier, we need to have a balance, that is, we don’t adopt either method extremely.
Functions, documentation, and readability
If you have a lot of small functions, without proper documentation, you will not going to re-use it. You will re-use these functions only if you have pre-knowledge about the existence of the function. As a result, you will create a new function instead. Then, it will be another function does similar task with similar function name. Therefore, the primary purpose of creating small functions is for unit tests, to produce reliable product.
In my opinion, there are two kinds of function: (1) general function and (2) specific function. General functions are the functions that can be re-used. Specific functions are the functions that less likely to be re-used. That is why, anonymous function or lambda expression becomes handy. For example,
[code language=“javascript”] // Conventionally, it can be written like this function getFirstFavouriteArticleByAuthor(articles, author) { return articles.find(function(article) { return article.favourite && article.author === author; }); } [/code]
Then you may refactor it with functions like,
[code language=“javascript”] function getFirstFavouriteArticleByAuthor(articles, author) { return articles.find(function(article) { return isArticleOfAuthorFavourite(article, author); }); } function isArticleOfAuthorFavourite(article, author) { return article.favourite && article.author === author; } [/code]
However, isArticleOfAuthorFavourite() is not really reusable. It can be just a specific function/statement without name. For example,
[code language=“javascript”] let getFirstFavouriteArticleByAuthor = (articles, author) => articles.find(article => article.favourite && article.author === author); [/code]
Next, issue is a deep call stack. For example,
[code language=“javascript”] function doAutoSaveArticle(article) { window.setInterval(function() { //There are other ways, I use setInterval() as example if (isEdited(article)) { save(article); } }, 500); } function isEdited(article) { return isArticleContentDifferent(article); } function isArticleContentDifferent(article) { return !isSame(article.previousContent, article.currentContent); } function isSame(a, b) { return a == b; } [/code]
The design can go even deeper. But if the function call stack is too deep, our short term memory may not able to remember what have happened in the beginning. If you have done debugging, I don’t think you will know what have happened after you do several times of “step out”.
The code is good for testing, but not good enough for reading and debugging. It can be just direct enough,
[code language=“javascript”] function doAutoSaveArticle(article) { window.setInterval(function() { if (article.previousContent !== article.currentContent) { save(article); } }, 500); } [/code]
The function is not very big, with a few lines that is still in a glance. It is able to be digest. Everything is still within the context.
Conclusion
Nevertheless, how we code depends on our perceptions. When develop in team, we need to have a conformity between the team members. And, balance is important, we should not adopt approach in a very strict manner. A space of flexibility allows the code to breathe.