Writing maintainable code is not something that is easy to do. Not only is a pretty subjective, a lot of the techniques like Clean Code, SOLID and such can be misinterpreted resulting in unconstructive dogma. I've made it my career's goal to find a good balance between all those different patterns, principles, practices and guidelines. In this talk, I like to share you my own ideas on what it takes to write maintainable code. I'll talk about architecture, class design, automated testing and any consequences on your development process. If you care about your code, be there with me.
A lab around the principles and practices for writing maintainable code (2019)
1. Dennis Doomen | @ddoomen | Aviva Solutions | The Continuous Improver
2.
3.
4.
5.
6.
7. Isolated
Can I do this without affecting anything else?
Testable
Is it doing what it is supposed to do?
Traceable
Why did we do this?
Readable
What does it try do and how?
Discoverable
Where is this done?
12. Application
Functional Slice Functional Slice Functional Slice Functional Slice
Composition
Architectural boundaries
protected by PR expert
review
Each component
owns/exposes its
reference data
Does not
depend on
anything
except for
curated
shared APIs
Owns
schema and
storage
(may use
shared
database
abstraction)
DRY within
component,
not outside
May use an
internal
container
Event
Sourcing &
CQRS are
component-
specific
concerns
Does not
touch other
components
’ data
directly
Shared Services
Connects the
components and
serves as anti-
corruption layer
Only services you
really think should
not be duplicated
Designed according to
Dependency Inversion Principle
Covers
front-end
and back-
end
Component
is the scope
of unit &
integration
tests
Align folder
structure
with
boundaries
Defines the
contracts
for the host
as well as
any services
it exposes
17. Long method
Not
immediately
clear what it
does
Magic
character
Deeply
nested “ifs”
Single entry,
single exit
Static mutable state
Magic key
Multiple dots
Magic keys
Single entry,
single exit
No idea what to do
if this fails
Encapsulates
concerns
24. using (var scope = new AssertionScope())
var depth = GetDepthOfObjectGraph();
var displayValue = GetDisplayValueOf(context);
var valueString = GetDisplayValueOf(context);
var dataType = row.GetColumnType(columnIndex);
var searchColumnsSql = BuildSearchColumnsSql(Columns);
var results = RunSearchQuery(searchColumnsSql);
var indexInSpan = 0;
var longIndexInSpan = 1;
Use explicit type if
type is relevant
What kind of
results?
Did the author
consider the
maximum size?
Use explicit type if
type is relevant
var
31. Unstructured Manual
Testing
User Interface Tests
HTTP Tests
Integration Tests
Unit Tests
Any structured
tests should be
automated
Generic code is
tested separately
“Unit” =
(architectural)
boundary
Do not accept
unstable UI tests
Only for UI
scenarios we
can’t cover with
integration tests.
Only for non-UI
scenarios we
can’t cover with
integration tests.
33. Hide what’s not
important
Show what is
important
Only if you can’t
make them clear
otherwise
Use in-line literals
Crystal-clear
cause…
…and effect
Names that explain
the expected
functional behavior
But be careful with
base-classes
!DRY
37. // SMELL a smell code description
// REFACTOR an idea for refactoring
// NOTE a comment explaining a part of the code
38.
39.
40.
41.
42.
43.
44. Quality
Definition of
Done
Work Breakdown
Design Log
Small PRs
Focused
Commits
Continuous
Refactoring
Review Speed
Review by non-
team member
Productivity
Natural Cause of
Refactoring
Review
Thoroughness
Traceability
Clean Source Control
History
Knowledge
Sharing
Tech Meetings
Red Hot
Developer
Readable Code
Good Unit
Tests