top of page
Anand Kumar Keshavan, Chief Architect

5 Simple Tips to Young Developers for Writing ‘Good’ and ‘Clean’ Code

Perhaps, this is the millionth blog post on this topic. Most of the discussions on this topic lead to SOLID principles or the application of some of the other design patterns. The problems with all of these are as follows:

  1. They are difficult to apply in practical situations, while under pressure

  2. These principles are subject to multiple interpretations and can confuse a young developer

  3. Understanding these principles requires a level of experience that new developers may not possess, and classroom teaching never really plugs the gap

For this post, ‘good code’ and ‘clean code’ are used interchangeably. They primarily address the issue of maintainability and extensibility of code by either the people who originally wrote the code or some other developers who may have joined the team later – sometimes years or decades later. Keeping this in mind, ‘good code’ is defined as code that can be easily read, understood, maintained, and extended by other human beings.

Here it goes:


1. Think before you type!

Yes. While this may be a ‘blinding flash of the obvious’, this principle is violated quite often as we get mechanical while typing code and using the same patterns repeatedly or emulating patterns created by developers in the project we have just joined. Remember, every line of code is a potential cause for a bug, and it also needs to be maintained for several years, perhaps, by someone else other than you. Think very deeply, before naming variables, organizing code into blocks, functions, and whatnots.


The more you think, the better your code will be. Think from the perspective of another developer who may work on the code after you. Would he/she be able to understand the code in order to maintain and extend it?


2. Get rid of the ‘ladder-to-hell’ patterns

This is my favorite. After spending a couple of decades reviewing millions of lines of code from across the world, I can safely say that the most used pattern in the world of programming is the ‘ladder-to-hell’, it is none of the SOLID patterns, and certainly not any of the GOF patterns!

What is the ‘ladder-to hell’? Well, it looks like this:

function ladderToHell(): void{

try{

if( some condtion){

if(anotherCondition){

...

if(one more condition){

.. and on and on

}else if{

...if( some more ){

}else{

...

}

}

}else{

...

}

}else{

//..

}

}else{

//....

}

}catch(e){

}

}

If one reads the else statements from below (or the if-statements from right to left), it looks like a ladder, a ladder that leads us to maintenance hell. Not only it is difficult for new developers to understand, but this pattern also becomes a norm for them, and they start creating their own ladders, and steeper steps to the existing one. And after a while, you will have such ladders across the codebase. The culprit was the first ladder which made itself acceptable.


How to avoid this? Simple. DO NOT WRITE NESTED IF-ELSE STATEMENTS. Period.


Edsger Dijkstra, the pioneer computer scientist, had identified the Goto statement as the fundamental cause of all problems in programming, and that gave birth to the structured programming movement. Languages that were created after 1980 or so, do not have the Goto statement.


The ‘ladder-to-hell’ is this generation of programmers’ version of the Goto statement, and hopefully newer languages will prevent this at the compiler level. One may use Sonar or other static analysis tools in the development cycle to detect and eliminate them.


The ‘ladder-to-hell’ has cousins in form of nested for and while loops. All of them are evil and make the code untestable, and un-maintainable.


Decompose the blocks to functions till the leaf(ves) has just ONE ‘if-else’ block.


Just don’t do it!


3. Do NOT mix I/O operations with computational logic

Write computational logic as ‘pure’ functions, whose results depend only on the arguments that are passed to it. Why? I/O operations are side-effects that make your computational logic difficult, if not impossible, to test using automation.


I have seen thousands of functions that look like the following:

function getSomethingFromStorage(someparam){

let resultSet= select .. where blah condtion of someparam;

for (let something in resultset){

do some heavy computational stuff sometimes running into 100 lines or

more

}

}

This function cannot be tested easily because one will have to mock the DB and build other scaffolding.

Instead it should be written as follows:

function getSomething(someParam){ //control function

let resultSet = getFromDataBase(someParam);

return processResultSet(resultSet)

}

function getFromeDataBase(someParams){ // pure I/O procedure

//SQL call

return resultSet;

}

function processResultSet(resultSet){ //pure computational function

// the returns the same value

// when called with the same resultSet

{

//actual logic

//that returns the final value

}

This makes the processResultSet() function testable using automated unit tests, without mocking and other scaffolding.


4. Keep the length of functions to less than 35


But why?


Programming is about decomposing problems into smaller problems and then composing them back into a whole. Smaller functions are easier to understand than long ones. Long functions also force people to scroll up and down many times to just understand what is going on, causing physical and mental strain.


The rule is very simple. A function should be visible on a single screen in the smallest device that one uses to write programs. At present, it would be an 11-inch laptop where 35-40 lines are easily visible without resorting to any scrolling.


5. Keep the number of conditionals in a function to less than 7

This is also known as restricting the cyclomatic complexity to less than 7. The number of conditionals (if-statements, termination of loops, and so on) should not exceed then number 7. Most modern IDEs can detect cyclomatic complexity and this rule can be enforced while writing code.


Why?


Human short-term memory can hold up to seven pieces of information at any given point in time. And as the number of such items increases, our ability to comprehend decreases at a faster rate. Ideally, as function should be restricted to a cyclomatic complexity of 5!


Additional advantage: the cyclomatic complexity determines the number of unit tests one must write for coverage. Therefore, for a function with a complexity of 5 then one has to write 6 test cases, while a function with a complexity of 20 needs 21 tests. The lower the number the better for unit testing.


And because of the fact that our short-term memory can hold five pieces of information, I will restrict the number of tips in this post to 5 as well!!


P.S.: Please make sure the programs work!!

Read other Extentia Blog posts here!

156 views0 comments

Comments


bottom of page