The Sweet Spot

Johnny Ray Austin

http://johnnyray.me

NodeDC 3/12/15

@recursivefunk

Finding the Right Level of Abstraction for Your Codebase

About Me

Director of Technology

Contributor

Champion

Fanboy!

#respect

*Obligatory Audience Participation*

Abstraction

The extent to which one hides the implementation of one's software

@recursivefunk

Low Level

High Level

  • Easy to use/understand
  • Fosters focus
  • Brevity
  • Potentially "brittle"
  • Flexible
  • Performant
  • Difficult to use
  • Steep learning curve

@recursivefunk

Cake

You're the baker, I'm the customer

@recursivefunk

Remember

Low-Level != Bad

High-Level != Good

@recursivefunk

We Need Low-Level Software

@recursivefunk

High-Level Not Always Good

Disclaimer

Don't use this code

@recursivefunk

100110110001110111011
 MOV AL, 61h 
int JSStream::DoWrite(WriteWrap* w,
                      uv_buf_t* bufs,
                      size_t count,
                      uv_stream_t* send_handle) {
  CHECK_EQ(send_handle, nullptr);

  HandleScope scope(env()->isolate());

  Local<Array> bufs_arr = Array::New(env()->isolate(), count);
  for (size_t i = 0; i < count; i++)
    bufs_arr->Set(i, Buffer::New(env(), bufs[0].base, bufs[0].len));

  Local<Value> argv[] = {
    w->object(),
    bufs_arr
  };

  Local<Value> res =
      MakeCallback(env()->onwrite_string(), ARRAY_SIZE(argv), argv);

  return res->Int32Value();
}
    fs.createWriteStream( './foo.json' )
      .write( JSON.stringify({ oh: 'yea' }) );

Why Should I Care?

  • Dev mindshare
  • Profit

@recursivefunk

Considerations

  • Goals
    • Product?
    • OSS?
  • Audience
    • Co-workers
    • Application developers
    • API developers
    • You?

@recursivefunk

The Cost of Abstraction

@recursivefunk

Mo' Functions, Mo' Problems

EventEmitter.prototype.addListener = function addListener(type, listener) {
  var m;
  var events;
  var existing;

  if (typeof listener !== 'function')
    throw new TypeError('listener must be a function');

  events = this._events;
  if (!events) {
    events = this._events = {};
    this._eventsCount = 0;
  } else {
    // To avoid recursion in the case that type === "newListener"! Before
    // adding it to the listeners, first emit "newListener".
    if (events.newListener) {
      this.emit('newListener', type,
                listener.listener ? listener.listener : listener);

      // Re-assign `events` because a newListener handler could have caused the
      // this._events to be assigned to a new object
      events = this._events;
    }
    existing = events[type];
  }

  if (!existing) {
    // Optimize the case of one listener. Don't need the extra array object.
    existing = events[type] = listener;
    ++this._eventsCount;
  } else {
    if (typeof existing === 'function') {
      // Adding the second element, need to change to array.
      existing = events[type] = [existing, listener];
    } else {
      // If we've already got an array, just append.
      existing.push(listener);
    }

    // Check for listener leak
    if (!existing.warned) {
      m = $getMaxListeners(this);
      if (m && m > 0 && existing.length > m) {
        existing.warned = true;
        console.error('(node) warning: possible EventEmitter memory ' +
                      'leak detected. %d %s listeners added. ' +
                      'Use emitter.setMaxListeners() to increase limit.',
                      existing.length, type);
        console.trace();
      }
    }
  }

  return this;
};

Someone Wrote This


    writableStream
    
        .on('data', function(data){
            log.debug(data);
        })

        .on('err', function(err){
            log.err( 'oh no :(' );
        });

So you can write this

@recursivefunk

Effective Abstraction Strategies

For An Application Dev Audience

@recursivefunk

The Typical App Dev

  • Probably has a deadline
  • Iterates quickly
  • Has stakeholders
  • Practicality is key
    • No time to ponder your code's idiocentric properties

@recursivefunk

AbstractSingletonProxyFactoryBean

@recursivefunk

<3 Design Patterns

  • Design patterns are great
  • Use them wisely
  • Too many layers lead to the web of death

@recursivefunk

KISS?

  • Keep It Simple.....Sucker?
  • Simple may not be an option
  • Elegance is key

@recursivefunk

Don't Over Do It

@recursivefunk

AbstractSingletonProxyFactoryBean

Naming Is Important

@recursivefunk

Method Chaining

  • this or exports
  • Feels natural
  • Explicit API feedback
  • Enforce order of operations
    
    var thing = require( './thing' );
    
    thing
    
      .configure( {} )
    
      .on( 'connect', getItPoppin )
    
      .on( 'error', failWithDignity )
    
      .connect();

@recursivefunk

Thing Implementation


    'use strict'
    
    var EventEmitter = require( 'events' ).EventEmitter;
    var _nativeThing = require( 'native-thing' );
    var _ee          = new EventEmitter();
    var _connected   = false;
    
    exports.configure = function( opts ) {
      _opts = opts || _opts;
      return exports; // <== BOOM!
    };
    
    exports.on = function() {
      _ee.on.apply( _ee, arguments );
      return exports; // <== BAM!!
    };
    
    exports.connect = function( opts ) {
      _nativeThing
        .connect( opts.host, opts.port, function(err, remoteThing) {
          if ( !err ) {
            _connected = true;
            _ee.emit( 'connect' );
          } else {
            _ee.emit( 'error', err );
          }
        });
      return exports; // <== POW!!!
    };

TIF (Testing is Fundamental)


    'use strict'
    
    var thing = require( './thing' );

    describe('the thing', function(){
      
      beforeEach(function(){
        // reset config, disconnect etc
        thing.cleanup();
      });

      it('connects', function(done){
        thing
          .on('connect', function(){
            assertEqual( true, thing.isConnected );
            done();
          })
          .configure( {} )
          .connect();
      });

      it('fails gracefully', function(done){
        thing
          .on('error', function(){
            assertEqual( false, thing.isConnected );
            done();
          })
          .configure( { host: 'https://nowhere.io' } )
          .connect();
      });
      

    });
    

Method Names & Behavior

  • State inquiries ask questions
    • isConnected? hasProp?
  • State mutation
    • Returns the thing itself
      • foo.makeFunny().setFunky()
  • Argument based behaviors....

@recursivefunk

Argument Based Behavior

(Be Careful)


    var aVal = thing.val( 'some-val' ).val();
    log.debug( aVal ); // 'some-val'

    
    // meanwhile, in thing.js
    
    exports.val = function( newVal ) {
      if ( typeof newVal === 'string' ) {
        _privateVal = newVal;
        return exports;
      }
      return _privateVal;
    };

@recursivefunk

Multiple Configuration Paths

  • Files
  • Environment
  • Direct
  • Constructor
  • Setters

    // loads config file
    thing.configure( './thing-cofig.json' );
    // explicitly set options
    thing.configure( {} );
    // no args grabs environment variables
    thing.configure()
    // setters
    thing.set( 'host', 'foo.io' );

@recursivefunk

Versioning


    var thing = ThingFactory.construct( opts );

    // meanwhile in ThingFactory.js

    var ThingV1 = require( './v1/thing' );
    var ThingV2 = require( './v2/thing' );

    exports.construct = function( opts ) {
        var ThingClass;

        // don't assume people want to update
        opts.version = opts.version || 'v1';

        if ( opts.version === 'v2' ) {
            ThingClass = ThingV2;
        } else {
            ThingClass = ThingV1;
            log.warn( 'v1 is deprecated - update soon!' );
        }

        return new ThingClass( opts );
    };

Strive for Balance

  • Maintain extensibility 
  • Guard against invasive changes

@recursivefunk

Effective Abstraction Strategies

For An API Dev Audience

@recursivefunk

???

@recursivefunk

A Few Things

  • Give me the power to choose
    • I don't want everything
  • Engage with app devs
    • How will they use your code?
    • Important stuff first
      • Eas(y|ier)
  • Follow Specs

That's It!

http://jray.io/sweet-spot-abstraction

The Sweet Spot

By Johnny Ray Austin

The Sweet Spot

Finding the Right Level of Abstraction for Your Codebase

  • 916
Loading comments...

More from Johnny Ray Austin