Earning Readability badge

This article gives you pointers on how to write good, readable code. 

Code readability overview 

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand" - thus spake Martin Fowler.

In effect this is what code readability is about. Can  someone  else understand your code, and most importantly, can they confidently make changes to your code? We, at Geektrust, and the companies we work with, look for clean code, and readability is a very important aspect.

Good, albeit long blog on readability | Comic strip
We will use a deprecated Geektrust coding challenge to explain concepts. You can see the coding challenge here -> Cricket coding challenge

The usual suspects - common pitfalls while writing readable code

Summary of pitfalls:

  1. Long methods with complex logic
  2. If conditions used instead of classes and behaviour
  3. Poor variable and function names
  4. Magic numbers strewn across the code
  5. Incorrect language conventions
  6. Code not modularized

See details and examples below.


1. Long methods with complex logic

Take a look at the code and judge for yourself which is more readable. Getting code to work is not the point. Getting someone else to understand it, is. All the logic in once place with nested if-else makes your code unreadable. Add loop to the if-else and it becomes difficult to understand what's happening.

2. If conditions used instead of classes and behaviour
This is a very common one, and shows lack of sophistication in the code. Moving from writing a bunch of if-else statements and for loops, to leveraging the power of different coding paradigms (Object modeling/Functional) is a basic step-up for a good developer.

This code:
public class TheTieBreaker {

    public static void main(String[] args) {

        // contains players of the team
        HashMap < Integer, Player > team1 = new HashMap < Integer, Player > ();
        team1.put(0, new Player(0, "Kirat Boli", 5, 10, 25, 10, 25, 1, 14, 10));
        team1.put(1, new Player(1, "N.S Nodhi", 5, 15, 15, 10, 20, 1, 19, 15));

        HashMap < Integer, Player > team2 = new HashMap < Integer, Player > ();
        team2.put(0, new Player(0, "DB Vellyers", 5, 10, 25, 10, 25, 1, 14, 10));
        team2.put(1, new Player(1, "H Mamla", 10, 15, 15, 10, 20, 1, 19, 15));

        // contains list of teams
        List < HashMap < Integer, Player >> teams = new ArrayList < HashMap < Integer, Player >> ();
        teams.add(team1);
        teams.add(team2);
        teams.get(0).get(0).setStrike(true);
        teams.get(1).get(0).setStrike(true);

        // contains the total score of each team
        int[] teamScores = new int[2];

        // commentary
        StringBuffer[] commentary = {
            new StringBuffer(""),
            new StringBuffer("")
        };
        Ball ball;
        int ballCount = 0;
        // loops 2 times, for each team
        for (int i = 0; i < 2; i++) {
            List < Ball > balls = new ArrayList < Ball > ();

            // loops 6 times, 1 over
            for (ballCount = 1; ballCount < 7; ballCount++) {
                ball = new Ball();
                ball.setCount(ballCount);
                PlayerAndBall ballOutput = null;

                if (teams.get(i).get(0).isStrike()) {
                    ballOutput = Methods.getBallOutput(ball, teams.get(i).get(0));
                    ball.setScore(ballOutput.getBall().getScore());
                    ball.setPlayerId(ballOutput.getBall().getPlayerId());
                    balls.add(ball);
                    teams.get(i).get(0).addBall(ball);

                    if (ball.getScore() == -1) {
                        teams.get(i).get(0).setOutStatus(true);
                        commentary[i].append("0." + ballCount + " " + teams.get(i).get(0).getName() + " gets OUT.\n");
                        break;
                    } else {
                        teamScores[i] += ball.getScore();
                        commentary[i].append("0." + ballCount + " " + teams.get(i).get(0).getName() + " scores " +
                            ball.getScore() + " runs.\n");
                    }

                    // changes the strike of the batsman depending upon the runs
                    if (!ballOutput.getPlayer().isStrike()) {
                        teams.get(i).get(0).setStrike(false);
                        teams.get(i).get(1).setStrike(true);
                    }
                    if (i == 1 && teamScores[1] > teamScores[0])
                        break;

                } else if (teams.get(i).get(1).isStrike()) {
                    ballOutput = Methods.getBallOutput(ball, teams.get(i).get(1));
                    ball.setScore(ballOutput.getBall().getScore());
                    ball.setPlayerId(ballOutput.getBall().getPlayerId());
                    balls.add(ball);
                    teams.get(i).get(1).addBall(ball);

                    if (ball.getScore() == -1) {
                        teams.get(i).get(1).setOutStatus(true);
                        commentary[i].append("0." + ballCount + " " + teams.get(i).get(1).getName() + " gets OUT.\n");
                        break;
                    } else {
                        teamScores[i] += ball.getScore();
                        commentary[i].append("0." + ballCount + " " + teams.get(i).get(1).getName() + " scores " +
                            ball.getScore() + " runs.\n");
                    }

                    // changes the strike of the batsman depending upon the runs
                    if (!ballOutput.getPlayer().isStrike()) {
                        teams.get(i).get(1).setStrike(false);
                        teams.get(i).get(0).setStrike(true);
                    }
                    if (i == 1 && teamScores[1] > teamScores[0])
                        break;
                }
            }
        }

        // printing the match result
        if (teamScores[0] > teamScores[1]) {
            System.out.println("Lengaburu won by " + (teamScores[0] - teamScores[1]) + " runs");
        } else if (teamScores[0] < teamScores[1])
            System.out.println("Enchai won with " + (6 - ballCount) + " balls remaining.");
        else
            System.out.println("TIE AGAIN.");

        // printing players scores
        System.out.println();
        System.out.println("Lengaburu");
        for (int i = 0; i < teams.get(0).size(); i++) {
            if (teams.get(0).get(i).isOutStatus())
                System.out.println(teams.get(0).get(i).getName() + " - " + teams.get(0).get(i).getTotalScore() + " (" +
                    teams.get(0).get(i).getBalls().size() + " balls)");
            else
                System.out.println(teams.get(0).get(i).getName() + " - " + teams.get(0).get(i).getTotalScore() + "* (" +
                    teams.get(0).get(i).getBalls().size() + " balls)");
        }
        System.out.println();
        System.out.println("Enchai");
        for (int i = 0; i < teams.get(1).size(); i++) {
            if (teams.get(1).get(i).isOutStatus())
                System.out.println(teams.get(1).get(i).getName() + " - " + teams.get(1).get(i).getTotalScore() + " (" +
                    teams.get(1).get(i).getBalls().size() + " balls)");
            else
                System.out.println(teams.get(1).get(i).getName() + " - " + teams.get(1).get(i).getTotalScore() + "* (" +
                    teams.get(1).get(i).getBalls().size() + " balls)");
        }

        // printing commentary
        System.out.println();
        System.out.println("Commentary");
        for (int i = 0; i < commentary.length; i++) {
            if (i == 0)
                System.out.println("Lengaburu innings:");
            else
                System.out.println("Enchai innings:");
            System.out.println(commentary[i]);
        }

    }

}
Could've been written like this:
public class TheTieBreaker {
    private Team batting;
    private Team bowling;
    private Match match;
    
    public TieBreaker(Team batting, Team bowling) {
        this.batting = batting;
        this.bowling = bowling;
        match = new Match(batting, bowling);
    }

    public void playInning(int targetScore, int maxOvers) {
        int score = 0
        for (int over = 0; over < maxOvers; over++) {
            int runs = this.match.simulateOver(score, over, maxOvers, batting.OnStrike());
            score += runs;
        }
        printResult(score, targetScore);
    }

    public void printResult(int score, int target) {
        // logic to decide what need to be the result
        System.out.println(result);
    }
}

public class Match {
    // init state variables
    // constructor etc

    public int simulateOver(int currentScore, int currentOver, int maxOvers, Player player) {
        // logic to simulate an over
        printCommentary(ball, ballScore, player, currentScore);
        // do something
        return runs;
    }
}
Similar output but very differently written.

3. Poor variable and function names
“A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.”
― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship 
Variable and function names are a key part of allowing others to understand intent in your code, so that they can understand your code and edit it.

Some not so good names:
int plyrScr
boolean playerBattingStatus
string nameOfPlayersTeam
Player[] playerObjArray<br>
	
Could have been written as:
int playerScore
boolean isPlaying
string teamName
Player[] players<br>
	
4. Magic numbers strewn across the code
if (password.length() > 7)
	
What is 7 in this example? We'd have to guess. 

Doesn't this make the code more readable?
if (password.length() > MAX_PASSWORD_SIZE)<br>
	
The example above is a little simplistic but it conveys the point.
5. Incorrect language conventions
The idea behind conventions is to help keep the code uniform, and to help others understand it. So a constant in Ruby would be written  LIKE_THIS (as per convention) and a class  LikeThis. Following conventions makes the overall code base consistent and easier to read for others.
6. Code not modularized
The idea with modularization is to break down your application into as small code fragments as possible, arranged neatly across an easily understandable directory layout. If your code is modular, it makes it simpler to figure out how the program operates.

A few things to assert yourself:

  • Do you have duplicate code or how many times are you writing similar code ?
  • How many changes will you need to make if you have to add a new piece of code or modify and existing logic ?
  • Is it easy to navigate through your code ?
The below code:
if (ball.getScore() == -1) {
    teams.get(i).get(1).setOutStatus(true);
    commentary[i].append("0." + ballCount + " " + teams.get(i).get(1).getName() + " gets OUT.\n");
} else {
    teamScores[i] += ball.getScore();
    commentary[i].append("0." + ballCount + " " + teams.get(i).get(1).getName() + " scores " + ball.getScore() + " runs.\n");
}
	
could be written something like this:
if (ball.getScore() == -1) {
    updatePlayerStatus(OUT);
    updateCommentary(ballCount, player, score)
} else {
    updateScore(ball.getScore());
    updateCommentary(ballCount, player, score)
}
// do something else<br>
	
How this helps ? When we delegate the job of updating commentary to a separate method we:
- Avoid duplication of code
- Make it more readable and maintainable
- Any change to the commentary update logic will only require changes to be done in a single method

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us