Clean Code Rules in Java - ilya-khadykin/notes-outdated GitHub Wiki

Naming Conventions

1 Intention-Revealing Names

1.1 Why does it exist?

1.2 What does it do?

1.3 How is it used?

1.4 Do not use comments, name should be self-explanatory

Bad:

public class GarageServiceImpl implements GarageService {
    static final int[] GARAGES1 = {1, 7}; // more secure garages
    static final int[] GARAGES2 = {2, 3, 4, 5, 6};

    @Override
    public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
        Integer garage;
        if (car.isClassic()) {
            garage = someoneHasFreeSpace(GARAGES1);
        } else {
            garage = someoneHasFreeSpace(GARAGES2);
        }
     }
}

Good:

public class GarageServiceImpl implements GarageService {
    static final int[] SECURE_GARAGES = {1, 7};             
    static final int[] SIMPLE_GARAGES = {2, 3, 4, 5, 6};

    @Override
    public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {
        Integer garage;
        if (car.isClassic()) {
            garage = findFreeGarageFrom(SECURE_GARAGES);
        } else {
            garage = findFreeGarageFrom(SIMPLE_GARAGES);
        }
     }
}

2 Avoid disinformation

Do not use words that have more than one meaning

3 Make meaningful distinctions

Do not use a different spelling just because the name is already taken

4 Use pronounceable names

It will be easier to remember and use

Bad:

public class Car {
    private String sManufacturer;
    private String sModel;
    private int iYear;
    //is car classic or not?
    private boolean clsscCr;
}

Good:

public class Car {
    private String manufacturer;
    private String model;
    private int year;
    private boolean classic;
}

5 Avoid Encoding

No type encoding is needed, do not use 'I' for interfaces

Bad:

public interface IGarageService {
    int parkCarInFreeGarage(Car pCar) throws FreeGarageIsNotFoundException;
}

Good:

public interface GarageService {
    int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException;
}

6 Searchable names

6.1 Name should be search-friendly

6.2 Single-letter names should ONLY be used as local variables inside short methods

Bad:

@Override            
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {            
    Integer g;
    // Business logic below
}

Good:

@Override            
public int parkCarInFreeGarage(Car car) throws FreeGarageIsNotFoundException {            
    Integer garage;
    // Business logic below
}

7 Class and Object names

7.1 Should have noun phrase names

7.2 Should not be a verb

8 Method names

8.1 Should have verb phrase names

8.2 Method argument names should describe their intent

9 Do not be Cute

9.1 Choose Clarity over Humor

9.2 Say what you mean. Mean what you say

10 Pick One Word per Concept

10.1 One word per one abstract Concept

Bad:

public class GarageControllerImpl implements GarageService {
// ...
}

Good:

public class GarageServiceImpl implements GarageService {
// ...
}

10.2 A consistent lexicon is a great boon to the programmers who use your code

Ex. choosing among:

  • get/fetch/retrieve
  • controller/manager/driver

11 Use Solution Domain names

11.1 Computer Science terms

11.2 Algorithm names

11.3 Design Pattern names etc.

Ex.:

  • AccountVisitor when implemented using Visitor Pattern
  • jobQueue for a Queue dataStructure etc

12 Use Problem Domain names

Where there is no "programmer-ease"

13 Methods & Classes

13.1 The longer the scope of a function, the shorter its name should be

13.2 Long and precise names when used in small scopes

Bad:

    private Integer findFreeGarageFrom(int[] garages) {
        for (int garageNumberToCheckIfItsFree : garages) {
            if (free(garageNumberToCheckIfItsFree)) return garageNumberToCheckIfItsFree;
        }
        return null;
    }

    private boolean free(int garageNumber) {
        return garagesList.get(garageNumber) == null;
    }

Good:

    private Integer findFreeGarageFrom(int[] garages) {
        for (int garage : garages) {
            if (isGarageFree(garage)) return garage;
        }
        return null;
    }

    private boolean isGarageFree(int garage) {
        return garagesList.get(garage) == null;
    }

13.3 The longest function names should be given to those functions that are called from just one place

Ex.:

  • Long Method/Class scope = Short name
  • Short scope = Long name

14 Variables & Parameters

14.1 The length of a name should correspond to the size of its scope

14.2 Local variables of a short method or small block can have short names

14.3 Global variables should have long and self-descriptive names

14.4 Single-letter names should be used only for:

  • Counter variables for simple for loops
  • Exception instances in catch blocks
  • Arguments of very short functions

Ex.:

  • Long Variable scope = Long name
  • Short Variable scope = Short name

Summary

  • choosing good names requires good descriptive skills
  • don't be afraid to Rename
  • strive for Readability
  • use refactoring tools

Functions/Methods

Functions help us achieve:

  • Mudularity
  • High degree of reusability

Rules for writing good functions

Rule 1 - They should be small

Rule 2 - They should be smaller than that

How short should a function be?

  • Optionally readable
  • Easy to navigate
  • DRY (Don't repeat yourself)
  • Testable

Average functions should be 4 lines long

Blocks and Indenting

Conditional should be one line long

Do one thing

  • a function should do one thing
  • it should do it well
  • it should do only this

One level of abstraction

  • all code within the function should be at the same level of abstraction
  • the stepdown rule (each level of abstraction should introduce the next)

Descriptive Names

  • don't be afraid to use a long name
  • a long descriptive name is better than a short enigmatic name
  • a long descriptive name is better than a long descriptive comment

No side-effects

  • side-effects are lies
  • if a function promises to do something, it should not do other hidden things
  • Choose/modify the function name to have an inclusive name of everything it does

Command Query Separation

Functions should:

  • do something
  • return something
  • not do both

Prefer Exceptions to returning error codes

  • use exceptions, not error codes (error codes lead to deeply nested structures)

Error handling is one thing

  • functions should do one thing
  • error handling is one thing

try catch block should be the first line in function and there should not be any extra line before of after it

How do you write functions like this?

  • start with long functions
  • refactor
  • shrink methods and re-order them
  • break out into new Classes
  • Adhere to all rules discussed

How to extract functions during refactoring?

  • each block - separate function
  • each code section - separate function
  • long conditions - separate function
  • even only one line of code can be extracted

Function parameters

  • small number of parameters
  • no boolean flags as parameter
  • do not pass nulls and do not return a null
  • parameters for input - not for output

Small number of parameters

  • no parameters is the best case
  • two or three parameters are fine (even though it's hard to read the code with 3 params)
  • we should never use more than three parameters because it's unreadable

Function Side Effects

Side effect happens when function changes external state:

  • same function call gives another result
  • causes depending function to cascade side effects

Temporal Coupling

  • begin-commit
  • open-close
  • start process-join (finish)
  • new-delete

This approach is a source of errors because:

  • it's easy to forget to close/commit
  • order is important
  • increased logic complexity

It's better to encapsulate that logic in one function and use it throughout the Class\module

Function Structure

  • switch statement
  • structured programming
  • formatting

Switch statement and cases

  • missed polymorphism
  • dependency Inversion Principle violation
  • Open/Closed Principle violation

Right way:

  • use polymorphic interface instead
  • use Map instead (for simple cases)

Structured programming

  • one entry point
  • one exit point
  • no GOTO

Errors:

  • multiple return statements
  • break statement in the middle of a loop

Formatting

  • file size - keep small
  • horizontal length - no need to scroll

Refactoring with IntelliJ IDEA

  • Extract to constant - Ctrl+Alt+C
  • Ctrl+Alt+N - Refactor | Inline | Inline...
  • Ctrl+Alt+P - Refactor | Parameter...
  • Ctrl+Alt+M - Refactor | Extract | Method...
  • Ctrl+Shift+Down - Move Statement Down
  • Shift+F6 - rename occurrences

Comments

Comments do not make up for bad code

  • Comments lie
  • if the code is unclear - rewrite it

Comment is a failure

  • failure to express yourself in code
  • should say thing the code cannot say
  • should be reserved for technical notes
  • avoid non-public java docs comments

Good Comments

  • Legal Comments
  • Informative Comments
  • Explanation of Intent

Do not Repeat Yourself (DRY)

How does duplication arise?

  • imposed duplication (when you are required to leave duplicate information in comments, database class mirrors etc)

How to avoid?

  • use code generators

  • use post-processors

  • reserve comments for high-level explanation

  • inadvertent duplication (when developers do not realize they are duplicating code)

How to avoid?

  • code review

  • impatient duplication (when developers understand that they are duplicating code, but duplicate it regardless)

How to avoid?

  • team culture

  • education

  • regular code reviews

  • interdeveloper duplication (during team work)

How to avoid?

  • proper project structure
  • code analysis tools
  • collaboration

DRY - Exceptions

  • performance reasons
  • reduce coupling
  • unit/integration tests
  • auto-generated code

Error Handling

Errors must be handled where they occur

To handle an error, detailed information on the error must be provided

Use Exceptions Rather Than Return Codes

Write your try-catch-finally statement first

  • a program can abort at any point in try and resume in Catch
  • Try blocks are like transactions
  • Write tests that force exceptions before implementing handlers

Use Unchecked Exceptions

  • Checked exceptions violate the Open/Closed Principle and the Dependency Inversion Principle
  • Checked exception cause cascading changes when a new exception is introduced to the low level functions

Provide Context with Exceptions

  • Exceptions should provide enough context to determine the source and location of an error
  • Use informative error messages
  • Mention the operation that failed and the type of failure
  • Throw very Specific Exceptions

Code Quality

Cyclomatic Complexity

Indicates the complexity in the program

The number of linerly-independent paths within a section of code.

Affects other metrics, like Maintainability Index.

Cyclomatic Complexity ranges:

  • under 5 - Good
  • 5-10 - Ok
  • 10+ - Too complex, needs refactoring

Tools to track: SonarQube

Lack of Cohesion in Methods

Reflects the Design Quality.

A measure of the correlation between the methods and the local instance variables of a class.

High cohesion indicates good class subdivision.

Tools to track: IDE plugins

  • Java - MetricsReloaded (IntelliJ IDEA)

Coupling between Objects

Reflects the Design Quality.

The number of classes coupled to a given class.

Disadvantages of higher Coupling:

  • a change forces a ripple effect
  • harder to reuse or test

Tools to track: IDE plugins

  • Java - MetricsReloaded (IntelliJ IDEA)

Code Coverage

A metric for finding untested parts of a codebase.

The degree to which the source code of a program is executed when a particular test suite runs.

  • the higher the better
  • does not guarantee the effectiveness of tests
  • should be combined with appropriate assertions

Tools to track: SonarQube

Unit Tests Success Percentage

Successful unit tests during a give Unit Test execution

This should ideally be maintained at 100%

If tests fail:

  • fix the code to pass the unit tests
  • update the unit test
  • remove the obsolete ones and add new tests

Tools to track: SonarQube

Duplicate Code Percentage

Percentage duplicate code structures in the code base

A statement, a block of code, or an entire file.

Code duplication can lead to maintenance nightmares, poor refactoring, and logical contradictions

Tools to track: SonarQube

Technical Debt

Additional development effort needed when code that is easy to implement in the short run is used instead of applying the best overall solution.

Caused by a failure to:

  • document system architectures
  • provide adequate code coverage in QA processes
  • implement standards and conventions
  • properly understand the technologies leveraged
  • refactor code base or architecture to meet changing needs

Strategic and Non-Strategic Tech Debt:

  • Strategic: trade-offs are made in a proactive way and are tracked
  • Non-Strategic: a result of process deficiencies and bad code quality

Tools to track: SonarQube

Percentage comment

Density of comment lines

Density = Comment lines / (LOC + Comment lines) * 100

Use Public documented API % instead

Tools to track: SonarQube

Rules (static code) Compliance

  • a single number that indicates the violations
  • based on specific language in static code analysis tool
  • recommended values must be closer to 100%

Tools to track: SonarQube

Summary: Code Quality Metrics

  • no single metric alone can depict the code quality fully
  • setup the metrics in the IDE to be more effective
  • favor tracking trends over absolute numbers
  • use shorter tracking periods
  • change metrics when they stop driving change