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