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

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. AGILE MANIFESTO Welcome changing requirements, even late in development. Agile

    processes harness change for the customer's competitive advantage.
  2. 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.
  3. You have to finish things - that's what you learn

    from, you learn by finishing things. Neil Gaiman
  4. 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; } // ... }
  5. class LinkCrawler { // ... public function crawl($url) { $content

    = $this->browser->get( $url, ['content-type' => 'application/json'] ); $links = $this->linkExtractor->extract($content); $this->linkRepository->store($links); } }
  6. 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); } }
  7. 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); } }
  8. 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); } }
  9. 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); } }
  10. DESIGN SMELLS ✤ Rigidity ✤ Fragility ✤ Immobility ✤ Viscosity

    ✤ Needless complexity ✤ Needless repetition ✤ Opacity how pumpkins rot, Evil Erin, https://www.flickr.com/photos/evilerin/4158920495/
  11. 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/
  12. 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/
  13. COHESION Cohesion describes how closely are elements in a module

    related. Pflaster/Pavement, Sivi Steys, https://www.flickr.com/photos/38365223@N03/7913024500/
  14. 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
  15. COUPLING THROUGH A METHOD CALL class Crawler { private $c;

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

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

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

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

    https://www.flickr.com/photos/basheertome/3086908057/
  20. 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
  21. 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); } }
  22. 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
  23. 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
  24. 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); }
  25. 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'] ); } }
  26. 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); } }
  27. 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
  28. 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
  29. 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.
  30. DEPENDENCY INVERSION PRINCIPLE High-level modules should not depend on low-level

    modules. Both should depend on abstractions An abstraction!
  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: Add two more crawlers to extract images and meta attributes
  32. 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
  33. interface ContentProvider { /** * @param string $url * *

    @return string */ public function load($url); }
  34. 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'] ); } }
  35. 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(); } }
  36. 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
  37. 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
  38. 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(); } }
  39. class ProductController { private $productRepository; private $templating; public function __construct(

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

    = $this->productRepository->search($keywords); return $this->templating->renderResponse( 'product/search.html.twig', ['products' => $products] ); } }
  41. 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.
  42. 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.
  43. 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!
  44. Good code doesn't take more time to write. What takes

    a lot of time is the learning how to write good code.