Professional Documents
Culture Documents
Software Design and Refactoring: Cracking Complexity by Sending Messages and Building Pipelines
Software Design and Refactoring: Cracking Complexity by Sending Messages and Building Pipelines
refactoring
Cracking complexity by sending messages and building pipelines
Self introduction
Let’s start with a puzzle...
Happy company
class Employee; end
class Company
attr_accessor :members
end
def main
thong_ng = Engineer.new
khai = ProductAnalyst.new
company = Company.new
company.members = [thong_ng, khai, ...]
company.members.each(&:do_work)
end
How about Son?
class Engineer; def write_code; end; end
class ProductAnalyst; def design_product; end; end
company.engineers.each(&:write_code)
company.product_analysts.each(&:design_product)
# 3 months later...
son.becomes(ProductAnalyst) # ???
Agenda
module Engineer
def write_code; end
end
module ProductAnalyst
def design_product; end
end
son = Object.new.extend(Employee)
son.extend(Engineer)
son.extend(ProductAnalyst)
OOP without inheritance
class Engineer; end
class ProductAnalyst; end
1 + 1 == 1.send(:+, 1)
if (1 == 1)
'true'
end
son.send(:extend, ProductAnalyst)
Class A; end
A.is_a?(Object) # returns true - classes are objects
When you design a program, don't confuse class with role (interface).
● Class: language feature to construct object with certain roles. An object can
be created from a single class.
● Role: Messages an object with this role can reply to. An object can have
multiple roles. An object’s roles can change during run time.
Start with roles first: Employee role, Engineer role, ProductAnalyst role,
Persistable role, Movable role, etc.
The messages
Think about the behaviors the program must support, in other words, the
messages of each role:
● do_work → WorkingRole
● write_code → EngineeringRole
● design_product → ProductAnalysisRole
And then, when we want concrete behavior, we supply them via Factory, Class or
whatever the mechanism the language supports to construct objects. In pure
OOP, class is just a type of factory.
Recap
● OOP is not about class and inheritance
● OOP is about message sending
Software design is about
Part II cracking
complexity
Software Design and Refactoring
Writing software is complex...
How complex?
Example: Slack notification flowchart
As your software grows, complexity increases
Complexity is the root of all evil
cheese_mixture = cheese.beat(whisked)
folded_mixture = cream
.whip
.fold(cheese_mixture)
wet_fingers = espresso
.dissolve(sugar2)
.soak(fingers, seconds: 2)
ready_tiramisu = folded_mixture
.assemble(wet_fingers)
.sift(cocoa)
.refrigerate
ready_tiramisu
end
Part II
Combining theDesign
Software best of
andOOP and FP
Refactoring
Part IV
Functional core, object oriented shell
Refactoring is about reducing or isolating complexity
-- Thanh Dinh
-- Michael Feathers
What if we can get the best of both worlds?
All code can be classified into two distinct roles: code that
does work (algorithms) and code that coordinates work
(coordinators).
-- John Sonmez
Example
We need to design a game that has
The game also needs to handle physics, input and rendering, etc.
Traditional OO design
class Monster
attr_accessor id, position, health, attributes
def attack(humans)
attacked_human = humans.min { |h| abs(h.position - monster.position) }
attacked_human.health -= min(attributes.atk - attacked_human.attributes.dfs, 0)
end
class Human
attr_accessor id, position, health, attributes
def process_damage
humans = DamageService.attack(monsters, humans)
end
def run
loop do
process_input
process_damage
process_physics
render
end
end
end
Refactoring: reducing/isolating complexity
Mutations, side effects, dependencies, control flow can either be minimized or
isolated. We are doing both with this architecture.
● Reduce mutations by changing the core to functional. The only parts that still
have mutations are in the OO shell → Mutations are isolated
● Reduce unnecessary side effects by changing the core to functional. The
only parts that still have side effects are in the OO shell → Side effects are
isolated
● Reduce dependencies by moving to functional. Isolate dependencies to OO
shell.
● Number of control flows: Isolated inside the functional core
Benefits
● Testability: Trivial and very fast to unit test the core, minimal number of
integration tests needed for the coordinators
● Reliability: Validation can be easily added to value classes, ensure the
system is always in a valid state. Side effects and exceptions are isolated
within coordinator classes.
● Readability: Functional core has no dependency and self-contained → very
easy to read and understand in isolation
Benefits (cont.)