Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Optimizing for Developer Delight (JS Summit 2013)

Optimizing for Developer Delight (JS Summit 2013)

Earlier this year, I sat down with 25,000 lines of code written by a team of variously experienced developers in the crucible of a non-negotiable deadline.

I didn't have a list of features to add or a list of bugs to fix; my mandate, as more and more developers were trying and struggling to contribute to the project, was to focus on developer happiness.

From simple improvements like adding asserts and logging, to major changes that touched a scary-large portion of the repo, this talk will take a look at what we did to make a large codebase easier to understand, what we still need to do, and what you should start doing on your own project as soon as you get back to work.

Rebecca Murphey

November 20, 2013
Tweet

More Decks by Rebecca Murphey

Other Decks in Technology

Transcript

  1. @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.
  2. • 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.
  3. 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
  4. 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
  5. assert  :  function  (assertion,  message)  {    if  (!assertion)  {

           throw  new  Error('Assertion  failed:  '  +  message);    } } there are assertion libraries, but all we’re doing is this
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. ENV.trigger('showLoading'); LoadingOverlay.show(); maybe you implement things via a global, but

    hide that implementation! now we have a loading overlay class ...
  13. 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
  14. 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 ...
  15. 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
  16. 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
  17. "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
  18. 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
  19. a huge change i made when i showed up was

    to make components first-class citizens in our code
  20. _(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
  21. 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
  22. 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
  23. 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
  24. 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.
  25. 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.
  26. 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
  27. 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
  28. 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
  29. 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 ...
  30. document where to find things (and if you can’t say

    what a dir is for, it has too much stuff in it)
  31. 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
  32. 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.
  33. are we there yet? constant vigilance -- code reviews, code

    planning, constantly asking what is harder than it should be