by Andy Hunt, January 14, 2019
In no particular order, here are the four essential parts of a successful, modern software development strategy.
Let’s take a look at each of these.
You, your team, and ultimately your organization have to depend and rely on continuous, automated builds and deploys.
No matter how skilled you are at working with your users, inventing awesome new interfaces, coming up with great ideas, elegant designs and architecture and more—if you can’t reliably build and deploy the software you’re writing, you might as well not bother writing it at all.
“Civilization advances by extending the number of important operations which we can perform without thinking of them.”—Alfred North Whitehead
With the promotion of “DevOps” and better tooling, most teams will claim they have such concerns well in hand: version control, build, test and deploy. But are you really capable of continuous builds? Have a look at the assessment section of these related practices:
Details matter, which is why we’ve started trying to come up with concrete assessments for these basic activities. You might think your team is ready for continuous development, but maybe there are some practices you’re doing that are holding you back. For instance, if you have long-running feature branches that have to be re-integrated to Master at some point in the future, then congratulations! You’ve just re-invented “waterfall.” You might want to look to using feature switches in production instead.
“We have met the enemy and he is us.”—Pogo
“Us”, in this case, is everyone involved in the project: teams, executives, users. We all have to figure out a way to work together, and that can be hard as all of us have different agendas, different requirements, different skill sets, different values, and different viewpoints.
Regardless of the details of any disagreement on technology, schedule, staffing, etc., the first question to ask is some variation of, “what’s the point?”
In other words, what is the ultimate corporate vision we are trying to serve? Do we even know? If not, how can we possibly make correct decisions when facing the barrage of low-level, every day issues? One aspect of “effective collaboration” is that everyone—from executives to developers—needs to know what’s the point?. You can’t possibly hope to get the organization pointed in the same direction until everyone knows what that direction is, and why we’re headed that way.
Another critical aspect to collaboration is an idea commonly known as “psychological safety.” That means you have an environment where it’s safe for people to take personal risks: safe to express ideas and opinions; safe to experiment with alternative designs, different approaches. If team members are afraid of being ridiculed, or of losing status or even losing their positions, then your environment is not safe and you will not achieve any meaningful level of collaboration.
Do you have a sufficient level of psychological safety in your organization? Have a look at the assessments in these related practices and see:
The only defense against constant change is constant learning.
You have to “unlearn” almost as much as you learn.
This is one of the things that throws people who are trying to learn a new development methodology, or learn a new programming language paradigm (e.g., from procedural to object oriented to functional). You have to get rid of the old habits, the old mental maps, the old problems solving approaches, because all of those things are different now.
And yes, it’s constant. Never ending. It’s not restricted to just new and emerging technologies, either. Change comes from everywhere:
Any modern “methodology” should include learning practices for teams as well as individuals. It’s not something you can ignore or relegate to corporate once-a-year “sheep dip” training. Here’s a sampling of learning-oriented practices you should be doing:
You need to be able to easily try things out. One of the great fallacies of software development is the idea that you can somehow “figure things out” ahead of time, devoid of context, devoid of experience. It generally doesn’t work that way; instead, the most effective way to get answers is to experiment: to try it, for real, in the current context, to get real feedback.
No experiment fails unless it gives no feedback at all—in that case you have no idea whether it worked or not. Many experiments will generate negative feedback: it didn’t work as expected. That’s great to find out in the development environment instead of in production, but it means changes will have to be made.
A lot of effort, study, and ink has been spilled on just how to keep software soft and able to be changed easily over time. But as experience has shown us, almost all time spent on designing software to be “maintainable” or “extensible” is a waste.
Don’t make software “maintainable” or “extensible”; make it replaceable
Instead of wasting time trying to predict a future that will never happen, design software parts to be easily replaceable.
Functional Programming (FP) languages and approaches can help with this idea.
Object-Oriented Programming (OOP) sounded like a great idea at the time, and perhaps implemented purely it might have been, but as commonly adopted we did a very bad thing. Mutable state, temporally coupled and diffused throughout the system, in a tangled, spaghetti bowl of mud—with object-oriented meatballs. Despite all our best intentions, these systems often become brittle fast. In many OO programs, each object is sitting out there like a vengeful ex-spouse, with a long memory and a hidden grudge against the system, ready to pounce and wreak havoc when you least expect it.
Instead of a tangled, intricately coupled OO design, think of a system instead as a series of transforms on data, like a pipeline. Each step—each function—is stateless, with no hidden timebombs or surprises.
With a series of transforms it becomes much easier to test and reason about what the software is doing, and more importantly, what’s up when something goes wrong. It’s much easier to pinpoint the location of the problem and solve it. But it’s not just about testing, it’s about replaceability: it’s about disposable software.
When a component—a step of a pipeline, a function—is suddenly and unexpected made out of date, it should be easy to rip out and replace with something else. The pipeline approach lets you do that easily.
Andy’s Law of Design: If you can’t rip every piece out easily, then the design sucks.
Tracer Bullet Development, which we’ve suggested ever since The Pragmatic Programmer, works nicely with this idea. Make your initial tracer a pipeline: stateless, immutable. Graduate from there to immutable infrastructure. Now you can replace anything that needs replacing easily, and confidently, backed up by tests, with no time bombs or surprises.
For more on Tracer Bullet Development and related practices, see:
While these practices are easy to understand, they can be difficult to master. If you’re just starting out with new approaches, please take a look at our suggestions in Starting with GROWS.
For more information on these and other practices, head on over to growsmethod.com and sign up for our newsletter. Don’t worry, we won’t spam you: we’ll only send a few emails a month, and will never sell or rent your email address to any third party without your permission.
If you really want to jump start your change efforts, we offer a two-day workshop as well.
But no matter how you proceed, the important thing is to proceed. If you aren’t addressing these four key areas somehow, your team and your organization will not succeed in this rapidly changing world.
And we’d really like to see you succeed.
— Andy Hunt