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
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:
- Long methods with complex logic
- If conditions used instead of classes and behaviour
- Poor variable and function names
- Magic numbers strewn across the code
- Incorrect language conventions
- 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:
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
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:
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?
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:
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
- 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