professional-grade software design

Post on 26-May-2015

936 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

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 value, brittle tests)

Unit testing forces you to feel the pain of bad

design up front

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

SOLID

S – Single Responsibility

O – Open/Closed

L – Liskov Substitution

I – Interface Segregation

D – Dependency Inversion

Because no one can pronounce SRPOCPLSPISPDIP

Single Responsibility Principle

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

public function getPreferences() {}

public function save() {}}

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

public function getPreferences() {}}

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

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 updateControllers

Configuration

Templates

Liskov Substitution Principle

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

abstract class Shape{ public function getHeight();

public function setHeight($height);

public function getLength();

public function setLength($length);}

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

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

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 succinctly

If you can’t describe what it does, it does too much or you don’t understand what it does

Good Naming

Don’t abbreviate

Don’t be afraid to be verbose

Suspect Names

ClassesManagerHandler

MethodsProcess

“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 phpmd or codesniffer

You can (almost) always add more methods

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

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

$length > self::MAX_LENGTH

$limit = 0

$limit = self::RATE_UNLIMITED

“2 is a code smell”- Alex Miller

Dependency Injection

Pass external dependencies into objectsConstructor injection

Setter injection

Potential smell: too many dependencies

Ask for things, don’t look for them

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 ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData;}

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

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

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

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

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 { return implode(',', $row); }}

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

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

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

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

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

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

Don’t use else

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

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

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

return $user;}

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 inheritance

Treat objects like APIs

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

Resources - Presentations

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

Object Calisthenics

Questions?

https://joind.in/10562

top related