This series of articles is a compilation of the notes I gathered during my programming bootcamp at Green Fox Academy, last year.
You can read the other articles here:
Code refactoring: process of restructuring computer code without changing or adding to its external behavior and functionality.
Why refactor? Clean code is much easier to read, understand, and maintain, thereby easing future software development and increasing the likelihood of a quality product in shorter time.
· It is obvious to other programmers
· It contains no duplication
· It contains minimal moving parts, like number of classes
· It passes all tests
· It is easier to maintain
Naming conventions should follow Uncle Bob’s guidelines in “Clean Code”:
1. Use Intention-Revealing Names
2. Avoid Disinformation and Encodings
3. Make Meaningful Distinctions
4. Use Pronounceable Names
5. Use Searchable Names
6. Don’t Be Cute/Don’t Use Offensive Words
7. Pick One Word per Concept
8. Don’t Pun
9. Add Meaningful Context as a Last Resort
These are five design principles intended to make software designs more understandable, flexible and maintainable. The theory of SOLID principles was introduced by Robert Martin (Uncle Bob) in his 2000 paper Design Principles and Design Patterns, although the SOLID acronym itself was introduced later by Michael Feathers.
SINGLE RESPONSIBILITY PRINCIPLE
A class should have one, and only one, reason to change.
A responsibility can be defined as a reason for change. Whenever we think that some part of our code is potentially a responsibility, we should consider separating it from the class.
You can avoid these problems by asking a simple question before you make any changes: What is the responsibility of your class/component/microservice? If your answer includes the word “and”, you’re most likely breaking the single responsibility principle.
You should be able to extend a classes behaviour, without modifying it.
This principle is the foundation for building code that is maintainable and reusable.
Open for extension — This ensures that the class behaviour can be extended. As requirements change, we should be able to make a class behave in new and different ways, to meet the needs of the new requirements.
Closed for modification — The source code of such a class is set in stone, no one is allowed to make changes to the code.
How do we achieve this? Through abstractions.
LISKOV SUBSTITUTION PRINCIPLE
Derived classes must be substitutable for their base classes.
Let’s visualize the definition with a case study. Let’s say we have a Rectangle class, and we have a class that extends it, Square. Let’s also say that Rectangle has two methods, setWidth and setHeight, which, well, set the width and height of the rectangle respectively.
The problem is that the behaviour for the two methods differs between the Rectangle and the Square classes.
How do we achieve this? We should design by contract. What this means is that each method should have preconditions and postconditions defined. Preconditions must hold true in order for a method to execute, and postconditions must hold true after the execution of a method.
INTERFACE SEGREGATION PRINCIPLE
Make fine grained interfaces that are client specific.
It is better to have many smaller interfaces, than fewer, fatter interfaces.
For example, let’s say we had an interface called Animal, which would have “eat”, “sleep” and “walk” methods. This would mean that we have a monolithic interface called Animal, which would not be the perfect abstraction, because some animals can fly. Breaking this monolithic interface into smaller interfaces based on role, we would get CanEat, CanSleep and CanWalk interfaces. This would then make it possible for a species to eat, sleep and for example fly. A species would be a combination of roles, instead of being characterized as an animal.
DEPENDENCY INVERSION PRINCIPLE
Depend on abstractions, not on concretions.
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
By depending on higher-level abstractions, we can easily change one instance with another instance in order to change the behaviour. Dependency Inversion increases the reusability and flexibility of our code.
A code smell is a surface indication that usually corresponds to a deeper problem in the system.
Firstly, a smell is by definition something that’s quick to spot A long method is a good example of this.
The second is that smells don’t always indicate a problem. Some long methods are just fine. You have to look deeper to see if there is an underlying problem there — smells aren’t inherently bad on their own — they are often an indicator of a problem rather than the problem themselves.
Examples of code smells:
· Duplicated code
· Long methods
· Long parameters list
· Conditional complexity
· Dead code
· Shotgun surgery (when a change in one class requires changes in other classes)
· Data-only class