It must be considered that there is nothing more difficult to carry out nor more doubtful of success nor more dangerous to handle than to initiate a new order of things; for the reformer has enemies in all those who profit by the old order, and only lukewarm defenders in all those who would profit by the new order; this lukewarmness arising partly from the incredulity of mankind who does not truly believe in anything new until they actually have experience of it.Niccolò Machiavelli (1469 – 1572)
The above words are as true for software development as they are true for politics and human nature. Though unlike politics which requires a shift of mentality of the majority of the population and unlike the human nature which usually takes years to change or a very aggressive and disturbing moment of life (death, pain, illness) … we are “blessed” let’s say, in software development to have a choice.
While we may not control the aspects of what we do, we have stories or tickets to implement, we do however have an unbound freedom over the way we do things, even if constrained in freedom by code reviews and feedback from fellow peers, means for constructive collaboration.
Despite our freedom to build flexible systems we as software developers tend to build highly static, manual, overly-complex, over-engineered systems for the use of nobody. It is that under the pressure of time or our commitments to the outside world we take shortcuts, shortcuts which only fossilize in code and with them introduce inflexibility.
Let’s first look at the human aspect of the software development trade. When asked for an ETA, instead of replying: “I will return with an ETA to you by tomorrow as I need to properly assess the complexity” we as developers tend to directly think and speak-out the first estimate that comes to mind: “two weeks”. We do that as if asked to answer the question: “What’s 2 + 2” which for many cases doesn’t apply.
Instead and citing the book of “Agile Estimation and Planning” by Mike Cohn, we could communicate, always, in 3 layers of timing. Here’s an example, asked to do a complex project for which our initial estimate is 2 weeks:
- I estimate that I will finish in 2 weeks time given there are no hidden details of the upstream systems I must interact with and that I’m given proper priority in the DevOps team;
- the commitment I make is for 4 weeks (1 month) time since I can probably manage both development and deployment in this allowed amount of time;
- though we can agree on a deadline of 1.5 months (6 weeks) for now to allow for re-planning or re-prioritization that may affect this estimate or in case the business changes rules which I believe I can accommodate even if they happen to change mid-development;
Look a bit at the wording of things and how it builds expectations in our client, which can happily go about knowing that be 1.5 months he can have the expected feature implemented. We as developers estimate 2 weeks but allow ourselves time for refactoring by 4 weeks time and a buffer period of 2 weeks before the deadline for any polishing or to make the system more generic as we may want to do …
In the case however the two weeks were not enough and we expand we have at least a 3x time-window in which to deliver. Of course, there can’t be any time for refactoring or polishing, but at least we’d learn for next time how to estimate better given the context we’re given.
Though however, we never follow this estimate, commit, deadline approach of communication style. We want to prove ourselves to the detriment of the systems we build and the maintenance nightmare that comes from that in the years to pass and we stop short at the first estimate.
This pressure as it accumulates, forces us to cut-corners, hard-code, skip the infrastructure-as-code approach, skip the best-practices and at worst, put a manually deployed, hard-coded, barbed-wire deployment into production, only to fail under the week-end traffic at 2AM in the morning, on a Sunday.
Since we’re under this constant pressure of “delivering” we forget to search, research, read and try-out methods, patterns, technologies that are there to provide us with the necessary abstractions to allow us flexibility. Here are some ideas:
- we could build inflection points in our architectures by employing the ubiquitous language of integration patterns. These exists for example in the Java world as Apache Camel or Spring Integration and in the DevOps world different styles of deployments roll-outs (blue green, canary) in the right context actually provide us that flexibility;
- we can version our APIs or messages, also useful for above roll-out strategies and evolve in a lock-step manner. We could even take the HATEOAS approach of dumb clients and smart APIs (with a handshake or some other form of identifying the user);
- we can build anti-corruption layers in regards to upstream systems so if the interface changes without us being notified, we get an early warning and instead of allowing “corruption” to settle into our data, it gets stopped at the boundary;
- drawing from Eric Evan’s domain driven design, if we properly identify bounded contexts between collaborating services then for some we build ACLs (anti-corruption layers) while for others adapters, facades;
Our code must move and must change daily. If guarded by tests which evolve with the code itself, then that’s even better. If it fossilizes then we are to loose the dynamic equilibrium in which it exists risking to solidify into one direction or another at which point we can be sure that we have “legacy” or “monoliths” as we intent on calling code which we don’t want to touch, maintain or evolve.
But if code maintains a dynamic equilibrium and we invest in maintaining it every other day, by a dependency update, by a small refactoring here or there, by a proper renaming of that “a” variable you have in your tests, then it can’t solidify and cannot become “legacy“.