Extreme Programming had this idea of doing "the simplest thing that could possibly work". Don't get hung up on lots of architecture or design; make something simple that works. But of course, that doesn't work for long - the requirements get more sophisticated, and the simplest thing no longer works. Then you have to refactor/redesign/rearchitect. And that's OK, because now you know which direction to do it in. But don't do more than you need for now, because you don't know which direction to do "more" in.
Design up front for reuse is, in essence, premature optimization.
I guess that's sort of true but often you know what direction you will be heading, just not now. I see it like a game of chess, you want to be able to at least predict the next few moves and cater your architecture towards that. However, this is an acquired skill that takes practise.
I also think there is a difference between architecture and features where keeping it simple as possible is more about the features and less about the architecture, i.e. keep the features as simple as possible but have your architecture cater for the future.
It's also at the base of TDD: if you don't understand a problem, write exploratory code, then throw it away, write the specification (i.e. tests) for the parts you understand, then write the minimal code that satisfies the tests, iterate until you hit the next bit you don't understand, repeat.
And the entire "build one to throw away, then build the real thing" (i.e. prototype first) ideology is also tooting the same horn. Except that one is much harder to explain to non-programmers because they think "if it works, why throw it away?".
Interesting to think that complexity can be a result of trying to do proper software architecture. It occurs to me that the way software is typically built probably approaches the problem of complexity in software architecture from the wrong end. We try to build software systems that minimize complexity, have predictable behavior and operate under controlled conditions. Most software has extremely low tolerances for aberrant behavior.
Looking at biological systems though, that is the opposite of how reliable systems are constructed. If we design from the assumption of failure and runaway complexity, with watchdog and recovery processes, and using emergent behavior from small discrete components to obtain large-scale results, then we can build software components that are much better able to recover from unexpected interaction and produce more reliable systems. It seems to me that this is what the theory should be. I just have no idea how to do that in the real world when building a web app, aside from recycling server processes every now and then.
You might be interested in Erlang and Elixir, where systems are designed to "simply fail" and then recover rather than trying to catch every possible error condition. These languages are also built upon the idea of using lots* of lightweight processes, which also provides a really nice model (the actor model) for concurrency and parallelism. It's worth noting that the designer of the actor model claimed that it was informed by how physical systems operate, which is akin to your biology comparison. Erlang and Elixir take that actor model and make it operate in a highly reliable fashion.
Here's a quick intro to Elixir (it takes about an hour), where you'll build a distributed app and have it automatically recover from a failure in a process: http://howistart.org/posts/elixir/1
* I had 400,000 processes running on a modest VM without maxing out.
> If we design from the assumption of failure and runaway complexity
This is a good pattern (one of many), but it is no silver bullet, either. If you overdo this, you end up with overly defensive programming, where all code is littered with catching theoretical error conditions that will never occur. That hides the actual functionality and makes code really hard to read and to change.
The solution pattern for this is to move checks at the boundary of the module, so that the code inside can make safe assumptions on the data it receives, so it can concentrate on the real functionality.
As the article says, whatever you do, you can overdo. Software architecture is always about balancing and judging conflicting appoaches and goals.
One thing that is challenging in managing the boundaries is that often times our languages don't prescribe enough layering. We usually have modules, classes / functions and methods to define our boudnaries. The real world dictates we deal with networks, protocols and content types, yet our languages, environments and communities prefer to recommend simplicity at the language level.
I think functional languages have a more healthy appreciation for layering by nature of requiring functions to act on data rather than having mutable data types. I believe if we want to reduce complexity we HAVE to create more layered systems to scope the bounds of our complexity. The problem is many people see this as over-architecting things. With time, a design can evolve such that 10 or 20 layers, as compared to the 3 in MVC we often see, could be manageable with help from our languages and environments (IDE / text editor / etc.).
I'm sorry to bring the term since some say it's a buzzword, but I wish for an anti-fragile software design theory. Ward Cunningham did demo ad-hoc parsers that build grammar through data. Nothing is known in advance. Maybe software that can feed itself back from unknown condition would be possible ?
The article references a paper from 1980 called "On Understanding Laws, Evolution, and Conservation in the Large-Program Life Cycle". I just skimmed it - incredible insights that continue to be relevant.
You don't even try to "evolve" software but periodically rewrite it which "solves" the "Paradoxes of Software Architecture" in one fell swoop. Disposable instead of durable software.
Isn’t that just a new buzzword for something experienced developers have always done? Each time you perform maintenance on any existing piece of software, you necessarily rewrite it to some extent. The rest is just a matter of scale.
Maybe you make a small change in a logical expression or modify a control structure. Maybe you reimplement an entire function using a new algorithm, or redesign a whole module with different internal data structures. Maybe you rebuild the whole system with the extra N years of insight you have gained from how the previous one worked plus knowing about N more years of evolving requirements than you did last time around.
One way or another, you are always looking for the scale where the benefits of rewriting the whole instead of adapting what is already there — in other words, instead of rewriting one or more smaller parts — outweigh the costs. If a project has reached the stage where the cost of working around an existing architecture is prohibitive, it’s time for a rewrite on an architectural scale.
"Isn’t that just a new buzzword for something experienced developers have always done?"
Yes. c.f. "Write it, throw it away, rewrite it."
However, I'm not sure I agree with the rest of your post. It's certainly true - even tautological - that any change is a change of something. It is possible to change behavior without changing code, though (configuration), and it's possible to change code without changing architecture, and I think each of these things is somewhat different in kind not just degree.
I suppose it depends on how you define “architecture”. I usually take it to mean something along the lines of the properties of a software design that are difficult to change once determined and/or that potentially affect the entire code base and/or that are expected to last for a long time. Given some such definition, it is certainly very expensive to change architecture, and therefore relatively unlikely that you’d want to rewrite on such a sweeping scale. That still applies even if the architecture you’ve developed after a while isn’t what you’d ideally chose if you were starting over.
I don’t really see any qualitative difference from any other large (but not that large) scale rewrite, though. To me it’s still just a quantitative cost/benefit question, albeit one where the cost is very high so you probably need to have some severe obstacle to overcome before the big rewrite is justified.
To my mind architecture is more "organizational decisions that affect lots of code". There is substantial overlap with your definition, but they disagree at the margins, and that's probably the root of our disagreement upthread.
I'd say "you gotta do what you gotta do, but don't be lazy":
1. think before you do something. i.e. architect towards known knowns.
2. refactor when new information becomes available.
Most people have a problem with 1 and just go blindly into random search, with the thought: "we can just try this; we can always refactor later"
That is fine if you cannot predict the outcome. However, if you never try to become good at predicting outcomes you will get stuck in "lets try" mode.
If you practice predicting outcomes i.e. architecture code/product ( 'think without actual coding'), you will find that more often it guides the way to the highspeed lane and allowing you skip a couple of "iterations/sacrifices" ( but obviously not all... )
Note that this can only work if you divide your code properly into modules (or sub projects), where you separate the often-changing code from the rock stable code (and anything in between). Then, you rewrite mostly those modules which are subject to steady change.
If you don't do this, you'll always have to rewrite the whole system at once, which becomes bigger over the years. So you would need more and more time and developers for each rewrite.
In contrast, if you manage to always keep the amount of code you want to rewrite at a reasonable size, this approach may become workable over years.
I think this is so true. When you have an evolving system, just toss it out every few years and rebuild from scratch. Obviously, this only works if the people who are building the new version have also build the previous version, i.e. it's the people with the knowledge of the system that's reusable and precious, the code is disposable.
Alan Kay has been saying that at least since the 1970's, but the likes of Joel Spolsky have always argued against it. Now comes Martin Fowler to tell us that Alan Kay was right, without crediting him, of course.
To be fair, the idea itself is much older. It actually reminds me of the Japanese approach to mastery: do something, do the same thing again, repeat until you have mastered it.
Alan Kay famously went through several iterations of rewriting the first Smalltalks from scratch and he is a big believer in that approach. I don't know if it's easy to find him talk about that explicitly but here's one example:
> AK I had the world’s greatest group [in the Smalltalk era], and I should have made the world’s two greatest groups. I didn’t realize there are benefits to having real implementers and real users, and there are benefits to starting from scratch every few months.
I'd recommend his talks at OOPSLA or anything you can find on YouTube. Especially 'The Computer Revolution Hasn't Happened Yet' is a gospel for contrarians.
I have been having similar thoughts recently. Someone we work with was demonstrating some module he had built. My boss asked if he could make it do something. He explained he could make it do that, but didn't want to as it would break the loose coupling he had developed. Loose coupling is nice to have but surely functionality should come first if it is needed.
Design up front for reuse is, in essence, premature optimization.