Coding for Scale and Sanity Writing code you won’t regret later
Jim Keller Partner, Technology Director JimKellerES jimk@easternstandard.com http://easternstandard.com
Lamplighter 2001-2009
The natural state of code is that it is constantly in flux
Code is split into core and crust
A misconception? Code that was written well Code that was written quickly
A note about Preferences ( or: please don’t yell at me on Twitter )
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; } }
Nomenclature beware of function definitions like: check_access( $role_id ) get_token() Not only are they (probably) too ambiguous, but they’re titled backwards. access_check( $role_id ) token_get() or token_generate()
<?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(); } ?>
<?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(); } ?>
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; }
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.
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 }
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); }
Arguing about arguments 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: public function access_check ( $uid = null, $entity_id, $user_obj = null, role_id =0, $force_override = false, $check_cookie = true );
Arguing about arguments All hope is lost when you see a method called like this: $this->access_check ( null, $id, $user, null, true, false );
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 } }
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 }
I even have strong feelings about if statements If you find yourself writing long chains of if statements, considering writing a class factory instead. if ( $app_type == 'xbox' ) { $image_width = 100; $image_height = 120; } else if ( $app_type == 'android' ) { $image_width = 60; $image_height = 80; }
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; }
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;
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....
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; } }
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....
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();
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.
Happy Coding. @JimKellerES jimk@easternstandard.com http://easternstandard.com

Coding for Scale and Sanity

  • 1.
    Coding for Scaleand Sanity Writing code you won’t regret later
  • 2.
    Jim Keller Partner,Technology Director JimKellerES jimk@easternstandard.com http://easternstandard.com
  • 4.
  • 8.
    The natural stateof code is that it is constantly in flux
  • 9.
    Code is splitinto core and crust
  • 10.
    A misconception? Codethat was written well Code that was written quickly
  • 11.
    A note about Preferences ( or: please don’t yell at me on Twitter )
  • 12.
    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; } }
  • 13.
    Nomenclature beware offunction definitions like: check_access( $role_id ) get_token() Not only are they (probably) too ambiguous, but they’re titled backwards. access_check( $role_id ) token_get() or token_generate()
  • 14.
    <?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(); } ?>
  • 15.
    <?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(); } ?>
  • 16.
    Regarding Exceptions… Useexceptions 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; }
  • 17.
    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.
  • 18.
    Check your argumentsbefore 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 }
  • 19.
    Don’t be afraidof 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); }
  • 20.
    Arguing about arguments 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: public function access_check ( $uid = null, $entity_id, $user_obj = null, role_id =0, $force_override = false, $check_cookie = true );
  • 21.
    Arguing about arguments All hope is lost when you see a method called like this: $this->access_check ( null, $id, $user, null, true, false );
  • 22.
    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 } }
  • 23.
    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 }
  • 24.
    I even havestrong feelings about if statements If you find yourself writing long chains of if statements, considering writing a class factory instead. if ( $app_type == 'xbox' ) { $image_width = 100; $image_height = 120; } else if ( $app_type == 'android' ) { $image_width = 60; $image_height = 80; }
  • 25.
    A more scalableapproach 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; }
  • 26.
    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;
  • 27.
    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....
  • 28.
    Conditional objects classShippingConditional_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; } }
  • 29.
    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....
  • 30.
    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();
  • 31.
    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.
  • 32.
    Happy Coding. @JimKellerES jimk@easternstandard.com http://easternstandard.com