- download the labs from
git clone https://github.com/carlobonamico/clean-code-design-principles-in-action
or plain "Download Zip" from browser
Most developers agree that "Software must be Well-Designed".
But what is a "Good" Design?
This workshop presents a core set of Design Principles that will help you make your application easier to implement, change and understand.
Starting from a focused but realistic specification, we will practice with a Kata where each step introduces a challenge that can be solved by applying different Principles & Patterns.
Through the workshop you will learn to:
- understand the basic dynamics of a complex Software system, and key Design concepts such as Cohesion, Coupling, Abstraction and Impact of Change.
- apply core Design Principles to improve the structure of your applications
- balance the pros & cons of alternative Design choices
Basically, to all developers! Independently from the language / platform you are developing on, this workshop is for you if:
- you are moving from implementing a detailed specification to designing new features and application
- you are interested in improving your Software Design approach and skills
- you want to develop more robust and maintainable applications with more productivity
-
What is Software Design? what is a "good" Design?
-
Why is it important?
-
Key ideas from Event Storming & Domain Driven Design
-
Key forces in Software Design
-
The Travel Expenses Kata
- Single Responsibility Principle for methods
- Single Responsibility Principle for Classes
- Single Responsibility means splitting ...
- How to start?
- Reviewing your Design
- Collaborating with other classes
- Generalizing the model
- Incremental development and Evolutionary Design
- How to continue by yourself: references for further learning
- Working knowledge and practical experience in one programming language (you should be able to write/compile/test/debug by yourself a program which reads and parses input and presents output either on the command line or in a simple GUI).
- Good knowledge of Object Oriented concepts (Class, Interface, Method, Variable Scope and Visibility) and ability to "read" them from a sketch/diagram
- Basic knowledge of HTTP
- Laptop
- Web Browser (Chrome or Firefox)
- Text Editor (Sublime, Atom, Visual Studio Code,…)
- IDE (Eclipse, NetBeans, Intellij, Visual Studio,.. )
- Pen and Paper
-
All Labs and links available at
-
Clean Code: the book
- huge files
- deep interconnections between features
- cross-cutting mechanisms "spread" everywhere
- fragility and ripple effects
- risk of change increases
- productivity decreases over time
We need to better design the software
Among all the possible working implementations,
make a "minimum energy" choice
- that can tolerate some degree of changes ("applied force")
- choosing what to separate and what to keep together
See Carlo Pescio's work http://www.physicsofsoftware.com/ https://www.youtube.com/watch?v=WPgYju3KnIY
- reducing cost of change
- preventing fragility in the face of changes
- reducing cost of development
- making possible to create complex systems
- keeping collaboration effective as team grows
https://martinfowler.com/bliki/DesignStaminaHypothesis.html
Good design
-
is visible
-
is as simple as possibile
-
makes it easy to do the correct things
-
makes it difficult to do the wrong things
-
helps understanding
-
helps reuse
-
is sustainable
- up-front vs continuous design
- Design vs Agile
- Evolutionary vs Emergent Design
- Modeling vs Design
-
Making things easier to change
-
This does not mean that you do not have a vision
-
Plan the overall Path
- but execute a step at a time
- take decisions at the last responsible moment
- minimize unneeded complexity
- maximize available information
-
Rebecca Parsons' Book
- discovery from tests
- discovery from requirements
- making the invisible visibile
- continuous refactoring
Think of a building
- external structure
- floor height
- wiring / plumbing
- room layout
- furniture layout
-
interfacing with other system
-
splitting the system in modules
-
identifying compnents and their interfaces in modules
-
implementing low-level details
-
repetitive/standard areas vs custom/innovative areas
- UI
- Persistence layer
- Infrastructure
- Application-specific logic
- the two bottlenecks of Software Development: learning and communication
- Managing Complexity
Software development is a learning process
Working code is a side effect
Alberto Brandolini
- Kathy Sierra
- https://www.youtube.com/watch?v=FKTxC9pl-WM
- Users / Domain Experts and Developers
- Developers / Team members
- Future Developers / Maintainers
Software development tasks are increasingly challenging
We need design to make Complexity tractable
- Essential Complexity
- Accidental Complexity
- inherently present in the business Domain
If you oversimplify Essential Complexity, you get
- higher coupling
- worse defect rate / maintainability
Can be however reduced through
- Abstraction
- Composition
- Changing the Context Boundaries
- added during the Analysis, Design, Implementation
Can, and must be removed as much as possible
Simplicity --the art of maximizing the amount of work not done-- is essential.
Principles behind the Agile Manifesto
It takes a Deliberate approach and constant effort
To complicate is easy, to simplify is hard To complicate, just add, everyone is able to complicate Few are able to simplify
Bruno Munari
- Encapsulation, Cohesion, Decoupling, Abstraction are important because... they let us think about a single aspect at a time
Key ideas from Event Storming & Domain Driven Design
- Ubiquitous Language
- focus on the Domain
- "Big Picture" vs local consistency (Bounded Contexts)
Key ideas:
- focus on the Application Domain
- strong Domain Expert / Developer collaboration
- (Multiple) Consistent Models
- Bounded Contexts
-
agree on a single definition / word per concept
- split concepts whenever it is needed
-
always and consistently use it in
- code
- documents
- tests
- talk
- reduces ambiguity
- enables Developer - Domain Expert collaboration
- or at least review and validation by the Domain Expert
We think Model-Centered is better
All models are wrong. Some are useful
-
Model <--> Code
-
the code should reflect the model as much as possible
-
very strong consistency within a Context
- finite "thinking slots"
- Mental energy is finite
- attention over time
- amount of information: 7 +/- 2
- continuous refinement
- continuous learning
- progressively more explicit
- Focus on Domain Events
- things that happen
A great way to kickstart the Domain Model discovery https://www.slideshare.net/ziobrando/50000-orange-stickies-later
The Travel Expenses Kata
- what is a Kata?
- overview of the Kata
- basic requirements
- Learn by repeating a known track
- but trying to make it better every time
Deliberate Practice means
- iterate small skills until >90% perfect
- focus on improving a specific aspect at a time
https://jamesclear.com/deliberate-practice-theory
Approach
- focus on the business logic
- limited UI (only a TXT report)
- see how new requirements impact our design
Design on paper the macro-structure of an Expense Report Validator Use Case
- load a single Request from JSON
- (in a second phase, also load the Validation Rules)
- validate one or more conditions (e.g. amount limits)
- present a TXT or HTML report showing validation results
With the goal of letting the user take a decision and change the status of the request
Employee: Carlo Bonamico
Month: March
Year: 2017
Expenses: 3
Date Category Requested Amount Status Allowed Amount
10 March food 10.5 NODOC 0
10 March food 35.7 PARTIAL 25
10 March taxi 10.2 OK 10.2
...
March TOTAL 66.4 35.2
Warnings
- expenses with no document
Blocking Errors
- overall total > 200 euro
- Do everything incrementally
- start from the basics
- define on paper the main Domain Elements
- input data (e.g. Request... )
- output data (e.g. Report.. )
- processing steps/policies
- define their properties
- define their relationships
- define main operations
focus on the application domain
pay attention to the Ubiquitous Language
- which new "implicit" entities appeared in the model?
- what is the main operation?
- what are the main steps of this operation?
In 5-10 minutes
- how can we split the design and implementation in
- phases
- sub-tasks
It's difficult!
The general vision is needed, but we must implement it incrementally
make it smaller!
-
what if instead of X Y Z we only do X?
-
A & B -> A then B
-
top down vs bottom up
http://agileforall.com/resources/how-to-split-a-user-story/
-
Faster small steps beat slower bigger steps
- concept of One Piece Flow in Lean
-
also easier to parallelize
-
The smaller the better
-
choose a single case to process, from end to the end
- End-to-end means Request loading to Report output
-
keeping the input data
-
a simple case
-
a representative case
A complex system that works evolves from simpler systems that works
John Gall
https://signalvnoise.com/posts/1414-a-complex-system-that-works-is-invariably
- you need to be able to check that everything works
- review the model frequently
- run frequently
- test frequently
-
entire application / workflow structure
-
made of empty (or logging-only) components
-
incrementally filled-in / fleshed out
-
also useful for testing
-
in-app mocking
- Write it down
- comment it with temporary comments
- code it!
- write the API you ideally would like to have
- Define the main structure
- Split in sub-tastks with post-its
- Discuss the optimal order
- Introduce mock / support steps
- Parsing Json -> Minimal (always ok) Validation -> Generate Report
- generate empty Report
- generate report with the number of expenses header
- generate report with the first expense and allowed amount = amount
- Validate documentation present of the first expenses
- status in expense line
-
add warnings at the bottom
-
Validate amount limit on single expense
-
iterate validations on all expenses
-
Compute amount total and allowed amount total (introduce aggregation)
-
implement blocking validations ???
-
implement monthly overall limit
-
implement monthly limit per Category
-
Implement limit per day (Optional)
-
implement daily limit per Category (Optional)
- Each commit should start from a stable state and lead to a stable but more complete state
http://continuousdelivery.com/
- Elefant Carpaccio
Key forces in Software Design
- Cohesion
- Coupling
- Impact of Change
- fragile vs robust designs
Basically, Common Sense applied to Software Design
Treat your code like your kitchen
C.B., about 2013
easy in the real world...
practice "seeing this way" code and abstract concepts
Things which
- are related
- are used togehter
- change for the same reason
- change at the same time need to stay together (or at least nearby)
Think forks and knives
Impacts cost of changing one element
Couple related things, decouple unrelated things Thinks that
- are NOT related
- are NOT (necessarily) used togehter
- change for the DIFFERENT reasons
- change at DIFFERENT times need to stay separate
Think forks and milk, or bread and socks
Impacts propagation of changes from one element to the others
- Objects have a clear Outside and Inside
- helps minimizing the impact of change
- helps reducing coupling
Make code easy to delete
-
https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to
-
https://18f.gsa.gov/2016/06/24/5-lessons-in-object-oriented-design-from-sandi-metz/
How does our code become unmanageable? A practical example
- fast-forward demo through the life of an (apparently) trivial function
10/01/2015, 10.50
11/01/2015, 8.50
12/01/2015, 5.50
15/01/2015, 8.50
120 Lines, and already unmaintainable
- simple changes impact most of the codebase
- code-writing time -> decreases
- application-ready time -> never done
- time needed for bug fixes and new features -> increases
-
reading code vs writing code
-
understanding effort
-
fragility due to interdependence
-
Symptoms of Rotten Design
Clean Code, Design Principles and Lean to the rescue
- improving our code
- improving our design
- iterate
- practice, practice, practice and continuous / daily improvement (Kaizen)
paying attention to Cohesion and Coupling
- Parsing Json -> Minimal (always ok) Validation -> Generate Report
- generate empty Report
- generate report with the number of expenses header
- generate report with the first expense and allowed amount = amount
You should have at least classes for these concepts:
- loading and parsing the Request
- validator process
- validation result
- generating output txt from validation result
If you identified initially 1-2 classes, take some time to split them
And name them well
Google "Ignaz Semmelweis"
-
Ignaz Semmelweis
-
He championed washing hands before childbirth and surgery
-
cannot solve all development problems...
-
But can make them way more tractable!
especially if applied consistently.
Never underestimate the impact of doing something all the time
The power of compounding many small changes in the same direction
Leave the campsite a little better than you found it
Every time you touch some code, leave it a little better
Single Responsibility Principle for methods
- separing inputs from outputs
- Primitives vs orchestrators
- if you have to do 3 things, make 4 functions
- Steps vs Flow
Each function should do 1 thing
Or even better, have a single responsibility
- and reason to change
As with all Design Principles, this is more of a compass direction than an absolute rule
Ask yourself questions...
- What?
- Who?
- When?
- Why?
- Where?
And put the answer in different sub-functions
If your function needs to perform a non-trivial task:
- import data, transform it and store it in the DB
Instead of
readData(){
file.open();
while(..)
{
line = readLine();
obj = trasformLine(line);
saveInDB(obj);
}
}
what's better?
importData(){
data = readData();
obj = transformData (data);
saveInDB(obj);
}
- a function for each step
- a function to call the steps
- Particularly the validate() and generate() functions
- Split the methods in elementary responsibilities
At this point you should have at least separate methods for
- validating a single expense
- generating the header
- generating the validated expenses table
- generating a single line of the output
- generating the footer
-
Primitives: small, focused, typically use-case independent
-
Orchestrators: implement use-cases by combining primitives
-
rinse and repeat over multiple levels of abstraction
-
benefits:
- more reusable
- easier to test
- easier to understand
- Another example: have methods for each step of an algorithm
- another method to decide the flow among them
- Validate documentation present of the first expenses
- status in expense line
- add warnings at the bottom (Optional)
Single Responsibility Principle for Classes What is a Responsibility
- reason to change
- what if ...
- looks similar vs changes for the same reason
Have you ever seen your grandmother put dirty clothes in the fridge?
Or biscuits in the vegetable box?
So, why to we do this all the time in our code?
Responsibility == reason to change
A class should do one thing
- and have a single reason to change
Consequences:
- classes should be small
- classes should be focused
- classes need to collaborate to perform complex tasks
- Take the biggest class written up to now or any other code example
- Paste it in word / Google Docs
- Outline in different colors the various responsibilities
- Validate amount limit on single expense
- iterate validations on all expenses
Question: where do we take the limit from?
Look at parts of the code and ask yourself what if that changes?
At this point you should have separate classes for
- validating the amount of a single expense
- validating the documentation presence
- iterating on all expenses
https://web.archive.org/web/20090411030053/http://threeriversinstitute.org/blog?p=104
Sandy Metz https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
- UI from logic
- Logic from persistence
- I/O from logic
- Sync from Async
- Intent from implementation
- each layer depends only on lower layers
- ideally, with the domain layer at the bottom
- Hexagonal Architecture
Reviewing your Design
- naming
- model out loud
- validate with examples
- what if this changes?
- the Open / Closed Principle
- Knowledge crunching
- maintaining Model consistency
- continuous learning
- breaktroughs
- making implicit explicit
What is written without effort is in general read without pleasure.
Samuel Johnson
Most code is written once, but read
- every time you need to fix a bug
- to add new features
- by other developers
- including your future self
- nonsense / honest / honest & complete / does the right thing / intent
- domain abstraction
http://llewellynfalco.blogspot.it/p/infographics.html
- check naming
- check if you can make implicit concepts visible
Collaborating with other classes
- Prefer Composition to Inheritance
- Dependency Injection
-
We need a way of making collaboration easier
-
With Dependency Injection
- separate creation of classes from linking instances
- create A
- create B
- something else passes or injects B into A
Goal: using capabilities from another object should be almost as easy as calling a method in your object
- You do not (necessarily) need a framework for that...
- Inheritance is the strongest link between classes
- useful with caution
- combine parts
- a derived class becomes the composition of a base behaviour + additional custom behaviour
Achieve complex structures, behaviours, interactions by composing and coordinating simple elements
Compute amount total and allowed amount total (introduce aggregation)
- implement monthly overall limit
- delegating aggregation to dedicated class
Validate aggregate properties = Aggregate + Validate result
- Define the main structure
- Split in incremental sub-tastks
- Discuss the optimal order
- explicit vs implicit
- Decoupling changes and detecting regressions
- separate
- clean parts from dirty code
- Intent from Implementation
- what is effective in the small
- e.g. everyone talking at the same time in a room to solve a problem
- interaction within local variables
chaos if same approach applied in the large
- anyone talking to anyone
- global interactions
- Introduce the Validator interface
- inputs
- outputs
- Define a list of individual validators
- separate the validators from their application to the expenses
Open for extension, Closed for Modification
- implement limit per category
- Implement limit per day (Optional)
- Revise the expenses lab in the light of these concepts
- Consider the impact of the following additional requirements
- allow the operator to manually modify the allowed reimbursement
- enable the
Approve
button only if there are no blocking errors
- implement daily limit per Category (Optional)
Generalizing the model
- making the implicit explicit
- Levels of abstraction vs Levels of implementation
- breakthroughs
- Emergent Design
Introduce the concept of Validation Rule
- implement it
- istantiate it in code
- load validation rules from file
- You can do everything incrementally
- decouple release from deployment
- branch by abstraction
- do both
- expand-contract
Refactor from A to B
- First do A
- then do A + part of B
- then do B
- then remove A
Things that make code testable
- clear interfaces
- small classes / functions
- decoupled
- composition
Things that make code well-designed, easy to evolve
- clear interfaces
- small classes / functions
- decoupled
- composition
So what did we just do? Understand the principles
- the relationship between quality and productivity
- the need for a continuous chain of small, safe steps of design & implementation
Traditionally, Quality is seen as an alternative to raw development Speed
- this is partly true only in the short term
Four quadrants:
-
high quality, high productivity -> tends to further improve
-
high quality, low productivity -> tends not to improve, and go to
-
low quality, low productivity -> tends to get worse
-
low quality, high productivity -> tends to go to the previous one
-
Productivity curves at different quality ratio
- improving our design
- practice, practice, practice and continuous / daily improvement (Kaizen)
- Principles of Package Design
- More on TDD: Test-friendly vs Well-designed
It takes a Deliberate approach and constant effort
If I don't practice for a day, I notice
If I don't practice for two days, my orchestra notices
If I don't practice for three days, the public notice
Claudio Abbado
- practice, practice, practice and continuous / daily improvement (Kaizen)
The same concepts can be applied at the package level
- Java package / .Net namespace
- Java JAR / .Net assembly
- https://en.wikipedia.org/wiki/Package_principles
See Robert C. Martin
- http://matteo.vaccari.name/blog/tdd-resources
- https://github.com/xpmatteo/simple-design-in-action/tree/master/stage-01-hello-world/script
- http://codekata.com/
- https://www.industriallogic.com/blog/modern-agile/
- Kathy Sierra
- https://www.youtube.com/watch?v=FKTxC9pl-WM
http://www.slideshare.net/ziobrando/event-storming-recipes https://www.youtube.com/watch?v=veTVAN0oEkQ http://www.slideshare.net/ziobrando/idea-stickies-green-bar-wroclaw-edition
About how to improve teamworking and raise technical proficiency and autonomy
The phoenix Project http://itrevolution.com/books/phoenix-project-devops-book/
Drive (sulla motivazione) https://books.google.it/books?id=E0H_DIkg0I4C