Notes about SOLID Principles & Clean Coding
Last Updated: April 04, 2021 by Pepe Sandoval
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...
My personal notes from Uncle Bob clean code lectures SOLID video series, they have been paraphrased or written in my own words just for my personal reference, in some cases adding my personal opinions about certain topics
Architecture: the shape a system adopts to meet its use case
Details should depend on high level policies, policies should never not depend on details
The source code is the design
The running application or binary is the product and for SW building refers to compiling and executing so building is cheap in SW but design is expensive unlike other disciplines where building is much more expensive than design, for example think of design and build a house, in that case build is expensive compared to it's design
Source code dependencies should oppose the flow of control
Programming is about modeling the real world using SW but there is nothing special about OO that allows this
Encapsulation, inheritance and polymorphism are mechanism build within OO but they are not it's essential quality, its essential quality is the ability to invert dependencies to protect high level policies from low level details
SOLID principles control relationships and operations between classes, describe the way classes relate to one another, they are all about the dependencies about those classes and motivation to creating those dependencies
Component Cohesion Principles describe the forces that cause classes to be grouped as independently deployable components
Component Coupling Principles describe the forces that govern the dependencies between components
States that a SW module should be open for extension but closed for modification
Open for extension means changing the behavior of that module should be easy but Closed for modification means the source code should not change for this
the OCP wants us to be able to change the behavior of module without changing the actual code of the module
To implement this we need to separate the extensible behavior of a module, in an abstraction interface
Designing a whole system that conform to the OCP it is possible but usually so hard that it is not practical however we can create, modules, classes and functions that do conform to the OCP
The goal is not to completely eliminate the pain of change we just need to minimize it
Crystal ball problem: OCP can only protect you from change if you can predict the future. This means it is easy to design abstractions for future changes if you have some idea of how those changes are going to be. There are usually two solutions for this problem
States that you should be able to substitute a base type for a derivative type and everything must still work
A Type is just a bag of operations, because It doesn't matter what is contained on a type what matters are the operations that can be performed on that type (the results of those operations).
int
is stored, if it use one's or two's complements, where is the sign, etc as long as the int that represents 1 added to the int that represents 2 results in the integer that represents 3, then everything is fine
-E.x. you don't care about the internal representation or structure of a float
what you care is that 1.0/2.0 results in 0.5A class from the outside is just a bunch of methods the data is just hidden behind those methods
Subtype new type that can be used as another type (inheritance -> substitutable for its parent type)
Subtypes can be used as their parent types. You have a user U
that uses T
if they can use T
they should be able to use S
without knowing it
In dynamically typed languages we send messages (instead of calling methods but effect is the same), until runtime if the object can respond to the message, the method is invoked otherwise a runtime errors occur
Refuse Bequest occur when
Representatives of things do not share the relationships of the things they represent this is why a square is a rectangle but the representative SW that represent a rectangle and a square do not share this relationship. When you create models in SW, these models are mere representatives
The complex-real-integer representation is a real world model that makes sense in real world but makes no sense at all in SW
if we create a list of circles which are a derived of class shape, we can not pass that list to a method that expects a list of shapes, because that function may put a Square or a Triangle in the list and although those can also inherit from shape, having a Square or a Triangle in a list of circles do not make sense, other modules would expect to call !methods for Circles in the list!
if isinstance(myobj, list)
could be a LSP violation, you should only check the type if you already know what type it is because we are dynamically loading an objectTypecase
scenario (use if-elif-else asking for types) is likely an LSP violation because is not scalable and should be replaced with polymorphic dispatch (visitor pattern)Don't depend on things you don't need, Don't force your users to depend on things they don't need, users in this context can be (modules, people, tests, etc), don't force them to know more that they need to know
Inheritance is the strongest of the physical coupling but it is the weakest of the logical couplings
Interfaces has more to do with the classes that use them than with the classes that implement them
The class that uses the interface (The Switch) is the one that has the tight logical coupling cause this class can't do its job without the interface but the class that implements the interface (The light) it is forced to implement the interface but if the interface disappears from its view it doesn't really affect it, while the interface doesn't even know its implementation (The Light) exists
An interface is just an abstract class with abstract methods in it
The Deadly Diamond of Death caused by multiple inheritance is not that complicated to avoid so languages that don't support multiple-inheritance are crippled languages
The ISP addresses the problem of Fat Classes (classes with lots of methods) and making sure you don't depend on methods you don't call
Break a big monolithic program and turn it into a bunch of independently deployable modules
Invert the direction of the source code dependencies, try to make source code dependencies oppose flow of control (flow of execution)
Dependencies:
Structured design is a top down methodology: You start with main then design the sub-routines main should call, then the subroutines those other subroutines will call and so on. This leads to a design where the source code dependencies are an exact mimic of the runtime dependencies, high level functions call the low level functions which then call lower level functions and so on...
We don't want the source code dependency to follow the run time dependency
Using interfaces removes the source code direct dependency, it still has a runtime dependency, classes that uses and implement the interface will have a source dependency on the interface
Dependencies are inverted whenever the source code dependencies oppose the flow of execution/control
Inverting dependencies is the means by which we create boundaries between SW modules, which allows to create plugins
High level policy should not depend on low level details, while low level details should depend on high level policy
Modules that contain high level policies, like use cases, should not depend on modules than contain low level details (like DB, web formatting) and yet these high level modules must eventually invoke the low level functions
Building a reusable framework is HARD! if you don't build it in parallel with more than one application that reuses it will most likely fail
OS provides device independence, this means you should be able to access the device (read & write) without knowing what kind of device it is. IO drivers are just plugins to the OS, all their dependencies point towards the OS and the OS has no dependencies on the drivers
A good app architecture is a plugin architecture which is achieved through careful and a decisive use of the Dependency Inversion Principle
DIP is the inversion of source code dependencies against the flow of control (the callable object depends on the caller)
No design is fully SOLID compliant, no design should be fully SOLID compliant, when you ignore a principle you should know which principle you are ignoring and do it deliberately. Principles illuminate design issues and recommend solutions but are not immovable laws, respect them but down vow to them
A component is an independently deployable library (piece of code) like a .jar, a .dll, a .gem, a shared library, etc
Components are a larger scale entities made up of many classes, functions, variables
Applications call libraries (sub-routines), frameworks call applications
Noun-Verb Analysis General design process to decompose requirements into an object oriented design
SOLID design Example:
Cohesion: forces that glue together classes into components
Components that contain details must depend on the components that contain high level policy not the other way around
The fact a group of classes work together to achieve a certain goal is not necessarily a good reason to bind them together into a component
Release-reuse Equivalent Principle (REP)
Common Closure Principle (CCP):
Common Reuse Principle (CRP):
Systems are made up of layer that are made up of components that contain classes that house functions/methods which server single actors/users of the system
REP, CCP and CRP principles are not cooperative in nature
Coupling: the forces that bind together components into systems
Morning after syndrome when you work on something make it work and the next day it is not working because somebody else changed something you depend upon
Acyclic Dependencies Principle (ACP)
Stability: is measured by the amount of work needed to change its state therefor it can be an analog value
I
) we can count the number of classes that depend on a certain class (Fan-in) then we can count the number of classes a certain class depend (Fan-out), then we can take the ratio of these two number I=Fan-out/(Fan-in+Fan-out)
this will result around 0
for very stable (hard to change=a lot of components depend on this) and around 1
for unstable (easy to change=depends on lots of components)Stable Dependencies Principle (SDP)
I
The fact that a module is hard to change doesn't mean it is hard to extend
Stable Abstraction Principle (SAP)
A=Abstract Classes/Total Classes
, this will result on around 1
for very abstractA + I = 1
which means stuff at the top is concrete and the stuff at the bottom is abstractControllers use builders to create request data structures that they pass to interactors that they acquire through factories, interactors manipulate entities and the database in order to create response data structures, these response data structures are passed into presenters which create view models data structures which are then driven into the views
Move classes around components as the code is created, this is signal of a good refactoring discipline
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...