Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Code

Clean Code

This workshop is an introduction to the principles outlined by Robert C. Martin; on how to write clean code. We'll refactor code form popular open-source projects, and learn core concepts along the way.

Christopher Pitt

October 02, 2014
Tweet

More Decks by Christopher Pitt

Other Decks in Programming

Transcript

  1. Single responsibility principle interface Basket { public function getItems(); public

    function addItem($item); public function removeItem($item); public function calculateTotal(); public function processPayment($account, $amount); }
  2. 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); }
  3. 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; } } }
  4. Open-closed principle interface Adapter { public function charge($account, $amount); }

    class BasketPaymentProcessor { protected $adapters = []; public function addAdapter($key, Adapter $adapter) { $this->adapters[$key] = $adapter; }
  5. 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 } }
  6. Open-closed principle $processor = new BasketPaymentProcessor(); $processor->addAdapter( "paypal", new PaypalAdapter()

    ); $processor->addAdapter( "payfast", new PayfastAdapter() ); $processor->addAdapter( "debit", new DebitAdapter() );
  7. Lizkov substitution principle class Mailer { public function send($from, $to,

    $subject, $body) { mail($to, $subject, $body, "From: {$from}\n"); } }
  8. 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); } }
  9. Lizkov substitution principle function sendMail(Mailer $mailer) { print "send some

    mail..."; } //$mailer = new Mailer(); $mailer = new SecureMailer(); sendMail($mailer);
  10. Interface segregation principle interface ConnectionInterface { public function connect(array $credentials);

    } interface CacheInterface { public function set($key, $value); public function get($key); }
  11. Interface segregation principle class CacheManager { public function connect() {

    if ($this->adapter instanceof ConnectionInterface) { $this->adapter->connect([...]); } } }
  12. Dependency inversion principle class Logger { public function write($message) {

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

    __construct(Writer $writer) { $this->writer = $writer; } public function write($message) { $this->writer->write($message); } }
  14. Write a test → class CalculatorTest extends PHPUnit_Framework_TestCase { public

    function testItAdds() { $calculator = new Calculator(); $this->assertEquals( 4, $calculator->add(2, 2) ); } }
  15. Add another test → $calculator = new Calculator(); $this->assertEquals( 4,

    $calculator->add(2, 2) ); $this->assertEquals( 3, $calculator->add(1, 2) );
  16. 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; }
  17. 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]; } }
  18. Method names should start with a verb Methods are actions

    performed on and with things, they are actions
  19. Don't be cute If names need your sense of humour

    to understand, they are not good names
  20. Don't be cute public function kapow() { $this->repository->truncate(); } public

    function truncateRepository() { $this->repository->truncate(); }
  21. Pick one word per concept Using synonyms for the same

    action, on different objects, is confusing
  22. 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 }
  23. Add meaningful context If the good name you've picked is

    still confusing, out of context, add some context
  24. Add meaningful context class Product { public function purchaseWithAccount($account) {

    // ...enough context } } function purchaseProductWithAccount(Product $product, $account) { }
  25. Make functions do one thing If the name includes "and"

    or doesn't describe all the functionality well; it's doing too much
  26. Be consistent with arguments If you have many "add" methods;

    make them have the same argument names and order
  27. Don't use flag arguments Because calling functions with "true" and

    "false" as arguments says nothing about the arguments
  28. Use objects/arrays when you need many arguments A single type

    is easier to understand than many arguments which form that type
  29. Comments represent bad code If you need comments to understand

    what the code is doing then the code is poorly written
  30. 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 */
  31. Warning comments are ok Could the code cause problems if

    it is used? Should the reader refer to other code?
  32. 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... }
  33. Todo comments are ok Sometimes you can't make the changes

    you expect, and an IDE will point these out later
  34. 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) ]; }
  35. Type comments are ok In a dynamic language, documenting types

    is helpful, and an IDE will show inconsistencies
  36. 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); }
  37. Add spaces between concepts public function processPayment($account, $amount) { $chargeId

    = $this->adapter->chargeWithAmount($amount); if ($this->adapter->isSuccessful($chargeId)) { $this->repository->updateAccount($account); } }
  38. Order methods by depth If method "a" depends on "b"

    and then "c", that is the order in which they should appear
  39. Order methods by depth public function getSummaryArray() { return [

    "title" => $this->getTitle(), ... ]; } protected function getTitle() { return ucwords($this->title); }
  40. Order methods by conceptual affinity If methods "d" and "e"

    do the similar things, put them close together
  41. Order methods by conceptual affinity class TestCase { public function

    assertProductValid(Product $product) { // ...check the product } public function assertChargeValid(Charge $charge) { // ...check the charge } }
  42. Limit the line length Readers will already have to scroll

    vertically, don't make that horizontally also.
  43. 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