Embracing change at BrnoPHP

1a4e1f98f3aeef310273366c8c785207?s=47 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

1a4e1f98f3aeef310273366c8c785207?s=128

Jakub Zalas

November 14, 2015
Tweet

Transcript

  1. EMBRACING CHANGE Jakub Zalas 14.11.2015 @jakub_zalas @jakzal

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

  3. AGILE MANIFESTO Welcome changing requirements, even late in development. Agile

    processes harness change for the customer's competitive advantage.
  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.
  5. None
  6. None
  7. KNOWLEDGE TIME

  8. You have to finish things - that's what you learn

    from, you learn by finishing things. Neil Gaiman
  9. HOW DOES CODE ROT? Rotten pumpkin, Andy Hay, https://www.flickr.com/photos/andyhay/8196333166/

  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; } // ... }
  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); } }
  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); } }
  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); } }
  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); } }
  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); } }
  16. Code rots when we don’t revise the design.

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

  18. DESIGN SMELLS ✤ Rigidity ✤ Fragility ✤ Immobility ✤ Viscosity

    ✤ Needless complexity ✤ Needless repetition ✤ Opacity how pumpkins rot, Evil Erin, https://www.flickr.com/photos/evilerin/4158920495/
  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/
  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/
  21. COUPLING & COHESION

  22. COHESION Cohesion describes how closely are elements in a module

    related. Pflaster/Pavement, Sivi Steys, https://www.flickr.com/photos/38365223@N03/7913024500/
  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
  24. COUPLING THROUGH A PROPERTY use Buzz\Browser; class Crawler { /**

    * @var Browser */ private $browser; }
  25. COUPLING THROUGH A METHOD CALL class Crawler { private $c;

    public function crawl($url) { $this->c->getBrowser()->get($url); } }
  26. COUPLING THROUGH A REFERENCE use Buzz\Browser; class Crawler { public

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

    * @return Browser */ public function crawl($url) { // ... return $browser; } }
  28. COUPLING THROUGH AN IMPLEMENTATION / EXTENSION use Buzz\Browser; class Crawler

    extends Browser { public function crawl($url) { $this->get($url); } }
  29. None
  30. HOW TO COPE WITH CHANGE? Swiss Army Knife, Basheer Tome,

    https://www.flickr.com/photos/basheertome/3086908057/
  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
  32. OPEN/CLOSED PRINCIPLE Software modules should be open for extension but

    closed for modification.
  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); } }
  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
  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
  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); }
  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'] ); } }
  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); } }
  39. Software modules should be open for extension but closed for

    modification.
  40. None
  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
  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
  43. None
  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.
  45. DEPENDENCY INVERSION PRINCIPLE High-level modules should not depend on low-level

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

    modules. Both should depend on abstractions An abstraction!
  47. DEPENDENCY INVERSION PRINCIPLE Both should depend on abstractions

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

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

    should depend upon abstractions.
  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
  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
  52. interface ContentProvider { /** * @param string $url * *

    @return string */ public function load($url); }
  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'] ); } }
  54. None
  55. None
  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(); } }
  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
  58. FRAMEWORKS Professional Framework, ronramstew, https://www.flickr.com/photos/54996985@N00/5439941604/

  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
  60. None
  61. interface ProductRepository { /** * @return Product[] */ public function

    search($keywords); }
  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(); } }
  63. class ProductController { private $productRepository; private $templating; public function __construct(

    ProductRepository $productRepository, EngineInterface $templating ) { $this->productRepository = $productRepository; $this->templating = $templating; } // ... }
  64. class ProductController { // ... public function searchAction($keywords) { $products

    = $this->productRepository->search($keywords); return $this->templating->renderResponse( 'product/search.html.twig', ['products' => $products] ); } }
  65. None
  66. None
  67. None
  68. LAYERED ARCHITECTURE

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

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

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

    granule of release.
  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.
  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.
  75. THE ACYCLIC DEPENDENCIES PRINCIPLE Allow no cycles in the component

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

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

    as it is stable.
  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!
  79. DEFER COMMITMENT!

  80. Good code doesn't take more time to write. What takes

    a lot of time is the learning how to write good code.
  81. @jakub_zalas @jakzal Thank you! Rate this talk! https://joind.in/16266