62
Professional-grade software design

Professional-grade software design

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Professional-grade software design

Professional-grade software design

Page 2: Professional-grade software design

whoami

Brian Fenton

@brianfenton

www.brianfenton.us

Mashery, an Intel Company

Page 3: Professional-grade software design

Overview

Testing

SOLID

Object calisthenics

Smells

Patterns

Resources

Page 4: Professional-grade software design

Professional

Jeff Hebert, HeroMachine.com

Page 5: Professional-grade software design

Tests!

Page 6: Professional-grade software design

If you don’t have tests, you’re REWRITING, not refactoring

Page 7: Professional-grade software design

Tests first

Testing after you write the class won’t improve your design

More likely to just test the implementation (low value, brittle tests)

Page 8: Professional-grade software design

Unit testing forces you to feel the pain of bad

design up front

Page 9: Professional-grade software design

Some smells exposed by tests

Lots of dependenciesClass/method does too much

Requires lots of setup to do anythingClass is too coupled to its environment

Lots of protected/private methodsLikely another class worth of behavior hidden inside

Page 10: Professional-grade software design

SOLID

S – Single Responsibility

O – Open/Closed

L – Liskov Substitution

I – Interface Segregation

D – Dependency Inversion

Because no one can pronounce SRPOCPLSPISPDIP

Page 11: Professional-grade software design

Single Responsibility Principle

Page 12: Professional-grade software design

<?phpclass Person extends Model { public $name; public $birthDate; protected $preferences;

public function getPreferences() {}

public function save() {}}

Page 13: Professional-grade software design

<?phpclass Person extends Model { public $name; public $birthDate; protected $preferences;

public function getPreferences() {}}

class DataStore public function save(Model $model) {}}

Page 14: Professional-grade software design

Open/Closed Principle

Open for extension

Closed for modification

Page 15: Professional-grade software design

The OCP litmus test

Can you add/change a feature by only adding new classes?

Also allowed to updateControllers

Configuration

Templates

Page 16: Professional-grade software design

Liskov Substitution Principle

“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”

Page 17: Professional-grade software design

abstract class Shape{ public function getHeight();

public function setHeight($height);

public function getLength();

public function setLength($length);}

Page 18: Professional-grade software design

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; }}

Page 19: Professional-grade software design

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; }}

Page 20: Professional-grade software design

Interface Segregation Principle

Page 21: Professional-grade software design

Dependency Inversion Principle

“Higher level modules should not depend on lower level modules”

Page 22: Professional-grade software design

TL;DR

mysqli_query() BAD

DataStore->query() GOOD

Page 23: Professional-grade software design

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

Page 24: Professional-grade software design

Good Naming

Don’t abbreviate

Don’t be afraid to be verbose

Page 25: Professional-grade software design

Suspect Names

ClassesManagerHandler

MethodsProcess

“And”

Page 26: Professional-grade software design

Comments

Use inline comments sparingly

Do use docblocks though

Page 27: Professional-grade software design

TODOs

Tend to rot and never get fixed

If you use a TODO, add a ticket number

Page 28: Professional-grade software design

A well-named method that communicates intent is far

more valuable than a comment

Page 29: Professional-grade software design

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

Page 30: Professional-grade software design

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

Page 31: Professional-grade software design

Source order

Declare methods in the order they’re called

Public to private

Page 32: Professional-grade software design

Avoid “magic” values

DRY

Single source of truth

Self-documenting

Page 33: Professional-grade software design

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

Page 35: Professional-grade software design

“2 is a code smell”- Alex Miller

Page 36: Professional-grade software design

Dependency Injection

Pass external dependencies into objectsConstructor injection

Setter injection

Potential smell: too many dependencies

Ask for things, don’t look for them

Page 37: Professional-grade software design

Object Calisthenics(briefly)

Page 38: Professional-grade software design

No more than one level of indentation per method

Page 39: Professional-grade software design

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;}

Page 40: Professional-grade software design

…foreach ($data as $row) { // skip empty rows if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++;}…

Page 41: Professional-grade software design

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);}

Page 42: Professional-grade software design

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); }}

Page 43: Professional-grade software design

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); }}

Page 44: Professional-grade software design

Interlude… which is clearer?

$this->setActive(true);

$this->setActive(false);

OR

$this->activate();

$this->deactivate();

Page 45: Professional-grade software design

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));}

Page 46: Professional-grade software design

public function processRow($row, $headersOnly) { if ($headersOnly === true) { return $this->getHeaderRow($row); } else { return $this->toCsv($row); }}

public function toCsv($row) { return implode(',', $row);}

Page 47: Professional-grade software design

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;}

Page 48: Professional-grade software design

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;}

Page 49: Professional-grade software design

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;}

Page 50: Professional-grade software design

Don’t use else

Page 51: Professional-grade software design

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;}

Page 52: Professional-grade software design

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;}

Page 53: Professional-grade software design

Command-Query Separation

Complete separation between questions and commands

“Asking a question shouldn’t change the answer”

Page 54: Professional-grade software design

public function getUser($id) { $user = $this->dataStore->fetchUser($id); if (!$user) { $user = new User(array($id)); }

return $user;}

Page 55: Professional-grade software design

public function getUser($id) { return ($this->dataStore->fetchUser($id) ?: null;}

Page 56: Professional-grade software design

The final secret…

Page 57: Professional-grade software design

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

Page 58: Professional-grade software design

So decouple! Such

architecture

So amaze

Wow

Much message

Page 59: Professional-grade software design

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

Page 60: Professional-grade software design

Resources - Presentations

The Clean Code Talks -- Inheritance, Polymorphism, & Testing

Object Calisthenics

Page 62: Professional-grade software design

Questions?

https://joind.in/10562