32
Coding for Scale and Sanity Writing code you won’t regret later

Coding for Scale and Sanity

Embed Size (px)

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.

Citation preview

Page 1: Coding for Scale and Sanity

Coding for Scale and SanityWriting code you won’t regret later

Page 2: Coding for Scale and Sanity

Jim KellerPartner, Technology Director

JimKellerES

[email protected]

http://easternstandard.com

Page 3: Coding for Scale and Sanity
Page 4: Coding for Scale and Sanity

Lamplighter

2001-2009

Page 5: Coding for Scale and Sanity
Page 6: Coding for Scale and Sanity
Page 7: Coding for Scale and Sanity
Page 8: Coding for Scale and Sanity

The natural state of codeis that it is constantly in flux

Page 9: Coding for Scale and Sanity

Code is split into core and crust

Page 10: Coding for Scale and Sanity

A misconception?

Code that was written

well

Code that was written

quickly

Page 11: Coding for Scale and Sanity

A note aboutPreferences

( or: please don’tyell at me onTwitter )

Page 12: Coding for Scale and Sanity

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

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

<?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

<?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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

Page 32: Coding for Scale and Sanity

Happy Coding.

@JimKellerES

[email protected]

http://easternstandard.com