object calisthenics adapted for php

62
Object Calisthenics Adapted for PHP

Upload: chad-gray

Post on 15-Jan-2015

314 views

Category:

Technology


1 download

DESCRIPTION

The 9 "rules" of Object Calisthenics adapted for PHP development

TRANSCRIPT

Page 1: Object Calisthenics Adapted for PHP

Object CalisthenicsAdapted for PHP

Page 2: Object Calisthenics Adapted for PHP

A little about me

● Bedford, VA● Radford University● 9+ Years PHP● 8+ Years @ DE● Hampton Roads PHP● Comics● Every Hulk Issue

Page 3: Object Calisthenics Adapted for PHP

Object Calisthenics

● First introduced by Jeff Bay● Based on Java Development● Guidelines Not Rules● Difficulty varies● Write Better Code● Adapted for PHP

Page 4: Object Calisthenics Adapted for PHP

Object Calisthenics #1

“Only one level of indentation per method”

Page 5: Object Calisthenics Adapted for PHP

#1 Only one level of indentation

public function validate(array $products)

{

$requiredFields = ['price', 'name', 'description'];

$valid = true;

foreach ($products as $rawProduct) {

$fields = array_keys($rawProduct);

foreach ($requiredFields as $requiredField) {

if (!in_array($requiredField, $fields)) {

$valid = false;

}

}

}

return $valid;

}

Page 6: Object Calisthenics Adapted for PHP

#1 Only one level of indentation

public function validate(array $products)

{

$requiredFields = ['price', 'name', 'description'];

$valid = true;

foreach ($products as $rawProduct) {

$fields = array_keys($rawProduct);

foreach ($requiredFields as $requiredField) {

if (!in_array($requiredField, $fields)) {

$valid = false;

}

}

}

return $valid;

}

01

23

Page 7: Object Calisthenics Adapted for PHP

#1 Only one level of indentation

public function validate(array $products)

{

$requiredFields = ['price', 'name', 'description'];

$valid = true;

foreach ($products as $rawProduct) {

$valid = $valid && $this->validateSingle($rawProduct, $requiredFields);

}

return $valid;

}

public function validateSingle(array $product, array $requiredFields)

{

$fields = array_keys($product);

return count(array_diff($requiredFields, $fields)) == 0;

}

Page 8: Object Calisthenics Adapted for PHP

#1 Only one level of indentation

Key Benefits

● Encourages Single Responsibility● Increase Re-use

Page 9: Object Calisthenics Adapted for PHP

Object Calisthenics #2

“Do not use the ‘else’ keyword”

Page 10: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

} elseif (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

Page 11: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

} elseif (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

Page 12: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

} elseif (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

Page 13: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

} elseif (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

return early

Page 14: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

Page 15: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (is_string($exception)) {

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

} else {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

}

reverse condition

Page 16: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (!is_string($exception)) {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

}

Page 17: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (!is_string($exception)) {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

if ($exceptionArgs === null) {

throw new \Exception($exception);

} else {

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

}

return early

Page 18: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (!is_string($exception)) {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

if ($exceptionArgs === null) {

throw new \Exception($exception);

}

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

Page 19: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null)

{

if ($valueToEnsure === $valueToCheck) {

return $valueToCheck;

}

if ($exception === null) {

throw new \Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'");

}

if (!is_string($exception)) {

throw new \InvalidArgumentException('$exception was not a string, Exception or null');

}

if ($exceptionArgs === null) {

throw new \Exception($exception);

}

if (array_key_exists($exception, self::$_exceptionAliases)) {

$exception = self::$_exceptionAliases[$exception];

}

$reflectionClass = new \ReflectionClass($exception);

throw $reflectionClass->newInstanceArgs($exceptionArgs);

}

Page 20: Object Calisthenics Adapted for PHP

#2 Do not use the ‘else’ keyword

Key Benefits

● Helps avoid code duplication● Easier to read (single path)● Reduces cyclomatic complexity

Page 21: Object Calisthenics Adapted for PHP

Object Calisthenics #3

“Wrap all primitives and Strings”*If there is behavior

Page 22: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Item

{

final public static function find($id)

{

if (!is_string($id) || trim($id) == '')

throw new \InvalidArgumentException('$id must be a non-empty string');

// do find ...

}

final public static function create($id, array $data)

{

if (!is_string($id) || trim($id) == '')

throw new \InvalidArgumentException('$id must be a non-empty string');

// do create ...

}

}

Page 23: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Item

{

final public static function find($id)

{

if (!is_string($id) || trim($id) == '')

throw new \InvalidArgumentException('$id must be a non-empty string');

// do find ...

}

final public static function create($id, array $data)

{

if (!is_string($id) || trim($id) == '')

throw new \InvalidArgumentException('$id must be a non-empty string');

// do create ...

}

}

validation with id

validation with id

Page 24: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Id

{

private $value;

final public function __construct($value)

{

if (!is_string($value) || trim($value) == '')

throw new \InvalidArgumentException('$value must be a non-empty string');

$this->value = $value;

}

final public function getValue()

{

return $this->value;

}

}

Page 25: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Id

{

private $value;

final public function __construct($value)

{

if (!is_string($value) || trim($value) == '')

throw new \InvalidArgumentException('$value must be a non-empty string');

$this->value = $value;

}

final public function getValue()

{

return $this->value;

}

}

validation encapsulated in constructor

Page 26: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Item

{

final public static function find(Id $id)

{

// do find ...

}

final public static function create(Id $id, array $data)

{

// do create ...

}

}

Page 27: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

class Item

{

final public static function find(Id $id)

{

// do find ...

}

final public static function create(Id $id, array $data)

{

// do create ...

}

}

$id now guaranteed to be valid.

Page 28: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

Warning!!!

Using large amounts of objects will increase the memory footprint of PHP.

Page 29: Object Calisthenics Adapted for PHP

#3 Wrap primitives and Strings

Key Benefits

● Type Hinting● Encapsulation of operations

Page 30: Object Calisthenics Adapted for PHP

Object Calisthenics #4

“Use first class collections”

Page 31: Object Calisthenics Adapted for PHP

#4 Use first class collections

A class that contains a collection should contain no other instance variables.

Page 32: Object Calisthenics Adapted for PHP

#4 Use first class collectionsclass Items implements Iterator

{

private $items = [];

final public function add(Item $item)

{

// do add ...

}

final public function filter(array $filters)

{

// do filter ...

}

final public function merge(Items $items)

{

// do merge ...

}

}

Page 33: Object Calisthenics Adapted for PHP

#4 Use first class collections

Key Benefits

● Implements collection operations● Uses SPL interfaces● Easier to merge collections and not worry

about member behavior in them

Page 34: Object Calisthenics Adapted for PHP

Object Calisthenics #5

“One object operator per line”

Page 35: Object Calisthenics Adapted for PHP

#5 One object operator per line

$this->manager->getConfig()->getSegment()->setName('foo');

Page 36: Object Calisthenics Adapted for PHP

#5 One object operator per line

$this->manager->getConfig()->getSegment()->setName('foo');

Properties are harder to mock

Page 37: Object Calisthenics Adapted for PHP

#5 One object operator per line

$this->manager->getConfig()->getSegment()->setName('foo');

Properties are harder to mock

Previous call could return null

Page 38: Object Calisthenics Adapted for PHP

#5 One object operator per line

$this->manager->getConfig()->getSegment()->setName('foo');

Properties are harder to mock

Previous call could return null

May indicate feature envy

Page 39: Object Calisthenics Adapted for PHP

#5 One object operator per line

Key Benefits

● Readability● Easier Testing● Easier to Debug

Page 40: Object Calisthenics Adapted for PHP

Object Calisthenics #6

“Do not abbreviate”

Page 41: Object Calisthenics Adapted for PHP

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

#6 Do not abbreviate

Page 42: Object Calisthenics Adapted for PHP

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

#6 Do not abbreviateget what page from where?

Page 43: Object Calisthenics Adapted for PHP

#6 Do not abbreviate

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

get from where?

???

Page 44: Object Calisthenics Adapted for PHP

#6 Do not abbreviate

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

get what page from where?

???

Use clearer names:renderHomePage()downloadHomePage()

Page 45: Object Calisthenics Adapted for PHP

#6 Do not abbreviate

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

get what page from where?

???

Use clearer names:renderHomePage()downloadHomePage()

Use a thesaurus:fork, create, begin, open

Page 46: Object Calisthenics Adapted for PHP

#6 Do not abbreviate

public function getPage() { ... }

public function startProcess() { ... }

$smp->process("index");

get what page from where?

???

Use clearer names:renderHomePage()downloadHomePage()

Use a thesaurus:fork, create, begin, open

Easy understanding, complete scope:$siteMapProcessor

Page 47: Object Calisthenics Adapted for PHP

#6 Do not abbreviate

Key Benefits

● Clearer communication and maintainability● Indicates underlying problems

Page 48: Object Calisthenics Adapted for PHP

Object Calisthenics #7

“Keep classes small”

Page 49: Object Calisthenics Adapted for PHP

#7 Keep classes small

200 lines per class (including docblocks)10 methods per class15 classes per namespace (folder)

Page 50: Object Calisthenics Adapted for PHP

#7 Keep classes small

Key Benefits

● Single Responsibility● Objective and clear methods● Slimmer namespaces

Page 51: Object Calisthenics Adapted for PHP

Object Calisthenics #8

“Limit the number of instance variables in a class”

Page 52: Object Calisthenics Adapted for PHP

#8 Limit instance variables in a classclass Client

{

private $_adapter;

private $_cache;

private $_logger;

// ...

}

Limit: 2 - 5

Page 53: Object Calisthenics Adapted for PHP

#8 Limit instance variables in a class

Key Benefits

● Shorter dependency list● Easier Mocking for unit tests

Page 54: Object Calisthenics Adapted for PHP

Object Calisthenics #9

“Use getters/setters”

Page 55: Object Calisthenics Adapted for PHP

#9 Use Getters and Settersclass Tally

{

public $total = 0;

public function add($amount)

{

$this->total += $amount;

}

}

$tally = new Tally();

$tally->add(1);

// some other code ...

$tally->total = -1;

// some other code ...

$tally->add(1);

echo $tally->total . PHP_EOL;

Page 56: Object Calisthenics Adapted for PHP

#9 Use Getters and Settersclass Tally

{

public $total = 0;

public function add($amount)

{

$this->total += $amount

}

}

$tally = new Tally();

$tally->add(1);

// some other code ...

$tally->total = -1

// some other code ...

$tally->add(1);

echo $tally->total . PHP_EOL;

total can be changed without this instance knowing

Page 57: Object Calisthenics Adapted for PHP

#9 Use Getters and Settersclass Tally

{

public $total = 0;

public function add($amount)

{

$this->total += $amount

}

}

$tally = new Tally();

$tally->add(1);

// some other code ...

$tally->total = -1

// some other code ...

$tally->add(1);

echo $tally->total . PHP_EOL;

total can be changed without this instance knowing

Causes unexpected results

Page 58: Object Calisthenics Adapted for PHP

#9 Use Getters and Settersclass Tally

{

private $total = 0;

public function add($amount)

{

$this->total += $amount;

}

public function getTotal()

{

return $this->total;

}

}

$tally = new Tally();

$tally->add(1);

$tally->add(1);

echo $tally->getTotal() . PHP_EOL;

total cannot be “reset”

No unexpected results

Page 59: Object Calisthenics Adapted for PHP

#9 Use Getters and Setters

Warning!!!

Excessive setters and getters can be just as bad as public properties

Page 60: Object Calisthenics Adapted for PHP

#9 Use Getters and Setters

Key Benefits

● Injector operations● Encapsulation of transformations● Encourages Open/Closed principle

Page 61: Object Calisthenics Adapted for PHP

Recap

1. One level of indentation per method2. Don’t use the else keyword3. Wrap primitives that have behavior4. Use first class collections5. One object operator per line6. Do not abbreviate7. Keep classes small8. Limit instance variables in a class9. Use Getters and setters

Page 62: Object Calisthenics Adapted for PHP

Object Calisthenics

http://www.bennadel.com/resources/uploads/2012/ObjectCalisthenics.pdf

@chadicus78@hrphpmeetup

Give us feedback athttp://de12bcon.herokuapp.com/survey