Simple useful guide of coding concepts
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
- Martin Golding
In order to be able to solve a problem, we have to first understand it.
Understanding the data: what input we receive and what information we have available.
Question all the assumptions, constraints and conditions specified for the software.
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!)
An abstract class can not be instantiated. It's used just to declare properties and methods.
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;
}
}
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;
}
}
Code smells are symptoms of poor design or implementation choices.
- Martin Fowler.
Pay attention to the code smells.
Refactor, refactor, refactor!
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];
Feature envy: a class that uses methods of another class excessively.
Inappropriate intimacy: a class that has dependencies on implementation details of another class.
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);
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);
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();
}
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',
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;
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);
}
};
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
);
Cyclomatic complexity: too many branches or loops.
Compexity =
Arrows − Nodes + 2*Connected Components
Basically: Try to keep the conditions, etc. at minimum.
Leave the code better than you found it.
Brevity
Full feature
Performance
Robustness
Flexibility
Development time
Brevity of code.
Keep It Simple, Stupid
— K.I.S.S principle
<?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;
}
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?
A namespace is a way to encapsulate items.
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 {}
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();
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();
An interface specifies which methods a class must implement, without having to define how these methods are handled.
Interfaces can be extended.
<?php
interface Product {
public function getPrice();
}
interface Digital extends Product {
public function getQRCode();
}
class Ticket implements Digital {
}
Classes can extend multiple interfaces.
<?php
interface Product {
public function getPrice();
}
interface Preview {
public function getGooglePreview();
}
class Book implements Product, Preview {
}
A class is an extensible template for creating objects, providing initial values for state and implementations of behavior.
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() {
...
}
}
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];
}
}
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,
]);
}
}
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);
}
}
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;
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);
Traits are a mechanism for code reuse in single inheritance languages such PHP.
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;
}
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();
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();
An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.
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.
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);
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.
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.
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.
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.
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.
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/
Gorka Guridi
Drupal Architect, Cameron & Wilding Ltd.