$30 off During Our Annual Pro Sale. View Details »

Embracing change at BrnoPHP

Jakub Zalas
November 14, 2015

Embracing change at BrnoPHP

An agile developer wouldn't be really agile if they didn't welcome changes with open arms. This requires a certain discipline and approach to writing code, so it doesn't start to rot and smell of a bad design.

During the talk Jakub will remind you principles and techniques every developer claiming to be agile should use on a daily basis. You'll learn how to write loosely coupled code, make it less rigid, less fragile, and more fun!

https://www.brnophp.cz/conference-2015

Jakub Zalas

November 14, 2015
Tweet

More Decks by Jakub Zalas

Other Decks in Programming

Transcript

  1. EMBRACING CHANGE
    Jakub Zalas
    14.11.2015
    @jakub_zalas @jakzal

    View Slide

  2. CHANGE DAMAGE
    Change?, SomeDriftwood, https://www.flickr.com/photos/arthurjohnpicton/4383221264/

    View Slide

  3. AGILE MANIFESTO
    Welcome changing requirements, even late in development.
    Agile processes harness change for the customer's competitive
    advantage.

    View Slide

  4. We live in a world of changing requirements,
    and our job is to make sure that our software
    can survive those changes.
    If the design of our software degrades because
    the requirements have changed, we are not
    being agile.

    View Slide

  5. View Slide

  6. View Slide

  7. KNOWLEDGE
    TIME

    View Slide

  8. You have to finish things - that's what you learn from,
    you learn by finishing things.
    Neil Gaiman

    View Slide

  9. HOW DOES CODE ROT?
    Rotten pumpkin, Andy Hay, https://www.flickr.com/photos/andyhay/8196333166/

    View Slide

  10. use Buzz\Browser;
    class LinkCrawler
    {
    private $browser;
    private $linkExtractor;
    private $linkRepository;
    public function __construct(
    Browser $browser,
    LinkExtractor $linkExtractor,
    LinkRepository $linkRepository
    ) {
    $this->browser = $browser;
    $this->linkExtractor = $linkExtractor;
    $this->linkRepository = $linkRepository;
    }
    // ...
    }

    View Slide

  11. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }

    View Slide

  12. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }

    View Slide

  13. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    class LinkCrawler
    {
    public function crawl($url)
    {
    if (0 === strpos($url, 'http')) {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    } else {
    $content = file_get_contents($url);
    }
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }

    View Slide

  14. New requirements: Log if verbose mode is enabled
    class LinkCrawler
    {
    public function crawl($url)
    {
    if ($this->verbose) {
    $this->logger(sprintf('Crawling "%s".', $url));
    }
    if (0 === strpos($url, 'http')) {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    } else {
    $content = file_get_contents($url);
    }
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }

    View Slide

  15. New requirements: Publish links to elastic search
    class LinkCrawler
    {
    public function crawl($url)
    {
    if ($this->verbose) {
    $this->logger(sprintf('Crawling "%s".', $url));
    }
    if (0 === strpos($url, 'http')) {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    } else {
    $content = file_get_contents($url);
    }
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    $this->elasticSearch->publish($links);
    }
    }

    View Slide

  16. Code rots
    when we don’t revise the design.

    View Slide

  17. HOW TO
    DIAGNOSE
    PROBLEMS?
    Littmann Stethoscope, Mediative UK, https://www.flickr.com/photos/107621760@N03/10668574215/

    View Slide

  18. DESIGN SMELLS
    ✤ Rigidity
    ✤ Fragility
    ✤ Immobility
    ✤ Viscosity
    ✤ Needless complexity
    ✤ Needless repetition
    ✤ Opacity
    how pumpkins rot, Evil Erin, https://www.flickr.com/photos/evilerin/4158920495/

    View Slide

  19. SOLID PRINCIPLES
    ✤ Single Responsibility Principle
    ✤ Open/Closed Principle
    ✤ Liskov Substitution Principle
    ✤ Interface Segregation Principle
    ✤ Dependency Inversion Principle
    factory, Dean Hochman, https://www.flickr.com/photos/deanhochman/16685305845/

    View Slide

  20. PACKAGE DESIGN
    PRINCIPLES
    ✤ Reuse-release equivalence principle
    ✤ Common-reuse principle
    ✤ Common-closure principle
    ✤ Acyclic dependencies principle
    ✤ Stable-dependencies principle
    ✤ Stable-abstractions principle
    Packaging variations, Chris Carter, https://www.flickr.com/photos/chris_carter_/4111436287/

    View Slide

  21. COUPLING & COHESION

    View Slide

  22. COHESION
    Cohesion describes how closely are
    elements in a module related.
    Pflaster/Pavement, Sivi Steys, https://www.flickr.com/photos/38365223@N03/7913024500/

    View Slide

  23. COUPLING
    Modules are coupled if changing one of
    them requires changing another one.
    Martin Fowler
    In Chains, Christian Weidinger, https://www.flickr.com/photos/ch-weidinger/14756953490/
    http://martinfowler.com/ieeeSoftware/coupling.pdf

    View Slide

  24. COUPLING THROUGH A
    PROPERTY
    use Buzz\Browser;
    class Crawler
    {
    /**
    * @var Browser
    */
    private $browser;
    }

    View Slide

  25. COUPLING THROUGH A
    METHOD CALL
    class Crawler
    {
    private $c;
    public function crawl($url)
    {
    $this->c->getBrowser()->get($url);
    }
    }

    View Slide

  26. COUPLING THROUGH A
    REFERENCE
    use Buzz\Browser;
    class Crawler
    {
    public function crawl($url, Browser $b)
    {
    $response = $b->get($url);
    }
    }

    View Slide

  27. COUPLING THROUGH A
    REFERENCE
    use Buzz\Browser;
    class Crawler
    {
    /**
    * @return Browser
    */
    public function crawl($url)
    {
    // ...
    return $browser;
    }
    }

    View Slide

  28. COUPLING THROUGH AN
    IMPLEMENTATION / EXTENSION
    use Buzz\Browser;
    class Crawler extends Browser
    {
    public function crawl($url)
    {
    $this->get($url);
    }
    }

    View Slide

  29. View Slide

  30. HOW TO COPE WITH CHANGE?
    Swiss Army Knife, Basheer Tome, https://www.flickr.com/photos/basheertome/3086908057/

    View Slide

  31. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }
    New requirements: Load a file from the local filesystem if a local path
    was passed as an URL

    View Slide

  32. OPEN/CLOSED PRINCIPLE
    Software modules should be
    open for extension
    but
    closed for modification.

    View Slide

  33. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    class LinkCrawler
    {
    public function crawl($url)
    {
    if (0 === strpos($url, 'http')) {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    } else {
    $content = file_get_contents($url);
    }
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }

    View Slide

  34. class LinkCrawler
    {
    private $contentProvider;
    private $linkExtractor;
    private $linkRepository;
    public function __construct(
    ContentProvider $contentProvider,
    LinkExtractor $linkExtractor,
    LinkRepository $linkRepository
    ) {
    $this->contentProvider = $contentProvider;
    $this->linkExtractor = $linkExtractor;
    $this->linkRepository = $linkRepository;
    }
    }
    New requirements: Load a file from the local filesystem if a local path
    was passed as an URL

    View Slide

  35. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->contentProvider->load($url);
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }
    New requirements: Load a file from the local filesystem if a local path
    was passed as an URL

    View Slide

  36. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    interface ContentProvider
    {
    /**
    * @param string $url
    *
    * @return string
    */
    public function load($url);
    }

    View Slide

  37. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    class BuzzContentProvider implements ContentProvider
    {
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function load($url)
    {
    return $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    }
    }

    View Slide

  38. New requirements: Load a file from the local filesystem if a local path
    was passed as an URL
    class FileContentProvider implements ContentProvider
    {
    public function load($url)
    {
    return file_get_contents($url);
    }
    }

    View Slide

  39. Software modules should be
    open for extension
    but
    closed for modification.

    View Slide

  40. View Slide

  41. WHAT HAPPENED?
    1.We noticed a problem in the design
    2. We diagnosed the problem by applying solid principles
    3. We fixed the design by applying a design pattern
    4. We added a new feature easily

    View Slide

  42. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }
    New requirements: Add two more crawlers to extract images and
    meta attributes

    View Slide

  43. View Slide

  44. DEPENDENCY INVERSION
    PRINCIPLE
    High-level modules should not depend on low-level modules.
    Both should depend on abstractions.
    Abstractions should not depend upon details.
    Details should depend upon abstractions.

    View Slide

  45. DEPENDENCY INVERSION
    PRINCIPLE
    High-level modules
    should not depend
    on low-level
    modules.
    Low-level
    High-level

    View Slide

  46. DEPENDENCY INVERSION
    PRINCIPLE
    High-level modules
    should not depend
    on low-level
    modules.
    Both should
    depend on
    abstractions
    An abstraction!

    View Slide

  47. DEPENDENCY INVERSION
    PRINCIPLE
    Both should
    depend on
    abstractions

    View Slide

  48. DEPENDENCY INVERSION
    PRINCIPLE
    Abstractions
    should not depend
    upon details.
    A detail!

    View Slide

  49. DEPENDENCY INVERSION
    PRINCIPLE
    Abstractions
    should not depend
    upon details.
    Details should
    depend upon
    abstractions.

    View Slide

  50. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }
    New requirements: Add two more crawlers to extract images and
    meta attributes

    View Slide

  51. class LinkCrawler
    {
    // ...
    public function crawl($url)
    {
    $content = $this->contentProvider->load($url);
    $links = $this->linkExtractor->extract($content);
    $this->linkRepository->store($links);
    }
    }
    New requirements: Add two more crawlers to extract images and
    meta attributes

    View Slide

  52. interface ContentProvider
    {
    /**
    * @param string $url
    *
    * @return string
    */
    public function load($url);
    }

    View Slide

  53. class BuzzContentProvider implements ContentProvider
    {
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function load($url)
    {
    return $this->browser->get(
    $url, ['content-type' => 'application/json']
    );
    }
    }

    View Slide

  54. View Slide

  55. View Slide

  56. use Guzzle\Client;
    class GuzzleContentProvider implements ContentProvider
    {
    private $guzzle;
    public function __construct(Client $guzzle)
    {
    $this->guzzle = $guzzle;
    }
    public function load($url)
    {
    $request = $this->guzzle->createRequest(
    'GET', $url
    );
    $response = $request->send();
    return $response->getBody();
    }
    }

    View Slide

  57. WHAT HAPPENED?
    1.We noticed a problem in the design
    2. We diagnosed the problem by applying solid principles
    3. We fixed the design by applying a design pattern
    4. We added a new feature easily

    View Slide

  58. FRAMEWORKS
    Professional Framework, ronramstew, https://www.flickr.com/photos/54996985@N00/5439941604/

    View Slide

  59. class ProductController extends Controller
    {
    public function searchAction(Request $request)
    {
    $products = $this->get('doctrine')
    ->getRepository('Acme:Product');
    ->search($request->query->get(‘keywords’);
    return $this->render(
    'product/search.html.twig',
    ['products' => $products]
    );
    }
    }
    Count dependencies

    View Slide

  60. View Slide

  61. interface ProductRepository
    {
    /**
    * @return Product[]
    */
    public function search($keywords);
    }

    View Slide

  62. class DoctrineProductRepository implements ProductRepository
    {
    private $em;
    public function __construct(EntityManager $em)
    {
    $this->em = $em;
    }
    /**
    * @return Product[]
    */
    public function search($keywords)
    {
    return $this->em->createQueryBuilder()
    ->select('p')
    ->from(Product::class, 'p')
    ->where('p.title LIKE :keywords')
    ->setParameter('keywords', '%'.$keywords.'%')
    ->getQuery()
    ->getResult();
    }
    }

    View Slide

  63. class ProductController
    {
    private $productRepository;
    private $templating;
    public function __construct(
    ProductRepository $productRepository,
    EngineInterface $templating
    ) {
    $this->productRepository = $productRepository;
    $this->templating = $templating;
    }
    // ...
    }

    View Slide

  64. class ProductController
    {
    // ...
    public function searchAction($keywords)
    {
    $products = $this->productRepository->search($keywords);
    return $this->templating->renderResponse(
    'product/search.html.twig',
    ['products' => $products]
    );
    }
    }

    View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. LAYERED ARCHITECTURE

    View Slide

  69. View Slide

  70. HEXAGONAL ARCHITECTURE
    Application
    Data
    User-side
    Doctrine
    In
    memory
    UI
    Tests
    CLI

    View Slide

  71. PACKAGE DESIGN
    Design Blog Sociale, SOCIALisBETTER, https://www.flickr.com/photos/27620885@N02/2602771507/

    View Slide

  72. THE REUSE/RELEASE
    EQUIVALENCE PRINCIPLE
    The granule of reuse is the granule of release.

    View Slide

  73. THE COMMON REUSE
    PRINCIPLE
    The classes in a component are reused together.
    If you reuse one of the classes in a component, you
    reuse them all.

    View Slide

  74. THE COMMON CLOSURE
    PRINCIPLE
    The classes in a component should be closed
    together against the same kinds of changes.
    A change that affects a component affects all the
    classes in that component and no other
    components.

    View Slide

  75. THE ACYCLIC DEPENDENCIES
    PRINCIPLE
    Allow no cycles in the component dependency
    graph.

    View Slide

  76. THE STABLE DEPENDENCIES
    PRINCIPLE
    Depend in the direction of stability.

    View Slide

  77. THE STABLE ABSTRACTIONS
    PRINCIPLE
    A component should be as abstract as it is stable.

    View Slide

  78. SUMMARY
    1. Start simple
    2. Don’t feel committed to the decisions made in the past.
    3. Revise the design each time you make a change
    4. Don’t complicate. Make your design change-proof only
    where it’s needed.
    5. Constantly improve the design. No sprints for refactoring!

    View Slide

  79. DEFER COMMITMENT!

    View Slide

  80. Good code doesn't take more time to
    write.
    What takes a lot of time is the learning
    how to write good code.

    View Slide

  81. @jakub_zalas @jakzal
    Thank you!
    Rate this talk!
    https://joind.in/16266

    View Slide