Programming attracts people from a huge number of backgrounds. I’ve seen great programmers who started as mathematicians, journalists, musicians, physicists, and mechanical engineers. Even formally-trained programmers come from a wide range of liberal arts, engineering, and business schools. It’s one of few professions where you can be rewarded just for being smart and working hard.
To be honest, I’ve always been embarrassed by how personal our methods are — it’s difficult to separate effective methods from natural talent. Maybe our diversity is the reason I’ve heard us criticized as navel gazers pretending to be engineers. “Herding cats” was coined for us after all!
We are taught data structures, formal methods, computer architecture and technology. We self organize into communities and movements like literate, agile, and craftsman. We write tests, review code, follow best practices, decouple our classes, encapsulate our designs. It’s a little half-baked — even cargo-cultish on a bad day.
Fundamental software design is almost completely missing from programming. Is design valuable? Can we learn how to design? I think so, but not yet from the software community. Cross-train into mechanical design!
Learning Software Design from Mechanical Design
This year I was pushed outside my comfort zone and asked to mentor mechanical design on a FIRST robotics team. I trained on the shop tools, studied machines, and read as much as possible. Then I found a great lecture by professor Alex Slocum at MIT: FUNdaMENTALS of Design. I’ve never seen anything like this for software, but with effort, you can apply the lecture to programming. Your effort will be rewarded.
Mechanical design is more mature than software design, so it shouldn’t surprise anyone that fundamental principles are, well, more fundamental. For example, programmers learn YAGNI which is cartoonish compared to Occam’s Razor. Sometimes you do need it, and the razor will reveal that.
What’s really delightful, however, is that all the FUNdaMENTAL Principles focus on achieving a complete, efficient, and robust design. This differs from popular software design principles which focus more on subjective ease-of-change, e.g. Single Responsibility Principle, Liskov’s Substitution Principle and Open Closed Principle. Channeling Richard Feynman again: “The first principle is that you must not fool yourself, and you are the easiest person to fool.” It seems like we’ve collectively fooled ourselves into thinking that software reusability is more important than performance or reliability.
Choosing a Cost/Performance Curve
One idea shown early in the lecture is comparing the cost/performance curves of substitute technologies to choose appropriate components. You can also make predictions how a system might evolve. This is a stronger, more fundamental approach than following rules for encapsulation, cohesion, or dependency injection. It explains why we have those rules! So instead of merely following best practice rules, you can evaluate the risk of breaking those rules or identify when following them won’t be enough.
Here’s an example cost/performance curve:
You see three different (standard) approaches in black and a fourth (disruptive) approach in red. Requirements drives your choice, but depending on your confidence in that choice, you may hedge by reducing the switching cost in your design. If the disruptive choice is viable, it completely replaces the other choices — there’s no reason to consider the switching cost of going to one of the others.
“If you board the wrong train, there’s no use running along the corridor in the opposite direction.” Dietrich Bonhoeffer
The cost/performance analysis highlights another weakness in popular programming: we depend far too much on smells and opinions. For example, if a change causes a ripple through a tightly-coupled system, what does that really mean? Is it bad? In a mechanical system, I’ve learned the hard way that it means hours taking things apart and replacing them. In a software system though, it might be a few seconds waiting for a tool to refactor the code.
“A couple of months in the laboratory can frequently save a couple of hours in the library.” Frank Westheimer
Go Watch the Lectures!
I can’t do proper justice to the lectures here. Professor Slocum is smart, funny and insightful. Hopefully he will inspire you to look at software design from a different point of view.
P.S. This post talks about large-scale software design. Small-scale software design (aka algorithm design) is pretty well covered in school. If you aren’t familiar with it, I really like Steven Skiena’s book The Algorithm Design Manual.
Software Analogues to Mechanical Design Characteristics
There really aren’t mappings from most of the FUNdaMENTAL Principles to software designs, but it’s fun to experiment with the ideas to see if a principle can be directly interpreted instead of just inspirational.
Mechanical | Software |
---|---|
Speed | Responsiveness or Throughput |
Weight | Storage size |
Force | Data flow (input/output) |
Stiffness | Error handling |
Tolerance | Types and contracts |
h3. FUNdaMENTAL Principles
Here’s a summary of the FUNdaMENTAL Principles. The video lecture and course materials are really interesting, but this will give you a glimpse into what’s covered.
- Occam’s Razor: This is what layout is all about, keeping things simple to start and adding detail as the design develops.
- Newton and Conservation: Action and reaction, free-body-diagrams, work in = work out… The basics come first before the details!
- Saint-Venant’s Principle: When an object is to be controlled, sketch it being held at points several characteristic dimensions apart.
- Golden Rectangle: Don’t know what size it should be? Start with a ratio of about 1.6:1.
- Abbe’s Principle: Small angular deflections are amplified by distance to create large linear displacements (which can be good or bad).
- Maxwell & Reciprocity: Uncomfortable with a design? Try inverting it, or turning it on its side.
- Self-Principles: Use an object’s geometry or other property to prevent a problem, like using a tapered plug to withstand pressure, or preventing overextension of a spring with the use of a hard-stop or a string in parallel.
- Stability: Stable, neutrally stable, and unstable effects can help or hurt. Triggers, for example, can be made neutrally stable and fast.
- Symmetry: Try a design that is symmetric, and then impose Reciprocity to consider a design that is not symmetrical.
- Parallel Axis Theorem: Add mass away from the neutral axis to increase strength and stiffness.
- Accuracy, Repeatability, Resolution: Make your machine repeatable first, then tune for accuracy if you have fine enough resolution. Tell the same story each time, the correct story, and with enough detail so people can understand it.
- Sensitive Directions & Reference Features: Why pay for performance in a direction that is not needed? Establish reference planes (datums) from which you measure critical parameters.
- Structural Loops: Draw a line through the path that forces follow, and seek to minimize its length. If the path length and shape changes significantly as the machine moves, then the machine will have limited accuracy and may have limited repeatability.
- Free Body Diagrams & Superposition: To analyze a complex object, separate it into its parts and label the forces and moments on each part that are imposed by other parts.
- Preload: Loose fits between objects mean you cannot predict where one object will be with respect to the other. Apply loads between the objects as part of manufacturing and assembly to take out the slack in the system.
- Centers of Action: If forces are applied through the centers of mass, stiffness, and friction, there will be no moments and hence minimal Abbe and sine errors.
- Exact Constraint Design: The number of points at which a body is held/supported should be equal to the number of degrees of freedom that are to be restrained.
- Elastically Averaged Design: Hold/support a body with ten times (or more) more compliant points than there are degrees of freedom to be restrained, such that the errors in the compliant support points will average out.
- Stick Figures: Initially sketch an idea using simple stick figures, which also denote where major coordinate systems are located in the design.
“It seems like we’ve collectively fooled ourselves into thinking that software reusability is more important than performance or reliability.”
I can’t say I’ve ever felt that I’m choosing between reusability and reliability. Occasionally I feel like I have to choose between performance and reusability; however, the applications for which the performance afforded by reusable code is insufficient are extremely few and far between. Simply put, reusable code almost always (if not always) leads to reliable code, and the cost/benefit ratio rarely favors sacrificing reusability for performance (in my experience).
Design is mainly about making choices and I’m suspicious that we can always choose to optimize one aspect of our systems. After seeing a brief glimpse into how another profession practices design, focusing so heavily on one thing strikes me more as immaturity in our design discipline.
For example, following Postel’s law (conservative output, liberal input tolerance) creates flexible, robust systems. Doing the opposite creates stiffer, less robust systems with more predictable behavior. Exploring this further seems like we’d come up with a useful software design fundamental.
Bits are definitely different from atoms. Changes are relatively cheap. Decisions can often be deferred. Optimizing for change plays to our strengths of being smart and working hard. However, our industry’s problems with non-functional aspects (speed, weight, cost, reliability, security, etc.) show that programmers don’t know much about design yet.