Wednesday, October 18, 2023

Common Code Smells and Heuristics - Final Part

 https://ajhawrites.medium.com/common-code-smells-and-heuristics-final-part-6391a095fd5f


This is the final part of the learnings from “Clean Code”, please read my previous articles before reading this one:

Part 0

Part 1 :

Part 2 :


Universal:

Inconsistency: Any code whatever we write should follow a consistent pattern, during the code review sessions. I have found people following inconsistent practices across the code. For example: inconsistent variable naming, inconsistent indentation, inconsistent spacing, incorrect method naming, inconsistent use of quotation marks, and inconsistent variable types.

public class InconsistentCodeExample {

public static void main(String[] args) {
// Inconsistent variable naming and missing semicolon
int num1 = 10;
int Num2 = 20
int result;

// Inconsistent indentation
if (num1 > Num2) {
result = num1 - Num2;
} else {
result = Num2 - num1;
}

// Inconsistent spacing and incorrect usage of the println method
System.out.println("The result is: "+result );
System.out.println("The result is: " + result);

// Inconsistent method naming
int SUM = addNumbers(num1, Num2);
int sum = AddNumbers(num1, Num2);

// Inconsistent quotation marks and missing semicolon
String message1 = "This is a valid message";
String message2 = 'This is an invalid message'

// Inconsistent variable types
int x = 5;
String y = "Hello, World!";
}

// Inconsistent method definition
public static int addNumbers(int a, int B) {
return a + B;
}

// Inconsistent method definition
public static int AddNumbers(int a, int b) {
return a + b;
}
}

Clutter (H): Why have an empty default constructor? It just adds unnecessary stuff to the code — unused variables, functions that do nothing, and comments that don’t help. All of these are just clutter and should be deleted. Keep your source code neat, well-organized, and clutter-free.

public class ClutterExample {
int x; // Unused variable

public ClutterExample() {
// Empty constructor with no implementation
}

public void calculate(int a, int b) {
int result = a + b;
System.out.println("Result: " + result);
}

// Redundant comments
// This method multiplies two numbers
public int multiply(int num1, int num2) {
return num1 * num2;
}

// Unused method
public void unusedMethod() {
System.out.println("This method is never called.");
}

public static void main(String[] args) {
// Inconsistent indentation
int value = 10;
System.out.println("Value: " + value);
}
}

Artificial Coupling: Don’t make things to dependent on each if that’s not required. Think about the relationship and design your classes/interfaces/methods accordingly.

public class Order {
private Inventory inventory;

public Order() {
inventory = new Inventory();
}

public void placeOrder() {
// Place the order
inventory.updateInventory();
}
}

public class Inventory {
public void updateInventory() {
// Update inventory logic
}
}
public class Order {
private InventoryService inventoryService;

public Order(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}

public void placeOrder() {
// Place the order
inventoryService.updateInventory();
}
}

public interface InventoryService {
void updateInventory();
}

public class Inventory implements InventoryService {
@Override
public void updateInventory() {
// Update inventory logic
}
}

Feature Envy: “Feature envy” is a code smell in software development. It occurs when a section of code in one class seems to be more interested in the methods or data of another class, rather than its own.

public class HourlyPayCalculator {
public Money calculateWeeklyPay(HourlyEmployee e) {
int tenthRate = e.getTenthRate().getPennies();
int tenthsWorked = e.getTenthsWorked();
int straightTime = Math.min(400, tenthsWorked);
int overTime = Math.max(0, tenthsWorked - straightTime);
int straightPay = straightTime * tenthRate;
int overtimePay = (int)Math.round(overTime*tenthRate*1.5);
return new Money(straightPay + overtimePay);
}
}
public class HourlyEmployeeReport {
private HourlyEmployee employee ;
public HourlyEmployeeReport(HourlyEmployee e) {
this.employee = e;
}
String reportHours() {
return String.format(
"Name: %s\tHours:%d.%1d\n",
employee.getName(),
employee.getTenthsWorked()/10,
employee.getTenthsWorked()%10);
}
}

Selector Arguments: Avoid selector arguments, this makes your method to do multiple jobs by writing if else condition. Whenever you have to decide between a selector argument vs refactoring your method. Go for refactoring.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SelectorArgumentsExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Using selector arguments to filter elements
List<Integer> evenNumbers = numbers.stream()
.filter(number -> number % 2 == 0) // Selector argument (lambda expression)
.collect(Collectors.toList());

System.out.println("Even numbers: " + evenNumbers);
}
}

Specific to Java:

Avoid Long Import Lists by using wildcards: If you use two or more classes from a package, then import the whole package with import package.*;

Don’t Inherit Constants: Using static imports is recommended over inheriting constants. Instead of inheriting constants, it’s a best practice to use static imports.

public class Constants {
public static final int MAX_VALUE = 100;
public static final int MIN_VALUE = 0;
}
import static package_name.Constants.*;

public class Example {
public static void main(String[] args) {
int value = MAX_VALUE;
System.out.println("Maximum value: " + value);

int min = MIN_VALUE;
System.out.println("Minimum value: " + min);
}
}

Constants VS Enums: Using Enums over constants is recommended in many situations in Java because Enums provide several advantages, such as type safety, readability, and maintainability.

public class ConstantsExample {
public static final int RED = 0;
public static final int GREEN = 1;
public static final int BLUE = 2;

public static void main(String[] args) {
int color = GREEN; // Using a constant
if (color == RED) {
System.out.println("You selected Red.");
} else if (color == GREEN) {
System.out.println("You selected Green.");
} else if (color == BLUE) {
System.out.println("You selected Blue.");
} else {
System.out.println("Invalid color.");
}
}
}
public class EnumsExample {
public enum Color {
RED, GREEN, BLUE
}

public static void main(String[] args) {
Color color = Color.GREEN; // Using an enum
switch (color) {
case RED:
System.out.println("You selected Red.");
break;
case GREEN:
System.out.println("You selected Green.");
break;
case BLUE:
System.out.println("You selected Blue.");
break;
default:
System.out.println("Invalid color.");
}
}
}

Further reading: Learnings from — Clean Code: A Handbook of Agile Software Craftsmanship

Tests:

Insufficient tests: In a test suite, you might wonder how many tests you need. Some programmers rely on a vague sense of "that seems like enough." However, the key is to ensure that your test suite covers all potential points of failure. It's not enough if there are untested situations or unverified calculations. The goal is to be comprehensive and thorough in your testing.

Use a Coverage Tool: Coverage tools help you identify areas in your testing plan that may be incomplete. They highlight parts of your code that haven’t been tested well. In many code editors, you’ll see green lines for tested parts and red lines for untested parts, making it simple to spot code that you might have missed, like if statements where you haven’t checked what happens inside them.

Please read this further: A Tribute to Writers of Unorganized Test Code

Common Code Smells and Heuristics - Part 2

https://ajhawrites.medium.com/common-code-smells-and-heuristics-part-2-91658f3fcff7 


In my previous post, we discussed Common Code Smells and Heuristics pertaining to Comments, the Environment, and Methods/Functions.

In this post we will discuss about others….


Universal:

Multiple Languages in One Source File: Modern programming allows combining multiple languages in a single file. For instance, a Java file may include XML, HTML, YAML, JavaDoc, English, and JavaScript. Similarly, a JSP file can contain HTML, Java, tag library syntax, English comments, Javadocs, XML, and JavaScript. This can be confusing or careless. Ideally, one file should contain only one language, but we may need to use multiple. Still, we should minimize extra languages in our files.

Java with JavaScript:

public class MultiLanguageExample {
public static void main(String[] args) {
System.out.println("This is a Java program.");

// JavaScript code as a string
String javascriptCode = "console.log('This is JavaScript code.');";

// HTML code as a string
String htmlCode = "<html>\n" +
"<head>\n" +
" <title>HTML Example</title>\n" +
"</head>\n" +
"<body>\n" +
" <h1>Hello from HTML</h1>\n" +
"</body>\n" +
"</html>";

// Print the embedded code
System.out.println("Embedded JavaScript code:");
System.out.println(javascriptCode);

System.out.println("Embedded HTML code:");
System.out.println(htmlCode);
}
}

Java with SQL:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JavaWithSQL {
public static void main(String[] args) {
System.out.println("This is a Java program.");

// Embedded SQL code as a string
String sqlCode = "SELECT * FROM customers;";

// Connect to a database and execute the SQL code
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
// Execute SQL code here
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

Java with XML:

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;

public class JavaWithXML {
public static void main(String[] args) {
System.out.println("This is a Java program.");

// Embedded XML code as a string
String xmlCode = "<note>\n" +
" <to>Tove</to>\n" +
" <from>Jani</from>\n" +
" <heading>Reminder</heading>\n" +
" <body>Don't forget me this weekend!</body>\n" +
"</note>";

// Parse the embedded XML code
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xmlCode)));
// Manipulate XML document here
} catch (Exception e) {
e.printStackTrace();
}
}
}

Java with Python:

import org.python.util.PythonInterpreter;

public class JavaWithPython {
public static void main(String[] args) {
System.out.println("This is a Java program.");

// Embedded Python code as a string
String pythonCode = "print('Hello from Python!')";

// Execute Python code using Jython (Python for Java)
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec(pythonCode);
}
}

Java with Bash:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class JavaWithBashScript {
public static void main(String[] args) {
System.out.println("This is a Java program.");

// Embedded Bash script as a string
String bashScript = "echo 'Hello from Bash Script'";

// Execute Bash script using ProcessBuilder
try {
Process process = new ProcessBuilder("bash", "-c", bashScript).start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Obvious Behavior is Unimplemented: Adhering to “The Principle of Least Surprise,” any function or class should incorporate behaviors that align with what another programmer would reasonably anticipate.

public class Calculator {
public int add(int a, int b) {
return a - b; // Subtraction instead of addition
}

public int subtract(int a, int b) {
return a + b; // Addition instead of subtraction
}

public int multiply(int a, int b) {
return a / b; // Division instead of multiplication
}

public double divide(int dividend, int divisor) {
return (double) dividend * divisor; // Multiplication instead of division
}
}
public class Calculator {
public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}

public int multiply(int a, int b) {
return a * b;
}

public double divide(int dividend, int divisor) {
if (divisor == 0) {
throw new ArithmeticException("Division by zero is not allowed.");
}
return (double) dividend / divisor;
}
}
import java.io.*;

public class FileHandler {
private String fileName;

public FileHandler(String fileName) {
this.fileName = fileName;
}

public void writeToFile(String content) {
try {
FileWriter writer = new FileWriter(fileName);
writer.write(content);
writer.close();
} catch (IOException e) {
System.out.println("An error occurred while writing to the file: " + e.getMessage());
}
}

public String readFromFile() {
try {
FileReader reader = new FileReader(fileName);
BufferedReader bufferedReader = new BufferedReader(reader);
StringBuilder content = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
content.append(line);
}
reader.close();
return content.toString();
} catch (IOException e) {
System.out.println("An error occurred while reading from the file: " + e.getMessage());
return null;
}
}
}

Incorrect Behavior at the Boundaries: When developing software, it is crucial to consider boundary conditions. The quality of code and testing is defined by these boundary conditions.

public class RangeChecker {
private int lowerBound;
private int upperBound;

public RangeChecker(int lowerBound, int upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}

public boolean isInRange(int value) {
return value > lowerBound && value < upperBound;
}
}

Fix:

public boolean isInRange(int value) {
return value >= lowerBound && value <= upperBound;
}

Overridden Safeties: Turning off certain compiler warnings (or all warnings!) may help you get the build to succeed, but at the risk of endless debugging sessions. Turning off failing tests and telling yourself you’ll get them to pass later is as bad as pretending your credit cards are free money.

class Machinery {
private boolean safetyEnabled = true;

public void start() {
if (safetyEnabled) {
System.out.println("Machine started safely.");
} else {
System.out.println("Machine started without safety checks.");
}
}

public void enableSafety() {
safetyEnabled = true;
System.out.println("Safety features enabled.");
}

public void disableSafety() {
safetyEnabled = false;
System.out.println("Safety features disabled.");
}
}

public class MachineryDemo {
public static void main(String[] args) {
Machinery machine = new Machinery();

// Starting the machine with safety features enabled (the default state)
machine.start();

// Disabling safety features (overriding safeties)
machine.disableSafety();

// Starting the machine without safety features
machine.start();

// Enabling safety features again
machine.enableSafety();

// Starting the machine with safety features re-enabled
machine.start();
}
}

Duplication: The DRY (Don’t Repeat Yourself) principle emphasizes the avoidance of code duplication, which is a prevalent and significant problem. Maintaining duplicate code is challenging.

public class Employee {
private String firstName;
private String lastName;

public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public void printFullName() {
System.out.println("Full Name: " + firstName + " " + lastName);
}

public void printFullNameWithGreeting() {
System.out.println("Hello, " + firstName + " " + lastName);
}
}

public class DRYViolationExample {
public static void main(String[] args) {
Employee employee1 = new Employee("John", "Doe");
Employee employee2 = new Employee("Jane", "Smith");

employee1.printFullName();
employee1.printFullNameWithGreeting();

employee2.printFullName();
employee2.printFullNameWithGreeting();
}
}

Code at Wrong Level of Abstraction: When code is at the wrong level of abstraction, it means that the code’s structure and organization do not match the level of detail or generality needed for the task at hand. This can result in code that is needlessly complicated, difficult to comprehend, and problematic to keep up-to-date.

public class Vehicle {
private String make;
private String model;
private int year;
private String engineType;

public Vehicle(String make, String model, int year, String engineType) {
this.make = make;
this.model = model;
this.year = year;
this.engineType = engineType;
}

public void startEngine() {
if (engineType.equals("gasoline")) {
System.out.println("Starting the gasoline engine of " + make + " " + model);
} else if (engineType.equals("electric")) {
System.out.println("Starting the electric engine of " + make + " " + model);
} else {
System.out.println("Unknown engine type");
}
}

public void drive() {
System.out.println("Driving the " + make + " " + model);
}

public void stopEngine() {
System.out.println("Stopping the engine of " + make + " " + model);
}
}

public class WrongAbstractionExample {
public static void main(String[] args) {
Vehicle car1 = new Vehicle("Toyota", "Camry", 2022, "gasoline");
Vehicle car2 = new Vehicle("Tesla", "Model 3", 2022, "electric");

car1.startEngine();
car1.drive();
car1.stopEngine();

car2.startEngine();
car2.drive();
car2.stopEngine();
}
}
// Engine interface for different engine types
interface Engine {
void start();
void stop();
}

// GasolineEngine class implementing the Engine interface
class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("Starting the gasoline engine");
}

@Override
public void stop() {
System.out.println("Stopping the gasoline engine");
}
}

// ElectricEngine class implementing the Engine interface
class ElectricEngine implements Engine {
@Override
public void start() {
System.out.println("Starting the electric engine");
}

@Override
public void stop() {
System.out.println("Stopping the electric engine");
}
}

// Vehicle class with a reference to an Engine
public class Vehicle {
private String make;
private String model;
private int year;
private Engine engine;

public Vehicle(String make, String model, int year, Engine engine) {
this.make = make;
this.model = model;
this.year = year;
this.engine = engine;
}

public void start() {
engine.start();
}

public void drive() {
System.out.println("Driving the " + make + " " + model);
}

public void stop() {
engine.stop();
}

public static void main(String[] args) {
Engine gasolineEngine = new GasolineEngine();
Engine electricEngine = new ElectricEngine();

Vehicle car1 = new Vehicle("Toyota", "Camry", 2022, gasolineEngine);
Vehicle car2 = new Vehicle("Tesla", "Model 3", 2022, electricEngine);

car1.start();
car1.drive();
car1.stop();

car2.start();
car2.drive();
car2.stop();
}
}

Base Classes Depending on Their Derivatives: When base classes depend on their derivatives, it means that the parent class relies on specific characteristics or implementations of its subclasses. This can result in a strong interconnection between classes, limiting adaptability, and making it difficult to manage and expand the codebase.

class Vehicle {
public void startEngine() {
System.out.println("Starting the engine of the vehicle.");
}
}

class Car extends Vehicle {
@Override
public void startEngine() {
System.out.println("Starting the car's engine.");
}

public void drive() {
System.out.println("Driving the car.");
}
}

class Motorcycle extends Vehicle {
@Override
public void startEngine() {
System.out.println("Starting the motorcycle's engine.");
}

public void ride() {
System.out.println("Riding the motorcycle.");
}
}

public class DependencyOnDerivativesExample {
public static void main(String[] args) {
Vehicle vehicle1 = new Car();
Vehicle vehicle2 = new Motorcycle();

vehicle1.startEngine(); // Starts the car's engine
vehicle2.startEngine(); // Starts the motorcycle's engine
}
}
class Employee {
private String name;
private int employeeId;

public Employee(String name, int employeeId) {
this.name = name;
this.employeeId = employeeId;
}

public void introduce() {
System.out.println("Hello, I am Employee " + name + " with ID " + employeeId);
}
}

class Manager extends Employee {
private String department;

public Manager(String name, int employeeId, String department) {
super(name, employeeId);
this.department = department;
}

@Override
public void introduce() {
System.out.println("Hello, I am Manager " + getName() + " with ID " + getEmployeeId() + " in the " + department + " department.");
}

public String getDepartment() {
return department;
}
}

class Developer extends Employee {
private String programmingLanguage;

public Developer(String name, int employeeId, String programmingLanguage) {
super(name, employeeId);
this.programmingLanguage = programmingLanguage;
}

@Override
public void introduce() {
System.out.println("Hello, I am Developer " + getName() + " with ID " + getEmployeeId() + " specializing in " + programmingLanguage);
}

public String getProgrammingLanguage() {
return programmingLanguage;
}
}

public class DependencyOnDerivativesExample {
public static void main(String[] args) {
Employee employee1 = new Manager("Alice", 101, "HR");
Employee employee2 = new Developer("Bob", 202, "Java");

employee1.introduce(); // Introduce as a Manager
employee2.introduce(); // Introduce as a Developer
}
}

Too Much Infomation: When a class, method, or interface contains an excessive amount of information, it means that it has become overly complicated, long, or packed with numerous responsibilities and details. This can have detrimental effects on code readability, maintainability, and comprehension, ultimately making the code more error-prone and difficult to manage.

class Employee {
private String name;
private String address;
private String email;
private String phone;
private String jobTitle;
private double salary;
private String department;
private String supervisor;
// ... dozens of other attributes and methods ...
}
public void processCustomerOrder(Customer customer, List<Product> products, List<Discount> discounts, List<Payment> payments, boolean isExpressDelivery, boolean applyTax, boolean sendConfirmationEmail) {
// ... lengthy method body with numerous conditional branches ...
}
public interface BankingOperations {
void deposit(double amount);
void withdraw(double amount);
void transfer(double amount, String recipientAccount);
void checkBalance();
void openAccount(String accountType);
void closeAccount();
// ... many other unrelated banking operations ...
}

Dead Code: Dead code refers to sections of a software program that have been written but are never actually executed when the program runs. This can happen when code becomes obsolete, redundant, or unreachable due to changes in the program’s design or requirements. Dead code complicates the codebase without contributing to the program’s functionality.

public class DeadCodeExample {
public static void main(String[] args) {
int x = 5;
int y = 10;

// This is dead code, as it's never executed
if (x > y) {
System.out.println("x is greater than y.");
} else {
System.out.println("x is not greater than y.");
}

// The following code is live code and will be executed
int result = x + y;
System.out.println("The result is: " + result);
}
}

Vertical Seperation: Variables and functions should be placed near where they are used. Local variables should be declared just before their first use and should have a small vertical scope. We don’t want local variables declared far away from their usage, especially across hundreds of lines.

Private functions should be defined shortly after their first usage. Private functions belong to the class’s scope, but we should still aim to minimize the vertical distance between their invocations and definitions. Finding a private function should be as simple as scanning downward from its initial usage.

public class PoorCodeOrganizationExample {
private int count = 0;

public void processItems() {
for (int i = 0; i < 10; i++) {
// Local variable declared far from its usage
int item = computeItem(i);
// ...
}
}

private int computeItem(int i) {
// Private function defined far from its first usage
return i * 2;
}

public static void main(String[] args) {
PoorCodeOrganizationExample example = new PoorCodeOrganizationExample();
example.processItems();
System.out.println("Total items processed: " + example.count);
}
}
public class CodeOrganizationExample {
private int count = 0;

public void processItems() {
for (int i = 0; i < 10; i++) {
// Local variable declared close to its usage
int item = i * 2;
processItem(item);
}
}

private void processItem(int item) {
// Private function defined shortly after its first usage
System.out.println("Processing item: " + item);
count++;
}

public static void main(String[] args) {
CodeOrganizationExample example = new CodeOrganizationExample();
example.processItems();
System.out.println("Total items processed: " + example.count);
}
}

Inconsistency: Code inconsistency occurs when there are conflicting or contradictory coding practices, patterns, or styles within a software project. Such inconsistencies can lead to confusion, hinder code comprehension, and impact code quality.

Inconsistent naming convention:

public class InconsistentCodeExample {
private int userCount;
private String UserName;

public InconsistentCodeExample() {
this.userCount = 0;
}

public void IncrementUserCount() {
this.userCount++;
}

public void decrementUserCount() {
this.userCount--;
}

public void DisplayUserCount() {
System.out.println("Total User Count: " + userCount);
}

public static void main(String[] args) {
InconsistentCodeExample example = new InconsistentCodeExample();
example.IncrementUserCount();
example.decrementUserCount();
example.DisplayUserCount();
}
}

Inconsistent Indentation:

public class InconsistentIndentationExample {
public void methodA() {
// Code with inconsistent indentation
int x = 5;
int y = 10;
if (x > 0) {
System.out.println("x is positive.");
} else {
System.out.println("x is not positive.");
}
}
}

Inconsistent Comments:

public class InconsistentCommentsExample {
// This method does some stuff
public void doSomething() {
// TODO: Implement this
int x = 5;
// This is a temporary fix
x += 2;
}
}

Inconsistent Bracing Styles:

public class InconsistentBracingExample {
public void methodA() {
if (condition) {
doSomething();
} else
{
doSomethingElse();
}
}
}

To be continued….

Saturday, September 30, 2023

Common Code Smells and Heuristics

 


Before we start learning about code issues, improvements, and rules, let’s see how many IT professionals worldwide rely on code, whether they write it themselves or not (like managers, for example).

As per some stats:

  • There are an estimated 26.9 million professional software engineers in the world as of 2022
  • There are nearly 27 million developers worldwide. This number is expected to exceed 28.7 million by 2024
  • According to industry research firm Janco Associates, more than 4.18 million people are employed as IT professionals in the US

Now, we will go through the section wise code smells and heuristics.


Comments:

Inappropriate information: Don’t put info in comments that should be in other systems like source code control or issue tracking. Comments are for technical notes about the code and design, not for things like change histories or metadata.

/**
* Author: Jane Doe
* Last Modified: 2022-04-20
* Version: 1.2
* Description: This class represents a basic calculator.
* Change History:
* - Version 1.0: Initial implementation
* - Version 1.1: Fixed a bug in the add() method
* - Version 1.2: Added support for subtraction
*/

public class Calculator {

// Calculate the sum of two numbers
public int add(int a, int b) {
return a + b;
}

// Calculate the difference between two numbers
public int subtract(int a, int b) {
return a - b;
}
}

In the text above, it’s saying that the information about changes, history, and when something was last modified is written in a comment. But this information isn’t necessary because it’s already stored in a source code control system like Git.

Cleaner version:

/**
* This class represents a basic calculator.
*/

public class Calculator {

// Calculate the sum of two numbers
public int add(int a, int b) {
return a + b;
}

// Calculate the difference between two numbers
public int subtract(int a, int b) {
return a - b;
}
}

Obsolete comment: Obsolete comments, being outdated, should be avoided. Remove or update them promptly to prevent confusion. Sometime we change the code but not the comments :)

public class ObsoleteCommentsExample {

// Calculate the sum of two numbers
public int add(int a, int b) {
return a + b;
}

// Author: John Smith
// Last Modified: 2020-12-05
// This method is used to add two numbers.
// Change History:
// - Version 1.0: Initial implementation
// - Version 1.1: Fixed a bug in the addition logic (Obsolete)
public int addObsolete(int a, int b) {
// Obsolete comment: The bug was fixed in version 1.1, but the comment was not updated.
return a + b;
}

// Calculate the difference between two numbers
public int subtract(int a, int b) {
return a - b;
}

// Description: This method subtracts two numbers.
// Change History:
// - Version 1.0: Initial implementation
public int subtractObsolete(int a, int b) {
// Obsolete comment: The comment contains unnecessary information and is outdated.
return a - b;
}
}

Redundant comment: A comment is redundant if it describes something that adequately describes itself.

 i++; // increment i

Comments should say things that the code cannot say for itself, example:

 /**
* @param sellRequest
* @return
* @throws ManagedComponentException
*/

public SellResponse beginSellItem(SellRequest sellRequest)
throws ManagedComponentException

Poorly Written Comment: A well-written comment is a valuable addition. When you choose to include a comment, take the opportunity to create a high-quality one. Pay attention to word choice, maintain proper grammar and punctuation, avoid excessive elaboration, and keep it succinct.

public class PoorCommentExample {

// This is a function that adds two numbers
public int addition(int num1, int num2) {
// Here, we add num1 and num2 together
int result = num1 + num2; // The result is stored in the result variable
return result; // We return the result
}
}
public class CommentQualityExample {

// This method calculates the sum of two numbers.
// It takes two integers as input and returns their sum.
public int add(int a, int b) {
// Here, we add the two numbers and store the result in 'sum'.
int sum = a + b;

// Finally, we return the 'sum' as the result of the addition.
return sum;
}

// This method finds the difference between two numbers.
// It subtracts 'b' from 'a' and returns the result.
public int subtract(int a, int b) {
// To calculate the difference, we subtract 'b' from 'a'.
int difference = a - b;

// We then return the 'difference' as the result.
return difference;
}

// This method multiplies two numbers.
public int multiply(int a, int b) {
// Multiply 'a' and 'b' to get the result.
int result = a * b;

// Return the 'result'.
return result;
}
}

Commented out code: This issue frequently arises during code reviews. When questioned, developers often offer excuses like “someone else did it” or “we might need it later.” However, as a firm guideline, it’s best to eliminate commented-out code. You can always retrieve it from the version control system when necessary.

public class CommentedCodeExample {

public static void main(String[] args) {
int x = 5;
int y = 10;

// int result = x + y;
// System.out.println("The sum is: " + result);

// The code below is no longer needed.
// It was used for an earlier feature that has been removed.
// int z = 20;
// int finalResult = result + z;
// System.out.println("The final result is: " + finalResult);
}
}

Environment:

Build Requires More Than One Step: Starting a project should be easy. You shouldn’t need to fetch many code pieces, use complex commands, or hunt for extra files. Just one command to check out the system and one more to build it should suffice.

# Before applying simplification heuristic
git clone <repository-url>
cd project
./setup.sh
./build.sh
# Manually find and copy required JAR files
# Search for and configure XML files from different directories
# After applying simplification heuristic
git clone <repository-url>
./build.sh

Tests Require More Than One Step: Running all unit tests should be simple. You should be able to do it with one click in your IDE or with a single command in your shell. It’s a fundamental and essential task, so it should be quick and easy.

# Before streamlining testing
cd project-directory
./setup-environment.sh
./run-unit-tests.sh
# After streamlining testing
cd project-directory
./run-tests.sh

Functions:

Too Many Arguments: Functions are most effective when they accept a modest number of arguments. The ideal scenario is having zero arguments, followed by one, two, and three. If a function has more than three arguments, it becomes problematic and should be minimized whenever possible. This is because an excessive number of arguments often indicates that the function is handling multiple tasks, which goes against the Single Responsibility Principle (SRP).

public double calculateTotalPrice(double item1, double item2, double item3, double item4, double item5, double item6) {
double total = item1 + item2 + item3 + item4 + item5 + item6;
return total;
}
public double calculateTotalPrice(double[] items) {
double total = 0.0;
for (double item : items) {
total += item;
}
return total;
}

Output Arguments: Output arguments can be confusing. People usually think of arguments as things you put into a function, not things that come out of it. If your function needs to modify something, it’s better to have it change the thing it’s working on, like an object it’s called on.

public void appendToList(int item, List<Integer> outputList) {
// Try to add the item to the output list
outputList.add(item);
}
public List<Integer> createNewListWithItem(int item, List<Integer> inputList) {
List<Integer> newList = new ArrayList<>(inputList);
newList.add(item);
return newList;
}

Flag Arguments: When a method takes a flag as an argument, it often suggests an attempt to perform multiple tasks within the same method. This typically requires the use of conditional statements (if/else) to determine which task to execute.

public double calculateTotalPrice(List<Item> items, boolean applyDiscount) {
double totalPrice = 0.0;

for (Item item : items) {
if (applyDiscount) {
totalPrice += item.getDiscountedPrice();
} else {
totalPrice += item.getPrice();
}
}

return totalPrice;
}
public double calculateTotalPriceWithDiscount(List<Item> items) {
double totalPrice = 0.0;

for (Item item : items) {
totalPrice += item.getDiscountedPrice();
}

return totalPrice;
}

public double calculateTotalPriceWithoutDiscount(List<Item> items) {
double totalPrice = 0.0;

for (Item item : items) {
totalPrice += item.getPrice();
}

return totalPrice;
}

Dead Function: A method that is never called in any part of the code should be promptly removed from the codebase.

public class Calculator {
public int add(int a, int b) {
return a + b;
}

// This method is never called
public int subtract(int a, int b) {
return a - b;
}
}
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
  • Code coverage tools like JaCoCo (for Java), Istanbul (for JavaScript), and Coverage.py (for Python) can help identify code that is not executed by tests, which often indicates dead code.
  • SonarQube: SonarQube is a widely used code quality platform that can identify dead code and provide detailed reports on code quality.
  • FindBugs: FindBugs is a static analysis tool for Java that can detect various issues, including dead code.
  • ESLint and TSLint: For JavaScript and TypeScript projects, ESLint and TSLint can identify unused variables and functions.

More: https://ajhawrites.medium.com/common-code-smells-and-heuristics-fd7e15a8a602

Common Code Smells and Heuristics - Final Part

  https://ajhawrites.medium.com/common-code-smells-and-heuristics-final-part-6391a095fd5f This is the final part of the learnings from “Clea...