Every engineer loves experimenting with new technology. It’s a huge part of our job. Languages, frameworks, and libraries (both external and internal) are continually evolving. As an organization we need to keep up with these changes in order to quickly solve increasingly complex problems and to retain and attract talent. On the flip side, we also need to minimize risk and tech debt in our existing technology. So how can we balance our need to innovate with our need to have a stable, maintainable application?
In-App Innovation: A Cautionary Tale
Great ideas often start with cross-team collaboration. In this case, one of our product designers, Ross, was chatting with one of our frontend engineers, Rowan, about how our product would benefit from having a component system that could be updated by both engineers and designers. The idea grew into something called “primitive components”. These components were basic elements (buttons, icons, labels, etc) with basic styling and some limited configurability. Edits would simply be CSS changes in the code. Simple, right?
Ross and Rowan presented the idea of primitive components at one of our bi-weekly Frontend Guild meetings. It gained traction, and we started talking about how these components — with just a little bit more work — could be built in such a way that they could be used on both our client-facing and internal tooling applications. What a fun problem to solve! The meeting ended with general excitement and a clear path on how to proceed.
The plan was to use Styled Components and Storybook for prototyping (they were already part of our process). We would use the theming capability of Styled Components to handle different looks for different apps. Once that was done we would start integrating. We got to the integration phase surprisingly quickly, and started to use primitive components in our apps. It was smooth sailing to begin with, but then things started to get complicated.
It’s worth noting that our internal tooling and client-facing apps have very different needs when it comes to information density. This means that every component, regardless of how “primitive”, is stylistically different in the two apps. To handle this, we had to write increasingly complex render and styled component theme logic as our use cases for each primitive expanded.
Several months later, we re-organized our team structure and, as a result, got a lot of fresh eyes on our primitives. Not surprisingly, the new contributors quickly identified that the complexity of our primitives made them difficult to work with. This encouraged the existing contributors to also take a step back and look at the code. How had our simple idea become so complex?
We had started out on a journey to create components that could be edited by designers and engineers, but they had become so complex that designers were no longer able to update them by editing CSS. By using the same set of components across two apps (but not using the same design language on those apps) we had to more or less double the configurability of each component. The biggest problem, however, was that we were now using primitives in so many parts of our apps that it was very difficult to change or delete them.
We are now in the process of coming up with a second version of our primitives. We learned a lot from the first version, and now we think we have a much clearer idea of how to keep them on track to solve our original problem.
While working on primitive components, we skipped a few steps. We identified a problem that we wanted to solve, and immediately started to solve it using in-app code. This felt very “agile”, but the speed with which these components proliferated throughout our codebase actually stifled our ability to iterate quickly. When so many of our components were relying on these primitives, how on earth were we supposed to update them quickly?
Prototype code has no place in the master branch of a mature codebase.
What if instead of jumping straight from idea to production code, we introduce a few required steps along the way? On the Customer Experience engineering team at Bench, we decided to formalize that idea into three steps for implementing new technology: Research, Spike, and Build.
Research tickets are time-boxed units of work that give a developer complete freedom to spend time learning without having to focus on a code deliverable. The only deliverable of research tickets is more tickets. These tickets can either be more research tickets or spike tickets. The key here is the time box. If the task is bigger than originally thought, make another ticket to research it further!
If we had used this step while building primitive components, we could’ve spent time more clearly describing the problem we were trying to solve. We could then have described the prototypes we would build to determine how we would solve the problem. For example, we could have written spike tickets to test building the components as reusable or non-reusable between apps, and compared the pros and cons of each approach.
The term Spike comes from Xtreme Programming and is actually a rock climbing analogy.
When climbing, we might stop to drive a spike into the rock face. Driving the spike is not actual climbing — it does not move us closer to the top — but rather it enables future climbing. (Quora).
The XP folks used this analogy for tickets that answer the question ‘What is the simplest thing we can program that will convince us we are on the right track?’ (Agile Dictionary).
For us, spikes are synonymous with prototypes. They are loosely scoped, pointed tickets, and their deliverable is a working prototype. It doesn’t have to be pretty, it doesn’t have to be unit tested, but it needs to show how a problem can be solved with technology, ideally within our codebase (on a branch, of course!). We submit spike PRs to gather feedback, and for use during the build phase, but we don’t merge them.
If we had used this step while building primitive components, we would have been able to test our theories without immediately pushing them to production. We could have taken the ideas from the research step and tested them on a subset of components (ie. Button, Label, and Icon). We would then have had the ability to objectively compare multiple solutions before deciding which (if any) best suit our needs. This could have saved time by helping us iterate to a decision more quickly, and also by keeping our production codebase clean while we learned.
Once we’ve researched a problem and built a prototype, the build step is significantly easier to plan and estimate. What’s important here is that we don’t base the build code off of the prototype branch; we start from scratch. Prototype code has no place in the master branch of a mature codebase. We look at the prototype, choose the best parts, and integrate them with our existing code. We write unit tests to make sure the code works, and then we’re ready to put it in front of users.
If we had used this step while building primitive components, we could have re-routed the months-long learning process that was the first draft of primitive components. The research and spike tickets would have very clearly defined why and how we would solve this problem. As a result, implementation would have had considerably less unknowns, making it far easier to estimate.
A working example
We decided to use the Research/Spike/Build process for one of our recent features.
At Bench, one of the ways we provide value to our users is by displaying their financial data in easy-to-understand charts. As our data model becomes more sophisticated and approaches realtime, we need to give our users the power to build their own variations of our turnkey charts.
Our current charting library is aging, and won’t be able to handle this new use case. We need to choose a library that will. Let’s see what solving this problem looks like with Research/Spike/Build.
Research (3 points)
What are the most popular charting libraries for ReactJS? Of those, which ones will fit our use case? Choose the top three, and write a spike ticket to help us validate which library best suits our need.
We chose ChartJS, React Vis, and Nivo.
Spike (5 points * 3)
In each of the given libraries, build a prototype of a chart that allows a user to compare revenue and expenses by quarter. Assume that the chart will receive a formatted JS object as its initial state. Validate ease of implementation, flexibility, interactivity, and appearance. Create a Pull Request for team review. Once all spikes are complete, meet as a team to choose a winner.
ChartJS was our winner. It was mature, easy to configure, and had tons of activity on Github and Stack Overflow.
Build (Multiple tickets across multiple projects)
Our first implementation of the new chart library was for a new visualization of “money in” vs. “money out” grouped by week. Because we’d already chosen a library, the implementation was easy to estimate and offered few surprises. There was also something very reassuring about knowing we’d made the right choice before we even started.
Every addition to the codebase doesn’t need this pattern. It best applies to larger pattern-establishing innovations like libraries — whether open source or written by your organization. You can also use individual steps. Research tickets are a great way to avoid debate about tickets during Sprint Planning. Spike tickets are a great way to learn quickly outside the constraints of code quality, unit testing, and QA.
Perhaps most importantly, both research and spike tickets allow engineers to do work without having to worry about all the constraints associated with releasing to production. It lets them breathe a bit, and hack together a solution. This is super fun, and it also gives them the freedom to come up with great ideas. This process simply asks us to test those ideas before adding them to our application code.