Slide 1

Slide 1 text

Clean Code

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

SOLID

Slide 4

Slide 4 text

Single responsibility principle A class should only have one reason to change

Slide 5

Slide 5 text

Single responsibility principle interface Basket { public function getItems(); public function addItem($item); public function removeItem($item); public function calculateTotal(); public function processPayment($account, $amount); }

Slide 6

Slide 6 text

Single responsibility principle interface BasketCollection { public function getItems(); public function addItem($item); public function removeItem($item); } interface BasketPaymentProcessor { public function calculateTotal(BasketCollection $collection); public function processPayment($account, $amount); }

Slide 7

Slide 7 text

Open-closed principle A class should be easy to extend without changes

Slide 8

Slide 8 text

Open-closed principle class BasketPaymentProcessor { public function processPayment($account, $amount, $adapter) { switch ($adapter) { case "paypal": $this->chargeWithPaypal($account, $amount); break; case "payfast": $this->chargeWithPayfast($account, $amount); break; } } }

Slide 9

Slide 9 text

Open-closed principle interface Adapter { public function charge($account, $amount); } class BasketPaymentProcessor { protected $adapters = []; public function addAdapter($key, Adapter $adapter) { $this->adapters[$key] = $adapter; }

Slide 10

Slide 10 text

Open-closed principle public function processPayment($account, $amount, $adapter) { $this->adapters[$adapter]->charge($account, $amount); } } class DebitAdapter implements Adapter { public function charge($account, $amount) { // ...process some payment } }

Slide 11

Slide 11 text

Open-closed principle $processor = new BasketPaymentProcessor(); $processor->addAdapter( "paypal", new PaypalAdapter() ); $processor->addAdapter( "payfast", new PayfastAdapter() ); $processor->addAdapter( "debit", new DebitAdapter() );

Slide 12

Slide 12 text

Lizkov substitution principle Subclasses mustn't change expected behaviour

Slide 13

Slide 13 text

Lizkov substitution principle class Mailer { public function send($from, $to, $subject, $body) { mail($to, $subject, $body, "From: {$from}\n"); } }

Slide 14

Slide 14 text

Lizkov substitution principle class SecureMailer extends Mailer { public function send($from, $to, $subject, $body) { assert(filter_var($from, FILTER_VALIDATE_EMAIL)); assert(filter_var($to, FILTER_VALIDATE_EMAIL)); parent::send($from, $to, $subject, $body); } }

Slide 15

Slide 15 text

Lizkov substitution principle $mailer = new Mailer(); $mailer->send( "Chris ", "[email protected]", "...", "..." ); // works!

Slide 16

Slide 16 text

Lizkov substitution principle $mailer = new SecureMailer(); $mailer->send( "Chris ", "[email protected]", "...", "..." ); // breaks

Slide 17

Slide 17 text

Lizkov substitution principle function sendMail(Mailer $mailer) { print "send some mail..."; } //$mailer = new Mailer(); $mailer = new SecureMailer(); sendMail($mailer);

Slide 18

Slide 18 text

Interface segregation principle Interfaces shouldn't force classes to implement useless methods

Slide 19

Slide 19 text

Interface segregation principle interface CacheInterface { public function connect(array $credentials); public function set($key, $value); public function get($key); }

Slide 20

Slide 20 text

Interface segregation principle class FilesystemCache implements CacheInterface { public function connect(array $credentials) { // ...this does nothing! } }

Slide 21

Slide 21 text

Interface segregation principle interface ConnectionInterface { public function connect(array $credentials); } interface CacheInterface { public function set($key, $value); public function get($key); }

Slide 22

Slide 22 text

Interface segregation principle class DatabaseCache implements CacheInterface, ConnectionInterface { } class FilesystemCache implements CacheInterface { }

Slide 23

Slide 23 text

Interface segregation principle class CacheManager { public function connect() { if ($this->adapter instanceof ConnectionInterface) { $this->adapter->connect([...]); } } }

Slide 24

Slide 24 text

Dependency inversion principle High-level and low-level code should depend on abstractions

Slide 25

Slide 25 text

Dependency inversion principle class Logger { public function write($message) { file_put_contents(__DIR__ . "/application.log", $message); } }

Slide 26

Slide 26 text

Dependency inversion principle class Logger { protected $writer; public function __construct(Writer $writer) { $this->writer = $writer; } public function write($message) { $this->writer->write($message); } }

Slide 27

Slide 27 text

Test-driven development

Slide 28

Slide 28 text

Write a test → class CalculatorTest extends PHPUnit_Framework_TestCase { public function testItAdds() { $calculator = new Calculator(); $this->assertEquals( 4, $calculator->add(2, 2) ); } }

Slide 29

Slide 29 text

Write a fix → class Calculator { public function add($one, $two) { return 4; } }

Slide 30

Slide 30 text

Add another test → $calculator = new Calculator(); $this->assertEquals( 4, $calculator->add(2, 2) ); $this->assertEquals( 3, $calculator->add(1, 2) );

Slide 31

Slide 31 text

Write another fix → class Calculator { public function add($one, $two) { return $one + $two; } }

Slide 32

Slide 32 text

Meaningful Names

Slide 33

Slide 33 text

Use intention-revealing names Names should describe what things are are, how they are measured, what they do

Slide 34

Slide 34 text

Use intention-revealing names $d = 0; // elapsed time in days $elapsedTimeInDays = 0; function calculate($amount, $percentage) { return ($amount * $percentage) + $amount; } function calculateIncreaseWithPercentage($amount, $percentage) { return ($amount * $percentage) + $amount; }

Slide 35

Slide 35 text

Avoid disinformation Don't call something it's not!

Slide 36

Slide 36 text

Avoid disinformation $numberOfUsers = null; function addComment($text) { if (empty($text)) { return; } storeCommentInDatabase($text); }

Slide 37

Slide 37 text

Make meaningful distinctions Variables represent different things, and should be named to show that

Slide 38

Slide 38 text

Make meaningful distinctions $list1 = ["chris"]; $list2 = []; foreach ($list1 as $name) { $list2[] = ucwords($name); } function merge($array1, $array2) { foreach ($array1 as $key => $value) { $array2[$key] = $array1[$key]; } }

Slide 39

Slide 39 text

Avoid encodings Don't attach volatile or redundant meaning to names

Slide 40

Slide 40 text

Avoid encodings $strName = "Chris"; $dblPrice = 10; interface RepositoryInterface { }

Slide 41

Slide 41 text

Class names should be nouns Classes lead to objects which are things, not actions

Slide 42

Slide 42 text

Method names should start with a verb Methods are actions performed on and with things, they are actions

Slide 43

Slide 43 text

Don't be cute If names need your sense of humour to understand, they are not good names

Slide 44

Slide 44 text

Don't be cute public function kapow() { $this->repository->truncate(); } public function truncateRepository() { $this->repository->truncate(); }

Slide 45

Slide 45 text

Pick one word per concept Using synonyms for the same action, on different objects, is confusing

Slide 46

Slide 46 text

Pick one word per concept public function get($key, $default = null) { // ...gets a value with key, or returns default } public function retrieve($key, $default = null) { // ...gets a value with key, or returns default } public function read($key, $default = null) { // ...gets a value with key, or returns default }

Slide 47

Slide 47 text

Add meaningful context If the good name you've picked is still confusing, out of context, add some context

Slide 48

Slide 48 text

Add meaningful context class Product { public function purchaseWithAccount($account) { // ...enough context } } function purchaseProductWithAccount(Product $product, $account) { }

Slide 49

Slide 49 text

Functions

Slide 50

Slide 50 text

Make functions short Many shorter functions are better than fewer longer ones

Slide 51

Slide 51 text

Make functions do one thing If the name includes "and" or doesn't describe all the functionality well; it's doing too much

Slide 52

Slide 52 text

Don't use switch Using switch invites modification and adds responsibility

Slide 53

Slide 53 text

Be consistent with arguments If you have many "add" methods; make them have the same argument names and order

Slide 54

Slide 54 text

Don't use flag arguments Because calling functions with "true" and "false" as arguments says nothing about the arguments

Slide 55

Slide 55 text

Use objects/arrays when you need many arguments A single type is easier to understand than many arguments which form that type

Slide 56

Slide 56 text

Avoid side-effects Side-effects are hidden functionality, extra responsibility, stateful

Slide 57

Slide 57 text

Separate commands and queries Functions should either do something or answer something; doing both is confusing

Slide 58

Slide 58 text

Don't repeat yourself Functions are for reuse, not encapsulating repeated code

Slide 59

Slide 59 text

Don't mix concepts Don't abstract for things that can change independently

Slide 60

Slide 60 text

Comments

Slide 61

Slide 61 text

Comments represent bad code If you need comments to understand what the code is doing then the code is poorly written

Slide 62

Slide 62 text

Legal comments are ok Comments which show things like licenses, copyright and authors

Slide 63

Slide 63 text

Legal comments are ok /** * Licensed under the ACME PSL License. * * This source file is subject to the ACME PSL License that is * bundled with this package in the license.txt file. * * @package Acme\Explosions * @author ACME LLC * @license ACME PSL * @copyright (c) 2011-2014, ACME LLC * @link http://acme.com */

Slide 64

Slide 64 text

Warning comments are ok Could the code cause problems if it is used? Should the reader refer to other code?

Slide 65

Slide 65 text

Warning comments are ok /** * @group LongRunning */ public function testFullRequestCycle() { // Don't run this if you're short on time! } public function seedDatabase() { // This will overwrite production data... }

Slide 66

Slide 66 text

Todo comments are ok Sometimes you can't make the changes you expect, and an IDE will point these out later

Slide 67

Slide 67 text

Todo comments are ok public function getFormattedProduct(Product $product) { // TODO check for errors in product data return [ "title" => $this->getFormattedTitle($product->title), "price" => $this->getFormattedPrice($product->price) ]; }

Slide 68

Slide 68 text

Type comments are ok In a dynamic language, documenting types is helpful, and an IDE will show inconsistencies

Slide 69

Slide 69 text

Type comments are ok /** * @param Responder $responder * @param array $input * * @return Response */ public function validateInput(Responder $responder, array $input) { if ($this->validator->passes($input)) { return $responder->respondWithOk(); } return $responder->respondWithErrors($this->validator); }

Slide 70

Slide 70 text

Formatting

Slide 71

Slide 71 text

Add spaces between concepts Properties and methods are easier to read when there are spaces between them

Slide 72

Slide 72 text

Add spaces between concepts public function processPayment($account, $amount) { $chargeId = $this->adapter->chargeWithAmount($amount); if ($this->adapter->isSuccessful($chargeId)) { $this->repository->updateAccount($account); } }

Slide 73

Slide 73 text

Order methods by depth If method "a" depends on "b" and then "c", that is the order in which they should appear

Slide 74

Slide 74 text

Order methods by depth public function getSummaryArray() { return [ "title" => $this->getTitle(), ... ]; } protected function getTitle() { return ucwords($this->title); }

Slide 75

Slide 75 text

Order methods by conceptual affinity If methods "d" and "e" do the similar things, put them close together

Slide 76

Slide 76 text

Order methods by conceptual affinity class TestCase { public function assertProductValid(Product $product) { // ...check the product } public function assertChargeValid(Charge $charge) { // ...check the charge } }

Slide 77

Slide 77 text

Limit the line length Readers will already have to scroll vertically, don't make that horizontally also.

Slide 78

Slide 78 text

Have team standards Being consistent is important, even if your standards are strange

Slide 79

Slide 79 text

Have team standards → php-fig.org/psr/psr-1 → php-fig.org/psr/psr-2 → symfony.com/doc/current/contributing/code/standards.html → laravel.com/docs/4.2/contributions#coding-style → framework.zend.com/wiki/display/ZFDEV2/Coding+Standards