I have been working on a new course: Maintainable code – 31 concepts to help you achieve it
It seems to be taking me quite a while to finish it.
But I found that going through the content daily, is useful to write maintainable code, even if I am familiar with the concepts, since it’s my course.
The reason for this is that it’s not easy to have so many concepts in mind.
Some of them fade out, even if you know them.
So I created a cheat sheet for these concepts.
Every day, before writing code, I go through this cheat sheet.
It’s kind of a warm up before sport, but for programming.
I thought it might be useful for you also.
- Maintainable code is easy to understand code and code with plenty of unit tests.
- Maintainability comes in small packages. Keep functions and classes small.
Do one thing in a method and class.
Aim for 5 lines per function and 50 lines per class.
Indirection is the ability to reference something using a name instead of the value itself.
Use indirection to replace code with a method call.
- Extract method and extract class
The most important refactoring techniques for writing maintainable code.
- Beautify code
Replace hard to understand code with a well named method that explains what the code does.
Works especially well with conditionals (the part between the parenthesis, after the if).
- Take advantage of naming things to express intent
Naming things allows you to express yourself almost with free speech.
Err on the side of of being verbose rather than cryptic.
Keep often used public method names small thou.
- Less parameters is better than more parameters
Use “introduce parameter object” to make a data class that holds the parameters together.
- Single responsibility principle extended
.1 A class should do only one thing.
.2 A function should do only one thing.
.3 A row of code should do only one thing.
- Low coupling, high cohesion
You classes should be coupled, or use, or know about other classes as little as possible.
The things that happen in your class should be related to each other as much as possible.
- Command and query separation
There are two types of functions:
Queries are getters or methods that calculate something. They only return and don’t change the state of the system.
Commands change the state of the system and don’t return anything.
- Tell don’t ask
Avoid asking objects what their state is and tell them what to do directly.
- The law of Demeter
You may only call methods on objects that were available when your function started or that you created.
You may only call methods on: parameters, your class properties, globals and on objects you created.
—-Object Oriented programming concepts—-
- Encapsulation is a friend
Use encapsulation making methods and properties private with no setters and getters as much as possible.
Hide your state.
Make your objects smart.
- Abstraction (modeling) is a friend
Abstract or model the real world in your code.
- composition is a friend, inheritance is not
Use composition, where a class has an instance variable of another class.
Try to stay away from inheritance except for very simple cases.
Inheritance is too rigid.
- Polymorphism is not always a friend
Don’t use polymorphism if it leads to complex code.
- Data objects are a thing and they are ok
There are two type of objects:
.1 Classic objects that model the real world and encapsulate their state. They are smart objects.
.2 Data objects (or data classes) that only hold data and maybe some basic data related functionality. They expose their state. They are dumb objects.
Data objects have their use even if they seem to break OOP principles.
—-Programming patterns concepts—-
- Know about the pattern types
- Creational patterns, like builder, factory and singleton
- Structural patterns like Adapter and proxy
- Behavioral patterns like Null Object and Observer
- Concurrency patterns like Coroutine and thread pool
- Architectural patterns like MVC, Client-Server and Inversion of Control
- Use simple patterns
It’s ok to use or recognize simple patterns like singleton, builder, a wrapper, observer, MVC.
- Stay away from complex patterns
Complex patterns can kill your project.
Especially poorly implemented complex patterns.
—-Ways of writing maintainable code—-
- Always use TDD
Write a small test, just enough for it to fail.
Write code just enough for the test to pass.
Write a small test, just enough for it to fail.
And so forth.
- When not using TDD use High level first
Write the high level algorithm, with function calls to non existing functions, that you implement later.
- Use Continuous refactoring
Refactor when reading code.
Refactor when changing code.
Refactor when you are not even at your PC.
—-Areas of programming that will help you write maintainable code—-
- SOLID programming
S – The Single-responsibility principle – a class should only do one thing – great concept
O – Open close principle – open for extension, closed for modification – be careful so you don’t implement complexity to follow it
L – Liskov substitution – Child classes should be able to do everything their parent does – decent concept, proof that inheritance is evil
I – Interface segregation principle – many client specific interfaces are better than a general one – decent concept
D – Dependency inversion principle – depend upon abstraction not concretion – I disagree with it. use concretions unless you have a specific reason not to. Concretions are more familiar.
- Clean code
.1 Keep functions and classes very very small
—Give proper names to your methods variables and classes—
.2 Methods should start with a verb
.3 Classes and variables are nouns
.4 Boolean variables and methods should start with “is”
.5 Verbose vs Public methods: Err on the side of being verbose except for often used public methods and classes or public methods and classes with similar names
.6 Keep the parameter number small
.7 Follow your IDE guide about the line of code length
.8 Beautify your code, especially your predicates, the part inside IFs
.9 Don’t use comments, express yourself through code
.10 Exceptions are fine if your methods are small; don’t use checked exceptions
- Object calisthenics
.1. Only One Level Of Indentation Per Method – This forces you to Use Extract Method a lot
.2. Don’t Use The ELSE Keyword – Hard one. You will have to rely on early return and Null object a lot. Introducing a new variable and polymorphism I think bring too much complexity to be good solutions.
.3 Wrap All Primitives And Strings – the idea is to encapsulate your primitives into objects. This might draw behavior and turn them in to smart objects and takes the defense against primitive obsession to the extreme.
.4 First Class Collections – A class that contains a collection should have no other properties.
.5 One dot per line – this would be one -> per line in PHP.
So basically don’t use methods and properties on objects you obtain from other objects.
This prevents train wrecks or Message chains.
It also forces you to follow the law of Demeter.
It doesn’t mean you should just go on the next line.
.6 Don’t Abbreviate – a decent naming advice. Err on the side of being verbose when naming things.
.7 Keep All Entities Small – keep classes under 50 lines. Maintainability comes in small packages.
.8 No Classes With More Than Two Instance Variables – Hard one. If you only have two instance you will be forced to keep your class small. Also your cohesion will go through the roof. You will end up with a lot of classes thou. Tons of well named methods are a good thing but this might be artificial and counter productive when applied to classes.
.9 No Getters/Setters/Properties – Hard one. Maximum of encapsulation. Everyone hides their state from the world. Data classes should be an exception. It also promotes Tell don’t ask and they law of Demeter.
- KISS – Keep it simple, stupid.
The simpler your code is the more maintainable it is.
Complexity is expensive.
Complexity kills maintainability.
You Ain’t Gonna Need It
Do the simplest thing that could possibly work.
Don’t implement things that might be useful someday.
Don’t prepare for things to come because they might not come.
Don’t repeat yourself.
Use extract method.
- 80/20 rule
Or the Pareto principle.
80% of consequences come from 20% of the causes.
80% of functionality comes from 20% of code.
So it’s worth writing that code first and getting feedback.
80% of bugs come from 20% of functionality.
So it’s worth refactoring and testing more there.
- Code smells
This is just a cheat sheet. I did not explain these in the course. You need to read Refactoring by Martin Fowler to see the proper explanation and code examples.
30.1 Duplicated Code- Use Extract Method
30.2 Long method – Use Extract Method
30.3 Large Class – Use Extract class
30.4 Long parameter List – Use Introduce Parameter Object or Replace Parameter with method
30.5 Divergent Change – One class is changed in two different ways. Use extract class
30.6 Shotgun surgery – a change requires a lot of little changes all over the place. Reorganize code for more cohesion. Use “Move method”.
30.7 Feature envy – a method is more interested in another class rather than it’s own “this” class. Use “Move method” or Extract Method.
30.8 Data Clumps – Some fields seem to naturally group together. Use Extract Class to put them in the same class or Introduce Parameter Object.
30.9 Primitive obsession – Using a primitive that should be better of in a class, usually because of fear of objects. Use Replace Data Value with Object.
30.10 Switch statement – use polymorphism – I disagree with this one; leave the switch alone, just use Extract class to make it small and obvious
30.11 Parallel Inheritance Hierarchies – every time you change one subclass you have to change another parallel subclass. Use Move method
30.12 Lazy class – A class that isn’t doing enough to be worth having. Usually a side effect of refactoring. Use Inline Class.
30.13 Speculative Generality – Make complex generalizations to support use cases that aren’t requested yet. Use YAGNI and don’t do it.
30.14 Temporary Field – an object in which an instance is set only in certain circumstances; Use extract class and Introduce Null Object
30.15 Message chains – An object asks one object for another object and another. Also called train wrecks. Use Hide delegate or Extract method.
30.16 Middle man – The opposite of message chains. Too much encapsulation leads to a lot of middle men hiding the chain. Use Remove middle man or Inline Method if possible.
30.17 Inappropriate Intimacy – When a class know too much about another class. Use move method, move field, extract class to increase cohesion.
30.18 Data class – a class with it’s state expose and little behavior. I disagree with this being a code smell, Data classes are fine.
30.19 Refused Bequest – A class refuses functionality from it’s parent. Breaks Liskov substitution. Use composition over inheritance.
30.20 Comment – Comments mean you failed to express yourself with code. Use extract method and take advantage of naming things to express yourself.
- Refactoring techniques
31.1 Extract method – the most important refactoring technique. Basic use of Indirection applied to code using functions.
31.2 Inline method – opposite of extract method. This should be used very rarely.
31.3 Replace temp with query – a temp variable is assigned from a simple query and is getting in the way; replace it with the query everywhere. Use with caution, it doesn’t always apply.
31.4 Introduce Explaining variable – put an expression value inside a variable and take advantage of the variable name to explain what you are doing; consider if it would not be better to use extract method which would have other advantages also, like increase cohesion and movement towards SRP.
31.5 Split temporary variable – a variable is assigned more than once, not inside a loop or collecting a temporary variable; split it into multiple variables with proper names
31.6 Remove assignments to parameters – Don’t assign values to parameters; it introduces confusion; use a temporary variable instead
31.7 Replace method with method object – “Long methods is where objects go to hide” – Uncle Bob; replace a long method with an object, after all it is big enough to have a proper state and private methods; do this when extract method is not more convenient
31.8 Substitute Algorithm – Replace an algorithm with another one that is clearer
—-Moving features between objects—-
31.9 Move Method – A method is using another class more than it’s own -> it suffers from feature envy; move it to the class it is envious upon
31.10 Move Field – A field is used by another class more than it’s own -> it is the focus of envy; move it to the other class that uses it more
31.11 Extract Class – When a class does too much extract a class from it; the equivalent of extract method for classes
31.12 Inline class – the reverse of extract class; be careful with this, it usually is a step in the wrong direction
31.13 Hide Delegate – Encapsulate to avoid train wrecks, or Message chains, even if just using wrappers. From object A you get Object B from witch you get object C to call method x(). Encapsulate the state of B. Add to it method x() even if it is just a wrapper for C.x().
31.14 Remove middle man – the opposite of Hide Delegate; you find the right balance between the two in time, with refactoring
31.15 Introduce foreign method – An external class you can’t modify needs an additional method/functionality; create the method in your class, with an instance to the external class as the first parameter
31.16 Introduce Local extension – an external class you can’t modify needs more functionality; create a wrapper class
31.17 Self encapsulate field – add getters and setters
31.18 Replace Data Value with Object – You have a data item/primitive/language class that needs additional data or behavior. Turn the data into an object with that data as a member.
31.19 Change Value to Reference – You have a class with many equal instances. Replace those instances with a single object referred by everyone. Instead of multiple new Year(2020) use only one factory for example, that returns the same Year 2020.
31.20 Change Reference to Value – The opposite of Change Value to reference. When a single instance is awkward to work with.
31.21 Replace Array with Object – An array has elements that mean different things -> low cohesion. Turn the array into an object with a field for each element.
31.22 Duplicate Observed Data – You have data in the GUI that you need in your domain. Copy the data in the domain and create an observer to synchronize. Kind of sketchy and complex, use with caution.
31.23 Change Unidirectional Association to Bidirectional – Two classes use each other’s features but only one has a reference. Create a reference in the second class.
31.24 Change Bidirectional Association to Unidirectional – The opposite of “Change Unidirectional Association to Bidirectional”. When a bidirectional association is not needed.
31.25 Replace Magic Number with Symbolic Constant – Never use numbers and strings in code as types or special meaning. Use constants instead.
31.26 Encapsulate field – Make fields private and add getters and setters. Basically the same with 17. Self encapsulate field
31.27 Encapsulate Collection – Instead of returning a collection, return a read only view and provide add remove methods
31.28 Replace Record with Data Class – Put records from APIs or Databases into Data Classes with the proper fields.
31.29 Replace Type Code with Class – Instead of keeping Strings and Numbers for certain types like Blood Type move them into their own Blood Type class
31.30 Replace Type Code with subclasses – subclasses as in classes that extend the main class; be careful thou because inheritance can turn against you; instead of Employee type Developer int 1 and type Manager int 2, Developer and Manager extend Employee
31.31 Replace Type code with State/Strategy – Like the last one but don’t extend directly employee, create an EmployeeType extended by Developer and Manager, used by Employee with composition.
31.32 Replace Subclass with Fields – the opposite of the last one
—-Simplifying Conditional Expressions—-
31.33 Decompose conditional- same as beautify predicate; move predicates/conditionals/the part inside if() into well named methods
31.34 Consolidate Conditional Expression; same thing happens after a number of conditionals – eg all return 0; extract them inside a well name method; extract method and beautify code to the rescue
31.35 Remove Control Flag – you have a variable that is acting as a control flag for a series of Boolean expressions. Use a break or return instead.
31.36 Replace Nested Conditional with Guard Clauses – basically return early; get the exception cases out of the way with returns to avoid complex conditionals
31.37 Replace Conditional with Polymorphism – better don’t; it usually doesn’t justify the added complexity
31.38 Introduce Null Object – instead of checking for null pass around Null Objects; Null objects encapsulate the behavior of what should happen when the object would be null; eg: print ~No results~; kind of dangerous because it might hide errors; also Nobody Expects the Null Object; also if you use inheritance you might end up with weird behavior
31.39 Introduce assertion – express an expected state with an assertion to ease debug
—-Making Method Calls Simpler—-
31.40 Rename Method – When the name of the method doesn’t reveal it’s purpose, rename it
31.41 Add Parameter – add a param when needed – kind of basic for a refactor technique, but here it is anyway
31.42 Remove Parameter – remove parameters that are no longer needed
31.43 Separate Query from Modifier – A method that returns a value also changes the state of the object; a clear violation of Command and Query separation; create two methods, one for the query, one for the command
31.44 Parameterize Method – several methods do similar things but with different values inside the method of the body; replace them with a value that uses a param; increaseHitByTenPercent(); increaseHitByTwentyPercent(); replace them with increaseHit(percentage)
31.45 Replace Parameter with Explicit Methods – a parameter controls the flow of your method with conditionals; create a method for each value of the parameter; the opposite of last one; use according to context
31.46 Preserve Whole Object – You are getting several values from an object and sending them as different parameters; send the whole object instead
31.47 Replace Parameter with method – You invoke a method, than pass the result as a parameter; the receiver can also invoke this method; remove the parameter and let the receiver invoke the method; on one hand it’s good to have less parameters; on the other this will increase the coupling of the method and probably reduce the cohesion
31.48 Remove setting method – a field should be set at creation and never changed; remove it’s setter
31.49 Hide method – A method is not used by any other class; make it private; this should only happen after a refactoring; methods should be as hidden as possible on creation, preferably private
31.50 Replace constructor with factory method – You want to do more than simple construction when you create an object – replace the constructor with a factory method
31.51 Replace Error code with Exception – a method returns a special code to indicate error; throw an exception instead
31.52 Replace Exception with test – Check inside the caller rather than checking inside the callee and throwing an exception; defensive programming is a code smell unless you are working with data from users
—-Dealing with generalization – warning: inheritance ahead; use composition instead—-
31.53 Push up field and method, pulldown field and method – move members up and down when it makes sense
31.54 Extract subclass – A class has features that are used only in some instances; create a subclass for that subset of features
31.55 Extract superclass – two classes have similar features; extract a superclass with the common features; the inheritance way to avoid duplicate code; this is how it lures you in and then it ties you down and then you drown
31.56 Extract interface – several clients use the same subset of a class’s interface, or two classes have part of their interfaces in common
31.57 Collapse Hierarchy – a superclass and subclass are not very different; merge them together
31.58 Replace Inheritance with Delegation – a subclass uses only part of a superclass’s interface or does not want to inherit data; basically Composition over Inheritance extended; create a field for the superclass, adjust methods to delegate to the superclass, remove subclassing and enjoy composition; the problem here is a violation of Liskov substitution in most cases
31.59 Replace Delegation with Inheritance – You’re using delegation and are often writing many simple delegations for the entire interface. Make the delegating class a subclass of the delegate. Why would anyone do a thing like this?
31.60 Tease Apart Inheritance – You have an inheritance hierarchy that is doing two jobs at once. Create two hierarchies and use delegation to invoke one from the other; delegation is about the same notion as composition
31.61 Convert procedural design to objects You have code written in a procedural style; turn the data records into objects, break up the behavior and move the behavior tp the objects
31.62 Separate Domain from Presentation – You have GUI classes that contain domain logic; why would someone do something like this? separate the domain logic into separate domain classes;
31.63 Extract hierarchy – You have a class that is doing too much work, at least in part through many conditional statements; create a hierarchy of classes in which each subclass represents a special case