I talk a lot about costs. I believe good engineering is about finding the most cost-effective solution to a problem, whether that cost is measured in dollars, hours, morale or lost opportunities. Some costs are paid immediately and some are assumed as debt. Everyone in business knows this intuitively, but some costs are less obvious than others, so it’s important to point them out to your team.
Among the most dangerously unconsidered costs is what I’ve been calling complexity cost. Complexity cost is the debt you accrue by complicating features or technology in order to solve problems. An application that does twenty things is more difficult to refactor than an application that does one thing, so changes to its code will take longer. Sometimes complexity is a necessary cost, but only organizations that fully internalize the concept can hope to prevent runaway spending in this area.
For years, the two things that most frustrated me to hear from product managers were “how hard would it be…” and “can’t you just…” It took me quite a while to figure out why I had such a strong, visceral reaction to these phrases. The answer seems obvious now: The work of implementing a feature initially is often a tiny fraction of the work to support that feature over the lifetime of a product, and yes, we can “just” code any logic someone dreams up. What might take two weeks right now adds a marginal cost to every engineering project we’ll take on in this product in the future. In fact, I’d argue that the initial time spent implementing a feature is one of the least interesting data points to consider when weighing the cost and benefit of a feature.
It’s often subtle or intentionally hidden features that cost the most in the long term. Yammer has long had a feature that allows you to begin a message with “to:” and a username to send a private message to someone, or followed by a group name to post to a group. This probably took me an hour to implement, test and deploy back in early 2008. I have easily spent 40 hours of my time — and, necessarily, 40 hours of others’ time — over the intervening 5 years explaining this feature and its justification. That’s a 40x communications overhead just to carry the feature, even before we consider its impact on people once they get into the code. You might argue that documentation solves this problem, but I assure you that this is well documented in the code… somewhere.
Once you’re in the code, the complexity cost gets more obvious. The first time you open a new section of code, you need to orient yourself. The fewer things the code does, the quicker this process. Even well-factored, well-written, clean code can take a while to become familiar with if it does a lot of things. Anything missed creates rework later: Maybe you break a unit test; maybe you don’t and the code makes it to users who report bugs, costing your support or QA team time even before it gets back to cost you more time.
Jim Patterson, Yammer’s former head of product, established his team’s mission as accelerating the development team. He was specifically talking about figuring out ways to have the product managers and engineers on the same page about carrying complexity cost. This is the kind of relationship you need between your product team and development teams. Everyone owns velocity, and the least important factor in your engineering team’s velocity is how hard everyone is working. Developers need to openly discuss the complexity costs they’re paying right now, those they’re worried they’re taking on with proposed specs, and to propose alternative product designs that can save complexity.
Your QA and customer service teams will all be more effective if you can keep complexity costs down as well. If an area of your product has two toggles that interact, there are 4 states (off-off, on-on, off-on, on-off) that must be tested and that must be understood when working through issues with customers. If it has three toggles, you have 8 states. Four makes 16. The third toggle added 4 states was probably just about as easy to spec and implement as the fourth toggle, which added an additional 8. The same is true of the sixth toggle, which adds 32 states. Again, “how hard would it be to add this toggle?” shows a lack of understanding of total costs. “How much more complicated does it get with this toggle?” gets us closer to the right discussion.
A pathological example of complexity cost that needs to be driven out of engineering teams entirely is a strange habit I’ve seen of people carrying bugs forward as features. Here’s how this usually works: Behavior in cases not defined in the original requirement ends up working one way or another based on incidental implementation decisions. Eventually, users find and adapt to these things and will report bugs when the logic changes, despite the fact that nobody intentionally designed the behavior the users expect into the system. I’ve seen systems freshly refactored get shimmed with strange if/then conditionals to explicitly add behavior that used to be an implicit side-effect of the previous implementation. This is allowing entropy to be your product manager and to take on complexity debt. Fight this instinct. Your product management, QA and development teams should be using judgment to decide what should continue to exist and not just blindly adding everything that used to exist.
Users don’t want complexity, either. Unless you’ve been uniquely disciplined and passionate about keeping your product simple, you’ll find that the vast majority of your users are using a tiny minority of your features. Users also pay a complexity cost in the form of cognitive overhead when you add things to your product. You might think you’ve found a way to get complexity for free by making these hidden or advanced features, but you’re fooling yourself. I can’t count the times I’ve somehow triggered a hidden feature in software, only to spend an obnoxious amount of time trying to figure out what happened or how to revert it.
On the product side, your best tool for eliminating complexity cost is data.Studies showthat most product ideas fail to show value, and many even remove value. If you methodically test the impact of each change you make, you can discard those that are negative, but equally relevant to complexity cost, also discard those that are neutral. A change that doesn’t hurt the key performance indicators still hurts the product because it adds complexity debt that must be paid on all future projects. Often, the best thing for a product is taking something away from it.
Beyond establishing the impact of changes, data also helps you to shed complexity over time. All of your features should be logging their usage, and this usage must be made transparent to everyone. Show your team where to find this data, and given them the means to ask how to interpret it if it’s unclear. At Yammer, where data is collected on most every feature and is made available to everyone, I’ve repeatedly seen this pattern: A developer is working in an area of the code and discovers that it will take more than a trivial amount of time to carry a feature forward in a refactor or other change. Instead of blindly taking on that work, he or she will run reports against the usage data to find out whether a given feature is often used. That data can then be run past the product managers who can help decide whether it’s sensible to just drop the feature. This can also save time before something even becomes a proposal, because product managers will start by asking how well-used is a feature area they intend to enhance. Many things that seemed like great ideas are revealed to be wastes of time, and complexity is saved.
Train your entire engineering department to understand complexity cost and to use data to keep it down.
Complexity is also introduced by well-meaning people on the implementation side as well. Software developers like challenges, and the challenge of building a complex system that serves up a simple interface is especially alluring. Consider DSLs, abstractions and the attraction to being the one to build a framework that gets leveraged for years. This drives us to introduce huge complexity debt we defend with statements like “it makes it so easy once you understand” and “it will save us so much coding.” Writing the lines of code is rarely the big cost in engineering: it’s the understanding, the communication and the maintenance.
Embrace simplicity in your engineering. The best engineering usually isn’t showy or intense-looking. Given the same result, the simpler code is more valuable to your organization. This will often be unsatisfying to people’s egos, but the best engineers have nothing to prove. I remember early in my career reveling in the documentation part of my project. It was an opportunity to brag about how clever the code was, how much it could do, and how easily it was to extend for anyone who took the time to read all this great documentation. I was straight-up giddy when I got to draw a complex system I’d designed on a whiteboard with boxes, arrows and lines. I was sure this documentation and illustration was proof of what a real engineer I was. When I actually became a real engineer, I realized the simpler I could build something and the less it needed documentation and illustration, the better off my coworkers were — the faster we could all build the thing we were hired to build.
Embrace simplicity in your product and in your code. The value is in what gets used, not what gets built.
Embrace simplicity even in your communication. Push out a culture of jargon, acronyms and puffed-up descriptions of things. Celebrate simple, effective communication. Your best people can explain to a child everything that your organization does. Your worst people are the ones who sound smart and official at the expense of being widely understood.
Simplicity is a very hard engineering challenge, but complexity compounds like interest over time. Without keeping simplicity, you will never keep up with competitors who are trying to disrupt you. Make sure your whole team knows this.
Kris Gale, VP Engineering at Yammer, joined us recently for First Round CTO Summit and blew everyone away with his talk on “why the traditional engineering organizational structure is dead.” He’s now back for round two with a new piece on how features can create complexity cost in an engineering organization.