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.
Professional-grade
software design
whoami
Brian Fenton
@brianfenton
www.brianfenton.us
Mashery, an Intel Company
Overview
Testing
SOLID
Object
calisthenics
Smells
Patterns
Resources
Professional
Jeff Hebert, HeroMachine.com
Tests!
If you don’t have tests, you’re
REWRITING, not refactoring
Tests first
Testing after you write the class won’t
improve your design
More likely to just test the
implementation (low v...
Unit testing forces you to
feel the pain of bad design
up front
Some smells exposed by
tests
Lots of dependencies
Class/method does too much
Requires lots of setup to do anything
Class i...
SOLID
S – Single Responsibility
O – Open/Closed
L – Liskov Substitution
I – Interface Segregation
D – Dependency Inversion...
Single Responsibility Principle
<?php
class Person extends Model {
public $name;
public $birthDate;
protected $preferences;
public function getPreferences...
<?php
class Person extends Model {
public $name;
public $birthDate;
protected $preferences;
public function getPreferences...
Open/Closed Principle
Open for extension
Closed for modification
The OCP litmus test
Can you add/change a feature by only adding new
classes?
Also allowed to update
Controllers
Configurat...
Liskov Substitution
Principle
“objects in a program should be
replaceable with instances of their
subtypes without alterin...
abstract class Shape{
public function getHeight();
public function setHeight($height);
public function getLength();
public...
class Square extends Shape{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight...
class Rectangle extends Shape{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}...
Interface Segregation Principle
Dependency Inversion
Principle
“Higher level modules should not depend
on lower level modules”
TL;DR
mysqli_query() BAD
DataStore->query() GOOD
Naming
Care about your names
(class/method/variable)
If it’s hard to name something, it means
you can’t describe it succin...
Good Naming
Don’t abbreviate
Don’t be afraid to be verbose
Suspect Names
Classes
Manager
Handler
Methods
Process
“And”
Comments
Use inline comments sparingly
Do use docblocks though
TODOs
Tend to rot and never get fixed
If you use a TODO, add a ticket number
A well-named method that
communicates intent is far
more valuable than a comment
Methods
You can (almost) always make a
method smaller
Pay attention to your execution path
Check your CRAP index with phpm...
Cognitive load
Declare variables as close to when they
will be used as possible
If you can pass data from method to
method...
Source order
Declare methods in the order they’re
called
Public to private
Avoid “magic” values
DRY
Single source of truth
Self-documenting
Which of these is easier to
understand?
json_last_error() == 5;
json_last_error() == JSON_ERROR_UTF8;
$length > 1024
$leng...
Not what I mean!
unsigned three = 1;
unsigned five = 5;
unsigned seven = 7;
https://github.com/torvalds/linux/blob/d158fc7...
“2 is a code smell”
- Alex Miller
Dependency Injection
Pass external dependencies into objects
Constructor injection
Setter injection
Potential smell: too m...
Object Calisthenics
(briefly)
No more than one level of
indentation per method
public function processData($data) {
$newData = array();
$count = 1;
foreach ($data as $row) {
if (!$row) {
continue;
}
if...
…
foreach ($data as $row) {
// skip empty rows
if (!$row) {
continue;
}
if ($count === 1) {
$newData[] = implode(',', arra...
public function processData($data) {
$newData = array();
$count = 1;
$data = $this->filterEmptyRows($data);
foreach ($data...
public function processData($data) {
$newData = array();
$count = 1;
$data = $this->filterEmptyRows($data);
foreach ($data...
public function processData($data) {
$newData = array();
$headersOnly = true;
$data = $this->filterEmptyRows($data);
forea...
Interlude… which is
clearer?
$this->setActive(true);
$this->setActive(false);
OR
$this->activate();
$this->deactivate();
public function processRow($row,
$headersOnly) {
if ($headersOnly === true) {
return $this->getHeaderRow($row);
} else {
r...
public function processRow($row,
$headersOnly) {
if ($headersOnly === true) {
return $this->getHeaderRow($row);
} else {
r...
public function processData($data) {
$newData = array();
$data = $this->filterEmptyRows($data);
$firstRow= array_pop($data...
public function transformToCsv($data) {
$data = $this->filterEmptyRows($data);
$firstRow= array_pop($data);
$csv = array()...
public function processData($data) {
$newData = array();
$count = 1;
foreach ($data as $row) {
if (!$row) {
continue;
}
if...
Don’t use else
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {...
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
retu...
Command-Query
Separation
Complete separation between questions
and commands
“Asking a question shouldn’t change the
answer”
public function getUser($id) {
$user = $this->dataStore->fetchUser($id);
if (!$user) {
$user = new User(array($id));
}
ret...
public function getUser($id) {
return ($this->dataStore->fetchUser($id) ?: null;
}
The final secret…
OOP is all about message
passing and behaviors
It’s not about inheritance
It’s not about code reuse
Favor composition over...
User
Event
Listener
Mailer
"Send
Notification"
w/User data
So decouple!
Such architecture
So amaze
Wow
Much message
Summary
Write small objects
Write tiny methods
Strive for good names
Seek loose coupling
Focus on message passing
Treat ob...
Resources - Presentations
The Clean Code Talks -- Inheritance,
Polymorphism, & Testing
Object Calisthenics
Resources – Books
Code Complete: A Practical Handbook of
Software Construction, Second Edition
Growing Object-Oriented Sof...
Questions?
https://joind.in/10562
Upcoming SlideShare
Loading in …5
×

Professional-grade software design

1,667 views

Published on

Talk given at MidwestPHP 2014 about guidelines for SOLID object-oriented design

Published in: Technology
  • Be the first to comment

Professional-grade software design

  1. 1. Professional-grade software design
  2. 2. whoami Brian Fenton @brianfenton www.brianfenton.us Mashery, an Intel Company
  3. 3. Overview Testing SOLID Object calisthenics Smells Patterns Resources
  4. 4. Professional Jeff Hebert, HeroMachine.com
  5. 5. Tests!
  6. 6. If you don’t have tests, you’re REWRITING, not refactoring
  7. 7. Tests first Testing after you write the class won’t improve your design More likely to just test the implementation (low value, brittle tests)
  8. 8. Unit testing forces you to feel the pain of bad design up front
  9. 9. Some smells exposed by tests Lots of dependencies Class/method does too much Requires lots of setup to do anything Class is too coupled to its environment Lots of protected/private methods Likely another class worth of behavior hidden inside
  10. 10. SOLID S – Single Responsibility O – Open/Closed L – Liskov Substitution I – Interface Segregation D – Dependency Inversion Because no one can pronounce SRPOCPLSPISPDIP
  11. 11. Single Responsibility Principle
  12. 12. <?php class Person extends Model { public $name; public $birthDate; protected $preferences; public function getPreferences() {} public function save() {} }
  13. 13. <?php class Person extends Model { public $name; public $birthDate; protected $preferences; public function getPreferences() {} } class DataStore public function save(Model $model) {} }
  14. 14. Open/Closed Principle Open for extension Closed for modification
  15. 15. The OCP litmus test Can you add/change a feature by only adding new classes? Also allowed to update Controllers Configuration Templates
  16. 16. Liskov Substitution Principle “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
  17. 17. abstract class Shape{ public function getHeight(); public function setHeight($height); public function getLength(); public function setLength($length); }
  18. 18. class Square extends Shape{ protected $size; public function getHeight() { return $this->size; } public function setHeight($height) { $this->size = $height; } public function getLength() { return $this->size; } public function setLength($length) { $this->size = $length; } }
  19. 19. class Rectangle extends Shape{ protected $height; protected $length; public function getHeight() { return $this->height; } public function setHeight($height) { $this->height= $height; } public function getLength() { return $this->length; } public function setLength($length) { $this->length= $length; } }
  20. 20. Interface Segregation Principle
  21. 21. Dependency Inversion Principle “Higher level modules should not depend on lower level modules”
  22. 22. TL;DR mysqli_query() BAD DataStore->query() GOOD
  23. 23. Naming Care about your names (class/method/variable) If it’s hard to name something, it means you can’t describe it succinctly If you can’t describe what it does, it does too much or you don’t understand what it does
  24. 24. Good Naming Don’t abbreviate Don’t be afraid to be verbose
  25. 25. Suspect Names Classes Manager Handler Methods Process “And”
  26. 26. Comments Use inline comments sparingly Do use docblocks though
  27. 27. TODOs Tend to rot and never get fixed If you use a TODO, add a ticket number
  28. 28. A well-named method that communicates intent is far more valuable than a comment
  29. 29. Methods You can (almost) always make a method smaller Pay attention to your execution path Check your CRAP index with phpmd or codesniffer You can (almost) always add more methods
  30. 30. Cognitive load Declare variables as close to when they will be used as possible If you can pass data from method to method directly, no need for a variable
  31. 31. Source order Declare methods in the order they’re called Public to private
  32. 32. Avoid “magic” values DRY Single source of truth Self-documenting
  33. 33. Which of these is easier to understand? json_last_error() == 5; json_last_error() == JSON_ERROR_UTF8; $length > 1024 $length > self::MAX_LENGTH $limit = 0 $limit = self::RATE_UNLIMITED
  34. 34. Not what I mean! unsigned three = 1; unsigned five = 5; unsigned seven = 7; https://github.com/torvalds/linux/blob/d158fc7f36a25e19791d 25a55da5623399a2644f/fs/ext4/resize.c#L698-700
  35. 35. “2 is a code smell” - Alex Miller
  36. 36. Dependency Injection Pass external dependencies into objects Constructor injection Setter injection Potential smell: too many dependencies Ask for things, don’t look for them
  37. 37. Object Calisthenics (briefly)
  38. 38. No more than one level of indentation per method
  39. 39. public function processData($data) { $newData = array(); $count = 1; foreach ($data as $row) { if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; }
  40. 40. … foreach ($data as $row) { // skip empty rows if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } …
  41. 41. public function processData($data) { $newData = array(); $count = 1; $data = $this->filterEmptyRows($data); foreach ($data as $row) { if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; } public function filterEmptyRows($rows) { return array_filter($rows); }
  42. 42. public function processData($data) { $newData = array(); $count = 1; $data = $this->filterEmptyRows($data); foreach ($data as $row) { $newData[] = $this->processRow($row, $count); $count++; } return $newData; } public function processRow($row, $count) { if ($count === 1) { return implode(',', array_keys($row)); } else { return implode(',', $row); } }
  43. 43. public function processData($data) { $newData = array(); $headersOnly = true; $data = $this->filterEmptyRows($data); foreach ($data as $row) { $newData[] = $this->processRow($row, $headersOnly); $headersOnly = false; } return $newData; } public function processRow($row, $headersOnly) { if ($headersOnly === true) { return implode(',', array_keys($row)); } else { return implode(',', $row); } }
  44. 44. Interlude… which is clearer? $this->setActive(true); $this->setActive(false); OR $this->activate(); $this->deactivate();
  45. 45. public function processRow($row, $headersOnly) { if ($headersOnly === true) { return $this->getHeaderRow($row); } else { return implode(',', $row); } } public function getHeaderRow($row) { return implode(',', array_keys($row)); }
  46. 46. public function processRow($row, $headersOnly) { if ($headersOnly === true) { return $this->getHeaderRow($row); } else { return $this->toCsv($row); } } public function toCsv($row) { return implode(',', $row); }
  47. 47. public function processData($data) { $newData = array(); $data = $this->filterEmptyRows($data); $firstRow= array_pop($data); $newData[] = $this->getHeaderRow($firstRow); foreach ($data as $row) { $newData[] = $this->toCsv($row); } return $newData; }
  48. 48. public function transformToCsv($data) { $data = $this->filterEmptyRows($data); $firstRow= array_pop($data); $csv = array(); $csv[] = $this->getHeaderRow($firstRow); foreach ($data as $row) { $csv[] = $this->toCsv($row); } return $csv; }
  49. 49. public function processData($data) { $newData = array(); $count = 1; foreach ($data as $row) { if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; }
  50. 50. Don’t use else
  51. 51. public function addThreeInts($first, $second, $third) { if (is_int($first)) { if (is_int($second)) { if (is_int($third)) { $sum = $first + $second + $third; } else { return null; } } else { return null; } } else { return null; } return $sum; }
  52. 52. public function addThreeInts($first, $second, $third) { if (!is_int($first)) { return null; } if (!is_int($second)) { return null; } if (!is_int($third)) { return null; } return $first + $second + $third; }
  53. 53. Command-Query Separation Complete separation between questions and commands “Asking a question shouldn’t change the answer”
  54. 54. public function getUser($id) { $user = $this->dataStore->fetchUser($id); if (!$user) { $user = new User(array($id)); } return $user; }
  55. 55. public function getUser($id) { return ($this->dataStore->fetchUser($id) ?: null; }
  56. 56. The final secret…
  57. 57. OOP is all about message passing and behaviors It’s not about inheritance It’s not about code reuse Favor composition over inheritance Treat objects like APIs
  58. 58. User Event Listener Mailer "Send Notification" w/User data So decouple! Such architecture So amaze Wow Much message
  59. 59. Summary Write small objects Write tiny methods Strive for good names Seek loose coupling Focus on message passing Treat objects like APIs Write tests (first) Refactor w/discipline Limit nesting/no else Use guard clauses Avoid magic (anything) Use CQS Avoid in-line comments Reduce cognitive load
  60. 60. Resources - Presentations The Clean Code Talks -- Inheritance, Polymorphism, & Testing Object Calisthenics
  61. 61. Resources – Books Code Complete: A Practical Handbook of Software Construction, Second Edition Growing Object-Oriented Software, Guided by Tests Refactoring: Improving the Design of Existing Code
  62. 62. Questions? https://joind.in/10562

×