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

Designing Beautiful Software

Designing Beautiful Software

Meditation on the various steps a developer takes from writing a simple one-liner to designing a full-featured, reusable, maintainable component, and the choices made. Uses the example of a mailer component in PHP.

Matthew Weier O'Phinney

September 27, 2011
Tweet

More Decks by Matthew Weier O'Phinney

Other Decks in Technology

Transcript

  1. 26 May 2011 Designing Beautiful Software 1 – 1 /

    46 Designing Beautiful Software Matthew Weier O’Phinney 26 May 2011
  2. Software Evolution 26 May 2011 Designing Beautiful Software 4 –

    4 / 46 or, how to go from code monkey to architect
  3. Requirements 26 May 2011 Designing Beautiful Software 6 – 6

    / 46 The application needs to send email
  4. Requirements 26 May 2011 Designing Beautiful Software 6 – 6

    / 46 The application needs to send email I know the sender is always the same
  5. Requirements 26 May 2011 Designing Beautiful Software 6 – 6

    / 46 The application needs to send email I know the sender is always the same I want to BCC an address for verification
  6. 26 May 2011 Designing Beautiful Software 7 – 7 /

    46 § function shop_mail($to , $subject , $body) { $headers = "From: [email protected]\r\n" .= "Bcc: shop [email protected]\r\n"; mail($to , $subject , $body , $headers); }
  7. Is it beautiful? 26 May 2011 Designing Beautiful Software 8

    – 8 / 46 It’s succinct It prevents us having to specify $headers manually each call
  8. Is it beautiful? 26 May 2011 Designing Beautiful Software 8

    – 8 / 46 It’s succinct It prevents us having to specify $headers manually each call It’s little more than a wrapper on mail()
  9. What if we introduce requirements? 26 May 2011 Designing Beautiful

    Software 9 – 9 / 46 For instance, we add another shop on a different domain, using much (if not all) the same code.
  10. What if we introduce requirements? 26 May 2011 Designing Beautiful

    Software 9 – 9 / 46 For instance, we add another shop on a different domain, using much (if not all) the same code. Now the “From” and “Bcc” addresses need to be different.
  11. New problems 26 May 2011 Designing Beautiful Software 10 –

    10 / 46 We don’t want to change all the places in our code that call this function...
  12. New problems 26 May 2011 Designing Beautiful Software 10 –

    10 / 46 We don’t want to change all the places in our code that call this function... ...at least, not after this change.
  13. New problems 26 May 2011 Designing Beautiful Software 10 –

    10 / 46 We don’t want to change all the places in our code that call this function... ...at least, not after this change. So, let’s introduce a “configuration” parameter.
  14. 26 May 2011 Designing Beautiful Software 11 – 11 /

    46 § function shop_mail($to , $subject , $body , $shop = ’original’) { switch ($shop) { case: ’subdomain’: $from = ’[email protected]’; $bcc = ’shop [email protected]’; break; case: ’original’: default: $from = ’[email protected]’; $bcc = ’shop [email protected]’; break; } $headers = "From: $from\r\n" .= "Bcc: $bcc\r\n"; mail($to , $subject , $body , $headers); }
  15. Is it beautiful? 26 May 2011 Designing Beautiful Software 12

    – 12 / 46 Switch statements will grow, and need to be documented.
  16. Is it beautiful? 26 May 2011 Designing Beautiful Software 12

    – 12 / 46 Switch statements will grow, and need to be documented. We need to know what the value of that last argument will be.
  17. Is it beautiful? 26 May 2011 Designing Beautiful Software 12

    – 12 / 46 Switch statements will grow, and need to be documented. We need to know what the value of that last argument will be. The number of arguments may not justify wrapping mail()
  18. Base functionality 26 May 2011 Designing Beautiful Software 14 –

    14 / 46 § class ShopMail { protected static $from = ’[email protected]’; protected static $bcc = ’shop [email protected]’; public static function send($to , $subject , $body) { $headers = "From: " . static::$from . "\r\n" .= "Bcc: " . static::$bcc . "\r\n"; mail($to , $subject , $body , $headers); } }
  19. Subclass 26 May 2011 Designing Beautiful Software 15 – 15

    / 46 § class SubdomainMail extends ShopMail { protected static $from = ’[email protected]’; protected static $bcc = ’shop [email protected]’; }
  20. Potential usage 26 May 2011 Designing Beautiful Software 16 –

    16 / 46 § define(’MYENV’, ’Subdomain’); $mailer = MYENV . ’Mail::send’; call_user_func($mailer , $to , $subject , $body);
  21. Is it beautiful? 26 May 2011 Designing Beautiful Software 17

    – 17 / 46 Requires extension Strategy selection requires knowledge of environment
  22. Is it beautiful? 26 May 2011 Designing Beautiful Software 17

    – 17 / 46 Requires extension Strategy selection requires knowledge of environment Debugging requires knowledge of environment
  23. 26 May 2011 Designing Beautiful Software 19 – 19 /

    46 § $config = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); $config ->env = "Subdomain ’; $mailer = $config ->env . ’Mail::send ’; call_user_func($mailer , $to , $subject , $body);
  24. Is it beautiful? 26 May 2011 Designing Beautiful Software 20

    – 20 / 46 Would be easier to just indicate the class to use, and have it be the same throughout the application(s).
  25. Is it beautiful? 26 May 2011 Designing Beautiful Software 20

    – 20 / 46 Would be easier to just indicate the class to use, and have it be the same throughout the application(s). What if I have a new requirement, such as sending HTML mails?
  26. Mailer class 26 May 2011 Designing Beautiful Software 22 –

    22 / 46 § class Mailer { protected $from = ’[email protected]’; protected $bcc = ’shop [email protected]’; protected $contentType = ’text/plain’; public function setFrom($from) { ... } public function setBcc($bcc) { ... } public function setContentType($type) { ... } public function send($to , $subject , $body) { $headers = "From: " . $this ->from . "\r\n" .= "Bcc: " . $this ->bcc . "\r\n" .= "Content -Type: " . $this ->contentType . "\r\n"; mail($to , $subject , $body , $headers); } }
  27. Usage 26 May 2011 Designing Beautiful Software 23 – 23

    / 46 § $mailer = new Mailer(); $mailer ->setFrom($config ->from) ->setBcc($config ->bcc) ->setContentType(’text/html’); $mailer ->send($to , $subject , $body);
  28. Is it beautiful? 26 May 2011 Designing Beautiful Software 24

    – 24 / 46 Better, but not great Specific headers are hard-coded
  29. Is it beautiful? 26 May 2011 Designing Beautiful Software 24

    – 24 / 46 Better, but not great Specific headers are hard-coded What if we don’t want to use mail()?
  30. 26 May 2011 Designing Beautiful Software 26 – 26 /

    46 We’ve identified several needs:
  31. 26 May 2011 Designing Beautiful Software 26 – 26 /

    46 We’ve identified several needs: Configurable, arbitrary headers Configurable, arbitrary transports
  32. Mail transports and headers 26 May 2011 Designing Beautiful Software

    28 – 28 / 46 § interface MailTransport { public function send($to , $subject , $body , $headers); } class MailHeaders extends ArrayObject { public function toString() { $headers = ’’; foreach ($this as $header => $value) { $headers .= $header . ’: ’ . $value . "\r\n"; } return $headers; } }
  33. Mailer: setters 26 May 2011 Designing Beautiful Software 29 –

    29 / 46 § class Mailer { protected $headers , $transport; public function setHeaders(MailHeaders $headers) { $this ->headers = $headers; return $this; } public function getHeaders() { return $this ->headers; } public function setTransport(MailTransport $transport) { $this ->transport = $transport; return $this; } /* ... */ }
  34. Mailer: construction and functionality 26 May 2011 Designing Beautiful Software

    30 – 30 / 46 § class Mailer { protected $headers , $transport; public function __construct(MailTransport $transport) { $this ->setTransport($transport); $this ->setHeaders(new MailHeaders()); } public function send($to , $subject , $body) { $this ->transport ->send( $to , $subject , $body , $this ->headers ->toString() ); } }
  35. Mailer: usage 26 May 2011 Designing Beautiful Software 31 –

    31 / 46 § $mailer = new Mailer(new SmtpTransport); $headers = new MailHeaders(); // or $headers = $mailer->getHeaders(); $headers[’From’] = $config ->from; $headers[’Bcc’] = $config ->bcc; $headers[’Content -Type’] = ’text/html’; $mailer ->setHeaders($headers) // if instantiated separately ->send($to , $subject , $body);
  36. Is it beautiful? 26 May 2011 Designing Beautiful Software 32

    – 32 / 46 Headers management and serialization is self-contained
  37. Is it beautiful? 26 May 2011 Designing Beautiful Software 32

    – 32 / 46 Headers management and serialization is self-contained Sending is separate from message composition
  38. Is it beautiful? 26 May 2011 Designing Beautiful Software 32

    – 32 / 46 Headers management and serialization is self-contained Sending is separate from message composition Problems No validation or normalization of header keys
  39. Is it beautiful? 26 May 2011 Designing Beautiful Software 32

    – 32 / 46 Headers management and serialization is self-contained Sending is separate from message composition Problems No validation or normalization of header keys No validation of header values
  40. Is it beautiful? 26 May 2011 Designing Beautiful Software 32

    – 32 / 46 Headers management and serialization is self-contained Sending is separate from message composition Problems No validation or normalization of header keys No validation of header values Should a message send itself? or should we pass a message to the transport?
  41. Message 26 May 2011 Designing Beautiful Software 34 – 34

    / 46 § interface MailMessage { public function setTo($to); public function setSubject($subject); public function setBody($body); public function setHeaders(MailHeaders $headers); public function getTo(); public function getSubject(); public function getBody(); public function getHeaders(); }
  42. Headers and Transport 26 May 2011 Designing Beautiful Software 35

    – 35 / 46 § interface MailHeaders { public function addHeader($header , $value); public function toString(); } interface MailTransport { public function send(MailMessage $message); }
  43. Usage 26 May 2011 Designing Beautiful Software 36 – 36

    / 46 § $message = new Message(); $headers = new MessageHeaders(); $headers ->addHeader(’From’, $from) ->addHeader(’Content -Type’, ’text/html’); $message ->setTo($to) ->setSubject($subject) ->setBody($body) ->setHeaders($headers); $transport = new SmtpTransport($config ->transport); $transport ->send($message);
  44. Is it beautiful? 26 May 2011 Designing Beautiful Software 37

    – 37 / 46 Headers can now potentially have validation and normalization...
  45. Is it beautiful? 26 May 2011 Designing Beautiful Software 37

    – 37 / 46 Headers can now potentially have validation and normalization... ...and implementation can be varied if necessary.
  46. Is it beautiful? 26 May 2011 Designing Beautiful Software 37

    – 37 / 46 Headers can now potentially have validation and normalization... ...and implementation can be varied if necessary. The message is self-contained, and contains all metadata related to it.
  47. Is it beautiful? 26 May 2011 Designing Beautiful Software 37

    – 37 / 46 Headers can now potentially have validation and normalization... ...and implementation can be varied if necessary. The message is self-contained, and contains all metadata related to it. The mail transport accepts a message, and determines what to use from it, and how.
  48. What are the real questions? 26 May 2011 Designing Beautiful

    Software 39 – 39 / 46 Can I maintain it?
  49. What are the real questions? 26 May 2011 Designing Beautiful

    Software 39 – 39 / 46 Can I maintain it? Can I change how it works easily as needed?
  50. Other considerations 26 May 2011 Designing Beautiful Software 40 –

    40 / 46 Adopt a coding standard. Adopt a sane class -> filesystem convention.
  51. Other considerations 26 May 2011 Designing Beautiful Software 40 –

    40 / 46 Adopt a coding standard. Adopt a sane class -> filesystem convention. Think about how classes relate semantically, and apply this to the class hierarchy.
  52. Other considerations 26 May 2011 Designing Beautiful Software 40 –

    40 / 46 Adopt a coding standard. Adopt a sane class -> filesystem convention. Think about how classes relate semantically, and apply this to the class hierarchy. Consider how this relates to namespaces.
  53. Test first 26 May 2011 Designing Beautiful Software 41 –

    41 / 46 Define your requirements as tests.
  54. Test first 26 May 2011 Designing Beautiful Software 41 –

    41 / 46 Define your requirements as tests. Play with the API and how you use the code before you write it.
  55. Test first 26 May 2011 Designing Beautiful Software 41 –

    41 / 46 Define your requirements as tests. Play with the API and how you use the code before you write it. Having tests ensures that as you fix bugs or introduce features, you don’t break your original contract.
  56. Trade offs 26 May 2011 Designing Beautiful Software 42 –

    42 / 46 You can always write shorter code. It just likely won’t be as configurable or extensible.
  57. Trade offs 26 May 2011 Designing Beautiful Software 42 –

    42 / 46 You can always write shorter code. It just likely won’t be as configurable or extensible. There’s nothing wrong with the following code. It’s fast, and easily understandable. § $headers = "From: " . $config ->from . "\r\n" .= "Bcc: " . $config ->bcc . "\r\n"; mail($to , $subject , $body , $headers);
  58. Trade offs 26 May 2011 Designing Beautiful Software 42 –

    42 / 46 You can always write shorter code. It just likely won’t be as configurable or extensible. There’s nothing wrong with the following code. It’s fast, and easily understandable. § $headers = "From: " . $config ->from . "\r\n" .= "Bcc: " . $config ->bcc . "\r\n"; mail($to , $subject , $body , $headers); However, we can’t swap out the transport easily, or test it.
  59. Trade offs 26 May 2011 Designing Beautiful Software 43 –

    43 / 46 Writing configurable or extensible code usually requires some verbosity.
  60. Trade offs 26 May 2011 Designing Beautiful Software 43 –

    43 / 46 Writing configurable or extensible code usually requires some verbosity. Separating out objects by areas of concern leads to a proliferation of objects.
  61. Trade offs 26 May 2011 Designing Beautiful Software 43 –

    43 / 46 Writing configurable or extensible code usually requires some verbosity. Separating out objects by areas of concern leads to a proliferation of objects. Deal with it.
  62. Trade offs 26 May 2011 Designing Beautiful Software 43 –

    43 / 46 Writing configurable or extensible code usually requires some verbosity. Separating out objects by areas of concern leads to a proliferation of objects. Deal with it. It’s easier to digest small bites than it is a whole roast at a time.
  63. Trade offs 26 May 2011 Designing Beautiful Software 44 –

    44 / 46 Configuration can be either inline, or from a container.
  64. Trade offs 26 May 2011 Designing Beautiful Software 44 –

    44 / 46 Configuration can be either inline, or from a container. Inline is nice, but makes it difficult to swap out later.
  65. Trade offs 26 May 2011 Designing Beautiful Software 44 –

    44 / 46 Configuration can be either inline, or from a container. Inline is nice, but makes it difficult to swap out later. Containers are nice, but you then need to pass the container around somehow.
  66. Choose beauty 26 May 2011 Designing Beautiful Software 45 –

    45 / 46 Think about long-term maintainability Think about extensibility
  67. Thank you! 26 May 2011 Designing Beautiful Software 46 –

    46 / 46 Feedback: http://joind.in/3395 Twitter http://twitter.com/weierophinney