Do as I say, not as I do

Lessons learned from a year of trial by fire

Forrest L Norvell (tw / gh: othiym23), New Relic

http://is.gd/nodepdx2013

Who / What / Why

Spent the last year writing a language agent for New Relic in Node.js

 


New Relic's job is not to tell you how to write your code. Our job is to give you the tools you need to figure out how to improve your code yourself.

 

This talk is a mixture of practical advice and naked self-interest: if everyone followed these guidelines, my job would be much easier.

Writing A New Relic Agent Is Easy

  • must not crash
  • must be easy to use
  • must be dead simple to deploy
  • must be fast
  • must be small
  • must work like other New Relic Agents
  • must run synchronously
  • must be deterministic
  • should support troubleshooting
  • shouldn't confuse concerns
  • should alter semantics as little as possible
  • should alter operational environment as little as possible
  • should depend on as little outside core as possible
  • should care very little about how user code is organized or written

...wh- what?

THE GOAL:

Write tools for other developers.

THE PROBLEM:

JavaScript is not a pure language.

  • It is possible to use fundamental functional programming techniques in JavaScript.
  • It is possible to use inheritance and simulate classes in JavaScript.
  • This doesn't mean either is a good idea.

OO vs FP, Mutation vs Closures: FIGHT!

  • OO is based on encapsulation of state via mutation.
  • FP is based on the flow of data without mutation.
  • Closures can prevent JIT optimization.
  • Functional techniques that depend on purity don't get along with mutation.

THE PROBLEM, cont'd:

  • JavaScript, being dynamic, has no type system safety net.
  • Cleverness impairs collaboration.
  • In JavaScript, clarity and effectiveness are always in tension.

THE PROBLEM: COMPLEXITY IS YOUR ENEMY

Writing tooling is a special challenge

  • Absolutely can't break anything.
  • Almost as importantly, can't change anything.
  • Needs to be as simple as possible without compromising performance.
  • Lots of great tools and modules that app developers can use aren't available to developers writing instrumentation.

Prerequisites for Working with Node core

  • Learn how V8 works: mrale.ph and wingolog.org are your guides.
  • Learn to use node debug or node-inspector.
  • Spend some time with src/node.js, src/node.cc, and lib/*.
  • Don't use Node's source as a model. Tuned for performance, not for simplicity.

Solutions, workarounds, gross hacks

Tools

  • 'use strict';
  • jshint
  • node debug
  • Testing and benchmarking framework included with Node's source.

Use Node's core abstractions

  • EventEmitters
  • streams -- in.pipe(out) if nothing else
  • domains

use ES5

  • forEach, map, and reduce are built into Array.
  • Object.keys() is a simple way to enumerate object properties.
  • Function.bind() is a cleaner way of capturing this

Monkeypatching


var http = require('http');
var _request = http.request;
http.request = function () {
  console.log("requestin'");
  return _request.apply(this, arguments);
};
        

Monkeypatch safely

Fail via early return rather than throwing:


function wrap(nodule, name, wrapper) {
  if (!nodule || !nodule[name] || !wrapper) return;
  // other stuff
}
        

Monkeypatch safely

Log everything:


var logger = require('logger');
function wrap(nodule, name, wrapper) {
  logger.debug("Attempting to wrap %s.%s", nodule, name);
  if (!nodule || !nodule[name]) {
    logger.warn("no function to wrap provided");
    return;
  }
  if (!wrapper) {
    logger.warn("no wrapper function provided");
    return;
  }
  // other stuff
}
        

Monkeypatch safely

Leave visible traces:


function wrap(nodule, name, wrapper) {
  // setup
  var wrapped = wrapper(nodule[name]);
  wrapped.__WRAPPED = "yo dawg";

  nodule[name] = wrapped;
}
        

Monkeypatch safely

Wrap and unwrap idempotently:


function wrap(nodule, name, wrapper) {
  // other setup
  if (nodule[name].__WRAPPED) return;
  var original = nodule[name];

  var wrapped = wrapper(original);
  wrapped.__WRAPPED = "yo dawg";
  wrapped.__UNWRAP = function () {
    nodule[name] = original;
  };

  nodule[name] = wrapped;
}

function unwrap(method) {
  if (method.__UNWRAP) method.__UNWRAP();
}
        

Monkeypatch safely

Always return the results of wrapped calls:


function wrapper(original) {
  return function () {
    console.log("yo dawg: before");

    var returned = original.apply(this, arguments); // XXX

    console.log("yo dawg: the dawggening");

    return returned;
  };
}
        

Keep it simple: Don't try to metaprogram in JavaScript

  • Magic is bad.
  • Magic is super fun.
  • Metaprogramming is magic.
  • Therefore, metaprogramming is super fun.
  • Therefore, metaprogramming is bad.

How not to metaprogram in JavaScript

  • Leave req and res alone: prefer composition to extension.
  • Object.defineProperty() and friends can be confusing and slow.
  • Don't control flow via function arity.

Keep it simple: Use ONE strategy for control flow

raw events / streams, async, a promises library, TameJS, streamline, fibers, monads, reducers, Iced CoffeeScript, something you wrote yourself...

...just pick one per project and stick with it.

also, can we please stop arguing about this now?

your friends, domains

A whole other topic: http://is.gd/domainion.

  • allow you to layer error handling
  • domain.bind() and domain.intercept() allow you to pull all error handling into domains
  • Domains can be used for problems aside from error-handling...
  • ...but that's not what they're good at.

keep it simple: handle errors one way

  • when in doubt, use domains
  • try clauses are computed gotos
  • uncaughtException is unsafe

I hate to be a jerk, but...

  • Develop using whatever the hell you want, but publish JavaScript
  • ...better yet, just write JavaScript.

In conclusion

Keep it simple, keep it safe.

 

Gandalf says keep it safe

 

http://is.gd/domainion

http://twitter.com/othiym23 / http://github.com/othiym23

http://npmjs.org/package/newrelic

http://newrelic.com/ (we're hiring)

http://is.gd/nodepdx2013