Software Design for Flexibility
mitpress.mit.edu
mitpress.mit.edu
The problem is I'd rather make a medium size changes to a simple system than a minor change to a complex system.
>The authors explore ways to enhance flexibility by:• Organizing systems using combinators to compose mix-and-match parts, ranging from small functions to whole arithmetics, with standardized interfaces• Augmenting data with independent annotation layers, such as units of measurement or provenance• Combining independent pieces of partial information using unification or propagation• Separating control structure from problem domain with domain models, rule systems and pattern matching, propagation, and dependency-directed backtracking• Extending the programming language, using dynamically extensible evaluators
And this doesn't sound like a simple system.
"The truth is, everyone is going to hurt you. You just got to find the ones worth suffering for."
To some degree or another, everything sucks and complexity is unavoidable. You just have to find the type of complexity you're willing to live with.
That being said --
I suppose if you're used to that kind of complicated stuff and used to typing out the boiler plate code, it doesn't seem quite so inscrutible. Tedious, but it doesn't blow up in your face.
It's like I had a hard time understanding the strategy pattern the first time I saw it, especially when generics were involved. It felt mind bending.
Now I happily use it and it's all straightforward. The formerly intellectually difficult parts are now autopilot for me. And it's a hell of a lot less stressful than adding another set of random if statements to an important code path.
And keep in mind many of these phrases are very fancy ways of something that makes intuitive sense if you're familiar with it.
> And keep in mind many of these phrases are very fancy ways of something that makes intuitive sense if you're familiar with it.
Every piece of code is easy to deal with once you're familiar with it. But maintainable code is about allowing other people to easily work on it.
I pity the guy trying to fix some old legacy code base, while also trying to figure out what the hell a combinator is https://en.wikipedia.org/wiki/Combinatory_logic
And "simple" code has far more risk of side effect than well architected code, which is quite the opposite of what you assert.
Getting to the bottom of what you need to change is complicated if you're new, but once you get past the learning curve it's straightforward.
On the other hand, a "simple" code structure can becoming a steaming pile of garbage if you want to make any real changes to it.
To be very clear, I'm talking using something like composition instead of making a big file with cyclomatic complexity -- not arguing for some of those other things, which in all honesty I'm not completely familiar with.
Really just arguing that sometimes this stuff seems impossible and dumb when you're starting out, but easy and intuitive when you learn more.
> I pity the guy trying to fix some old legacy code base, while also trying to figure out what the hell a combinator is
That goes to my other point: it's wrapped up in excessively formal language, but the actual ideas are simple. I think they might be for instance just advocating the use of functional programming, and juniors use Javascript lambdas every single day without knowing what the hell a combinator is.
Even though they might feel the same, on a human level, there’s clearly a difference between being early in the learning curve and facing down a complex system.
But as OP said, it is selective. For instance, I would much rather have the OP use the strategy pattern. I have used it before, I will recognize it and it will take me tons of time when trying to understand the codebase and making a change that doesn't break things.
We cannot find a global optimum, we have to decide if we prefer to make it easier for experts/experienced developers or for beginners. I prefer the former and then teach beginners to become experts/experienced (in some area) as quickly as possible. That costs resources but pays off in the end.
Google went the opposite way. They created Go to be beginner's friendly but at the same time hurting productivity of more experienced developers. Let's see how that turns out in the end.
well, they started the Fuschia kernel in C++, not in Go... that should be telling enough :)
Things like "Separating control structure from problem domain" enable separation of concerns so that the person deal with resource utilization – efficiency/capacity-planning and scalability can be an expert in systems domain and doesn't have to deal with functional domain understanding and its complexity (and vice versa).
Solving for separation of concerns in such a way that it also enables separation of roles and responsibilities and hence facilitates developing expertise/specialization in a particular orthogonal discipline is super powerful.
In my experience, this is one of the most effective ways to scale a fundamentally human endeavor of building large software projects.
The risk there is you (or someone else less disciplined on your team) doesn't make the medium size change. They instead hack in the smallest possible size change they can get away with to satisfy the current problem.
After a few changes like that, you have a complicated system and all changes are major.
I've spent weeks trying to make minor changes to code like that because I'm afraid changing one variable is going to backfire because it's used in some unexpected way somewhere else.
On the other hand I've also been the bad guy before, too. I've realized how much work refactoring is going to be, and instead of making the project managers mad at me I just slapped in some additional cyclomatic complexity.
Everyone wins... in the present. We get to check off our to do lists. Everyone in the future loses. Technical debt is an apt metaphor.
Spending one hour thinking to make a one-line change might not feel productive if one is used to just hacking away, but, IMHO, it is a net win by some orders of magnitude (assuming we are working on some software with solid abstractions).
> And this doesn't sound like a simple system.
Unless one understands it, in which case that list sounds like a collection of solutions/patterns one would come up with after the n-th rewrite anyway. If, and that is were we often fail, we manage to document the design decisions adequately, code can be easier to read as well.
There's a huge misunderstanding of 'flexibility' within codebases. You can achieve 'flexibility' via the lack of programming rules. Yet objects/abstractions/encapsulations are literal rules applied onto code to achieve some benefit (less code, increased surface understandability, etc), it's less about flexibility. And the quoted systems feel like they have little benefit to one's codebase.
I would rather navigate code with a clear, high level pipeline of transformations, rather than each path having its own idiosyncratic low level data assembly.
It's quite dated, but has a lot of stuff that is still relevant.
I remember that one of his sections was titled "FLEXIBILITY BREEDS BUGS".
Here's the first paragraph:
Another strategy you can use to prevent bugs is to strip unnecessary flexibility from your designs.
You've seen me use this principle throughout the book.
In Chapter 1, I used optional compiler warnings to disallow redundant and risky C language idioms.
In Chapter 2, I defined ASSERT as a statement to prevent the macro from being mistakenly used in expressions.
In Chapter 3, I used an assertion to catch NULL pointers passed to FreeMemory even though it's quite legal to call the free function with a NULL pointer.
From every chapter I could list examples in which I reduced flexibility in order to prevent bugs.
I like to put flexibility into my designs, but I've learned to be very careful about it, and usually test the unused code paths anyway.“Hanson and Sussman's Software Design for Flexibility has introduced additive programming, a game changer. An additive style allows for making changes to existing designs without the programmer's efforts looking like the work of a contortionist. With elegance, clarity, and care, they point out long-overlooked problems in software design and offer their Scheme-friendly, clever solutions. Enjoy!”
Dan FriedmanProfessor of Computer Science, University of Indiana; author of The Little Prover
The logo is a stylized "mitp" for MIT Press.
[1] https://www.gnu.org/education/teaching-my-mit-classes-with-o...
Sorry for the ambiguity - I thought of going back to edit for clarity, but punted.
[1] https://mitpress.mit.edu/books/structure-and-interpretation-... [2] https://mitpress.mit.edu/sites/default/files/sicp/full-text/...
The only real solution, that most managers don't get, is to know all requirements upfront.
The only way to predict what all requirement will be in a product lifetime would be to invent a time machine. This is not what I call "a real solution".
The only real solution is to communicate a lot with the business analyst and make informed decisions on flexibility vs simplicity.
For me, the appeal of microservices is the organizational challenges it solves (allowing teams to move and deploy at their own pace).
My current codebase is a monolith because we're a tiny team, but the different parts are not tightly coupled more than they need to be and it doesn't feel slow to change things.
Similarly, you build a system with the best information available, and be prepared to change it when the information changes.