Engineering

Using flags to ease new feature development

Commit early and often. It’s an oft-recited adage.
Ideally, every large change can be broken down into some number of smaller changes which can be committed quickly, say, at least once per day (ideally, multiple times).
Large, uncommitted changes in one’s personal workspace cause lots of well-known pain.1

However, there are often too many interdependent moving parts that need to be modified and tested together to allow you to commit something working and complete into the trunk on a reasonable time scale. Even when using a sophisticated source control tool like git, dealing with remote branches is cumbersome and can create hellish merge conflicts.

At Asana, we have found that a fairly simple device can go a long way toward alleviating this pain: flags.

Flags at Asana

A flag system allows customization of the application’s overall behavior, the way command-line flags do for unix commands. Like a switch that turns on and off portions of the code, a boolean flag lets you provide a control mechanism to enable or disable new features as you write them. For a complex client-server application like Asana, both server and client code need to know the values of flags for the running session.

There are many benefits from a properly-designed flag system. For Asana, the flag system makes the following things easier:

  • No remote branches. We can commit as often as we like to the main line if the code we’re changing is behind a flag. The feature doesn’t have to be complete—it doesn’t even have to work!—it just has to pass whatever tests we wrote.
  • Staged rollout. We can turn flags on for individual users or companies. We start with ourselves (“dogfooding”), then roll out to a handful of customers, then launch to all users. This is a powerful tool for staged rollouts and lower-risk experiments of new behavior.
  • Instant rollback. If we discover a problem with a recently launched feature, we can turn it off and be confident everything still works.
  • Testing. We run automated tests hourly, once with all flags off and once with them on. This gives unlaunched, unfinished features the same resilience to breakage as the rest of our code.
  • A/B testing. Our flags are a natural fit for this.
  • Collaboration. If a teammate needs our code we can share it, behind a flag, without needing a separate branch. Our designer can enable that flag and see how the feature is progressing, providing early feedback.

Usage

Declaring flags is easy! We just need to include a statement like the following in any one of our source files:

// Boolean flag to enable the attachments feature
FlagSystem.defineBool({
  name: "enable_attachments",
  help: "Show an attachments section in the property sheet.",
  is_launched: false
});

And using the flag in code couldn’t be simpler—it’s as easy as:

if (Flags.enable_attachments) {
   ...
}

Conclusion

Flags aren’t the right fit for every large change. Sweeping changes that touch huge amounts of the code (like a visual redesign) result in too many places to introduce conditionals. Changes involving data migrations must be handled with care.

But in general, flags have been a very useful tool at Asana, enabling us to stay nimble in our development and avoid the many problems with large changes. Sometimes there are little solutions to big problems.

Have any interesting problems you’ve leveraged your flag system to solve? Feel free to leave a comment!

  1. For unfamiliar readers, here are some of the pain points of large changes:

    • Increased liability. Uncommitted code is “invisible.” It creates assumptions about the code that the rest of the team is unaware of. Teammates can break those assumptions with their changes, causing you pain in various forms: merge conflicts, test breakage, naming mismatches, and more subtle defects, all of which further delay the commit and facilitate yet more breakage.
    • Missed opportunities for collaboration. Your teammates cannot help with, review, or leverage any intermediate work you’re doing if they have to wait until it’s all done before seeing it.
    • Snowballing change size. Refactorings and tangential fixes you make during your change will depend on the newer code. You can’t commit these unless you commit everything, so your change snowballs.
    • Difficulty of review. For organizations that employ code reviews, a large review requires someone keep a lot of state in their head, obscures the thinking and motivation behind details of the code, and can be very daunting. A single two-hundred-line review is more difficult than two one-hundred-line reviews, and either productivity or review quality suffers.

    The list goes on and on.

Would you recommend this article? Yes / No