Clean code

cleancode
cleancode

Contents

  • SOLID principles
  • Programming principles
  • Refactoring techniques
  • Code smells
  • Good / Bad practices


SOLID principles

  • Single responsibility principle
  • Open closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency Inversion principle

 

 Single responsibility principle

solid

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

 

Open-closed Principle

Open-Closed

Objects or entities should be open for extension, but closed for modification.

 

Liskov substitution principle

Liskov

A subclass should override the parent class methods in a way that does not break functionality from a client’s point of view.

 

Interface segregation principle

Interface segregation

Clients should not be forced to implement the full interface, and / or to add dummy methods in the process.

 

Dependency inversion principle

dependency inversion

Entities must depend on abstractions not on concretions.

The high level module must not depend on the low level module, but they should depend on abstractions.


Programming principles

Code reuse

Code reuse is the use of existing software, or software knowledge, to build new software, following the reusability principles.

Examples:

  • Software libraries: Authors of new programs can use the code in a software library to perform these tasks, instead of “re-inventing the wheel“
  • Design patterns: A design pattern is a general solution to a recurring problem.
  • Frameworks: Developers generally reuse large pieces of software via third-party applications and frameworks.

Composition over inheritance / Composite reuse principle

  • Is the principle that classes should achieve polymorphic behavior and code reuse by their composition (by containing instances of other classes that implement the desired functionality) rather than inheritance from a base or parent class.

High Cohesion

  • Cohesion is the degree to which elements of a whole belong together. Methods and fields in a single class and classes of a component should have high cohesion.
  • High cohesion in classes and components results in simpler, more easily understandable code structure and design.

Loose Coupling

  • Two classes, components or modules are coupled when at least one of them uses the other. The less these items know about each other, the looser they are coupled.
  • Low coupling is often a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability
  • A component that is only loosely coupled to its environment can be more easily changed or replaced than a strongly coupled component

Don’t repeat yourself (DRY)

  • Is a principle of software development aimed at reducing repetition of information of all kinds.
  • The DRY principle is stated as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system“.

KISS principle (“Keep it simple, stupid“)

  • The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided.

Pareto principle

  • The Pareto principle (also known as the 80/20 rule) states that, for many events, roughly 80% of the effects come from 20% of the causes.
  • Can be applied to optimization efforts.
  • “20 percent of the code has 80 percent of the errors. Find them, fix them!”

Separation of concerns (SoC)

  • Separation of concerns is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern.
  • A program that embodies SoC well is called a modular program. Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface.
  • The value of separation of concerns is simplifying development and maintenance of computer programs. When concerns are well-separated, individual sections can be reused, as well as developed and updated independently.

You aren’t gonna need it (YAGNI)

  • “You aren’t gonna need it” states that a programmer should not add functionality until deemed necessary.
  • Always implement things when you actually need them, never when you just foresee that you need them.

Refactoring techniques

Why should we refactor?

  • Clean up messes in the code
  • Simplify the code
  • Increase readability and understandability
  • Find bugs
  • Reduce debugging time
  • Improve performance
  • Change the architecture / deal with bad architecture
  • Change code that is very difficult to maintain or test

The important thing isn’t the refactoring – it’s learning to recognize the scent of your own code.

 

Composing Methods

Very frequently the problems come from methods that are too long. Long methods are troublesome because they often contain lots of information, which gets buried by the complex logic that usually gets dragged in.

The key refactoring is Extract Method, which takes a clump of code and turns it into its own method.

Extract method

You have a code fragment that can be grouped together.

Inline method

A method’s body is just as clear as its name.

Put the method’s body into the body of its callers and remove the method.

Introduce explaining variable

You have a complicated expression.

Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.

Remove assignments to parameters

The code assigns to a parameter.

Use a temporary variable instead.

It is much clearer if you use the parameter only to represent what has been passed in, because that is a consistent usage.

 

Moving features between objects

One of the most fundamental, if not the fundamental, decision in object design is deciding where to put responsibilities.

Most of the problems are resolved by simply using Move Method and Move Field to move the behavior around.

Move method/field

A method/field is used by more features of another class than the class on which it is defined.

Extract class

You have one class doing work that should be done by two.

Create a new class and move the relevant fields and methods from the old class into the new class.

Inline class

A class isn’t doing very much.

Move all its features into another class and delete it.

Inline Class is the reverse of Extract Class.

Hide delegate

A client is calling a delegate class of an object.

Create methods on the server to hide the delegate.

Introduce local extension

A server class you are using needs several additional methods, but you can’t modify the class.

Create a new class that contains these extra methods. Make this extension class a subclass or a wrapper of the original.

 

Organizing data

In this chapter I discuss several refactorings that make working with data easier.

Encapsulate field

You are accessing a field directly.

Create getting and setting methods for the field and use only those to access the field.

Replace data value with Object

You have a data item that needs additional data or behavior.

Turn the data item into an object.

Often in early stages of development you make decisions about representing simple facts as simple data items.

As development proceeds you realize that those simple items aren’t so simple anymore.

Replace Magic Number with Symbolic Constant

You have a literal number with a particular meaning.

Create a constant, name it after the meaning, and replace the number with it.

Magic numbers are really nasty when you need to reference the same logical number in more than one place.

If the numbers might ever change, making the change is a nightmare.

Even if you don’t make a change, you have the difficulty of figuring out what is going on.

Encapsulate Collection

A method returns a collection.

Make it return a read-only view and provide add/remove methods.

Replace Type Code with Class

A class has a numeric type code that does not affect its behavior.

Replace the number with a new class.

Replace Type Code with Subclasses

You have an immutable type code that affects the behavior of a class.

Replace the type code with subclasses.

if the type code affects behavior, the best thing to do is to use polymorphism to handle the variant behavior.

This situation usually is indicated by the presence of case-like conditional statements. These may be switches or if-then-else constructs. In either case they test the value of the type code and then execute different code depending on the value of the type code.

Simplifying Conditional Expressions

Conditional logic has a way of getting tricky, so here are a number of refactorings you can use to simplify it.

The core refactoring here is Decompose Conditional, which entails breaking a conditional into pieces.

Decompose Conditional

You have a complicated conditional (if-then-else) statement.

Extract methods from the condition, then part, and else parts.

Consolidate Conditional Expression

You have a sequence of conditional tests with the same result.

Combine them into a single conditional expression and extract it.

Consolidate Duplicate Conditional Fragments

The same fragment of code is in all branches of a conditional expression.

Move it outside of the expression.

Replace Nested Conditional with Guard Clauses

A method has conditional behavior that does not make clear the normal path of execution.

Use guard clauses for all the special cases.

Replace Conditional with Polymorphism

You have a conditional that chooses different behavior depending on the type of an object.

Move each leg of the conditional to an overriding method in a subclass. Make the original method abstract.

Making Method Calls Simpler

This chapter explores refactorings that make interfaces more straightforward.

Often the simplest and most important thing you can do is to change the name of a method.

Naming is a key tool in communication. If you understand what a program is doing, you should not be afraid to use Rename Method to pass on that knowledge.

Rename Method

The name of a method does not reveal its purpose.

Change the name of the method.

Add Parameter

A method needs more information from its caller.

Add a parameter for an object that can pass on this information.

Remove Parameter

A parameter is no longer used by the method body.

Remove it.

Parameterize Method

Several methods do similar things but with different values contained in the method body.

Create one method that uses a parameter for the different values.

Replace Parameter with Explicit Methods

You have a method that runs different code depending on the values of an enumerated parameter.

Create a separate method for each value of the parameter.

Introduce Parameter Object

You have a group of parameters that naturally go together.

Replace them with an object.

Hide Method

A method is not used by any other class.

Make the method private.

Dealing with Generalization

Generalization produces its own batch of refactorings, mostly dealing with moving methods around a hierarchy of inheritance.

Pull Up Field / Method both promote function up a hierarchy, and Push Down Field / Method push function downward.

Pull Up Method/Field

Two subclasses have the same method / field.

Move the method / field to the superclass.

Push Down Method/Field

Behavior on a superclass is relevant only for some of its subclasses.

Move it to those subclasses.


Code smells

What are code smells?

  • According to Martin Fowler, “a code smell is a surface indication that usually corresponds to a deeper problem in the system”.
  • Code smells are usually not bugs—they are not technically incorrect and do not currently prevent the program from functioning. Instead, they indicate weaknesses in design that may be slowing down development or increasing the risk of bugs or failures in the future.

Duplicated code

  • Problem
    • Code repeated in multiple places
  • Reasons for refactoring
    • Violates the DRY principle
  • Solution
    • Extract Method
    • Pull Up Field

Long method

  • Problem
    • Methods with many statements, loops, variables, or a big number of lines
  • Reasons for refactoring
    • A shorter method is easier to read, easier to understand, and easier to troubleshoot.
  • Hints
    • Look for comments
    • Names that require “And” or “Or” usually indicate a method that should be split.
  • Solution
    • Extract Method
    • Introduce Parameter Object
    • Preserve Whole Object

What about the circumstances where the two methods are called together several times?

Large Class

  • Problem
    • A class contains many fields/methods/lines of code
  • Reasons for refactoring
    • Large classes, like long methods, are difficult to read, understand, and troubleshoot.
    • Does the class contain too many responsibilities? Can the large class be restructured or broken into smaller classes?
  • Solution
    • Extract Class
    • Extract Subclass

Try to describe a class in 25 words or less, and not to use “and” or “or”. If you can’t do this, then you may actually have more than one class.

Are there different methods used by different clients? Those might go in separate interfaces. Those interfaces might in turn have separate implementations.

Long Parameter List

  • Symptoms
    • The more parameters a method has, the more complex it is.
  • Reasons for refactoring
    • You might be trying to minimize coupling between objects. Instead of the called object being aware of relationships between classes, you let the caller locate everything.
  • Solution
    • Replace Parameter with Method
    • Preserve Whole Object
    • Introduce Parameter Object

A function can only have too many parameters if some of the parameters are redundant. If all the parameters are used, the function have the correct number of parameters. 

Divergent Change

  • Problem
    • Having to change many unrelated methods when you make changes to a class. For example, when adding a new product type you have to change the methods for finding, displaying, and ordering products.
    • When we make a change we want to be able to jump to a single clear point in the system and make the change.
  • Reasons for refactoring
    • Violates the Single Responsibility Principle (which has to do with separation of concerns).
    • We structure our software to make change easier. Adding a new enum item, for example, should not force us to also add it in 5 switches in different functions of the same class.
  • Solution
    • Extract Class

Feature envy

  • Problem
    • A method accesses the data of another object more than its own data.
  • Reasons for refactoring
    • Methods that make extensive use of another class may belong in another class.
    • Feature envy creates excessive coupling between classes
  • Solution
    • Move method
    • Extract method -> Move method

Strategy and Visitor allow you to change behavior easily, because they isolate the small amount of behavior that needs to be overridden, at the cost of further indirection.

Data clumps

  • Problem
    • Sets of variables usually passed together in multiple places
  • Reasons for refactoring
    • If you always see the same data hanging around together, maybe it belongs together.
  • Solution
    • Extract Class
    • Introduce Parameter Object or Preserve Whole Object

Passing an entire object in the parameters of a method, instead of passing just its values (primitive types), may create an undesirable dependency between the two classes.

Primitive obsession

  • Problem
    • Changing a primitive type or adding a new one will break the function skeleton
  • Reasons for refactoring
    • Code becomes more flexible thanks to use of objects instead of primitives.
    • Having primitives received as parameters can lead to a new code smell => long number of parameters
  • Solution
    • Replace Data Value with Object
    • Replace Type Code with Class

Switch Statements

  • Problem
    • You have a complex switch operator or sequence of if statements.
    • The problem with switch statements is essentially that of duplication.
  • Reasons for refactoring
    • When a new condition is added, you have to find all the switch code and modify it.
    • Violation of Open and close principle
    • Difficult to maintain
  • Solution
    • As a rule of thumb, when you see switch you should think of polymorphism.

Lazy Class

  • Problem
    • Every additional class increases the complexity of a project.
  • Reasons for refactoring
    • A class that isn’t doing enough work to justify its maintenance
  • Solution
    • Inline Class

Striking the right balance between Lazy Class and Feature Envy is sometimes challenging.

Speculative Generality

  • Problem
    • There is an unused class, method, field or parameter.
  • Reasons for refactoring
    • Sometimes code is created “just in case” to support anticipated future features that never get implemented. As a result, code becomes hard to understand and support.
  • Solution
    • Inline Class
    • Remove Parameter

Write code to solve today’s problems, and worry about tomorrow’s problems when they actually materialize.

Data Class

  • Problem
    • A class that contains only fields and crude methods for accessing them (getters and setters). These are simply containers for data used by other classes. These classes do not contain any additional functionality and cannot independently operate on the data that they own.
  • Reasons for refactoring
    • Such classes are dumb data holders and are almost certainly being manipulated in far too much detail by other classes.
  • Solution
    • Move method

Refused Bequest

  • Problem
    • If you inherit from a class, but never use any of the inherited functionality, should you really be using inheritance?
  • Reasons for refactoring
    • If a subclass uses only some of the methods and properties inherited from its parents, the hierarchy is off-killer.
  • Solution
    • Push Down Method
    • Push Down Field

Comments

  • Problem
    • Avoid comments that don’t add anything to code
    • Comments can become stale (out of date) and can be difficult to maintain.
  • Reasons for refactoring
    • They are less reliable than the code itself. Comments can get out of sync with the code if people change the code without updating the comments. Then anyone reading the comments will be mislead. Even if they are in sync, you can never be sure of that, so you have to read the code itself to make sure anyway.
  • Solution
    • Extract Method
    • Rename Method

Adding a comment should be considered a last-resort choice, when what needs to be said cannot be said clearly in the active parts of the programming language.

Can you refactor the code so the comments aren’t required? And remember, you’re writing comments for people, not machines.

Dead Code

  • Problem
    • Variables, functions, classes that are not used.
  • Solution
    • Ruthlessly delete code that isn’t being used.

Type Embedded in Name

  • Problem
    • Variables contain the type in their name. The variable name should convey information about the contents, usage, and/or purpose of the variable.
  • Solution
    • Avoid placing types in method names; it’s not only redundant, but it forces you to change the name if the type changes.

Uncommunicative Name

  • Problem
    • The name of the method does not clearly describe what that method does
  • Solution
    • Rename the function

Read the method’s name to another developer and have them explain to you what it does.

Inconsistent Names

  • Problem
    • Inconsistent names will eventually make the source code harder to read and maintain.
  • Solution
    • Pick a set of standard terminology and stick to it throughout your methods. For example, if you have Open(), you should probably have Close().

Indecent Exposure

  • Problem
    • Classes that unnecessarily expose their internals
    • Violates the encapsulation principles
  • Solution
    • Aggressively refactor classes to minimize their public surface.
    • You should have a compelling reason for every item you make public. If you don’t, hide it.

Long method names

  • Problem
    • Long method names are often an indication that the method is in the wrong class
    • Often these names include prepositions like “from”, “to”, “with”. Names like this can be used as an indication that the method or variable might benefit from being moved to another class, where its name would be shorter.
  • Solution
    • If the function name can be reduced, do it
    • If the function can be moved in a different class, use Extract method


Good / Bad Practices

Class related

  • Static properties are highly questionable. Question yourself strongly about whether or not they are really needed.
  • Most properties/attributes of a class should be private (accessible only by the object instance) or protected, accessible only by an instance of the class or of a derived class (subclass).
  • Public Getter/Setter methods or properties are questionable as they primarily expose object state (which see item #2 above).
  • If a property/attribute of a class is visible to the general public, it should most likely be read-only.
  • For the most part, the state of an object instance should change only by its responding to a method.

Understandability – Good practice

  • Be consistent
    • If you do something a certain way, do all similar things in the same way
    • Favor symmetric designs (e.g. Load – Save) and designs that follow analogies
  • Use Explanatory Variables
    • Write the variable and function names in a way that they express what they are used for
  • Prefer Dedicated Value Objects to Primitive Types
    • Instead of passing primitive types like strings and integers, use dedicated primitive types: e.g. AbsolutePath instead of string.
  • Vertical Separation
    • Variables should be defined close to where they are used. Local variables should be declared just above their first usage.
  • Positive Conditionals
    • Positive conditionals are easier to read than negative conditionals.

Understandability – Bad practice

  • Hidden Logical Dependency
    • A method can only work when invoked correctly depending on something else in the same class, e.g. a DeleteItem method must only be called if a CanDeleteItem method returned true, otherwise it will fail.
  • Dead Comment, Code
    • Delete unused things. You can find them in your version control system

Methods – Good practice

  • Methods Should Do One Thing
    • Loops, exception handling etc. should be extracted in sub-methods
  • Methods Should Descend 1 Level of Abstraction
    • The statements within a method should all be written at the same level of abstraction, which should be one level below the operation described by the name of the function.

Methods – Bad practice

  • Method with Too Many Arguments
    • Prefer fewer arguments. Maybe functionality can be outsourced to a dedicated class that holds the information in fields.
  • Selector / Flag Arguments
    • public int Foo( bool flag )
    • Split method into several independent methods that can be called from the client without the flag.
  • Inappropriate Static
    • Static method that should be an instance method

Maintainability Killers – Good practice

  • Duplication
    • Eliminate duplication. Violation of the “Don’t repeat yourself” (DRY) principle.
  • Magic Numbers / Strings
    • Replace Magic Numbers and Strings with named constants to give them a meaningful name when meaning cannot be derived from the value itself.

 

References

  • Fowler, Martin et al. Refactoring. Addison-Wesley Professional, July 8, 1999.
  • Martin, Robert C. Clean Code. Prentice Hall, August 11, 2008.