This document outlines an exercise for practicing software design principles through refactoring racing car kata code. Participants will work in pairs, writing tests and refactoring code to address violations of SOLID principles. The principles - single responsibility, open/closed, Liskov substitution, interface segregation, and dependency inversion - are defined. Examples are given of how to apply each principle when refactoring code. The goal is to practice design skills by exploring problems, discussing solutions, and sharing learning with others.
3. let’s practice together
● all professionals practice
● how about us programmers?
● when was the last time you sat down to really
understand a problem?
● tonight we have time to do that
4. the ground rules
● write tests
● work together in pairs
● write down
– �: what did you learn
– �: what made you lose focus
– �: what confused you
– � : what annoyed you
● share with everybody
● be excellent to each other
5. the ground rules
● if you have the keyboard, you get to decide what to type
● if you have the keyboard and you don’t know what to
type, ask for help
● if you are asked for help, kindly respond to the best of
your ability
● if you are not asked, but you see an opportunity for
improvement or learning, choose an appropriate
moment to mention it like the next green test or the
retrospective
from: Emily Bache – The Coding Dojo Handbook
6. the exercise
● choose one exercise
● add tests for the class
– Leaderboard: add tests using SelfDrivingCar
● write down where SOLID principles were violated
● repeat
● share with your fellow craftspeople
8. single responsibility principle
● responsibility = reason to change
● if nothing ever changed: no problem
● change is the fundamental reality of our
business
9. SRP - ExpensesReport
● store/retrieve expenses:
– Excel, CSV, database
– cause: infrastructure
● compile report from expenses
– taxes, sorting
– cause: business rules
● format and print report
– plain text, HTML
– cause: cosmetic
10. open/closed principle
● open for extension, closed for modification
● huh? ��♂� ️
● when facing change: introduce abstractions
● abstraction = interface (see DIP)
● we need to open/close our design
● this is a voluntary choice
● driven by the fact/requirement of change
● avoid BDUF, practice refactoring
11. OCP - FizzBuzz
● add one if per number: �
● possible abstractions:
– NumberSayer (immediate choice)
– NumberStrategy (higher abstraction)
● open: extending behavior by adding interface
implementation
● closed: original code does not change when adding a
new number
● cf.: Craig Larman – Protected Variation
12. Liskov substitution principle
● polymorphism must be transparent to clients
● the client must not care which implementation is used
● instanceof is a smell: broken abstraction
● all contracts of superclass must be kept:
– preconditions
– postconditions
– invariants
– history constraint
13. LSP - square/rectangle
● class Square extends Rectangle ?
● change width of square
● height has to change too
● contract of Rectangle:
postcondition: setWidth(n) getWidth ()== n⇒
● contract of Square:
invariant: height == width
14. LSP - square/rectangle
● you cannot drop in a square for a rectangle
● unit tests help you see these contracts
● try to use the same test for subclasses!
● OOP inheritance does not have to follow real
live inheritance
● cf.: design by contract
15. interface segregation principle
● do not depend on more than you need
● keep your interfaces slim
● extract interface from dependency if you only
use some of the methods
16. ISP – primitive obsession
● use first class collections:
– do you need all 32 public methods of java.util.List ?
– clarity: Calendar instead of List<Appointment>
● use value types
– DeviceId instead of String
– 76 exposed methods of String
17. ISP – service locator
● <T> ServiceLocator.getService(Class<T> class)
● infinite number of methods
● ServiceLocator.getService(String.class)
● invisible preconditions:
– serviceClass needs to be in directory
– only discovered at runtime
● constructor:
– explicit precondition
– discovered at compile time
18. dependency inversion principle
● do not depend on low level details
● depend on high level abstractions instead
● business rules (high level) should not change
when implementation details (low level) change
● e.g. report calculation has to change when data
storage changes?
19. DIP – button/light
● class Button
– void toggle()
● class LightBulb
– void turnOn()
– void turnOff()
● change: button should work with TV
● class TV
– void powerOn()
– void powerOff()
20. DIP – button/light
● option 1:
– public Button(LightBulb lightBulb)
– public Button(TV tv)
– void toggle():
● if(lightBulb != null && tv == null) { … }
● if(tv != null && lightBulb == null) { … }
– now add five more classes: �
21. DIP – button/light
● option 2:
– interface Switchable
● void turnOn()
● void turnOff()
– class LightBulb implements Switchable
– class TV implements Switchable
– public Button(Switchable switchable)
– void toggle(): straightforward
22. DIP – button/light
● client: Button
– depends on high level abstraction
– “something that can be switched on and off”
– interface, abstract class, multimethod
● low level details:
– turnOn/powerOn, turnOff/powerOff
– depend on abstraction too
– by implementing it
● no dependency on low level details
● interface “owned” by the client
23. the exercise
● racing car katas
● by Emily Bache & Luca Minudel
● git clone
https://github.com/emilybache/Racing-Car-Katas.git