When was the last time you wrote a Technical Design Document or a Product Requirements Document? As software engineers, it's just a part of the job. There are lots of different ways to write these, but they all usually end up including context, problem, and solution.
Writing documents this way feels mature, and that feeling of maturity is heightened when you provide multiple solutions. From your perspective, your document is ironclad. You spent hours examining everything, doing your research, and presenting a convincing solution. After all, if you can convince yourself (your harshest critic), then it should be easy to convince your peers and stakeholders... right?
You release it for review, and it goes just about as well as it ever has. Maybe folks disagree with your solution or provide harsh criticism, fueling debates. Maybe they don't agree with the problem. Maybe they add comments and never follow up with responses. Maybe they blindly agree. Maybe they don't read it.
You sunk hours into this! The least folks can do is work with you on it! They owe it to you... right? The response caught you off guard... again. What went wrong this time? Never mind - just do it anyway. Done.
You present the finished product with a Q&A session. Their questions focus on your rationality and implementation. You feel like they're digging into your competence. Why did you do it this way? You mean I have to change the way I work now? Didn't they read your document? You gave them plenty of chances!
Depending on your position in the organization, your next step might be to ignore the flack and carry on. After all, this is your domain to control and this is how you've set it up. You agree to provide ongoing support... and that support really does go on... and on... and on... and on... (assuming they use it).
Solution Wait - What's the Problem Again?
I had a conversation with a very respectable software engineer some time ago. The conversation was in response to a comment I made on their document, presented as previously described. It was near the tail end of the review process, which was going as well as it usually does.
The document was essentially something like this:
Context: All of our services and shared libraries exist in a monorepo. All shared libraries are versioned by commit hash.
Problem: We cannot update the code in a shared library without making breaking changes to dependent libraries and services.
Solution: Implement semantic versioning so that dependent libraries and services can upgrade independently.
On the surface, the rationale behind the idea made sense, but pulling this off would be no small feat in this repository. In addition to the high upfront implementation cost, the underlying effect on the ecosystem of the monorepo would be greatly affected and would therefore likely need additional unforeseen changes because this monorepo followed the One Version Rule.
Me: Tell me about semantic versioning.
SWE: Semantic versioning is a formal convention for determining the version number of new software releases. The standard helps software users to understand the severity of changes in each new distribution. A project that uses semantic versioning will advertise a major, minor and patch number for each release (source).
Me: What's the difference between major, minor, and patch?
SWE: A major version is updated when you make incompatible API changes. A minor version is updated when you add functionality in a backward-compatible manner. A patch version is updated when you make backward-compatible bug fixes (source).
Me: If I make a patch change to a shared library (e.g. update to a readme file), should I expect that change to break dependent libraries and services?
SWE: No. By definition, it is innocuous.
Me: If I make a minor change to a shared library (e.g. refactor a function), should I expect that change to break dependent libraries and services?
SWE: No. A minor version change requires that the change be backward compatible.
Me: I can imagine that if I made a change that causes an API incompatibility, then I should expect upstream dependents to be affected.
SWE: Right. A major change is the main issue here.
Me: Since we're only concerned about breaking upstream dependents, can we agree that minor and patch changes don't matter in the context of this problem?
SWE: Sure. However, even in that case, it still makes sense to use semantic versioning because it grants upstream dependents the independence to bump a major version when they are ready.
Me: If a major version bump denotes a change in an API such that each version behaves or is presented differently than the other, then is it fair to say that the two major versions of the library are not the same?
SWE: That's fair.
Me: I'm thinking of Angular v1 and Angular v2. These are two successive web development frameworks that aim to accomplish nearly the same thing. They have the same name. They were developed by many of the same people. From the outside, the only distinguishing feature is the major version change.
SWE: They are wildly different... I mean... there are some similarities, but not many. I would even hesitate to say they aim to accomplish the same thing. They probably shouldn't even be called the same thing.
Me: I agree. They are different things that share a name. What about different things that do not share a name? For instance, there are several popular Node.js logging libraries: Winston, Pino, Bunyan, LogLevel, and Npmlog (source). They all aim to accomplish logging in Node.js. They all do it slightly differently from each other. They all have different APIs.
SWE: Yeah - they're all different, but they're all loggers. So?
Me: Angular showed us that names don't matter. If a library or framework has a different API, then that difference is equivalent to the difference between different libraries or frameworks. Angular v1 is to Angular v2 as Winston is to Bunyan.
SWE: So what you're saying is that different major versions, because they change the API, are different libraries or frameworks.
Me: Precisely. They are not drop-in replacements. Their APIs are different and therefore they are different. What this means is that there is another option instead of bumping a major version. You could make a new library instead. This doesn't mean you have to rewrite everything - you could certainly use parts of the old library for the new one.
SWE: Instead of making breaking changes to an existing library and suffering the consequences of integrating that into all upstream dependents, you're suggesting that I create an entirely new library?
Me: That's right. The only difference is that if you make a new library, then you won't have to suffer the greater consequences of introducing a new versioning paradigm to all shared code in the monorepo. Folks could import your new library when they're ready.
What do you want to accomplish and why?
I want to introduce new functionality into the monorepo that can be adopted at the leisure of library and service owners so that they have ample time to integrate and test it with their existing systems.
How will you measure success?
I will be successful if the new functionality is adopted by 80% of the engineering organization within 90 days. My leading indicator centers around the rate of adoption per week.
What other conditions must exist?
I'm going into this assuming that folks want this new functionality as much as I do. Actually... before I start, I should probably check that. It's a killer assumption. If folks don't care, then there's no reason to do anything.
How will you get there?
The easiest way to make a new library is to create a new directory in the monorepo and add some code.
Thinking with Objectives
In my experience, focusing on problems leads to focusing on implementation details. This can easily get out of control until the only thing you see is a narrow solution. Instead of focusing on what's wrong, try focusing on what you want to accomplish and why.
The problem isn't gone from this way of thinking. It manifests itself through the measurable indicators of success. You want to get from the current undesirable state to a new desirable state. Whether they are qualitative or quantitative, the two states are linked through the measurement of their differences and help to solidify the objective.
Instead of presenting "there is a problem in this context, so here is my solution", try bringing your peers and stakeholders along with you at the beginning of your journey. Start with something like, "I'm pretty sure that we would all like to see productivity increase. Let's set an objective to increase productivity by 20% within the next six months. Right now, it's at x and we want it to be at y. Assuming things continue to be this way and that way, if we see a 10% change in foo and a 10% change in bar, then that should lead to that 20% we want. I have a few ideas for changing foo and bar. What do you think? Do you have any ideas?"
Turn that frown upside down. By simply changing the semantics of problems, you can easily turn them into objectives. This is especially powerful when problems are causally linked. Instead of "this problem caused that problem, which caused another problem", you can simply reword it to say "if we achieve this objective, then that will contribute to fulfilling that objective, which contributes to fulfilling another objective". This simple change can lead to productive conversations and collaborative solutions.
Collaborate with your colleagues by discussing objectives, not problems. Working together towards positive change in an environment from the outset often leads to clarity, commonality, and successful adoption of real improvements for everyone involved.