Common Code Smells and Heuristics — Part 2
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….
Follow me here as well: https://hubpages.com/@ajhawrites