No bullshit guide to SOLID and DI in V #18412
Molochnikov
started this conversation in
Blog
Replies: 3 comments
-
That would be interesting to be on the blog |
Beta Was this translation helpful? Give feedback.
0 replies
-
This is the best thing I've ever read about SOLID! |
Beta Was this translation helpful? Give feedback.
0 replies
-
Great stuff have you thought about making a small repo showing a practical example where the animals are split across files to show project structure? |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
No bullshit guide to SOLID and DI in V
Refactoring
Refactoring is the process of restructuring existing computer code without changing its external behavior. Refactoring wastes programmer's time (and someone's money) in efforts to improve code readability and reduced complexity. Careless refactoring can create new bugs and errors, accidentally changes in code behavior. Diffs in version control system are chaotic and big in every "Refactoring" commit that you can't even explain with one commit message. You can't even merge without conflicts if you are refactoring some code which actively developed by others. If refactoring is so hard why programmers do it?
Changes is the answer. Code always develops with new business requirements. Some things and rules that yesterday was rock-solid today seems wrong. Because something already written we need to save existing right behaviour with having ability to add new functionality.
Can we write our code to be ready for any new requirement and any new change without refactoring any piece of old code?
Yes we can. SOLID is the answer.
SOLID
SOLID is a set of principles that programmers can use if they want to minimize refactoring procedures for their already written code.
Here is SOLID priciples modified for V language for clarity:
DI and CQRS
Dependency injection (DI) is a possible implementation of the Dependency inversion principle. DI assumes that you create instances of your structs and substitute them for interfaces (compose them) only in one place of your program that called composition root. Composition root is some place in your program that is very close to the program start. In this guide it will be content of
main()
function. Structs called dependencies and interfaces called abstractions. You injecting dependencies in abstractions and compose overall behaviour of your program at the composition root.Command and Query Responsibility Segregation (CQRS) is a possible implementation of the Interface segregation principle for CRUD (Create,Read,Update,Delete) interfaces like for example database repositories with their database transfer objects (DTOs). CQRS goal is to make them more predictable and maintanable by splitting them to:
get_all_filtered_sorted_paged[T](filter Filter, sort Sort, page_num int, page_size int) []T
andget_by_id[T](id string) T
that return results but not changing state of the system;register_car_owner(cod CarOwnerDTO)
orcalculate_car_tax(car CarDTO)
ordelete_sold_cars(sc SoldCarDTO)
that changing state but not return results.Practice
Let's start our program with first simple business requirement:
Here is the code:
Dependency inversion principle
Cool! It works. Everyone is happy. So whats the problem? After a week you received next business requirement:
Your code needs refactoring. It is because you violated dependency inversion principle. See here:
If you has written your code with DIP in mind it will not needed to be refactored. You just need to implement new dependency and inject it to abstraction in composition root:
Single-responsibility principle
New business requirement on the way:
Now you need to refactor
sleep()
code for dogs. But if you has splitted responsibility to sleep from responsibilities to make noices you never need to refactor this:Let's go. This is the code you must has started with:
Interface segregation principle
Next please!
Wow! We already splitted our resposibilities. But it seems that we forced all animals to growl. If it is not possible to know what interfaces will be segregated in the future because of changing requirements how we can even know what to write at the begining of development?
My recommendation is: use no more than one function with group of semanticly related fields for any interface.
This recommendation works well with CQRS too. On all commands you can make generic interface with one function that will use any DTO as the parameter:
To return to our example let's think about right way to not violate ISP. Firstly we need to separate interface:
Liskov substitution principle
Secondly we have a problem now. Mouse is not an Animal. In our Animal interface we defined that all animals must growl. Suppose its too late and all our clients already uses that interface and we can't change it. The first obvious solution of this problem to really make Mouse an Animal and to throw error if some clients want to use its growl. Like this:
But this violates next principle that i want to discuss.
My interpretation for LSP for V language: you can't subtitute structs for interfaces that are not fully behaviorally correct.
Example from source paper on C++:
If Square implements Rectangle then SetWidth and SetHeight method for Squares sets all sides to the same value. Then we have 4 * 4 = 16 not 20 and clients operations can be failed. But it will be failed also if an error will be raised for one of the SetWidth and SetHeight methods.
The only right solution is to add all possible combinations of Animal interface methods to their own interfaces and give clients only thinnest interfaces they needed from the start of our developing. This is the foundations of true DI architecture. Interface embedding in V greatly helps with that:
So we started our practice with 45 lines. Now there are 162 but we added some new requirements and a whole bunch of interfaces. Finally our code true SOLID. Or SLID?
Open–closed principle
Now after we introduced all other principles i want to show you my second formulation of OCP for V language: you should not modify or delete any dependency or abstraction ever written in any source file with the only exception of composition root.
Why? Because this is what refactoring is! Rewriting old code and creating huge cryptic error prone commits and merges with lost of old functionality!
And here we have the real power of V for this principle: your structs (dependencies) are not tied in source code to the interfaces (abstractions) in V. You can always implement new interfaces or structs on the fly without modifying anything already written.
And because it is the last principle it will be unfair if we not start again with the first business requirement and will test it against any changes:
Here is the starting SOLID listing:
Now if this code is true SOLID i even don't need to show you full listing again on every new requirement! I need to show only changes in our
main()
composite root and new dependencies/abstractions. Let's do this! The party's just getting started:You didn't expect that. Huh?😉
// WITH SOLID CODE YOU DON'T NEED TO CHANGE ANYTHING! ONLY ADD NEW! BYE!
Beta Was this translation helpful? Give feedback.
All reactions