Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Be pragmatic, be SOLID

6,770 views

Published on

Be pragmatic, be SOLID. Presented at:
* Wrocław Symfony Group (22.01.2015)
* 4developers (20.04.2015)

Published in: Software, Engineering, Technology
  • Nice !! Download 100 % Free Ebooks, PPts, Study Notes, Novels, etc @ https://www.ThesisScientist.com
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Hi there! Essay Help For Students | Discount 10% for your first order! - Check our website! https://vk.cc/80SakO
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Be pragmatic, be SOLID

  1. 1. Be pragmatic, be SOLID
  2. 2. Krzysztof Menżyk practises TDD believes that software is a craft loves domain modelling obsessed with brewing plays squash  kmenzyk  krzysztof@menzyk.net
  3. 3. Do you consider yourself a professional software developer?
  4. 4. New client Greenfield project Starting from scratch
  5. 5. What went wrong?
  6. 6. The code started to rot
  7. 7. The design is hard to change Rigidity
  8. 8. The design is easy to break Fragility
  9. 9. Immobility The design is hard to reuse
  10. 10. It is easy to do the wrong thing, but hard to do the right thing Viscosity
  11. 11. Your software is bound to change
  12. 12. Design stamina hypothesis time cumulative functionality design payoff line no design good design by Martin Fowler
  13. 13. What is Object Oriented Design then?
  14. 14. Design Principles and Design Patterns Robert C. Martin
  15. 15. Single Responsibility Open Closed Liskov Substitution Interface Segregation Dependency Inversion
  16. 16. Single Responsibility Principle A class should have only one reason to change
  17. 17. Gather together those things that change for the same reason Separate those things that change for different reasons
  18. 18. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } public function asJson() { // ... } public function save() { // ... } public function delete() { // ... } }
  19. 19. Try to describe what the class does
  20. 20. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } public function asJson() { // ... } public function save() { // ... } public function delete() { // ... } }
  21. 21. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } public function asJson() { // ... } public function save() { // ... } public function delete() { // ... } }
  22. 22. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } public function asJson() { // ... } public function save() { // ... } public function delete() { // ... } }
  23. 23. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } public function asJson() { // ... } public function save() { // ... } public function delete() { // ... } } violation
  24. 24. class Employee { public static function hire($name, $forPosition, Money $withSalary) { // ... } public function promote($toNewPosition, Money $withNewSalary) { // ... } } class EmployeeSerializer { public function toJson(Employee $employee) { // ... } } class EmployeeRepository { public function save(Employee $employee) { // ... } public function delete(Employee $employee) { // ... } } the right way
  25. 25. What about applying SRP to class methods?
  26. 26. What about applying SRP to test methods?
  27. 27. /** @test */ public function test_employee() { $employee = Employee::hire('John Doe', 'Junior Developer', $this->fiveHundredEuros); $this->assertEquals($this->fiveHundredEuros, $employee->getSalary()); $employee->promote('Senior Developer', $this->sixHundredEuros); $this->assertEquals($this->sixHundredEuros, $employee->getSalary()); $employee->promote('Technical Leader', $this->fiveHundredEuros); $this->assertEquals($this->sixHundredEuros, $employee->getSalary()); }
  28. 28. /** @test */ public function it_hires_with_salary() { $employee = Employee::hire('John Doe', 'Junior Developer', $this->fiveHundredEuros); $this->assertEquals($this->fiveHundredEuros, $employee->getSalary()); } /** @test */ public function it_promotes_with_new_salary() { $employee = Employee::hire('John Doe', 'Junior Developer', $this->fiveHundredEuros); $employee->promote('Senior Developer', $this->sixHundredEuros); $this->assertEquals($this->sixHundredEuros, $employee->getSalary()); } /** @test */ public function it_does_not_promote_if_new_salary_is_not_bumped() { $employee = Employee::hire('John Doe', 'Senior Developer', $this->sixHundredEuros); $employee->promote('Technical Leader', $this->fiveHundredEuros); $this->assertEquals($this->sixHundredEuros, $employee->getSalary()); } the right way
  29. 29. One test covers one behaviour
  30. 30. Open Closed Principle Software entities should be open for extension, but closed for modification
  31. 31. Write once, change never!
  32. 32. Wait! What?
  33. 33. class Shortener { public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); } // do stuff to shorten valid url return $shortenedUrl; } private function hasHttpScheme(Url $longUrl) { // ... } } /** @test */ public function it_does_not_shorten_url_without_http() { $urlToFtp = // ... $this->setExpectedException(InvalidUrl::class, 'Url has no "http" scheme'); $this->shortener->shorten($urlToFtp); }
  34. 34. class Shortener { public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); } if (!$this->hasPlDomain($longUrl)) { throw new InvalidUrl('Url has no .pl domain'); } // do stuff to shorten valid url return $shortenedUrl; } private function hasHttpScheme(Url $longUrl) { // ... } private function hasPlDomain(Url $longUrl) { // ... } }
  35. 35. /** @test */ public function it_shortens_only_urls_with_pl_domains() { $urlWithEuDomain = // ... $this->setExpectedException(InvalidUrl::class, 'Url has no .pl domain'); $this->shortener->shorten($urlWithEuDomain); }
  36. 36. /** @test */ public function it_shortens_only_urls_with_pl_domains() { $urlWithEuDomainButWithHttpScheme = // ... $this->setExpectedException(InvalidUrl::class, 'Url has no .pl domain'); $this->shortener->shorten($urlWithEuDomainButWithHttpScheme); }
  37. 37. /** @test */ public function it_shortens_urls() { $validUrl = // make sure the url satisfies all "ifs" $shortenedUrl = $this->shortener->shorten($validUrl); // assert }
  38. 38. Testing seems hard?
  39. 39. class Shortener { public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); } if (!$this->hasPlDomain($longUrl)) { throw new InvalidUrl('Url has no .pl domain'); } // do stuff to shorten valid url return $shortenedUrl; } private function hasHttpScheme(Url $longUrl) { // ... } private function hasPlDomain(Url $longUrl) { // ... } } violation
  40. 40. Abstraction is the key
  41. 41. interface Rule { /** * @param Url $url * * @return bool */ public function isSatisfiedBy(Url $url); }
  42. 42. class Shortener { public function addRule(Rule $rule) { // ... } public function shorten(Url $longUrl) { if (!$this->satisfiesAllRules($longUrl)) { throw new InvalidUrl(); } // do stuff to shorten valid url return $shortenedUrl; } private function satisfiesAllRules(Url $longUrl) { // ... } } the right way
  43. 43. class HasHttp implements Rule { public function isSatisfiedBy(Url $url) { // ... } } class HasPlDomain implements Rule { public function isSatisfiedBy(Url $url) { // ... } }
  44. 44. ”There is a deep synergy between testability and good design” – Michael Feathers
  45. 45. Liskov Substitution Principle Subtypes must be substitutable for their base types
  46. 46. class Tweets { protected $tweets = []; public function add(Tweet $tweet) { $this->tweets[$tweet->id()] = $tweet; } public function get($tweetId) { if (!isset($this->tweets[$tweetId])) { throw new TweetDoesNotExist(); } return $this->tweets[$tweetId]; } }
  47. 47. class BoundedTweets extends Tweets { const MAX = 10; public function add(Tweet $tweet) { if (count($this->tweets) > self::MAX) { throw new OverflowException(); } parent::add($tweet); } }
  48. 48. function letsTweet(Tweets $tweets) { // ... // $tweets has already 10 tweets $tweets->add(new Tweet('Ooooops')); }
  49. 49. function letsTweet(Tweets $tweets) { // ... try { $tweets->add(new Tweet('Ooooops')); } catch (OverflowException $e) { // What to do? } } violation
  50. 50. Design by contract
  51. 51. Design by contract (because public API is not enough)
  52. 52. What does it expect? What does it guarantee? What does it maintain?
  53. 53. class Tweets { public function add(Tweet $tweet) { // ... } /** * @param $tweetId * * @return Tweet * * @throws TweetDoesNotExist If a tweet with the given id * has not been added yet */ public function get($tweetId) { // ... } }
  54. 54. interface Tweets { public function add(Tweet $tweet); /** * @param $tweetId * * @return Tweet * * @throws TweetDoesNotExist If a tweet with the given id * has not been added yet */ public function get($tweetId); } class InMemoryTweets implements Tweets { // ... }
  55. 55. public function retweet($tweetId) { try { $tweet = $this->tweets->get($tweetId); } catch (TweetDoesNotExist $e) { return; } // ... }
  56. 56. class DoctrineORMTweets extends EntityRepository implements Tweets { public function add(Tweet $tweet) { $this->_em->persist($tweet); $this->_em->flush(); } public function get($tweetId) { return $this->find($tweetId); } }
  57. 57. class EntityRepository implements ObjectRepository, Selectable { /** * Finds an entity by its primary key / identifier. * * @param mixed $id The identifier. * @param int $lockMode The lock mode. * @param int|null $lockVersion The lock version. * * @return object|null The entity instance * or NULL if the entity can not be found. */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { // ... } // ... }
  58. 58. function retweet($tweetId) { if ($this->tweets instanceof DoctrineORMTweets) { $tweet = $this->tweets->get($tweetId); if (null === $tweet) { return; } } else { try { $tweet = $this->tweets->get($tweetId); } catch (TweetDoesNotExist $e) { return; } } // ... } violation
  59. 59. Violations of LSP are latent violations of OCP
  60. 60. class DoctrineORMTweets extends EntityRepository implements Tweets { // ... public function get($tweetId) { $tweet = $this->find($tweetId); if (null === $tweet) { throw new TweetDoesNotExist(); } return $tweet; } } the right way
  61. 61. LSP violations are difficult to detect until it is too late
  62. 62. Ensure the expected behaviour of your base class is preserved in derived classes
  63. 63. Dependency Inversion Principle High-level modules should not depend on low-level modules, both should depend on abstractions Abstractions should not depend on details. Details should depend on abstractions
  64. 64. class PayForOrder { public function __construct(PayPalApi $payPalApi) { $this->payPalApi = $payPalApi; } public function function pay(Order $order) { // ... $token = $this->payPalApi->createMethodToken($order->creditCard()); $this->payPalApi->createTransaction($token, $order->amount()); // ... } }
  65. 65. PayForOrder PayPalApi Business Layer Integration Layer
  66. 66. High-level module Low-level module PayForOrder PayPalApi
  67. 67. Abstraction is the key
  68. 68. PaymentProvider PayPalApi PayForOrder
  69. 69. class PayForOrder { public function __construct(PaymentProvider $paymentProvider) { $this->paymentProvider = $paymentProvider; } public function function pay(Order $order) { // ... $token = $this->paymentProvider->createMethodToken($order->creditCard()); $this->paymentProvider->createTransaction($token, $order->amount()); // ... } }
  70. 70. class PayForOrder { public function __construct(PaymentProvider $paymentProvider) { $this->paymentProvider = $paymentProvider; } public function function pay(Order $order) { // ... $token = $this->paymentProvider->createMethodToken($order->creditCard()); $this->paymentProvider->createTransaction($token, $order->amount()); // ... } } Abstractions should not depend on details. Details should depend on abstractions
  71. 71. Define the interface from the usage point of view
  72. 72. class PayForOrder { public function __construct(PaymentProvider $paymentProvider) { $this->paymentProvider = $paymentProvider; } public function function pay(Order $order) { // ... $token = $this->paymentProvider->charge( $order->creditCard(), $order->amount() ); // ... } } the right way
  73. 73. Concrete things change alot Abstract things change much less frequently
  74. 74. PaymentProvider PayPalProvider PayForOrder PayPalApi 3rd party
  75. 75. class PayPalProvider extends PayPalApi implements PaymentProvider { public function charge(CreditCard $creditCard, Money $forAmount) { $token = $this->createMethodToken($creditCard); $this->createTransaction($token, $forAmount); } }
  76. 76. PaymentProvider PayPalProvider PayForOrder PayPalApi 3rd party
  77. 77. class PayPalProvider implements PaymentProvider { public function __construct(PayPalApi $payPal) { $this->payPal = $payPal; } public function charge(CreditCard $creditCard, Money $forAmount) { $token = $this->payPal->createMethodToken($creditCard); $this->payPal->createTransaction($token, $forAmount); } }
  78. 78. class TestPaymentProvider implements PaymentProvider { //... }
  79. 79. Interface Segregation Principle Clients should not be forced to depend on methods they do not use
  80. 80. Many client specific interfaces are better than one general purpose interface
  81. 81. interface EventDispatcherInterface { public function dispatch($eventName, Event $event = null); public function addListener($eventName, $listener, $priority = 0); public function removeListener($eventName, $listener); public function addSubscriber(EventSubscriberInterface $subscriber); public function removeSubscriber(EventSubscriberInterface $subscriber); public function getListeners($eventName = null); public function hasListeners($eventName = null); }
  82. 82. class HttpKernel implements HttpKernelInterface, TerminableInterface { public function terminate(Request $request, Response $response) { $this->dispatcher->dispatch( KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response) ); } private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // ... $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); // ... $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); // ... } private function filterResponse(Response $response, Request $request, $type) { // ... $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); // ... } // ... }
  83. 83. class ImmutableEventDispatcher implements EventDispatcherInterface { public function dispatch($eventName, Event $event = null) { return $this->dispatcher->dispatch($eventName, $event); } public function addListener($eventName, $listener, $priority = 0) { throw new BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeListener($eventName, $listener) { throw new BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function addSubscriber(EventSubscriberInterface $subscriber) { throw new BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } // ... } violation
  84. 84. It serves too many different types of clients
  85. 85. Design interfaces from clients point of view
  86. 86. interface EventDispatcherInterface { public function dispatch($eventName, Event $event = null); } interface EventListenersInterface { public function addListener($eventName, $listener, $priority = 0); public function removeListener($eventName, $listener); } interface EventSubscribersInterface { public function addSubscriber(EventSubscriberInterface $subscriber); public function removeSubscriber(EventSubscriberInterface $subscriber); } interface DebugEventListenersInterface { public function getListeners($eventName = null); public function hasListeners($eventName = null); } the right way
  87. 87. class EventDispatcher implements EventDispatcherInterface, EventListenersInterface, EventSubscribersInterface { // ... } class DebugEventDispatcher extends EventDispatcher implements DebugEventListenersInterface { // ... }
  88. 88. Design is all about dependencies
  89. 89. Think about the design!
  90. 90. Listen to your tests
  91. 91. ”Always leave the code a little better than you found it.”
  92. 92. But...
  93. 93. Be pragmatic
  94. 94. "By not considering the future of your code, you make your code much more likely to be adaptable in the future."
  95. 95. At the end of the day what matters most is a business value
  96. 96. Know the rules well, so you can break them effectively
  97. 97. Be pragmatic, be SOLID  kmenzyk  krzysztof@menzyk.net Thanks!
  98. 98. Worth reading http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod https://gilesey.wordpress.com/2013/09/01/single-responsibility-principle/ ”Clean Code:A Handbook of Agile Software Craftsmanship” by Robert C. Martin http://martinfowler.com/bliki/DesignStaminaHypothesis.html
  99. 99. Photo Credits https://flic.kr/p/5bTy6C http://www.bonkersworld.net/building-software/ https://flic.kr/p/jzCox https://flic.kr/p/n37EXH https://flic.kr/p/9mcfh9 https://flic.kr/p/7XmGXp http://my.csdn.net/uploads/201205/13/1336911356_6234.jpg http://bit.ly/1cMgkPA https://flic.kr/p/qQTMa http://bit.ly/1EhyGEc https://flic.kr/p/5PyErP http://fc08.deviantart.net/fs49/i/2009/173/c/7/The_Best_Life_Style_by_Alteran_X.jpg https://flic.kr/p/4Sw9pP https://flic.kr/p/8RjbTS

×