Coding CONCEPTS

Simple useful guide of coding concepts

CODING

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

- Martin Golding

CODING: steps

In order to be able to solve a problem, we have to first understand it.

CODING: steps

  • Are the specifications precise enough?
     
  • Is there a clear relation between input and output?
     
  • Are all the use-cases covered?
     
  • Are there no contradictions or collisions of requirements?
     
  • We don't see fuzzy words like "probably", "usually", "fast", etc.?

CODING: steps

Understanding the data: what input we receive and what information we have available.

CODING: steps

  • Is there any unused input/data provided?
     
  • Is concurrency/stability considered accessing the data?
     
  • Are the relationships between data elements strong?
     
  • Are there security threats related to data exposed/stored?
     
  • Is there data available during development? Can it be simulated?
     
  • What happens if the data is unavailable for a time?
     
  • Can the old data be purged?

CODING: steps

Question all the assumptions, constraints and conditions specified for the software.

CODING: steps

  • Are the constraints realistic?
     
  • Are the constraints hiding other problems?
     
  • Can some constraints be removed to make it more generic?
     
  • Can some constraints be added to simplify some scenarios?
     
  • Could there be unnecessary conditions?

CODING: steps

Focus in the goal, not in the description.

 

Try to find the minimum change to achieve the solution.

 

Create/remove constraints as necessary and if possible (ask!)

 

 

ABSTRACT CLASSES

An abstract class can not be instantiated. It's used just to declare properties and methods.

ABSTRACT CLASSES

Force child declaration.

abstract class Product {

  protected $price;

  abstract function getTax();

  protected function getPrice() {
    return $this->price;
  }
}

class Book extends Product {

  public function getTax() {
    return $this->getPrice() * 0.07;
  }
}

ABSTRACT CLASSES

Never duplicate code. Abstract it!

class AgileWhitepaperSignupBlockForm ext... {

  public function buildForm(...) {
    // Company name.
    $form['company'] = array(
      '#type' => 'textfield', ...

    // The name field.
    $form['name'] = array(
      '#type' => 'textfield', ...

    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'), ...
    return $form;
  }
}
class D8WhitepaperSignupBlockForm ext... {

  public function buildForm(...) {
    // Company name.
    $form['company'] = array(
      '#type' => 'textfield', ...

    // The name field.
    $form['name'] = array(
      '#type' => 'textfield', ...

    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'), ...
    return $form;
  }
}

CODING: SMELLS

Code smells are symptoms of poor design or implementation choices.

- Martin Fowler.

CODING: SMELLS

Pay attention to the code smells.

 

Refactor, refactor, refactor!

coding: smells

Duplication of code: If three lines are repeated, that's enough to abstract the functionality.

abstract class FormContactBase {

  protected function submitCampaign(
    FormStateInterface $fs) {
    $contact = new CreateSendManager([]);
    $contact->setName($fs->getValue('name'));
    $contact->setMail($fs->getValue('mail'));
    $contact->setCompany(
      $fs->getValue('company'));
    $contact->postUserToList(
      $fs->getBuildInfo()['args'][0]);
  }
}
// The company custom field as an array.
$company = array(
  'Key' => 'Company',
  'Value'=>$form_state->getValue('company'),
);

$senderParams = new SendMailParamsManager(
  $form_state->getValue('name'), 
  $form_state->getValue('email'), 
  array($company)
);

$listID = $form_state->getBuildInfo()
  ['args'][0];

coding: smells

Feature envy: a class that uses methods of another class excessively.

Inappropriate intimacy: a class that has dependencies on implementation details of another class.

coding: smells

Contrived complexity: forced usage of overcomplicated design patterns where simpler design would suffice.

$contact = $this->whitepaper;
$contact->setName($fs->getValue('name'));
$contact->setMail($fs->getValue('mail'));
$contact->setCompany(
  $fs->getValue('company'));
$contact->postUserToList(
  $fs->getBuildInfo()['args'][0]);
// The company custom field as an array.
$company = array(
  'Key' => 'Company',
  'Value'=>$form_state->getValue('company'),
); 
$senderParams = new SendMailParamsManager(
  $form_state->getValue('name'), 
  $form_state->getValue('email'), 
  array($company)
);

$listID = $form_state->getBuildInfo()
  ['args'][0];

$this->whitepaper->postUserToList(
  $listID, $senderParams);

coding: smells

Large class: a class that has grown too large. Check always the Single Principle Response.

 

Long method: a method, function, or procedure that has grown too large.

See next slide for an example.

(function ($) {

Drupal.behaviors.applicationslist = {
  attach: function (context, settings) {    
    if ($('.field-type-node-reference input').is(":visible")) {
      ...
      $('.field-type-node-reference select').change(function() {
        if (text == '- None -') { ... }
        else { ...
          if (href[href.length-1] == 'erg-summary') {
            $.getJSON(basePath+'node/', function(result) {
              if (result) {
                $.each(result, function(i, item) { ...
                  if (typeof result[i].field_ef_initiative.und != 'undefined') {
                    initiative += initials+result[i].field_ef_initiative.und[0].value+"\n";  
                  }
                  if (typeof result[i].field_ef_rigour.und != 'undefined') {
                    rigour += initials+result[i].field_ef_rigour.und[0].value+"\n";
                  }
                  if (typeof result[i].field_ef_othercomm.und != 'undefined') {
                    comments += initials+result[i].field_ef_othercomm.und[0].value+"\n";
                  }
                }); } }) }          
          if (href[href.length-1] == ...) { 
            $.getJSON(basePath+'node/'+nid, function(result) {
              if (result) {
                $.each(result, function(i, item) {});  
              }
            })
          }
        }
      }); } } };
})(jQuery);

CODING: SMELLS

Lazy class / Freeloader: a class that does too little.

class SendMailParamsManager {
  protected $name;
  protected $email;
  protected $customFields = array();

  public function __construct($name, $email, $customFields = array()) {
    $this->name = $name;
    $this->email = $email;
    $this->customFields = $customFields;
  }

  public function getName();

  public function getEmail();

  public function getCustomFields();
}

CODING: SMELLS

Excessive use of literals: these should be coded as named constants.

  ILCore.TileGrid = {
    /**
     * Selector for the tile grid
     */
    TILE_GRID: '.tile-grid',

    /**
     * Selectors for the tile containers
     */
    TILE_CONTAINER_ONE: '.tile-grid__content-block-one',
    TILE_CONTAINER_TWO: '.tile-grid__content-block-two',
    TILE_CONTAINER_THREE: '.tile-grid__content-block-three',
    TILE_CONTAINER_FOUR: '.tile-grid__content-block-four',

    /**
     * Selectors for the tiles
     */
    TILE_ONE: '.tile-grid .one',
    TILE_TWO: '.tile-grid .two',

CODING: SMELLS

Downcast: Type cast breaking the Liskov substitution.

// Parent class
class Fruit{}  
// Child class
class Apple extends Fruit{}  

// The following is an upcast:
$parent = (Fruit) new Apple();

// The following is a downcast. Here, 
// it works since the variable `parent` is
// holding an instance of Apple:
$child = (Apple) $parent;

CODING: SMELLS

Orphan Variable or Constant class.

var Logger = {


  log: function(type, message) {
    ...
  }
};

var Notification = {

  LOG_INFO : 'info',
  LOG_ERROR : 'error',

  execute : function(message) {
    Logger.log(this.LOG_ERROR, message);
  }
};
var Logger = {

  LOG_INFO : 'info',
  LOG_ERROR : 'error',

  log: function(type, message) {
    ...
  }
};

var Notification = {

  execute : function(message) {
    Logger.log(Logger.LOG_ERROR, message);
  }
};

CODING: SMELLS

Too many parameters: It may indicate that the purpose of the function is ill-conceived.

<?php

setcookie (
  $name, 
  $value = null, 
  $expire = null, 
  $path = null, 
  $domain = null, 
  $secure = null, 
  $httponly = null
);
<?php

setcookie (
  Cookie $cookie
);

CODING: SMELLS

Cyclomatic complexity: too many branches or loops.

Compexity =

Arrows − Nodes + 2*Connected Components

Basically: Try to keep the conditions, etc. at minimum.

CODING: REFACTOR

Leave the code better than you found it.

CODING: dimensions

Brevity

Full feature

Performance

Robustness

Flexibility

Development time

clean code: STARTING

Brevity of code.

  • Start by writing the minimum functionality.
     
  • Generic behaviour is often briefer.
     
  • Extend as necessary in the other dimensions.

Keep It Simple, Stupid

— K.I.S.S principle

clean code: STARTING

<?php

public static function isEnabled($code) {
  if ($code == ACCESS_CODE_DISABLED) {
    return  FALSE;
  }
  // Set it as enabled by default.
  return TRUE;
}
<?php

public static function isEnabled($code) {
  if ($code == ACCESS_CODE_ENABLED) {
    return TRUE;
  }
  elseif ($code == ACCESS_CODE_DISABLED) {
    return  FALSE;
  }
  // Set it as enabled by default.
  return TRUE;
}
<?php

public static function isEnabled($code) {
  return $code != ACCESS_CODE_DISABLED;
}

clean code: STARTING


moreLink: 
  '<a ... class="contracted">Read more</a>',
lessLink: 
  '<a ... class="expanded">Close</a>'

moreLink: 
  '<a ... class="contracted">'
    + Drupal.t('Read more') + 
  '</a>',
lessLink: 
  '<a ... class="expanded">'
    + Drupal.t('Close') + 
  '</a>'

Think before hardcoding. Might that value change?

NAMESPACES

A namespace is a way to encapsulate items.

NAMESPACES

Avoid name collisions between code.

<?php
# Component 1 declaration.

namespace Component1;

class Logger {);
<?php
# Component 1 declaration.

class Logger {}
<?php
# Component 2 declaration.

namespace Component2;

class Logger {);
<?php
# Component 2 declaration.

class Logger {}

NAMESPACES

Ability to alias.

<?php
# Component 1 declaration.

namespace Component1;

class Logger {);
<?php
# Component 1 declaration.

class Component1ClassNameToAvoidCollisionIsLong {}

<?php

use Component1\Logger as Logger;

$a = new Logger();
<?php

$a = new Component1ClassNameToAvoidCollisionIsLong();

NAMESPACES

PSR-4 is specification for autoloading classes from file paths.

<?php
# Component 1 declaration.

namespace Component1;

class Logger {);
<?php
# Component 1 declaration.

class Component1ClassNameToAvoidCollisionIsLong {}

<?php

use Component1\Logger as Logger;

$a = new Logger();
<?php

$a = new Component1ClassNameToAvoidCollisionIsLong();

INTERFACE

An interface specifies which methods a class must implement, without having to define how these methods are handled.

INTERFACES

Interfaces can be extended.

<?php

interface Product {

  public function getPrice();
}

interface Digital extends Product {

  public function getQRCode();
}

class Ticket implements Digital {

}

INTERFACES

Classes can extend multiple interfaces.

<?php

interface Product {

  public function getPrice();
}

interface Preview {

  public function getGooglePreview();
}

class Book implements Product, Preview {

}

CLASSES

A class is an extensible template for creating objects, providing initial values for state and implementations of behavior.

CLASSES

Do not over use static methods.

class Discount extends Multiton {

  public static function getDiscount($id) {
    ... 
  }

  protected function loadDiscount() {
    ...
  }

  public function removeDiscount() {
    ...
  }
}
class Discount {

  public static function getDiscount($id) {
    ...
  }

  public static function removeDiscount() {
    ...
  }
 
  public static function isBookDiscount() {
    ...
  }
}

CLASSES

Implement interfaces if we can have more than one subtype.

interface Product {

  public function getPrice();
}

class Book implements Product {

  protected $price;

  public function getPrice() {
    return $this->price;
  }
}
interface Language {

  public function getLanguage($id);
}

class LanguageImpl implements Language {

  public function getLanguage($id) {
    return $this->languages[$id];
  }
}

CLASSES

Subtype polymorphism.

class Node {

  public function getUrl($options) {
    return url('node/'.$this->nid, $options);
  }
}

class Article extends Node {

  public function getUrl($options) {
    $options += ['absolute' => TRUE];
    return parent::getUrl($options);
  }
}
class Node {

  public function getUrl() {
    return url('node/'.$this->nid);
  }
}

class Article extends Node {

  public function getArticleUrl() {
    return url('node/' . $this->nid, [
      'absolute' => TRUE,
    ]);
  }
}

classes

Extend functionality, do not copy+paste.

class Node {

  public function save() {
    $this->node->created = now();
    return node_save($this->node);
  }
}

class Article extends Node {

  public function save() {
    $this->node->type = 'article';
    return parent::save();
  }
}
class Node {

  public function save() {
    $this->node->created = now();
    return node_save($this->node);
  }
}

class Article extends Node {

  public function save() {
    $this->node->type = 'article';
    $this->node->created = now();
    return node_save($this->node);
  }
}

classes

Javascript has objects & classes, use them if necessary.

# This is a class

var image = function(src) {

  this.src = src;

  this.getUrl = function() {
    return '/' + this.src;
  }
}

image.prototype.getSource = function() {
  return this.src;
}

var i = new image('picture.png');
# This is an object, not a class.

var image = {

  src : '',
  
  getUrl : function() {
    return '/' + this.src;
  }

  getSource : function() {
    return this.src;
  }
}

var i = image.src;

classes

In javascript extension is merely cloning methods & properties.

# This is a class
var content = function(src) {
  this.src = src;

  this.getUrl = function() {
    return '/' + this.src;
  }
}

var image = function(src) {};

image.prototype = jQuery.extend({

}, content.prototype);
# This is an object, not a class.
var content = {
  src : '',

  getUrl : function() {
    return '/' + this.src;
  }

  getSource : function() {
    return this.src;
  }
}

var image = jQuery.extend({
  getUrl : function() {
    return 'http://cdn.com/' + this.src;
  }
}, content);

TRAIT

Traits are a mechanism for code reuse in single inheritance languages such PHP.

TRAIT

They don't provide multi-inheritance. They can't implement interfaces.

interface Product {

  public function getPrice();
}

trait ProductImpl implements Product {

  #NO!
}
interface Product {

  public function getPrice();
}

trait ProductImpl {

  protected $price;

  public function getPrice() {
    return $this->price;
  }
}

class Book implements Product {
  use ProductImpl;
}

TRAIT

They can't be instantiated. They declare properties & methods.

trait Printable {

  protected $printData;

  protected function print() {
    print $this->printData;
  }
}

class Book {

  use Printable;
}

$book = new Book();
$book->print();

TRAIT

Traits are concrete implementations, not abstractions.

trait Translate {

  public function t($text) {
    Drupal::getContainer();
    return $container->get('translate')
      ->translate($text);
  }
}

class Manager {

  use Translate;
}

new Manager()->t('hello');
trait Printable {

  protected function print() {
    print $this->__toString();
    $this->logger->log('Printed!');
  }
}

class Controller {

  public $logger;

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

new Controller($logger)->print();

EXCEPTIONS

An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

EXCEPTIONS

EXCEPTIONS

Exceptions bubble up until someone captures them.

function force_fail() {
  throw new Exception('Message here');
}

function function2() {
  force_fail();
  print 'Function 2 is here!';
}

function function1() {
  function2();
  print 'Function 1 is here!';
}

try {
  print 'Starting test.';
  function1();
} catch (Exception $e) {
  print 'Exception captured!';
}
# Output

Starting test.
Exception captured.

EXCEPTIONS

Used for exceptional errors.

class Product {

  public function getInstance($id) {
    if (!$id) {
      throw new Exception('No id?');
    }
    return self::$products[$id];
  }
}

$p = Product::getInstance($value);
function is_admin($user) {
  $permissions = user_permissions($user);
  if (!in_array('admin', $permissions)) {
    throw new Exception("It's not admin!!!");
  }
  return TRUE;
);

global $user;
is_admin($user);

EXCEPTIONS

class PDF {

  public function create() {
    $pdf = pdf_create($this->data);
    if (!$pdf) {
      throw new Exception('Unable to create PDF');
    }
    return $pdf;
  }
}

Or when you don't know what to do with the error.

EXCEPTIONS

try {
  #Code to evaluate.
}
catch (RuntimeException $e) {
  #Code to execute if RuntimeException
  #instance or subinstance is detected.
}
catch (IOException $e) {
  #Code to execute if IOException
  #instance or subinstance is detected.
}
finally {
  #Before bubbling up the exception 
  #this is executed.
}

Understanding try ... catch ... finally statements.

EXCEPTIONS

function force_fail() {
  throw new Exception('Message here');
}

function test_statement() {
  try {
    print 'Executing code';
    force_fail();
  } catch (RuntimeException $e) {
    print 'Catched UnderflowException';
  } finally {
    print 'Executing finally statement';
  }
}

try {
  test_statement();
} catch (Exception $e) {
  print 'Exception captured';
}

Understanding try ... catch ... finally statements.

# Output

Executing code.
Executing finally statement.
Exception captured.

EXCEPTIONS

class ServiceException extends Exception {

  public function __construct($message, ServiceResponse $response) {
    parent::__construct($message);
    if ($response) {
      $this->setResponse($response);
      $this->message = $this->response->getErrorMessage() ?: $message;
    }
  }

  function setResponse($response) {
    $this->response = $response;
  }
}

throw new ServiceException('Error in the request', $response);

Exceptions can be extended.

EXCEPTIONS

set_exception_handler('general_exception_handler'); 

/**
 * Generic exception handler for uncaught exceptions.
 */ 
function general_exception_handler($e) { 
  print 'The website is experiencing some difficulties';
  drupal_mail(
    'module', 
    'key', 
    'admin@company.com, 
    language_default()
  );
} 
 
throw new Exception('This is a test exception.'); 

Unhandled exceptions can also be captured in PHP.

Sources

http://www.codeproject.com/Articles/858726/Problem-Solving-for-Software-Engineers

http://rosstuck.com/how-i-use-traits/

https://en.wikipedia.org/wiki/Code_smell#Common_code_smells

https://sourcemaking.com/refactoring/smells

https://blog.codinghorror.com/code-smells/

Questions

Gorka Guridi

Drupal Architect, Cameron & Wilding Ltd.

Coding concepts

By Gorka Guridi

Coding concepts

General coding concepts that everybody should be aware of and using in daily basis.

  • 564