Mastering Clean Code: Best Practices for Developers - Master Class 01
Art of writing clean code while building softwares
A lot of us can write code today, but writing clean code is something that not everybody could do without understanding the principles of it.
So as part of this article, let’s understand what are those concepts that we could use to implement clean code and refactor the existing code to clean way.
Using this newsletter I’ve tried a compact version of below book.
Image Ref : https://www.mudbath.com.au/insight/book-club-clean-code-by-robert-c-martin/
Principles of Clean Code
1. Meaningful Names
Descriptive Names
Choose names that reveal intent. Variables, functions, and classes should have meaningful names that convey their purpose.
Poor Way
public class M {
public int[] a(int[] x) {
int s = 0;
for (int i = 0; i < x.length; i++) {
s += x[i];
}
return new int[]{s, x.length};
}
}
Improved Way
public class ArrayProcessor {
/**
* Calculates the sum and count of elements in the input array.
*
* @param numbers an array of integers to be processed
* @return an array where the first element is the sum of the input array and the second element is the count of the input array
*/
public int[] calculateSumAndCount(int[] numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return new int[]{sum, numbers.length};
}
}
Avoid Encodings
Don't use prefixes or suffixes to convey type or scope information. Keep names simple and clear.
Poor Way
public class UserDTO {
private String strName;
private int iAge;
private boolean isActive;
public UserDTO(String strName, int iAge, boolean isActive) {
this.strName = strName;
this.iAge = iAge;
this.isActive = isActive;
}
public String getStrName() {
return strName;
}
public void setStrName(String strName) {
this.strName = strName;
}
public int getIAge() {
return iAge;
}
public void setIAge(int iAge) {
this.iAge = iAge;
}
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
}
Improved Way
public class User {
private String name;
private int age;
private boolean active;
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
2. Functions
Small and Focused
Functions should do one thing and do it well. They should be small, typically no longer than a few lines.
Poor Way
public class OrderProcessor {
public void processOrder(Order order) {
// Validate order
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
// Calculate total
double total = 0;
for (Item item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
order.setTotal(total);
// Save order to database
Database.save(order);
// Send confirmation email
EmailService.sendOrderConfirmation(order.getCustomerEmail());
}
}
Improved Way
public class OrderProcessor {
public void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
saveOrder(order);
sendConfirmationEmail(order);
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
}
private void calculateTotal(Order order) {
double total = 0;
for (Item item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
order.setTotal(total);
}
private void saveOrder(Order order) {
Database.save(order);
}
private void sendConfirmationEmail(Order order) {
EmailService.sendOrderConfirmation(order.getCustomerEmail());
}
}
Descriptive Names
Function names should be descriptive enough to understand what they do without needing additional comments.
Poor Way
public class Calculator {
public double calc(double a, double b) {
return a * b;
}
public double div(double a, double b) {
return a / b;
}
public double add(double a, double b) {
return a + b;
}
public double sub(double a, double b) {
return a - b;
}
}
Improved Way
public class Calculator {
/**
* Multiplies two numbers and returns the result.
*
* @param multiplicand the number to be multiplied
* @param multiplier the number by which to multiply
* @return the product of multiplicand and multiplier
*/
public double multiply(double multiplicand, double multiplier) {
return multiplicand * multiplier;
}
/**
* Divides the dividend by the divisor and returns the result.
*
* @param dividend the number to be divided
* @param divisor the number by which to divide
* @return the quotient of dividend and divisor
*/
public double divide(double dividend, double divisor) {
return dividend / divisor;
}
/**
* Adds two numbers and returns the result.
*
* @param addend1 the first number to be added
* @param addend2 the second number to be added
* @return the sum of addend1 and addend2
*/
public double add(double addend1, double addend2) {
return addend1 + addend2;
}
/**
* Subtracts the subtrahend from the minuend and returns the result.
*
* @param minuend the number from which to subtract
* @param subtrahend the number to be subtracted
* @return the difference between minuend and subtrahend
*/
public double subtract(double minuend, double subtrahend) {
return minuend - subtrahend;
}
}
Single Responsibility Principle
Each function should have a single responsibility or task.
public class UserManager {
public void registerUser(String username, String password, String email) {
// Validate inputs
if (username == null || username.isEmpty() || password == null || password.isEmpty() || email == null || email.isEmpty()) {
throw new IllegalArgumentException("Invalid input");
}
// Hash the password
String hashedPassword = hashPassword(password);
// Save user to database
User user = new User(username, hashedPassword, email);
Database.save(user);
// Send welcome email
EmailService.sendWelcomeEmail(email);
}
private String hashPassword(String password) {
// Simple hash for illustration (not secure)
return Integer.toString(password.hashCode());
}
}
Improved Way
public class UserManager {
public void registerUser(String username, String password, String email) {
validateInputs(username, password, email);
String hashedPassword = hashPassword(password);
saveUser(username, hashedPassword, email);
sendWelcomeEmail(email);
}
private void validateInputs(String username, String password, String email) {
if (username == null || username.isEmpty() || password == null || password.isEmpty() || email == null || email.isEmpty()) {
throw new IllegalArgumentException("Invalid input");
}
}
private String hashPassword(String password) {
// Simple hash for illustration (not secure)
return Integer.toString(password.hashCode());
}
private void saveUser(String username, String hashedPassword, String email) {
User user = new User(username, hashedPassword, email);
Database.save(user);
}
private void sendWelcomeEmail(String email) {
EmailService.sendWelcomeEmail(email);
}
}
3. Bad Comments vs Good Comments
Bad Commenting style
public class Calculator {
// This method adds two numbers
public int add(int a, int b) {
return a + b;
}
// This method subtracts b from a
public int subtract(int a, int b) {
return a - b;
}
// This method multiplies two numbers
public int multiply(int a, int b) {
return a * b;
}
// This method divides a by b
public int divide(int a, int b) {
if (b == 0) {
// If b is zero, throw an exception
throw new IllegalArgumentException("Divisor cannot be zero");
}
return a / b;
}
// This method calculates the factorial of a number
public int factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("Number must be non-negative");
}
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i; // Multiply result by i
}
return result;
}
}
Good Commenting style
public class Calculator {
/**
* Adds two integers and returns the result.
*
* @param a the first integer
* @param b the second integer
* @return the sum of a and b
*/
public int add(int a, int b) {
return a + b;
}
/**
* Subtracts the second integer from the first and returns the result.
*
* @param a the first integer
* @param b the second integer
* @return the difference between a and b
*/
public int subtract(int a, int b) {
return a - b;
}
/**
* Multiplies two integers and returns the result.
*
* @param a the first integer
* @param b the second integer
* @return the product of a and b
*/
public int multiply(int a, int b) {
return a * b;
}
/**
* Divides the first integer by the second and returns the result.
* Throws an IllegalArgumentException if the divisor is zero.
*
* @param a the dividend
* @param b the divisor
* @return the quotient of a divided by b
* @throws IllegalArgumentException if b is zero
*/
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Divisor cannot be zero");
}
return a / b;
}
/**
* Calculates the factorial of a non-negative integer.
* Throws an IllegalArgumentException if the input is negative.
*
* @param n the integer for which to calculate the factorial
* @return the factorial of n
* @throws IllegalArgumentException if n is negative
*/
public int factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("Number must be non-negative");
}
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
}
4. Code Formatting
Consistent Style
Use a consistent coding style to make the codebase uniform and easy to read.
Readability
Format code to enhance readability. Proper indentation, spacing, and line breaks are essential.
5. Error Handling
Exceptions Over Error Codes
Use exceptions to handle errors rather than returning error codes.
Meaningful Exception Handling
Catch exceptions at the right level and handle them appropriately. Avoid empty catch blocks.
Poor Way
public class UserService {
public int createUser(String username, String password, String email) {
if (username == null || username.isEmpty()) {
return -1; // Error code for invalid username
}
if (password == null || password.isEmpty()) {
return -2; // Error code for invalid password
}
if (email == null || !email.contains("@")) {
return -3; // Error code for invalid email
}
// Simulate database save operation
boolean isSaved = Database.save(new User(username, password, email));
if (!isSaved) {
return -4; // Error code for database save failure
}
return 0; // Success code
}
}
Improved Way
public class UserService {
/**
* Creates a user after validating the input.
*
* @param username the username of the user
* @param password the password of the user
* @param email the email of the user
* @throws InvalidInputException if any input is invalid
* @throws DatabaseException if there is a failure saving the user to the database
*/
public void createUser(String username, String password, String email) throws InvalidInputException, DatabaseException {
validateInputs(username, password, email);
saveUserToDatabase(username, password, email);
}
private void validateInputs(String username, String password, String email) throws InvalidInputException {
if (username == null || username.isEmpty()) {
throw new InvalidInputException("Username cannot be null or empty");
}
if (password == null || password.isEmpty()) {
throw new InvalidInputException("Password cannot be null or empty");
}
if (email == null || !email.contains("@")) {
throw new InvalidInputException("Invalid email address");
}
}
private void saveUserToDatabase(String username, String password, String email) throws DatabaseException {
try {
boolean isSaved = Database.save(new User(username, password, email));
if (!isSaved) {
throw new DatabaseException("Failed to save user to the database");
}
} catch (Exception e) {
throw new DatabaseException("An unexpected error occurred while saving the user", e);
}
}
}
// Custom exception classes
public class InvalidInputException extends Exception {
public InvalidInputException(String message) {
super(message);
}
}
public class DatabaseException extends Exception {
public DatabaseException(String message) {
super(message);
}
public DatabaseException(String message, Throwable cause) {
super(message, cause);
}
}
By this time we have covered clean code practices on
Meaningful names
Functions
Comments
Code formatting
Error handling
But there are couple of more topics to work on, Let’s cover those in another newsletter so our article don’t get too big.
By this time you must have got an idea of following clean code practices.
If you really like my content you can subscribe me below.
Youtube Channel - https://www.youtube.com/channel/UCpF3Y8AxzgYZnI8Zcf_G_fg
You can follow me on linkedin here - https://www.linkedin.com/in/suchait-gaurav-944479109/
Github Repo - https://github.com/suchait007