Continuous Design
Explore my other writingsReading notes: Tidy First? A Personal Exercise in Empirical Software Design
This is a short summary, or rather reading notes, about the book Tidy First? A Personal Exercise in Empirical Software Design by Kent Beck.
Kent Beck’s new book talks about software design, introducing a definition to explore in a future book series:
Software design is an exercise in human relationships
Nothing technical about design, because design is not achieved for the computer. Design is the code structure and it doesn’t directly impact behavior.
The structure of the system doesn’t matter to its behavior.
So why should we bother about design ?
The answer, as always, is because you are not just instructing a computer, you are explaining your intentions for the computer to other people. The shortest path to instructing the computer is not an interesting end goal.
This book describes different means to tidy the code, using a short catalog of pragmatic code modifications. Then Kent Beck explains how it is related to the development process and when to apply these tidyings. In the last section he explains why it works, why software design creates as much value than behavior or features—but value of a different nature.
What is tidying?
Essentially, tidyings are refactorings, but the smallest ones, so small that nobody can argue against them.
The usual situation is:
- we want to make some behavior changes
- if design were different, making that change would be easier.
So, first, make the design like we need.
We change the structure to obtain the structure that will facilitate the next change in behavior.
But there are also two other use cases:
- When we learn something about the code (after some hard work to understand), we need to feedback our new knowledge in the code to make it more explicit
- When we did change the behavior in the code, but the task seems not complete until we also make some little improvement in structure
Tidying Catalog
Kent beck provides a catalog of 15 tidyings:
-
Guard Clauses (sometimes called early returns in literacy)
- Separate exit conditions (aka Guard Clauses) of a routine from the body of this routine
if (not condition) return if (other condition) return ...some code...
- Separate exit conditions (aka Guard Clauses) of a routine from the body of this routine
-
Dead Code
- Delete it. That’s what version control is for.
- May be hard to identify: add observability (logs…), become confident, then delete it
-
Normalize Symmetries
-
Convert accidental variations of a pattern to keep only one way to express the same idea.
-
Example: different flavor for lazy initialization of a value
foo() foo = foo ?? initialValue return foo foo() if !foo foo = initialValue return foo foo() return foo ? foo : initialVale
-
-
Kent Beck’s quote:
As a reader, you expect that difference means difference. Here you have difference that obscures the fact that the same thing is going on.
-
-
New Interface, Old Implementation
- Create and call the interface the code deserved, implement it by calling any old difficult/complicated/ confusing/tedious interface
- Whenever we think a change should be easier if structure were different we should create this structure and make our change easier.
-
Reading Order
- Reorder the elements in source code in the order you would prefer to encounter it because a specific sequence is more understandable.
-
Cohesion Order
- Reorder cohesive elements of code that change together so that our current modification is not dispersed in the whole codebase
- When we can it’s better to remove coupling, but it's not always possible
-
Move Declaration and Initialization together
- Name of variable gives hint on its role, initialization may reinforce it.
- It’s often better to declare and initialize variables in the same code section.
-
Explaining Variables
- Create a variable to give a name to a big expression
x := ...big long expression... y := ...another big long expression... return new Point(x, y) // instead of return new Point( ...big long expression..., ...another big long expression... )
-
Explaining Constants
- Replace literal constant value by a symbolic constant with an explicit name
- Don’t replace same values with different meaning
- Don’t replace when we already understand the literal (e.g. don’t replace
1
by a constant namedONE
)
-
Explicit Parameters
- Split routines that uses large parameter objects to make any dependency explicit in a new subroutine
- The same is about environment variables, should be injected as parameters
params = { a: 1, b: 2 } function foo(params) foo_body(params.a, params.b) function foo_body(a, b) // dependencies are explicit ...a... ...b...
-
Chunk Statements
- Add blank line between different parts of code to separate block of codes.
- Seems simple. It is. It adds expressiveness and save time, why not doing it?
-
Extract Helper (aka Extract Method)
-
Extract subroutine to replace a block of code with narrow purpose and limited interaction with the rest of the code in the routine.
- It creates cohesion
- I adds semantic to a suboperation
New interfaces emerge when we’re ready to think more abstractly, to add words to our design vocabulary.
-
Two special cases:
-
When we need to modify a subpart of a big routine, we can first extract a cohesive subroutine, then modify it.
- After the modification, we always can revert the extraction if it makes sense.
-
Make some temporal coupling explicit.
When two statements are often called in a sequence like...some code... a() b() ...other code...
we create a new helper that encapsulate the two calls and make their relation more explicit
ab() a() b()
-
-
Use automated refactoring tools.
It's usual for IDE to expose some features to extract subroutines.
-
-
One Pile
- Inline small subroutines that give no semantic information to understand the big picture before modifying it. When facing the big pile of code, it becomes more understandable and the real structure will appear.
-
Explaining Comments
- Write a comment immediately
- When you have understood a complex part of code after hard effort.
- Or when you need to explain something to someone specific
- Or when you have find a defect, a coupling or any valuable information.
- Write a comment immediately
-
Delete Redundant Comments
- Remove any comment saying exactly what the code says, or that provides cost without benefits.
How to use these Tidyings in the development cycle
When tidying code, we separate tidyings and bevahior changes in different Pull Requests (PRs).
Kent Beck gives an example of a big PR with a sequence of behavior (B) and structure (S) changes:
[BSSBSSSBBS]
He proposes to split it in different PRs:
[B] [SS] [B] [SSS] [BB] [S]
Hence, sometimes you need to:
- discard your current work
- do the tidying first
- recreate the behavior change; but easier, because of tidyings
About the tidying PRs there are pitfalls to worry about:
- Do not create too small PRs.
- We need to respect reviewers. Small tidying PRs seems pointless and are “noise” in activity.
- We also can work to reduce the cost of merging some identified PRs containing only some little tidyings
- Avoid creating a sequence containing too many modifications. Especially on the same file, we need to resist the urge to go too fast, or to mix different tidyings at the same time.
- Bigger PRs increase the cost and possibilities of collision (with another PR), accidental modification of behavior and… speculation about future instead of tidying the minimum tu enable our desired behavior change.
- Each tidying seems small, but like already said, changing structure introduces new ways of thinking about this structure. Smaller steps are more comfortable to grasp this new knowledge.
Tidyings are the Pringles of software design. When you’re tidying first, resist the urge to eat the next one. Tidy to enable the next behavior change. Save the tidying binge for later, when you can go nuts without delaying the change someone else is waiting for.
At this point, it is logical to wonder if we will enter in an infinite loop: tidy the structure, change the behavior, change the structure, change the behavior, and so on. Kent beck reminds us the Pareto principle: 80% of the changes occurs in 20% of the code. In other words, after the first tidyings are done, it will occur more and more frequently to work in an already tidied code.
The right moment to apply tidyings
Before to start tidying, we need to understand there is a problem in design, that our current task should be easier if we had a different code structure.
Untangling a ball of yarn starts with noticing that you have a tangle.
And sometimes we discover it only after we are done. Or maybe it’s too much effort for a code that barely change.
Of course, Kent Beck always encourages us to tidy the code, and before any other change: a tidy job done sooner is smaller.
But he also provides the following heuristic:
Tidy never when:
- You’re never changing this code again.
- There’s nothing to learn by improving the design.
Tidy later when:
- You have a big batch of tidying to do without immediate payoff.
- There’s eventual payoff for completing the tidying.
- You can tidy in little batches.
Tidy after when:
- Waiting until next time to tidy first will be more expensive.
- You won’t feel a sense of completion if you don’t tidy after.
Tidy first when:
- It will pay off immediately, either in improved comprehension or in cheaper behavior changes.
- You know what to tidy and how.
Design as a software value
The value of a software is usually estimated by its features and its capabilities, in other word its behavior. Tidying and refactoring are only working about design and structure of the code. It brings no value.
Instead, structure can create options.
Design makes it easier to change behavior—or makes it harder.
We need to tidy only if it creates options (changing the structure, learning…)
Kent Beck explains it with this formula:
cost(tidying) + cost(behavior change after tidying) < cost(behavior change without tidying)
In this case, there is no question. Apply tidyings it brings more value (or it brings value earlier, if reduced cost is about delivery time).
But what if we are in this situation:
cost(tidying) + cost(behavior change after tidying) > cost(behavior change without tidying)
We need to estimate the value of options created by our tidying. The amortized cost of these options may be worth it. It’s hard to say, here we let our experience and skills take the decision.
Tidyings listed in the catalog above are not big changes or long refactoring, they should limit the discussion about their usefulness.
Coupling and Cohesion
The last chapters of Tidy First? are dedicated to the concepts of Coupling and Cohesion.
Coupling is a relation between two elements: changing one element requires to change the other. Nature of the change is as important as the relation. For instance, a change that never happens is a coupling that doesn’t bother us.
So coupling is not a problem until we need do a specific change
But under some circumstances, coupling is expensive. The cost of a software, is the cost of its change: the initial write to first release is nothing compared to the cumulative cost of all evolutions during its lifetime. And big changes are very expensive compared to the cost of all minor changes. A big change is where we have to impact several parts in the software: our modifications are spread in codebase or we don’t know a change will have some impacts on other behaviors… or even break them (directly in production, most of the time).
In summary:
cost(software) ~= coupling
But remember coupling is not a problem: it’s here for reason, either it was logical at some time, or it wasn’t a problem until we need to change a specific behavior.
Question is: do we pay the cost of coupling (extra care, complexity) or the cost of decoupling (tidying first)?
Decoupling is creating more options for now or for future, so it’s valuable.
Cohesion is what we obtain when we arrange coupled elements together. Kent Beck summarize two way to obtain cohesion:
- moving coupled elements together
- extract not related elements to only keep cohesive ones.
Tidyings are about creating decoupling and creating cohesion.
Conclusion
Kent Beck calls “tidyings” the small changes we can make about expressiveness, coupling and cohesion. So small they have quite no cost in time and effort. But with high impact on code structure and optionality.
At least they help us to be more happy about our work, which is a real indicator for Kent Beck:
Will tidying bring peace, satisfaction, and joy to your programming? Maybe some. This is important because if you are your best self, you are a better programmer. You can’t be your best self if you’re always rushing, if you’re always changing code that’s painful to change.
Big refactors will come. In this book we are mainly talking about the daily diffs we can make to our code to make the whole work easier.
Make no sudden moves. You’re working with incomplete and changing information about what’s coupled with what. Don’t dramatically rearrange everything. Move one element at a time. Make the code tidier for the next person. If everyone follows the Scout rule (“leave it better than you found it”), the code will become more livable-with over time.
We need to make tidyings a concrete thing, in adapted PRs, and respect these improvements even the smallest: this is real work, creating options—creating value.
To go further:
Youtube: A Daily Practice of Empirical Software Design - Kent Beck - DDD Europe 2023
Home