Presented by Larry Garfield
Code intended for other code
A concise statement containing a subjective truth or observation cleverly and pithily written.
Clever and pithy sayings about good code.
Learn the rules like a pro
so you can break them like an artist.
— Pablo Picasso
If there are multiple universes,
then there are infinite universes
— The Gods Themselves
If there are multiple possible implementations,
there are infinite possible implementations
Machine names > Constants
const STATUS_PUBLISHED = 1;
const STATUS_DRAFT = 0;
function load_articles_by_status($status) {
// Find articles $ids for $status
return article_load_multiple($ids);
}
Oops
const MYMODULE_STATUS_PENDING = 3;
const YOURMODULE_STATUS_IN_REVIEW = 3;
function load_articles_by_status($status = '') {
// Find articles $ids for $status
return article_load_multiple($ids);
}
Boolean != either/or
Booleans don't have 2 values
Booleans are TRUE
or not
Access is a boolean
Access control has N-possibilities
One is a special case of many.
node_load_multiple()
// Fetch one node.
function node_load($nid) {
$record = db_query("SELECT * FROM node WHERE nid=:nid", [':nid' => $nid])
->fetchObject();
$record = load_extra_stuff($record);
return $record;
}
// Fetch multiple nodes.
function node_load_multiple(array $nids) {
$records = db_query("SELECT * from node WHERE nid IN (:nids)", [':nids' => $nids])
->fetchAll();
foreach ($records as $record) {
$loaded[] = load_extra_stuff($record);
}
return $loaded;
}
(aka, melt your database)
// Fetch one node.
function node_load($nid) {
$nodes = node_load_multiple(array($nid));
return reset($nodes);
}
// Fetch multiple nodes.
function node_load_multiple(array $nids) {
$records = db_query("SELECT * from node WHERE nid IN (:nids)", [':nids' => $nids])
->fetchAll();
$records = load_extra_stuff($records);
return $records;
}
N is the only number
Fail Fast, Fail Cheap, Be Lazy.
— Rasmus Lerdorf
Make the code debug for you
Don't plan for everything that could happen
Plan for how it will break
function theme($hook) {
// ...
if (!isset($hooks[$hook])) {
return;
}
// ...
return $output;
}
function theme($hook, $variables = array()) {
// ...
if (!isset($hooks[$hook])) {
if (!isset($candidate)) {
watchdog('Theme key "{$hook}" not found.');
}
return '';
}
// ...
return $output;
}
General failure reading Drive C:
PHP Warning: Invalid argument supplied for foreach() in /includes/form.inc on line 2975
Code failure is like voting in Chicago
Constrain inputs
Fail usefully
class InsertQuery extends Query {
public function fields(array $fields, array $values = []) {
// ...
}
public function preExecute() {
if (array_intersect($iFields, $defaultFields)) {
throw new FieldsOverlapException('...');
}
// ...
return TRUE;
}
}
A good programmer is someone who always looks both ways before crossing a one-way street.
— Doug Linder
If you're not developing under
E_ALL|E_STRICT,
you're doing it wrong.
Fail fast, fail cheap, fail usefully
You can't teach what you don't know.
You don't know what you can't teach.
You don't understand what you can't document.
I can't understand what you don't document.
abstract class FileTransferFTP extends FileTransfer {
/**
* Return an object which can implement the FTP protocol.
*
* @param string $jail
* @param array $settings
* @return FileTransferFTP
* The appropriate FileTransferFTP subclass based on available
* options. If the FTP PHP extension is available, use it.
*/
static function factory($jail, $settings) { }
}
/**
* Getter callback to return date values as datestamp in UTC from the field.
*/
function date_entity_metadata_field_getter($object, array $options,
$name, $obj_type, &$context) { }
No, seriously, WTF?
No exceptions (Well, document those, too)
A picture is worth 1000 words
A code sample is worth 1000 comments
You wish you were as cool as Gearman's docs
Clearly written code with well-named methods is self-documenting.
protected function normalizeCharset($value) {
switch (mb_detect_encoding($value)) {
case 'ASCII':
break;
case 'UTF-8':
$value = htmlentities($value, ENT_NOQUOTES);
$value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
break;
case FALSE:
$value = iconv('Windows-1252', 'UTF-8//TRANSLIT', $value);
$this->log->warn("Detected Windows-1252 character encoding.");
break;
default:
$this->log->warn("Unrecognized character encoding.");
}
return $value;
}
WTF is up with UTF-8? And why is FALSE Windows-1252?
protected function normalizeCharset($value) {
// mb_detect_encoding() in most situations supports only two character sets,
// ASCII and UTF-8. However, it is sadly not unusual for incoming data
// to be in Windows-1252, aka MS Word. We therefore guess that a
// false/not-found character set is Windows-1251, and try to convert that
// to UTF-8.
// Note: The odds of this breaking on some other character encoding besides
// those three is rather high.
// ...
}
switch (mb_detect_encoding($value)) {
// I have absolutely no idea why UTF-8 strings need to be converted
// from UTF-8 to UTF-8, but if this code is removed many strings end up
// failing with invalid multi-byte encoding.
case 'UTF-8':
// Convert any characters we possibly can to their HTML encoded entities.
// If we don't specify a character encoding then this should do at least
// a passingly decent job of detecting it, or at least doesn't care as much
// as other APIs do.
$value = htmlentities($value, ENT_NOQUOTES);
// Convert those HTML entities back into real characters, but this time
// insist on UTF-8. This will at worst convert UTF-8 characters back
// to UTF-8 and at best convert ISO-8859-1 characters to HTML entities and
// from HTML entities to UTF-8 characters.
$value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
break;
// ...
// A False return from mb_detect_encoding() means that it couldn't
// figure out what the encoding is. In a standard configuration mb_* only
// knows about ASCCI and UTF-8, so that's not especially useful. We will
// make an assumption that anything else is Windows-1252, aka MS Word legacy
// garbage. If correct, this will convert $value from Windows-1252 to
// UTF-8, transliterating where necessary rather than just failing.
case FALSE:
$value = iconv('Windows-1252', 'UTF-8//TRANSLIT', $value);
$this->log->warn("Detected Windows-1252 character encoding.");
break;
}
Docs or it didn't happen
A UI is not an API
A User Interface is not an Application Programming Interface
A User is not a Program
A UI is a client of your API
What good is a website without a UI?
Who said you're building a web site?
(Tests or it didn't happen)
A website is not an API
A website uses an API
An API does not need a web site
You're not done until you have
three implementations
A UI is not an API
You know that saying about standing on the shoulders of giants?
Drupal is standing on a huge pile of midgets.
— Jeff Eaton
Don't add to API bloat
Go with the flow... it makes documentation easier
The best API is the API you didn't have to write
Use uncertainty as a driver.
— Kevlin Henney, 97 Things Every Software Architect Should Know
Don't make decisions if you don't have to.
Make changing your mind cheap.
(Your client will change his mind.)
(Twice.)
You can only change what is encapsulated.
Don't decide for me!
interface LogInterface {
public method log($message, $severity);
}
namespace Psr\Log;
interface LoggerInterface {
public function log($level, $message, array $context = array());
public function emergency($message, array $context = array());
public function alert($message, array $context = array());
public function critical($message, array $context = array());
public function error($message, array $context = array());
public function warning($message, array $context = array());
public function notice($message, array $context = array());
public function info($message, array $context = array());
public function debug($message, array $context = array());
}
Don't decide for me!
interface DrupalCacheInterface {
function get($cid);
function getMultiple(&$cids);
function set($cid, $data, $expire = CACHE_PERMANENT);
function clear($cid = NULL, $wildcard = FALSE);
function isEmpty();
}
Stay tuned... ;-)
Encapsulation avoids decision making
Avoiding decision making requires loose coupling
namespace Psr\Log;
interface LoggerInterface {
public function log($level, $message, array $context = array());
public function emergency($message, array $context = array());
public function alert($message, array $context = array());
public function critical($message, array $context = array());
public function error($message, array $context = array());
public function warning($message, array $context = array());
public function notice($message, array $context = array());
public function info($message, array $context = array());
public function debug($message, array $context = array());
}
Not just for testing...
Also for day-before-launch-changes
Avoid making decisions
Delegation adds indirection
Indirection requires abstraction
Abstraction solves hides complexity
Abstraction is not free
What the heck is going on under the hood?
... 14 method calls later I still don't know.
There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.
— C. A. R. Hoare
The unavoidable price of reliability is simplicity.
— C. A. R. Hoare
There is no problem that cannot be solved by adding
another layer of abstraction...
except abstraction
DrupalYour software has idiosyncrasies, but it's not special.
No one understands Drupalisms,
except Drupal developers
(And not even all of them)
No one understands Symfonyisms,
except Symfony developers
(And not even all of them)
No one understands PHPisms,
except PHP developers
(And not even all of them)
No one understands your system,
except you
(And sometimes not even then)
No matter how cool you are,
John Resig knows more Javascript than you do.
Odds of you doing something original...?
I can go home and rest and not design from scratch
Feature no software needs
You are not a special and unique snowflake
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
— Martin Golding
You will always know where you live.
In six months, some one will need to do something you never thought of with your code and will not be able to edit it.
That will be you.
Plan accordingly.
https://prague2013.drupal.org/session/aphorisms-api-design