coding for scale and sanity
DESCRIPTION
Presentation for Drupaldelphia 2014. Given by Jim Keller of EasternStandard (easternstandard.com). Description: No developer in history had enough time and enough up-front information to make perfectly scalable architecture decisions, get everything right the first time, and craft all of their code exquisitely right out of the gate. Coding is an organic process, and often one that's driven by changing requirements, dreadful deadlines, and unreliable third parties. It's a fact of our lives: you will inevitably end up writing code you're not proud of because you needed to get something done in a pinch. That said, the tradeoff between speed, flexibility, and quality doesn't have to be as drastic as you might think. In this session, I will share a few methodologies and tricks for writing quick, flexible code that doesn't lock you into technical debt and doesn't require you to sacrifice your dignity as a software developer. Also included are some general tips and techniques for writing scalable code that will help future-you not hate current-you for some of the decisions you've been making.TRANSCRIPT
![Page 1: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/1.jpg)
Coding for Scale and SanityWriting code you won’t regret later
![Page 3: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/3.jpg)
![Page 4: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/4.jpg)
Lamplighter
2001-2009
![Page 5: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/5.jpg)
![Page 6: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/6.jpg)
![Page 7: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/7.jpg)
![Page 8: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/8.jpg)
The natural state of codeis that it is constantly in flux
![Page 9: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/9.jpg)
Code is split into core and crust
![Page 10: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/10.jpg)
A misconception?
Code that was written
well
Code that was written
quickly
![Page 11: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/11.jpg)
A note aboutPreferences
( or: please don’tyell at me onTwitter )
![Page 12: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/12.jpg)
Objects. Use them, even within procedural code.
function _some_module_function() { try { require_once( dirname(__FILE__) . DIRECTORY_SEPARATOR . 'my_class.php' ); $obj = new MyClass(); $obj->some_function(); } catch( Exception $e ) { watchdog( __FUNCTION__, $e->getMessage(), array(), WATCHDOG_ALERT ); return false; } }
![Page 13: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/13.jpg)
beware of function definitions like:check_access( $role_id )get_token()
Not only are they (probably) too ambiguous, but they’re titled backwards.
Nomenclature
access_check( $role_id )token_get() or token_generate()
![Page 14: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/14.jpg)
<?php
class MyAuthenticationClass { public function access_check_by_role_id( $role_id ); public function access_check_by_uid( $uid );
public function token_generate(); public function token_get_existing(); public function token_save();
}
?>
![Page 15: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/15.jpg)
<?php
class MyAuthenticationClass {
public $access_is_admin = false; public $access_force_override = false;
public $token_serialize = false; public $token_check_cookie = true;
public function access_check_by_role_id( $role_id ); public function access_check_by_uid( $uid );
public function token_generate(); public function token_get_existing(); public function token_save(); }
?>
![Page 16: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/16.jpg)
Regarding Exceptions…
Use exceptions for error handling. Lots of them.
Throw ‘em, catch ‘em, handle them.
Don’t return “false” or “0” on error.
public function count_jawns() {
$count = some_other_func(); // zero might be a valid count... // so how will I know if this function // failed if some_other_function() returned false?
return $count; }
![Page 17: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/17.jpg)
Check your function & method arguments
Check your arguments to make sure you got what you think you’ve got.
If you’re expecting a uid, check to make sure it looks like a uid.
This can help against security compromises (e.g. SQL injection), but more likely it’s just going to help you troubleshoot faster.
![Page 18: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/18.jpg)
Check your arguments before you try to use them.
public function access_check_by_uid( $uid, $args = array() ) { if ( !is_numeric($uid) ) { throw new InvalidArgumentException( 'non-numeric UID found in ' . __METHOD__ ); }
// stuff
}
![Page 19: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/19.jpg)
Don’t be afraid of utility functions
public function access_check_by_uid( $uid, $args = array() ) {
if ( !self::Uid_is_sane($uid) ) { throw new InvalidArgumentException( 'non-numeric UID found in ' . __METHOD__ ); }
// stuff }
public static function Uid_is_sane( $uid ) {
return is_numeric($uid);
}
![Page 20: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/20.jpg)
Your methods/functions should take as few arguments as possible.
If you need many arguments, you either need to split into multiple functions, or pass an array of optional args.
Don’t do this:
Arguing about arguments
public function access_check ( $uid = null, $entity_id, $user_obj = null, role_id =0, $force_override = false, $check_cookie = true );
![Page 21: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/21.jpg)
All hope is lost when you see a method called like this:
Arguing about arguments
$this->access_check ( null, $id, $user, null, true, false );
![Page 22: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/22.jpg)
Arguing about arguments
public function access_check_by_uid( $uid, $entity_id, $args = array() ) {
if ( !empty($args['force_override']) ) { //do something here } }
public function access_check_by_role_id( $role_id, $entity_id, $args = array() ){
if ( !empty($args['force_override']) ) { //do something here } }
![Page 23: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/23.jpg)
Arguing about arguments
public function access_check_by_uid( $uid, $args = array() ) {
$this->_Access_check( $uid, $args, self::ACCESS_PARAM_UID );
}
public function access_check_by_role_id( $role_id, $args = array() ){
$this->_Access_check( $role_id, $args, self::ACCESS_PARAM_ROLE_ID );}
protected final function _Access_check( $id, $args, $type ) {
if ( $type == self::ACCESS_PARAM_ROLE ) { // do some role stuff here }
if ( $type == self::ACCESS_PARAM_UID ) { // do some uid-only stuff here }
// shared stuff here}
![Page 24: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/24.jpg)
I even have strong feelings about if statements
if ( $app_type == 'xbox' ) { $image_width = 100; $image_height = 120; } else if ( $app_type == 'android' ) { $image_width = 60; $image_height = 80; }
If you find yourself writing long chains of if statements, considering writing a class factory instead.
![Page 25: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/25.jpg)
A more scalable approach
interface ExternalAppOptionsManager() { public $image_height; public $image_width; }
class OptionsManager_android implements ExternalAppOptionsManager(){ public $image_height = 80; public $image_width = 60; }
class OptionsManager_xbox implements ExternalAppOptionsManager() { public $image_height = 120; public $image_width = 100; }
![Page 26: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/26.jpg)
A quick & dirty class factory
$options_class_name = 'OptionsManager_' . $app_type;
if ( !require_once($options_class_name . '.php') || !class_exists($options_class_name) ) { throw new Exception('Invalid class: ' . $options_class_name); }
$options_manager = new $options_class_name; $image_height = $options_manager->image_height; $image_width = $options_manager->image_width;
![Page 27: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/27.jpg)
Conditional objects
if ( $item_type == 't_shirt' ) { if ( $_SESSION['shipping_provider'] == 'USPS' ) { $base_price = 3.00; if ( $_SESSION['shipping_method'] == 'first_class' ) { include( 'usps_funcs.php' ); $real_rate = usps_get_rate( $_SESSION['shipping_method'], $item_weight ); $final_rate = $base_price + $real_rate; } } else if ( $_SESSION['shipping_provider'] == 'UPS' ) { $base_price = 3.00; if ( $_SESSION['shipping_method'] = '2_day' ) { include( 'ups_funcs.php' ); $real_rate = ups_get_rate( $_SESSION['shipping_method'], $item_weight ); } }
// and so forth....
![Page 28: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/28.jpg)
Conditional objects
class ShippingConditional_usps extends ShippingConditional use USPS_API_Class;
protected $_Shipping_method; protected $_Item;
public function pricing_calculate() { $base_price = $this->base_price_by_shipping_method ( $this->_Shipping_method );
$usps = new USPS_Api_Class; $usps->method_set( $this->shipping_method ); $real_price = $usps->rate_calculate( $item->weight );
return $real_price + $base_price; } }
![Page 29: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/29.jpg)
Conditional objects
if ( $item_type == 't_shirt' ) { if ( $_SESSION['shipping_provider'] == 'USPS' ) { $base_price = 3.00; if ( $_SESSION['shipping_method'] == 'first_class' ) { include( 'usps_funcs.php' ); $real_rate = usps_get_rate( $_SESSION['shipping_method'], $item_weight ); $final_rate = $base_price + $real_rate; } } else if ( $_SESSION['shipping_provider'] == 'UPS' ) { $base_price = 3.00; if ( $_SESSION['shipping_method'] = '2_day' ) { include( 'ups_funcs.php' ); $real_rate = ups_get_rate( $_SESSION['shipping_method'], $item_weight ); } }
// and so forth....
![Page 30: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/30.jpg)
A rewrite
$shipping_class_name = "ShippingConditional_{$item_type}";
if ( !class_exists($shipping_class_name) ) { throw new InvalidArgumentException( "Invalid shipping class name: {$shipping_class_name}" ); }
$shipping_obj = new $shipping_class_name;
$shipping_obj->shipping_method_set($_SESSION['chosen_shipping_method']); $shipping_obj->item_set ( $cart_items[$j] ); $shipping_price = $shipping_obj->pricing_calculate();
![Page 31: Coding for Scale and Sanity](https://reader033.vdocuments.us/reader033/viewer/2022061609/5589e836d8b42ac2378b45b3/html5/thumbnails/31.jpg)
In Closing
- Assume that your code will have to change. Plan accordingly.
- Learn to identify areas of code that are likely to get messy or change suddenly. Isolate these components in a way that it’s easy to work on them without having to refactor in many places.
- For complex logic, don’t just write the logic in your code like a long narrative story. Break it out into classes and methods that process individual bits of the overall process.