Slide 1

Slide 1 text

Optimizing for Developer Delight Rebecca Murphey 2013 JavaScript Summit

Slide 2

Slide 2 text

@rmurphey • rmurphey.com bazaarvoice.com these days, I’m a developer at Bazaarvoice, working on a 3pjs app that lets sites add ratings and reviews to their sites by just adding a script tag to their page. think disqus but for ratings & reviews instead of comments; more than 100M pageviews last month js app is >25kloc. part of my job has been to clean up the code and make it easier to work with & more predictable; wanted to talk about three things we’ve done.

Slide 3

Slide 3 text

• a clear path for getting up to speed • points of entry for junior & senior devs • as few surprises as possible: it just works • isolated complexity • easy development and debugging • nothing more difficult than it “should” be “developer delight” decent-sized codebases with lots of devs are interesting creatures. if you don’t have someone owning the big picture, then even really smart people can make decisions that solve their immediate problem, but create other problems down the road. when i joined BV, my first take was to take stock of the bigger picture. i wasn’t focused on delivering features; i was focused on making it easier for other developers to deliver features. obviously there are lots of things that make developers happy, but in the context of a codebase, these are the things i’ve zeroed in on. some of the things we’ve done to achieve these goals have been deceptively simple; others are a bit less obvious.

Slide 4

Slide 4 text

assert all the things assertions are one of the simplest things you can do, so simple you might never think to do it i came to appreciate it while working w/ember

Slide 5

Slide 5 text

how often have you seen an error like this?

Slide 6

Slide 6 text

BVReporter.assert(    this.componentId,      'View  '  +  this.name  +  '  must  have  componentId' ); assertions let us make sure basic expectations are met before code executes this is *especially* useful for catching errors long before async code has a chance to fail

Slide 7

Slide 7 text

assert  :  function  (assertion,  message)  {    if  (!assertion)  {        throw  new  Error('Assertion  failed:  '  +  message);    } } there are assertion libraries, but all we’re doing is this

Slide 8

Slide 8 text

now, we get useful error messages as soon as something goes wrong

Slide 9

Slide 9 text

lifecycle logging log the lifecycle another deceptively simple thing: tell people what’s going on

Slide 10

Slide 10 text

when i arrived, this is what you saw in your console when you loaded the app in dev mode if you wanted to understand what was going on, you had to add your own logging especially hard to understand what’s going on in an async system

Slide 11

Slide 11 text

BVReporter.group(BVReporter.INFO,  'Component  Initialization'); _(componentData).each(function  (cd,  componentName)  {    var  c  =  new  BComponent(cd);    this.components[componentName]  =  c; },  this); BVReporter.groupEnd(BVReporter.INFO,  'Component  Initialization'); we added lifecycle logging, including groups

Slide 12

Slide 12 text

now, at a noisy log level, you can see the steps that occur to generate a given view, for example this is great for newcomers -- they can learn how the system works by watching the console also great for debugging, of course -- you can see exactly how far the process got before it failed

Slide 13

Slide 13 text

of course, if you still want this, you can change the log level

Slide 14

Slide 14 text

eliminate temptation this one’s a bit more complex most large codebases are full of temptations to make bad design decisions often this temptation comes in the form of a global, which is an invitation to do things that are likely to affect other people’s code

Slide 15

Slide 15 text

define([    'ENV',    'underscore',    'bv/api/fetch' ],  function  (ENV,  _,  api)  { for us, it’s ENV. it used to be a dependency for just about every file. it turns out that using a global gives other devs permission to use a global next thing you know, the global becomes the solution for everything

Slide 16

Slide 16 text

ENV.trigger('showLoading'); it was a global event bus

Slide 17

Slide 17 text

ENV.trigger(    'track:error',      new  Error('Missing  parent  in  Collection  :  '  +  this.name) ); it was an error aggregator

Slide 18

Slide 18 text

ENV.trigger('track:conversion',  data,  conversion); it was the pass-through for analytics

Slide 19

Slide 19 text

if  (      !ENV.get('userTokenDfd')  ||      ENV.get('userTokenDfd').state()  ===  'resolved' )  {    ENV.set({  userTokenDfd  :  $.Deferred()  }); } it was the keeper of anything we couldn’t figure out a better way to pass around

Slide 20

Slide 20 text

this.on('pageto',  this.fetchPage,  ENV); it was even a magical last argument that would change method behavior

Slide 21

Slide 21 text

ENV.trigger('showLoading'); LoadingOverlay.show(); maybe you implement things via a global, but hide that implementation! now we have a loading overlay class ...

Slide 22

Slide 22 text

ENV.trigger(    'track:error',      new  Error('Missing  parent  in  Collection  :  '  +  this.name) ); BVTracker.error(  new  Error(        'Missing  parent  in  Collection  :  '  +  this.name )  ); ENV.trigger('track:conversion',  data,  conversion); BVTracker.conversion(data,  conversion); we created a BVTracker class for tracking things

Slide 23

Slide 23 text

this.on('pageto',  this.fetchPage,  ENV); this.subscribe('pageto',  this.fetchPage); and the magical ENV arg is gone globals are inviting, they’re confusing, they’re easy to overload, and global events are just asking to be abused. globals leave it up to users to decide how to use them; different users will make different decisions, and then all users will have to live with them. make those decisions for your users as much as possible, and eliminate potential confusion. this leads nicely to the next topic ...

Slide 24

Slide 24 text

code for every concept this is a bit more abstract than everything I’ve talked about so far, but, basically: if there’s a concept you talk about when you’re explaining your system, there should probably be a piece of code -- an object, or a class -- that represents that concept

Slide 25

Slide 25 text

ENV.trigger(    'track:error',      new  Error('Missing  parent  in  Collection  :  '  +  this.name) ); BVTracker.error(  new  Error(    'Missing  parent  in  Collection  :  '  +  this.name )  ); we saw this just a second ago, where we created a BVTracker object for tracking things

Slide 26

Slide 26 text

"reviewContentList"  :  {    "features"  :  {        "self"  :  ["headToHead",  "contentFilter",  "contentItemCo        "contentItem"  :  ["has:stars",  "has:secondaryRatings",  "        "pagination"  :  ["ugcCount"],        "secondaryContentList"  :  ["secondaryContentItemCollecti        "secondaryContentItem"  :  ["avatar",  "feedback"],        "contentFilter"  :  ["has:filterButton"]    },    //  ... }, another example: our application is centered around configurable components; the configuration for a component might look like this, but it doesn’t really matter

Slide 27

Slide 27 text

var  components  =  ENV.get('components'); components.each(function  (component,  index)  {    var  view  =  init.initView(component,  index);    //  ... }); when i showed up, we just stored these raw component objects in ... ENV. shocking, i know. we didn’t ask components to do things -- we passed around raw component objects, and did things to them. if a new developer asked “what’s a component?” there was no code they could look at to learn the answer

Slide 28

Slide 28 text

a huge change i made when i showed up was to make components first-class citizens in our code

Slide 29

Slide 29 text

_(componentData).each(function  (config,  componentName)  {    var  c  =  new  Component(config);    this.components[componentName]  =  c;    var  view  =  c.initView(index);    //  ... },  this); there’s now a component class that receives the raw component object components can now do things, rather than having things done to them best of all, there’s a documented, tested Component class that devs can look at to understand everything they need to know about components

Slide 30

Slide 30 text

interfaces, not internals there’s a lot that goes on under the hood to make components work, which brings me to my next point: don’t make developers understand the implementation details of a system

Slide 31

Slide 31 text

as an example: deferreds and promises landed in jquery back in early 2011 like lots of new concepts, it’s easy to get carried away -- see also pubsub, custom events, etc

Slide 32

Slide 32 text

Backbone.Model.extend({    initialize  :  function  (config,  options)  {        this.set({            dataReadyDfd  :  $.Deferred(),            postDataReadyDfd  :  $.Deferred()        },  {  silent  :  true  });        //  ...    },    dataReadyDfd  :  function  ()  {        return  this.get('dataReadyDfd');    },    postDataReadyDfd  :  function  ()  {        return  this.get('postDataReadyDfd');    }    //  ... }); originally, our code passed deferreds around to anything that wanted them. if you wanted to know if a model’s data was ready, you asked for the data ready deferred

Slide 33

Slide 33 text

myModel.dataReadyDfd().done(function  (data)  {    doSomething(data);    doSomethingElse(); }); myModel.dataReadyDfd().resolve(someData); so if you had a reference to a model instance, you could get its data ready deferred -- you could even resolve it, because we were handing back deferreds, not promises! this was terrible -- basically we were handing out implementation details to any piece of code that wanted them we were also requiring developers to know implementation details in order to interact with a model.

Slide 34

Slide 34 text

myModel.dataReadyDfd().done(function  (data)  {    doSomething(data);    doSomethingElse(); }); myModel.onDataReady(function  (data)  {    doSomething(data);    doSomethingElse(); }); promises and deferreds are great! use them! but use them behind *an interface* our new interface still uses promises under the hood, but consumers of a model don’t interact with those promises directly -- we just use the promises to implement an interface. developers learn that interface -- not how it’s implemented under the hood.

Slide 35

Slide 35 text

automate everything no really, automate everything. no matter what xkcd says.

Slide 36

Slide 36 text

we have an internal admin tool for doing all sorts of things that would be hard or impossible to do manually for example, looking up a URL for previewing a client in our local environment, or deconstructing an API request the great thing is that once you have this tool, you’ll think of all sorts of things to add to it

Slide 37

Slide 37 text

we decided even that wasn’t easy enough, so we also have grunt tasks for doing lots of the same things from the command line

Slide 38

Slide 38 text

did your environment get into a borked state? there’s a command line tool for that. command line tools for scaffolding new features, too, and of course our tests are automated

Slide 39

Slide 39 text

document you already know you should be documenting everything, right? to be honest, i’m a little skeptical of that; there’s a lot to be said for focusing on readable, modular code but there are a few things that even the best code can’t tell you ...

Slide 40

Slide 40 text

document the rationale and thinking behind your codebase

Slide 41

Slide 41 text

document how to do things

Slide 42

Slide 42 text

document how to fix things

Slide 43

Slide 43 text

document where to find things (and if you can’t say what a dir is for, it has too much stuff in it)

Slide 44

Slide 44 text

document things you might wish you didn’t have to document

Slide 45

Slide 45 text

changelogs: treat your code like an open-source project

Slide 46

Slide 46 text

do it all in the repo (at least to start)

Slide 47

Slide 47 text

measure progress (and side effects) how do you know you’re delivering developer happiness? i actually have no idea hard to quantify the speed of feature delivery

Slide 48

Slide 48 text

i did add complexity reporting fairly early on, and i was able to see that some of the changes we were making were reducing measured complexity.

Slide 49

Slide 49 text

we’re also making sure that our changes aren’t hurting performance (they seem to be helping)

Slide 50

Slide 50 text

are we there yet? constant vigilance -- code reviews, code planning, constantly asking what is harder than it should be

Slide 51

Slide 51 text

@rmurphey • rmurphey.com bazaarvoice.com