Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Accelerate development with simple design - ShareIT

Accelerate development with simple design - ShareIT

One thing you sometimes hear is “SOLID principles are useless; all you need is to just write simple code!” Ha! It’s much easier said than done. In this talk I’d like to share what “simple code” means to me and a few tricks to get it. I do not present any novel idea… it’s well-known stuff, but seldom applied in practice.

Matteo Vaccari

October 28, 2021
Tweet

More Decks by Matteo Vaccari

Other Decks in Technology

Transcript

  1. © 2021 Thoughtworks
    Accelerate development
    with simple design
    Matteo Vaccari
    ShareIT online meetup, 2027/10/2021

    View Slide

  2. © 2021 Thoughtworks 2

    View Slide

  3. © 2021 Thoughtworks 3
    So…… what works?

    View Slide

  4. © 2021 Thoughtworks 4

    View Slide

  5. © 2021 Thoughtworks 5
    🤔

    View Slide

  6. © 2021 Thoughtworks
    Kent Beck on Simple Design
    6
    https://martinfowler.com/bliki/BeckDesignRules.html

    View Slide

  7. © 2021 Thoughtworks 7
    Kent Beck on “good code”
    There are a few things I look for:
    ● Once and only once [...]
    ● Lots of little pieces [...]
    ● Replacing objects [... In a really good system, every time the user says “I
    want to do this radically new thing”, the developer says, “Oh, I’ll have to
    make a new kind of X and plug it in.” When you can extend a system
    solely by adding new objects without modifying any existing objects,
    then you have a system that is flexible and cheap to maintain
    ● Replacing objects [...]
    ● Moving objects [...]
    Kent Beck, Smalltalk Best Practice Patterns, 1997

    View Slide

  8. © 2021 Thoughtworks
    The problem
    8

    View Slide

  9. © 2021 Thoughtworks
    @Test
    void addItemToCart_returnOk_addGiftItem_withNoFraudAlert() {
    Item previousItem = new Item("a tv");
    Item addedItem = new Item("a book");
    Item giftItem = new Item("gift", ItemFlag.GIFT);
    Cart cart = new Cart().withItem(previousItem);
    AddItemToCartCommand command = new AddItemToCartCommand(cart, addedItem);
    when(inventoryService.checkAvailability(addedItem)).thenReturn(AVAILABLE);
    when(fraudDetectionService.looksSuspicious(cart)).thenReturn(false);
    when(giftItemService.getTodaysGiftItem()).thenReturn(giftItem);
    AddItemToCartResult addItemToCartResult = cartService.addItem(command);
    assertThat(addItemToCartResult).isEqualTo(AddItemToCartResult.ok());
    ArgumentCaptor cartArgumentCaptor = ArgumentCaptor.forClass(Cart.class);
    verify(cartRepository).update(cartArgumentCaptor.capture());
    assertThat(cartArgumentCaptor.capture()).isEqualTo(cart
    .withItem(previousItem)
    .withItem(addedItem)
    .withItem(giftItem)
    );
    verify(alertService, never()).signal(any());
    }
    9
    In a codebase near you…

    View Slide

  10. © 2021 Thoughtworks
    public CartService(CartRepository cartRepository, InventoryService inventoryService, FraudDetectionService
    fraudDetectionService, AlertService alertService, GiftItemService giftItemService) {
    this.cartRepository = cartRepository;
    this.inventoryService = inventoryService;
    this.fraudDetectionService = fraudDetectionService;
    this.alertService = alertService;
    this.giftItemService = giftItemService;
    }
    public AddItemToCartResult addItem(AddItemToCartCommand command) {
    if (inventoryService.checkAvailability(command.getItem()) == NOT_AVAILABLE) { // 󰍹
    return AddItemToCartResult.itemNotAvailable();
    }
    Cart cart = command.getCart();
    cart.addItem(command.getItem()); // 󰍽
    Item todaysGiftItem = giftItemService.getTodaysGiftItem(); // 󰍼
    if (!cart.containsGiftItem()) {
    cart.addItem(todaysGiftItem);
    }
    if (fraudDetectionService.looksSuspicious(cart)) { // 󰍶
    alertService.signal(new SuspiciousCartAlert(cart));
    }
    cartRepository.update(cart); // 󰍵
    return AddItemToCartResult.ok();
    }
    10
    And the culprit is…

    View Slide

  11. © 2021 Thoughtworks
    Searching for a solution
    11

    View Slide

  12. © 2021 Thoughtworks 12
    Favor object composition over inheritance
    – Gamma, Helm, Johnson, Vlissides, 1995
    All modules should be open for extension,
    and closed for modi ication
    – Bertrand Meyer (more or less), 1988
    Be able to extend a system solely by adding new
    objects, without modifying any existing objects
    – Kent Beck, 1998
    Composition is the Essence of Programming
    – Bartosz Milewski, 2014
    Objects should be composable
    – David West, 2004

    View Slide

  13. © 2021 Thoughtworks 13

    View Slide

  14. © 2021 Thoughtworks 14
    New features should be coded in new code files,
    with no modification to existing code files
    – Paraphrasing Kent Beck

    View Slide

  15. © 2021 Thoughtworks 15
    The goal of the Open-Closed Principle
    The aim of the OCP is to organize software around backplanes, that allow extension
    simply by developing and connecting a new module, just like hardware backplanes or
    cartridges.

    View Slide

  16. © 2021 Thoughtworks 16
    New features should be coded in new code files,
    with no modification to existing code files
    Consequences:
    ● If a feature must be removed, it can be removed by simply
    removing its code files;
    ● If a feature must be changed, it can be changed by changing
    only its code files.
    ● All the code that implements a given story/feature/business
    rule can be found in specific files, dedicated to implement
    just that story/feature/business rule

    View Slide

  17. © 2021 Thoughtworks
    My life with the Open-Closed Principle
    17
    2010 blog about the OCP kata
    2010 XP Days Benelux
    2012 London Code Dojo
    2013 XP Manchester
    2014 Github repository created
    2014 XP2014 in Rome

    2021 why is this kind of code still prevalent?
    http://matteo.vaccari.name/blog/archives/293

    View Slide

  18. © 2021 Thoughtworks
    Unless we are fluent, we will not
    be able to do this at work!
    18
    18
    © 2021 Thoughtworks

    View Slide

  19. © 2021 Thoughtworks
    19
    Most kata are solved in a single object
    public class BowlingGame {
    private int rolls[] = new int[21];
    private int currentRoll = 0;
    public void roll(int pins) {
    rolls[currentRoll++] = pins;
    }
    public int score() {
    int score = 0;
    int frameIndex = 0;
    for (int frame = 0; frame < 10; frame++) {
    if (isStrike(frameIndex)) {
    score += 10 + strikeBonus(frameIndex);
    frameIndex++;
    } else if (isSpare(frameIndex)) {
    score += 10 + spareBonus(frameIndex);
    frameIndex += 2;
    } else {
    score += sumOfBallsInFrame(frameIndex);
    frameIndex += 2;
    }
    }
    return score;
    }
    private boolean isStrike(int frameIndex) {
    return rolls[frameIndex] == 10;
    }
    public class FizzBuzzGame {
    public String say(int number) {
    if (number % 3 == 0 && number % 5 == 0) {
    return "FizzBuzz";
    }
    if (number % 3 == 0) {
    return "Fizz";
    }
    if (number % 5 == 0) {
    return "Buzz";
    }
    return String.valueOf(number);
    }
    }
    The classic Bowling
    Game kata, in the
    version of Robert
    Martin
    Typical solution to
    the Fizz Buzz kata
    http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata

    View Slide

  20. © 2021 Thoughtworks
    What about upfront design?
    20
    Robert Martin starts his discussion of the Bowling Kata
    with what would be his thinking before TDD
    Then he discards the upfront design and proceeds to a
    totally different solution in a single class
    public class BowlingGame {
    private int rolls[] = new int[21];
    private int currentRoll = 0;
    public void roll(int pins) {
    rolls[currentRoll++] = pins;
    }
    public int score() {
    int score = 0;
    int frameIndex = 0;
    for (int frame = 0; frame < 10; frame++) {
    if (isStrike(frameIndex)) {
    score += 10 + strikeBonus(frameIndex);
    frameIndex++;
    } else if (isSpare(frameIndex)) {
    score += 10 + spareBonus(frameIndex);
    frameIndex += 2;
    } else {
    score += sumOfBallsInFrame(frameIndex);
    frameIndex += 2;
    }
    }
    return score;
    }
    This is an OK way to
    do TDD.
    The result is fine if no
    new requirements
    arive

    View Slide

  21. © 2021 Thoughtworks
    An OK solution if no new requirements arrive…
    21
    😂😂😂
    https://www.slideshare.net/xpmatteo/20101125-ocpxpday

    View Slide

  22. © 2021 Thoughtworks 22
    If you try to
    implement all these…
    …You end up with IF spaghetti

    View Slide

  23. © 2021 Thoughtworks 23
    It seemed so simple…
    This design is inflexible.
    It is at the limit of what we can grasp
    intuitively
    You add one more IF and it becomes
    very hard to read
    Perhaps it’s not
    public class BowlingGame {
    private int rolls[] = new int[21];
    public int score() {
    int score = 0;
    int frameIndex = 0;
    for (int frame = 0; frame < 10; frame++) {
    if (isStrike(frameIndex)) {
    score += 10 + strikeBonus(frameIndex);
    frameIndex++;
    } else if (isSpare(frameIndex)) {
    score += 10 + spareBonus(frameIndex);
    frameIndex += 2;
    } else {
    score += sumOfBallsInFrame(frameIndex);
    frameIndex += 2;
    }
    }
    return score;
    }
    }

    View Slide

  24. © 2021 Thoughtworks 24
    The original design
    might have been
    useful
    public class BowlingGame {
    private List frames;
    public int score() {
    int score = 0;
    for (Frame frame : frames) {
    score += frame.score();
    }
    return score;
    }
    }
    Simple polymorphism takes
    care of most planetary
    Bowling requirements
    😎

    View Slide

  25. © 2021 Thoughtworks
    public CartService(CartRepository cartRepository, InventoryService inventoryService, FraudDetectionService
    fraudDetectionService, AlertService alertService, GiftItemService giftItemService) {
    this.cartRepository = cartRepository;
    this.inventoryService = inventoryService;
    this.fraudDetectionService = fraudDetectionService;
    this.alertService = alertService;
    this.giftItemService = giftItemService;
    }
    public AddItemToCartResult addItem(AddItemToCartCommand command) {
    if (inventoryService.checkAvailability(command.getItem()) == NOT_AVAILABLE) {
    return AddItemToCartResult.itemNotAvailable();
    }
    Cart cart = command.getCart();
    cart.addItem(command.getItem());
    Item todaysGiftItem = giftItemService.getTodaysGiftItem();
    if (!cart.containsGiftItem()) {
    cart.addItem(todaysGiftItem);
    }
    if (fraudDetectionService.looksSuspicious(cart)) {
    alertService.signal(new SuspiciousCartAlert(cart));
    }
    cartRepository.update(cart);
    return AddItemToCartResult.ok();
    }
    Developers don’t train
    nearly enough, and
    when they do, they
    train to build
    monolithic, inflexible
    solutions
    25
    25
    © 2021 Thoughtworks

    View Slide

  26. © 2021 Thoughtworks
    Becoming fluent at program composition
    26

    View Slide

  27. © 2021 Thoughtworks
    An exercise to train programming by composition
    27
    1. Take any standard kata solution
    2. Add a new test for a new
    requirement
    3. Can you make it pass by only
    adding new classes, without
    changing any existing code except
    construction code?
    4. Yes → you rock! go back to 2
    5. No → disable the new test and
    refactor until you can

    View Slide

  28. © 2021 Thoughtworks
    Example: The FizzBuzz Game
    28
    Example:
    1, 2, Fizz!, 4, Buzz!, Fizz!, 7, 8,
    Fizz!, Buzz!, 11, Fizz!, 13, 14,
    FizzBuzz!, 16, 17, Fizz!...
    Rules:
    If the number is a multiple of 3, say “Fizz”
    If it is a multiple of 5, say “Buzz”
    If it is a multiple of 3 and 5, say “FizzBuzz”
    Otherwise, just say the number.
    public class FizzBuzzGameTest {
    private FizzBuzzGame game;
    @BeforeEach
    public void setUp() {
    game = new FizzBuzzGame();
    }
    @Test
    public void justSayTheNumber() {
    assertEquals("1", game.say(1));
    assertEquals("2", game.say(2));
    }
    @Test
    public void multiplesOfThree() {
    assertEquals("Fizz", game.say(3));
    assertEquals("Fizz", game.say(6));
    }
    @Test
    public void multiplesOfFive() {
    assertEquals("Buzz", game.say(5));
    assertEquals("Buzz", game.say(10));
    }
    @Test
    public void multiplesOfFiveAndThree() {
    assertEquals("FizzBuzz", game.say(15));
    assertEquals("FizzBuzz", game.say(30));
    }

    View Slide

  29. © 2021 Thoughtworks
    1. Take a standard solution
    29
    public class FizzBuzzGameTest {
    private FizzBuzzGame game;
    @BeforeEach
    public void setUp() {
    game = new FizzBuzzGame();
    }
    @Test
    public void justSayTheNumber() {
    assertEquals("1", game.say(1));
    assertEquals("2", game.say(2));
    }
    @Test
    public void multiplesOfThree() {
    assertEquals("Fizz", game.say(3));
    assertEquals("Fizz", game.say(6));
    }
    @Test
    public void multiplesOfFive() {
    assertEquals("Buzz", game.say(5));
    assertEquals("Buzz", game.say(10));
    }
    @Test
    public void multiplesOfFiveAndThree() {
    assertEquals("FizzBuzz", game.say(15));
    assertEquals("FizzBuzz", game.say(30));
    }
    public class FizzBuzzGame {
    public String say(int number) {
    if (isMultipleOf(3, number) && isMultipleOf(5, number)) {
    return "FizzBuzz";
    }
    if (isMultipleOf(3, number)) {
    return "Fizz";
    }
    if (isMultipleOf(5, number)) {
    return "Buzz";
    }
    return String.valueOf(number);
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }

    View Slide

  30. © 2021 Thoughtworks
    2. A new requirement!
    30
    If it is a multiple of 7, say “Bang”
    @Test
    public void multiplesOfSeven() {
    assertEquals("Bang", game.say(7));
    assertEquals("Bang", game.say(14));
    }
    public class FizzBuzzGame {
    public String say(int number) {
    if (isMultipleOf(3, number) && isMultipleOf(5, number)) {
    return "FizzBuzz";
    }
    if (isMultipleOf(3, number)) {
    return "Fizz";
    }
    if (isMultipleOf(5, number)) {
    return "Buzz";
    }
    return String.valueOf(number);
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }
    Can we implementing by adding
    new classes, with no modification
    to existing classes?

    View Slide

  31. © 2021 Thoughtworks
    5. I don’t see a way!
    31
    So: Disable the new test
    and refactor until you can
    public class FizzBuzzGame {
    public String say(int number) {
    if (isMultipleOf(3, number) && isMultipleOf(5, number)) {
    return "FizzBuzz";
    }
    if (isMultipleOf(3, number)) {
    return "Fizz";
    }
    if (isMultipleOf(5, number)) {
    return "Buzz";
    }
    return String.valueOf(number);
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }
    public class FizzBuzzGame {
    public String say(int number) {
    String response = "";
    if (isMultipleOf(3, number)) {
    response += "Fizz";
    }
    if (isMultipleOf(5, number)) {
    response += "Buzz";
    }
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }

    View Slide

  32. © 2021 Thoughtworks
    Keep going…
    32
    public class FizzBuzzGame {
    public String say(int number) {
    String response = "";
    if (isMultipleOf(3, number)) {
    response += "Fizz";
    }
    if (isMultipleOf(5, number)) {
    response += "Buzz";
    }
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }
    public String say(int number) {
    String response = "";
    response += sayFizz(number);
    response += sayBuzz(number);
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private String sayBuzz(int number) {
    if (isMultipleOf(5, number)) {
    return "Buzz";
    }
    return "";
    }
    private String sayFizz(int number) {
    if (isMultipleOf(3, number)) {
    return "Fizz";
    }
    return "";
    }

    View Slide

  33. © 2021 Thoughtworks
    Keep going…
    33
    public String say(int number) {
    String response = "";
    response += sayFizz(number);
    response += sayBuzz(number);
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private String sayBuzz(int number) {
    if (isMultipleOf(5, number)) {
    return "Buzz";
    }
    return "";
    }
    private String sayFizz(int number) {
    if (isMultipleOf(3, number)) {
    return "Fizz";
    }
    return "";
    }
    public String say(int number) {
    String response = "";
    response += sayWord(number, 3, "Fizz");
    response += sayWord(number, 5, "Buzz");
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private String sayWord(int number, int divisor, String word) {
    if (isMultipleOf(divisor, number)) {
    return word;
    }
    return "";
    }

    View Slide

  34. © 2021 Thoughtworks
    Almost there…
    34
    public String say(int number) {
    String response = "";
    response += sayWord(number, 3, "Fizz");
    response += sayWord(number, 5, "Buzz");
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private String sayWord(int number, int divisor, String word) {
    if (isMultipleOf(divisor, number)) {
    return word;
    }
    return "";
    }
    public class FizzBuzzGame {
    public String say(int number) {
    String response = "";
    response += new WordRule(3, "Fizz").say(number);
    response += new WordRule(5, "Buzz").say(number);
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    }
    public class WordRule {
    private final int divisor;
    private final String word;
    public WordRule(int divisor, String word) {
    this.divisor = divisor;
    this.word = word;
    }
    public String say(int number) {
    if (isMultipleOf(divisor, number)) {
    return word;
    }
    return "";
    }
    private boolean isMultipleOf(int divisor, int number) {
    return number % divisor == 0;
    }
    }

    View Slide

  35. © 2021 Thoughtworks
    Refactoring
    done
    35
    public class FizzBuzzGame {
    private List rules;
    public FizzBuzzGame(List rules) {
    this.rules = rules;
    }
    public String say(int number) {
    String response = "";
    for (WordRule rule: rules) {
    response += rule.say(number);
    }
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    }
    public class FizzBuzzGameTest {
    private FizzBuzzGame game;
    @BeforeEach
    public void setUp() {
    game = new FizzBuzzGame(List.of(
    new WordRule(3, "Fizz"),
    new WordRule(5, "Buzz")
    ));
    }
    public String say(int number) {
    String response = "";
    response += sayWord(number, 3, "Fizz");
    response += sayWord(number, 5, "Buzz");
    if (response.isEmpty()) {
    return String.valueOf(number);
    }
    return response;
    }
    private String sayWord(int number, int divisor, String word) {
    if (isMultipleOf(divisor, number)) {
    return word;
    }
    return "";
    }

    View Slide

  36. © 2021 Thoughtworks
    There!
    36
    public class FizzBuzzGameTest {
    private FizzBuzzGame game;
    @BeforeEach
    public void setUp() {
    game = new FizzBuzzGame(List.of(
    new WordRule(3, "Fizz"),
    new WordRule(5, "Buzz")
    ));
    }
    public class FizzBuzzGameTest {
    private FizzBuzzGame game;
    @BeforeEach
    public void setUp() {
    game = new FizzBuzzGame(List.of(
    new WordRule(3, "Fizz"),
    new WordRule(5, "Buzz"),
    new WordRule(7, "Bang")
    ));
    }

    View Slide

  37. © 2021 Thoughtworks
    Applying the OCP at work
    37

    View Slide

  38. © 2021 Thoughtworks
    public CartService(CartRepository cartRepository, InventoryService inventoryService, FraudDetectionService
    fraudDetectionService, AlertService alertService, GiftItemService giftItemService) {
    this.cartRepository = cartRepository;
    this.inventoryService = inventoryService;
    this.fraudDetectionService = fraudDetectionService;
    this.alertService = alertService;
    this.giftItemService = giftItemService;
    }
    public AddItemToCartResult addItem(AddItemToCartCommand command) {
    if (inventoryService.checkAvailability(command.getItem()) == NOT_AVAILABLE) { // 󰍹
    return AddItemToCartResult.itemNotAvailable();
    }
    Cart cart = command.getCart();
    cart.addItem(command.getItem()); // 󰍽
    Item todaysGiftItem = giftItemService.getTodaysGiftItem(); // 󰍼
    if (!cart.containsGiftItem()) {
    cart.addItem(todaysGiftItem);
    }
    if (fraudDetectionService.looksSuspicious(cart)) { // 󰍶
    alertService.signal(new SuspiciousCartAlert(cart));
    }
    cartRepository.update(cart); // 󰍵
    return AddItemToCartResult.ok();
    }
    38
    How to fix this?!
    🤔🤔
    🤔

    View Slide

  39. © 2021 Thoughtworks 39
    Sources of ideas:

    View Slide

  40. © 2021 Thoughtworks
    Sketching at a whiteboard…
    40
    Check inventory
    Add bought
    item
    Add gift item
    Fraud check
    Persist changes
    An OO perspective: these
    could all be decorators
    An FP perspective: these
    could all be “functions”

    View Slide

  41. © 2021 Thoughtworks
    Let’s try with functions
    It’s the zeitgeist…
    41
    We can model a service as a function from command to result. In math notation:
    addItemToCart: AddItemToCartCommand → AddItemToCartResponse
    In Java:
    Function addItemToCart;
    Functions can be composed: (f ○ g)(x) = f(g(x))
    In Java:
    Our services will be composed of many small functions
    f.andThen(g)

    View Slide

  42. © 2021 Thoughtworks 42
    @Configuration
    public class AddItemToCartServiceConfig {
    @Autowired private InventoryService inventoryService;
    @Autowired private GiftItemService giftItemService;
    @Autowired private CartRepository cartRepository;
    @Autowired private FraudDetectionService fraudDetectionService;
    @Autowired private AlertService alertService;
    @Bean
    public Function addItemToCartService() {
    Function checkInventory = new CheckInventory(inventoryService);
    Function addItemToCart = new AddItemToCart();
    Function addGiftItem = new AddGiftItem(giftItemService);
    Function fraudCheck = new FraudCheck(fraudDetectionService, alertService);
    Function persistChanges = new PersistChanges(cartRepository);
    return checkInventory
    .andThen(addItemToCart)
    .andThen(addGiftItem)
    .andThen(fraudCheck)
    .andThen(persistChanges)
    ;
    }
    }

    View Slide

  43. © 2021 Thoughtworks 43
    Function addItemToCart = new AddItemToCart();
    @Test
    void addItem_ok() {
    Item previousItem = new Item("a tv");
    Item addedItem = new Item("a book");
    Cart cart = new Cart().withItem(previousItem);
    AddItemToCartCommand command = new AddItemToCartCommand(cart, addedItem);
    AddItemToCartResult addItemToCartResult = addItemToCart.apply(command);
    assertThat(cart.getItems()).containsExactly(previousItem, addedItem);
    assertThat(addItemToCartResult).isEqualTo(AddItemToCartResult.ok());
    }

    View Slide

  44. © 2021 Thoughtworks
    Unless we are fluent, we will not
    be able to do this at work!
    44
    44
    © 2021 Thoughtworks

    View Slide

  45. © 2021 Thoughtworks
    ● We should refactor our code towards composition
    ● We will never be able to apply this in practice
    unless we achieve fluency
    ● A good way to achieve fluency is by doing and
    repeating exercises
    45

    View Slide

  46. © 2021 Thoughtworks
    Start getting the OCP under your fingers today
    46
    ● Choose a kata from the Kata-log: https://kata-log.rocks/
    ● Solve it
    ● Invent a new requirement
    ● Apply the OCP kata rules
    ● Do it again;
    ● try different ways to make your code composable

    View Slide

  47. © 2021 Thoughtworks
    Resources
    47
    ● The OCP kata repository https://github.com/xpmatteo/ocp-kata
    ● How to organize a Coding Dojo https://leanpub.com/codingdojohandbook
    ● Code Retreats are awesome, look for one near you https://twitter.com/coderetreat
    ● A well-organized repository of examples: https://kata-log.rocks/
    ● Freeman & Pryce are very good at evolving a composable set of objects to
    express a complex domain:
    https://www.infoq.com/presentations/design-principles-code-structures/

    View Slide

  48. © 2021 Thoughtworks
    Thanks for listening
    Matteo Vaccari
    Grumpy Developer
    [email protected]
    48

    View Slide