On Github Crell / slides-aphorisms-api
Presented by Larry Garfield
Code intended for other code
Clever and pithy sayings about good code.
— Pablo Picasso
— 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
// 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
— 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; } }
— 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
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
A UI is not an API
— 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
— 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.
— C. A. R. Hoare
— C. A. R. Hoare
There is no problem that cannot be solved by addinganother 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...?
Feature no software needs
You are not a special and unique snowflake
— 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.