Clean code

Simple guide to improve our code

WHY?

Code is never permanent, it changes.

 

Easier improvements.

 

Welcome collaboration.

Programming is the art of telling another human what one wants the computer to do.
— Donald Knuth

WHY?

According to [Schach 1999] the maintenance part of the life-cycle accounts for 67% of the project cost.

WHat's clean code?

simple

it works

secure

commented

flexible

atomic

readable

no duplicated

no spaghetti code

follow standards

WHat's clean code?

simple

it works

secure

commented

flexible

atomic

readable

no duplicated

no spaghetti code

follow standards

clean code

No code is perfectly clean.

 

Code rots.

 

Code requires maintenance.

 

Requirements change and code gets obsolete.

clean code: BOY SCOUT RULE

Leave the code always cleaner than you found it.

Who owns the code?

Developer

Developer

Team

Team

Company / Client

clean code: DIMENSIONS

Brevity

Full feature

Performance

Robustness

Flexibility

Development time

clean code: STARTING

Brevity of code.

  • Start by writing the minimum functionality.
     
  • Extend as necessary in the other dimensions.

Keep It Simple, Stupid

— K.I.S.S principle

clean code: JUSTIFY

Do not be afraid of code reviews.

 

A developer should always be able to justify the code:

  • Never use "It works" as the only reason.
  • Never use "It's simple" as the only reason.

 

Ask for help or points of view if you feel your code smells.

  • Learning is the base of writing clean code.

 

STANDARDS

Agreed before the project.

 

Follow PSR-1/2 as guide

http://www.php-fig.org/psr/psr-1/
http://www.php-fig.org/psr/psr-2/

 

Follow drupal standards

https://www.drupal.org/coding-standards

 

STANDARDS

Make your life easier

  • Adjust IDE editor settings: tabs for spaces, how many, etc. 
  • Use PHP CodeSniffer for checking: will detect wrong formatting (and more) and will notify you.
  • Strict code reviews.
array(
  'id' => 'identifier',
  'description' => 'short description',
  'text' => 'full text',
);
array(
  'id'          => 'identifier',
  'description' => 'short description',
  'text'        => 'full text',
);

MEANINGFUL NAMING

You can read your code like a book.

 

The name explicitly says what does/contains/purpose.

 

Length is not a problem.

 

Avoid type information in the names.

function get_book_title(array $books) {
  foreach ($books as $book) {
    if ($book->hasTitle()) {
      return $book->getTitle();
    }
  }
  return '';
);
function get_bt(array $bookList) {
  foreach ($bookList as $b) {
    if ($b->title()) {
      return $b->title;
    }
  }
  return '';
);

Say what you mean. Mean what you say.

COMMENTS

Write code as if you weren't going to use comments.

 

Code changes fast.

Comments barely do.

 

If it doesn't provide useful info, better don't put it.

 

Ex. of good comments: API, business weird logic,

 

function get_books_with_title(array $books) {
  return array_filter($books, function($book) {
    return $book->hasTitle();
  });
);
function get_bt(array $bookList) {
  // Initialise aux list.
  $list = array();
  // Loop the list of books.
  foreach ($bookList as $b) {
    // Check if it has title.
    if ($b->title()) {
      $list[] = $b->title;
    }
  }
  return $list;
);

functions/methods

function get_book_list($data) {
  if ($data['list']) {
    return (array) $data['list'];
  }
  return array();
);

function get_book($id) {
  return ($id) ? Book::getInstance($id) 
    : Book::getEmptyInstance();
}
function get_book_list($data) {
  if ($data['list']) {
    return (array) $data['list'];
  }
  return FALSE;
);

function get_book($id) {
  return ($id) ? Book::getInstance($id) 
    : NULL;
}

Always return the same data type.

foreach (get_book_list($data) as $book) {
  print get_book($book['id'])->getTitle();
}

functions/methods

function redirect($path, $http_code) {
  if ($path) {
    header('Location: ' . $path, TRUE, 
      $http_code);
  }
}

function redirect_on_login() {
  redirect('/login', 302);
);
function redirect($path = '/login', 
  $http_code = 302) {
  header('Location: ' . $path, TRUE, 
    $http_code);
}

function redirect_on_login() {
  redirect();
);

Avoid default values. They hide functionality.

functions/methods

function get_book_info($data) {
  if ($data['id']) {
    return load_book_info($data);
  }
  if ($data['info']) {
    return $data['info'];
  }
  return array();
}
function get_book_info($data) {
  if (!$data['id']) {
    return array();
  }
  else if ($data['info']) {
    return $data['info'];
  }
  else {
    return load_book_info($data);
  }  
}

Place default value at the end for visibility.

functions/methods

function setcookie(Cookie $cookie) {
  ...
);

function get_distance(Coordinate $origin,
  Coordinate $destination) {
    ...
}
function setcookie($name, $value, 
  $expire, $path, $domain, $secure, 
  $httponly) {
    ...
);

function get_distance($originX, $originY,
  $destinationX, $destinationY) {
    ...
}

One argument (monad)

Two arguments (dyad)

Three arguments (triad)

- Think carefully. Can we use objects or lists?

Four arguments

- Probably you can split your function or join arguments.

OOP vs PROCEDURAL

Procedural

OOP

Stateless

Stateful

class Number {

  public function __construct(int $number) {
    $this->number = $number;
  }

  public function double() {
    return 2 * $this->number;
  }
}

$number = new Number(2)
echo $number->double(); // prints 4
$number = new Number(3);
echo $number->double(); // prints 6
function double(int $number) {
  return 2 * $number;
);

echo double(2); //prints 4
echo double(3); //prints 6

OOP vs PROCEDURAL

Procedural

OOP

One input = One output

One input >= One output

class Number {

  private $i;

  public function increment(int $increment) {
    $this->i += $increment;
    return $this->i;
  }
}

$number = new Number();
echo $number->increment(2); // prints 2
echo $number->increment(2); // prints 4
function incremental(int $increment) {
  static $i = 0;
  $i += $increment; 
  return $increment;
);

echo increment(2); //prints 2
echo increment(2); //prints 4

OOP vs PROCEDURAL

Procedural

OOP

Please don't use globals

- breaks encapsulation.

- naming collisions.

- anyone can change them.

Singleton

class Number implements Singleton {

  private $i;

  public function increment(int $increment) {
    $this->i += $increment;
    return $this->i;
  }
}

$number = Number::getInstance();
echo $number->increment(2); // prints 2
$number = Number::getInstance();
echo $number->increment(2); // prints 4
function incremental(int $increment) {
  global $i;
  $i += $increment; 
  return $increment;
);

echo increment(2); //prints 2
echo increment(2); //prints 4

OOP

static methods are procedural functions.

too many static methods is a smell indicator.

class Number {

  static function abs(int $number) {
    return ($number * $number) / $number;
  }

  static function decimals(float $number) {
    return $number % self::int($number);
  }

  static function int(float $number) {
    return (int) $number;
  }
}
class Number {

  protected $value;

  public function abs() {
    return abs($this->value);
  }

  static function getTypes() {
    return ['float', 'int', 'real'];
  }
}

OOP

do not extend constants

class Car  {

  protected $wheels = 4;

  public function getWheels() {
    return $this->wheels;
  }
}

class Motorbike extends Car {

  protected $wheels = 2;
}

$car = new Car();
echo $car->getWheels(); // prints 4
$motorbike = new Motorbike();
echo $motorbike->getWheels(); // prints 2
class Car {

  const WHEELS = 4;

  public function getWheels(int $increment) {
    return static::WHEELS;
  }
}

class Motorbike extends Car {

  const WHEELS = 2;
}

$car = new Car();
echo $car->getWheels(); // prints 4
$motorbike = new Motorbike();
echo $motorbike->getWheels(); // prints 2

S.O.L.I.D

Single responsibility

Open-closed

Liskov substitution

Interface segregation

Dependency inversion 

Single responsibility

Every class is responsible for one part of the functionality.

class Dashboard {

  public function getItems() { ... }

}

class Version {

  public function getMinorVersion() { ... }

  public function getMajorVersion() { ... }

  public function getBuildVersion() { ... }

}
class Dashboard {

  public function getItems() { ... }

  public function getBuildVersion() { ... }
}

Open/closed principle

entities should be open for extension, but closed for modification

interface Product {

  public function getPrice();
}

class Cart {

  function getTax(Product[] $products) {
    $tax = 0;
    foreach ($products as $product) {
      if (is_a($product, 'Book')) {
        $tax += $product->getPrice() * 0.21;
      }
      else if (is_a($product, 'Food')) {
        $tax += $product->getPrice() * 0.07;
      }
    }
    return $tax;
  }
}
interface Product {

  public function getPrice();

  public function getTax();
}

class Cart {

 function getTax(Product[] $products) {
    $tax = 0;
    foreach ($products as $product) {
      $tax += $product->getTax();
    }
    return $tax;
  }
}

INTERFACE SEGREGATION

no entity should be forced to depend on methods it does not use

interface Task {

  public function preprocess();

  public function process();

  public function print();

  public function fax();

  public function pdf();
}
interface Task {
  public function preprocess();

  public function process();
}

interface Print extends Task {
  public function print();
}

interface Fax extends Task {
  public function fax();
}

interface PDF extends Task {
  public function pdf();
}

DEPENDENCY INVERSION

Depend upon Abstractions. Do not depend upon concretions

interface Car { ... }

class Volkswagen implements Car { ... }

class Garage {

  public function fix(Volkswagen $car) {
    ...
  }

  public function fixVolkswagen($car) {
    ...
  }
}
interface Car { ... }

class Volkswagen implements Car { ... }

class Garage {

  public function fix(Car $car) {
    ...
  }
}

LISKOV SUBSTITUTION

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

abstract Car {

  public function getModel() : string {
    return $this->model; 
  }
}

class SEAT extends Car {

  public function getModel($type) {
    if (!$this->model) {
      throw new Exception('No model set');
    }
    return array($this->model);
  }
}
abstract Car { 

  public function getModel() : string {
    return $this->model; 
  }
}

class SEAT extends Car {

  public function getModel() : string {
    return random($this->spainCities);
  }
}

Sources

http://www.slideshare.net/hebel/clean-code-vortrag032009pdf

http://blog.codinghorror.com/the-best-code-is-no-code-at-all/

https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

https://cleancoders.com/category/clean-code

https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

http://www.irahul.com/2005/10/software-development-vs-software.html

Questions

Gorka Guridi

Drupal Architect, Cameron & Wilding Ltd.

www.cameronandwilding.com

@cameronwilding

Clean Code

By Gorka Guridi

Clean Code

Why we should, how do we do it and examples about how to write clean code.

  • 836