Clean Code ✨

Clean Code ✨

·

8 min read

In the world of software development, writing code that works is only half the battle. The other half is making it easy to understand and maintain. This is the main idea behind 'Clean Code,' a book I have been reading recently.

Clean Code Book by Robert Cecil Martin

Clean Code - Uncle Bob - all lessons - YouTube playlist

Abstract

While reading this book, I came across a wealth of insights on how to write clean and effective code. Here are a few points I'd like to mention, and I'll explain them in this blog:

  • Write your code so that it reads like well-crafted prose or a story.

  • No one can write code as a story at once, but one can always refactor it to make it better.

  • The above two points can be achieved only when the writer cares about the code.

⚡ Relying on comments to make your code read like a story is a poor habit that should be avoided.

Write your code so that it reads like well-crafted prose or a story 🪄

Meaningful Names

Using meaningful variable and function names is the first step towards achieving the aforementioned goal, and almost every developer knows this.

However, this book goes one step further and recommends breaking down your code with function calls and variables so that the reader can follow it like a story.

Let me provide an example to illustrate what I mean by "reading it like a story."

< BAD_CODE #1>

Hard-to-read comparison without proper variable names or function calls

let numCars = 30;
let roadCapacity = 50;
let minTrafficRatio = 0.8;

if ((numCars / roadCapacity) >= minTrafficRatio) {
  console.log('Road is congested.');
} else {
  console.log('Road is flowing smoothly.');
}

< GOOD_CODE #1>

Readable comparison using descriptive variable names and function calls

let numCars = 30;
let roadCapacity = 50;
let minCongestionPercentage = 80;

let trafficPercentage = calculatePercentage(numCars, roadCapacity);

if (isCongested(trafficPercentage, minCongestionPercentage)) {
  reportTrafficCongestion();
} else {
  reportTrafficFlow();
}

function calculatePercentage(part, whole) {
  return (part / whole) * 100;
}

function isCongested(trafficPercentage, minCongestionPercentage) {
  return trafficPercentage >= minCongestionPercentage;
}

function reportTrafficCongestion() {
  console.log('Road is congested.');
}

function reportTrafficFlow() {
  console.log('Road is flowing smoothly.');
}

< BAD_CODE #2>

If you come across this code in a codebase, it won't provide much information about its usage. I'm not saying you won't understand what it does, but you'll never get the context in which it's used.

function findHighestScore(a) {
  let max = a[0];
  for (let i = 1; i < a.length; i++) {
    if (a[i] > max) {
      max = a[i];
    }
  }
  return max;
}

let scores = [88, 73, 92, 69, 81];
let highest = findHighestScore(scores);
console.log(highest);

< GOOD_CODE #2>

This code reads like a story, effectively conveying its context and purpose.

function findHighestScore(studentScores) {
  let highestScore = studentScores[0];
  for (let i = 1; i < studentScores.length; i++) {
    if (studentScores[i] > highestScore) {
      highestScore = studentScores[i];
    }
  }
  return highestScore;
}

let classScores = [88, 73, 92, 69, 81];
let highestStudentScore = findHighestScore(classScores);
console.log("The highest score in the class is " + highestStudentScore + ".");

Abstraction

If you use abstraction, which means hiding low-level details inside a function, your code becomes easier to read. This is like a newspaper which gives a summary of the whole article at the beginning, so you can understand what the article is about without having to read the entire thing.

Using abstraction helps new developers quickly grasp what the code does, and makes it easier to maintain and update the code for everyone on the team.

Here is an example to illustrate the above idea,

< BAD_CODE #3>

function addStudent(studentName, studentAge, studentGrade, className) {
  if (!studentName || !studentAge || !studentGrade || !className) {
    console.log('Error: Invalid input')
    return;
  }

  let classExists = false;
  for (let i = 0; i < classes.length; i++) {
    if (classes[i].name === className) {
      classExists = true;
      break;
    }
  }

  if (!classExists) {
    classes.push({
      name: className,
      students: []
    });
  }

  for (let i = 0; i < classes.length; i++) {
    if (classes[i].name === className) {
      classes[i].students.push({
        name: studentName,
        age: studentAge,
        grade: studentGrade
      });
      break;
    }
  }

  console.log(`Added ${studentName} to ${className}`);
}

< GOOD_CODE #3>

Through the use of suitable abstractions, we can understand what the function addStudent does without needing to know all the little technical things.

function addStudent(studentName, studentAge, studentGrade, className) {

  if (!isValidInput(studentName, studentAge, studentGrade, className)) {
    throwError();     
    return;
  }

  let targetClass = getClassByName(className);
  if (!targetClass) {
    targetClass = createClass(className);
  }

  addStudentToClass(targetClass, studentName, studentAge, studentGrade);
  sendConfirmationMessage(studentName, className);
}


function isValidInput(studentName, studentAge, studentGrade, className) {
  if (studentName && studentAge && studentGrade && className) {
    return true;
  }
  return false;
}

function throwError(){
    console.log('Error: Invalid input');
}  

function getClassByName(className) {
  for (let i = 0; i < classes.length; i++) {
    if (classes[i].name === className) {
      return classes[i];
    }
  }
  return null;
}

function createClass(className) {
  let newClass = {
    name: className,
    students: []
  };
  classes.push(newClass);
  return newClass;
}

function addStudentToClass(targetClass, studentName, studentAge, studentGrade) {
  targetClass.students.push({
    name: studentName,
    age: studentAge,
    grade: studentGrade
  });
}

function sendConfirmationMessage(studentName, className){
    console.log(`Added ${studentName} to ${className}`);
}

Comments

The use of comments signifies a failure to accurately represent the ideas and structure of code in our minds by means of variable names and function calls.

Writing comments is not always beneficial as they tend to lose their relevance over time, especially when the function is modified to include more features.

Furthermore, relying on outdated comments can lead to misunderstandings among developers who may be extending the functionality of the function without updating the comments.

Obsolete comments are worse than no comments.

~ Douglas Crockford


No one can write code as a story at once, but one can always refactor it to make it better ✔️

As developers, we've all been there: while writing a new feature or function, our top priority is making it work, which often leads to a mess of code. This is not necessarily a problem, as our minds tend to focus on getting things working first rather than writing perfect code with nice structures.

However, it's our responsibility to clean up the code once it starts working and make it pleasant to read for our fellow developers and new team members. This involves refactoring the code and improving its readability by using meaningful variable and function names, adhering to coding standards, and breaking down complex logic into smaller, more manageable functions. By doing so, we can not only make our code easier to understand and maintain but also ensure that our projects are scalable and adaptable to future changes.

Here is an opinion that draws inspiration from the book "Clean Code."

No matter how well-written your code is, there will always be someone who finds imperfections or flaws in it based on their own opinions. Instead of trying to please everyone, focus on minimizing the number of "WTFs" (i.e. moments of confusion or surprise) that someone experiences while reading your code. The lower the rate of WTFs per minute, the better your code is.


The above two points can be achieved only when the writer cares about the code 💖

Clean code always looks like it was written by someone who cares.

~ Michael Feathers, author of Working Effectively with Legacy Code

By caring about the code, the programmer ensures that it is free of errors and follows the best practices and conventions of the programming language or framework used, resulting in code that is more efficient, reliable, and maintainable.

The book "Clean Code" taught me that sometimes when you start writing code, the codebase you are working with might not be very good. However, it's always best to try and improve the code and end up with a cleaner codebase.

Software is used in almost everything in our modern world, and it has the power to save lives or cause damage. A tiny mistake in the code that controls a car's brakes can lead to serious accidents. Similarly, an error in the software that manages a nuclear reactor's sensors can result in catastrophic consequences, including loss of lives and money. Therefore, it's crucial for software developers to take their work seriously and ensure that their code is free of bugs and errors.


A humble concern 🌹

Almost every serious profession like doctors and lawyers has an official code of ethics that sets standards for professional conduct, defines ethical responsibilities, and provides guidelines for decision-making in challenging situations.

But the software developers do not have any official code of ethics which is universally recognized and adopted. While there are several informal codes of ethics in the software development community, such as the Software Engineering Code of Ethics and Professional Practice developed by the IEEE-CS/ACM Joint Task Force, they are not widely enforced or legally binding.

This lack of a formal code of ethics has been a topic of debate and concern within the software development industry, as it raises questions about professional responsibility, accountability, and ethical decision-making in the field.

Without a formal code of ethics, the pressure to deliver software quickly can lead to the creation of software that is harmful or unethical, either by design or by accident.

Overall, the absence of a code of ethics in software development can lead to a lack of clarity, accountability, and professionalism, and may create ethical challenges for developers and users alike.


Conclusion 💐

It's important to remember that the software development industry is constantly evolving and changing. While there may be challenges associated with the absence of a formal code of ethics, it's heartening to see that many individuals and organizations are working to establish best practices and guidelines for ethical behavior.

By caring about the code we write and taking the time to refactor it as needed, we can improve the clarity, readability, and overall quality of our work. And by prioritizing ethical decision-making, we can help create a more transparent and accountable software development industry that benefits everyone. So let's keep striving for excellence in our work and doing our part to ensure that software is developed responsibly and ethically, for the good of all.