
Himanshu Khagta/Getty Images
Heuristics for Detecting Bad Code
University of Nantes - LS2N, France
Gerson Sunyé gerson.sunye@univ-nantes.fr

Himanshu Khagta/Getty Images
Building good quality software design is hard and requires experience
We will discuss good design principles and design improvement later
For now, let us learn how to flair bad design
A code smell is a hint that something has gone wrong somewhere in your code.
A sign that something in the code is not good
not necessarily a certainty
not necessarily bad
certainly ugly
| In other words: use your flair to find bad code! |
Comprehensive: when smells affect the whole code
Overweighted: when things get too big
Lack of abstraction: when some more design is needed
OO Gluttons: when too much OO is used
OO Timidness: when the code is not OO enough
Naming: when the problem is the identification
When distinct scents make the whole code smell
object DRY {
def main(args: Array[String]) = {
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
println("I will not repeat myself")
}
}Duplicated code
Comments
Dead code
Two or more code snippets that have a similar behavior
Often a result of «Copy and Paste Programming»
May occur when different developers independently write similar code
extern int array_a[];
extern int array_b[];
int sum_a = 0;
for (int i = 0; i < 4; i++)
sum_a += array_a[i];
int average_a = sum_a / 4;
int sum_b = 0;
for (int i = 0; i < 4; i++)
sum_b += array_b[i];
int average_b = sum_b / 4;Contrary to the principle «Once and Only Once»[1]
Duplicate code makes the system hard to understand and thus hard to maintain:
Any change must be duplicated
The maintainer must be aware of the duplications
Code never lies, comments sometimes do.
Lots of useless comments
Comments that disguise bad naming choices
Code snippets that are unintelligible without comments
Comments that do not correspond to the code
A comment should describe an intention and not explain an action
Too many unnecessary comments overloads the code and make it unreadable
Comments may hide deeper problems
Comments must be maintained along with the code
// convert to meters
a = x * 1000
// average meters driver
avg = a / nCode snippets, variables, parameters, methods, or classes that are never executed
Maintenance after requirement changes or error correction
Code used only for testing
public calculatePrice(Product p) {
double priceAfterTaxes = p.getPrice() * qty * tax;
return p.getPrice() * qty;
}Reduces understandability: lost of time reading dead code
Gives the impression of poor testing (bad code coverage)
When more is not enough

Large Classes
God Classes
Long Methods
Too many lines of code
How many?
No metric fits all cases
6,193 lines of code
Non-commenting source statements: 2,034
Methods: 115
Inner classes: 5

Too many lines of code reduces the Readability/Comprehensibility and thus, the Maintainability and the Debuggability
Often, an excessive number of methods and/or attributes hides a duplication of code.
The class probably hides more than one concept, reducing the Testability and the Reusability
Look for disparate sets of methods and instance variables
| Violates the Single Responsibility Principle |
A class that controls several other classes and has grown beyond all logic to become the class that does everything
The other classes are often «Data Classes»
Often, «God classes» are «Large Classes» (and inversely)
The class uses directly several attributes of other classes
Functional complexity is very high
Class cohesion is low

Hard to test and reuse
Contrary to the «Divide and Conquer» strategy
A class that does everything is not very different from a procedural program

Too many lines of code
How many?
Again, no metric will always be correct
Method parseIntoURI(String uri): about 300 lines.
The longer a method is, the more difficult it is to understand how it works
The more execution paths a method has, the less it is testable (more test data is needed)
The method is the smallest unit of overriding [1]:
It is hard to override complex behaviors
Statements within a method should be at the same level of abstraction
Should I create a new class only for that?
Use primitive types to represent simple domain data: amounts of money, telephone number, social security number, etc.
public class Account {
private String name;
private int accountNumber;
private String email;
private String address;
private int socialSecurityNumber;
private float weight;
private double balance;
}The lack of unities (g, m, sec, etc.) stimulates type mismatch errors
Introduces duplicate code:
For instance, if the socialSecurityNumber is used elsewhere in the code, the verification code will de duplicated
Methods with more that 3 or 4 parameters
Methods that only manipulates data from the parameters
public static URI createHierarchicalURI(String scheme, String authority,
String device, String[] segments,
String query, String fragment) {
if (device != null) {
if (isArchiveScheme(scheme)) {
throw new IllegalArgumentException("archive URI with device");
}
if (SCHEME_PLATFORM.equals(scheme)) {
throw new IllegalArgumentException("platform URI with device");
}
}
return POOL.intern(false, URIPool.URIComponentsAccessUnit.VALIDATE_ALL, true, scheme, authority, device, true, segments, query).appendFragment(fragment);
}A long parameter list often hides a missing abstraction
A method with too many parameters is seldom reusable
Error prone (argument permutation)
Two or more variables or parameters that are always found together
public double distance(double x1, double y1, double x2, double y2);
public double move(double x1, double y1, double x2, double y2);
public void saveThisMoment(int year, int month, int day, int hour, int minutes, int seconds);
public void setBirth(int year, int month, int day, int hour, int minutes, int seconds);Error prone (possible argument inversion)
They hide a lack of abstraction
Examples: Point, DateTime, etc.
Promote code duplication
A responsibility/concern that was split up among several methods
Speculative over layering
Copy-paste coding
Changing a simple feature/property results in several changes in other classes.
public class Account {
public void debit(double debit) throws Exception {
if (balance <= 100) {
throw new Exception("Mininum balance is 100");
}
balance = balance - debit;
}
public void transfer(Account from, Account to, double transferAmount) throws Exception {
if (from.balance <= 100) {
throw new Exception("Mininum balance is 100");
}
to.balance = balance + transferAmount;
}
public void sendWarningMessage() {
if (balance <= 100) {
System.out.println("Balance should be over 100");
}
}
}Time consuming:
modifications in the specific behavior imply several small modifications
Duplicated code:
merge conflicts become more likely
leads to bug introduction (partial changes)
The development of small features takes more time
Eases error introduction
Poor separation of concerns.
A sign that the developer failed to identify single responsibilities
Seep learning curves for newcomers

I see objects everywhere
Too many private methods
Parallel Inheritance Hierarchies
Message Chains
Middle Man
Speculative Generality
Too many private methods
As for large classes, no metric fits all cases
Code that cannot be tested, because it is private
Methods should be public, unless they violate a class invariant.
Indication that a class is doing too many things
Private methods cannot be tested.
They cannot be reused as well
Overenthusiasm to break each functionality as a separate interface
worked as long as the hierarchy stayed small
Every time you make a subclass of one class, you also have to make a subclass of another
Often, classes from both hierarchies share a same prefix and/or suffix
Misunderstanding of the single responsibility principle
Code maintenance and extension becomes harder and harder
Code snippets resembling o.a().b().c().d()
customer.getAddress().getState();
window.getBoundingbox().getOrigin().getX();There is an implicit dependency between the caller and the implementor through a chain of objects
Any change in the chain will impact the caller
The system becomes harder to test
Breaks the Law of Demeter
Over-generalized code in an attempt to predict future needs.
Unused classes, methods, attributes, or parameters.
«What if..» school of design
Against the YAGNI [1] principle
Wrong identification of the variability points
Code becomes hard to understand and maintain
Objects encapsulates (hides) details
Encapsulation leads to delegation
Sometimes, it goes to far!
A class that is doing too much simple delegation instead of really implementing a behavior
If a class performs only delegates work to other classes, why does it exist at all?
Some Design Patterns are Middle Man: Mediator and Facade
I’m not taking too much OO, I’m on a diet!
Data Classes
Feature Envy
Deeply Nested Code
Temporary Attributes
Refused Bequest
Alternative Classes with Different Interfaces
Utility Methods
Procedural programming influence, with procedures and records
Classes with attributes, getters and setters and nothing else
package fr.unantes.test.badcode;
public class Company extends Person {
private String companyName;
private String phone;
public String getCompanyName() {
return this.companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getPhone() {
return this.phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}Breaks encapsulation
Increases coupling
Reduces cohesion
The code is hard to understand and maintain
Procedural programming influence
A method that uses more features of another class than of its own.
Sometimes just a portion of a method
public class EnterpriseGroup extends Group {
public String toString() {
String display;
display = "Group: " + this.nom + "\n\n";
for (int i = 0; i < this.persons.size(); i++) {
display += ((Enterprise) this.persons.get(i)).getCompanyName() + "\n";
}
return display;
}
}Increases coupling
The code is hard to understand and maintain
Deeply nested code, usually loops and/or conditionals
public MappedField getMappedField(final String storedName) {
for (final MappedField mf : persistenceFields) {
for (final String n : mf.getLoadNames()) {
if (storedName.equals(n)) {
return mf;
}
}
}
return null;
}Symptom of methods in the wrong place
The code is hard to understand
They tend to grow more and more become complicated over time
developers keep adding conditions and more levels of nesting
Attributes that are only used by certain methods or under certain circumstances, but remain unused the rest of the time
A developer that didn’t to know where else to put a variable
An algorithm that requires a large number of input variables
The code is hard to understand, maintain, and debug
Why is this attribute null here?
Is it really needed?
Breaks the single responsibility principle:
a single class hiding two conceptual classes
A subclass that only uses some of the features from its parents
Wrong inheritance hierarchy
Violates the Liskov substitution principle
The code is hard to test
Two classes with similar behavior, but with different method signatures
The developer of one class wasn’t aware of the existence of the other
Duplicate code makes the code hard to maintain and understand
A method with no reference (explicit nor implicit) to self or this
The developer doesn’t know where to put the method
The class the methods should belong doesn’t exist
class Date {
private int day, month, year;
public static boolean isLeapYear(int year) {
return ((year % 4 == 0)
&& (year % 100 != 0))
|| (year % 400 == 0);
}
}Violates the single responsibility principle
Decreases cohesion
Decreases testability
Type Embedded in Name
Uncommunicative Name
Inconsistent Names
There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.
Methods that have the parameter’s type in their name
int priceInt = 5;
public void addCourse(Course c) {}Affects maintainability:
the identifier (method, parameter) must be renamed if the type changes
Uncommunicative identifier names
public void process(String data, String data2, String data3);/** Modify the value viewed through the lens, returning a `C` on the side. */
def modp[C](f: B1 => (B2, C), a: A1): (A2, C) = {
val (b, c) = f(get(a))
(set(a, b), c)
}Affects readability
Classes playing a same role, using different suffixes
Project with no terminology
Managers: ClientService, DocumentProcessor, ProductManager
Factories: ClientFactory, CustomerProvider, ProductCreator, ConnectionBuilder
Project with no style nor coherent terminology is harder to understand
Code Smells are heuristics to detect signs of bad design
Smells are not errors and are not necessarily bad