Tuesday, August 31, 2010

Set-Based Triangulation and Evolutionary Design

I like to rush forward in order to get enough divergent use cases to let me triangulate on a good design.

Whenever I speculate that I have a general solution, I invariably have to unroll it later when a divergent use case invalidates my assumptions. 

The only assumption that seems to escape consistent invalidation is the assumption that my assumptions seem to be consistently invalidated.

Alan Kay says to build it grand.  My experience is that I only tend to come up with something that is truly grand when I am forced into it by divergent use cases.  Triangulation is about forcing yourself to build it grand.

Before I get into the specifics of what I am calling the "set-based triangulation" approach, let's take a look at triangulation, set-based concurrent engineering, and evolutionary design. 

Triangulation

Description:
In trigonometry and geometry, triangulation is the process of determining the location of a point by measuring angles to it from known points at either end of a fixed baseline, rather than measuring distances to the point directly. The point can then be fixed as the third point of a triangle with one known side and two known angles.

Triangulation can also refer to the accurate surveying of systems of very large triangles, called triangulation networks...a point could be located from the angles subtended from three known points, but measured at the new unknown point rather than the previously fixed points...error is minimized if a mesh of triangles at the largest appropriate scale is established first, that points inside the triangles can all then be accurately located with reference to.

Triangulatio! n is about locating the point you care about and minimizing er! ror by m easuring based on several observations.

Set-Based Concurrent Engineering

Description:

1. Define the problem.
You'll want to capture the problem and share that upfront with all participants. You should include more than a standard story. Things like actions, persona, motivations, and goals make a lot of sense in giving people direction. You'll want to avoid leading questions, etc. 
2. Diverge: pursue multiple designs.
Break apart into multiple teams or as individuals. Take a fixed amount of time to design the solution using the given data from step #1 above. 
3. Collapse: synthesize results.
Have every design team (or individual) present their work. Combine the best parts of each design to attain a better outcome.

Description:
Set-based concurrent engineering (SBCE) begins by broadly considering sets of possible solutions (in parallel and relatively independently)and gradually narrowing the set of possibilities to converge on a final solution. Gradually eliminating weaker solutions increases the likelihood of finding the best or better solutions. In this way, Toyota can move more quickly toward convergence and production than their traditional, "point-based" counterparts. The authors develop the SBCE idea by describing three principles that guide Toyota's decision making in design: (1) simultaneous mapping of the design space according to functional expertise, (2) "integration by intersection" of mutually acceptable functional refinements introduced by the design and manufacturing engineering groups, and (3) establishment of feasibility before commitment. The authors also present a conceptual framework tied to t! he Toyota development system and discuss why the SBCE principl! es lead to highly effective product development.

Description:

considering a solution as the intersection of a number of feasible parts, rather than iterating on a bunch of individual "point-based" solutions. This lets several groups work at the same time, as they converge on a solution.
The point-based approach proposes a solution, and then iterates by revising to a different point...the point solution is in effect treated as "the right answer." If we find out it's not right, we move to a different point.
A set-based approach takes a different path. Instead of moving to a solution right away, it starts with options open, then narrows in on a solution.

Why might we want to take the set-based approach? Because it lets us:

  • defer decisions until the "last responsible moment," when we have the best information;

  • explo! re many possible designs at the same time; and

  • converge designs more quickly.

One way to think about allowing many options in parallel is modularity via interfaces or messaging. Consider the following:

...use modularity: create designs that explicitly enable different alternatives...Many examples of modularity are based around the ideas of abstraction and of an interface: two parts make a commitment to how they will "talk" to each other, but no commitment about what happens beyond the interface.

It can be extremely helpful to implement an interface around hard algorithms problems so that you can run many implementations in parallel and swap them in and out subject to the same regression tests and performance benchmarks.  Many times different algorithms are better in different situations depending upon the ! data; some advanced algorithms are self-aware and have the int! elligenc e to switch between different implementations depending on the data that they encounter at runtime.

Interface-oriented thinking is headed toward the spirit of triangulation, but there is one problem.  Interfaces tend to assume that the implementation space is set-based, but the interface itself is fixed-point.  This carries the implicit assumption that your interface is OK, and that you know how to create designs that enable different alternatives.  Can you be sure that you can design for modularity in advance of experiencing divergent use cases that evidence the particularities of the modularity that you actually require?  The right modularity is difficult to achieve, even with interfaces.  You
might not know the right interfaces.  You might make the system inflexible where it needs to be flexible, and flexible where it ends up being irrelevant. 

Interfaces are great, but there is more to modularity than just interf! aces.  Consider this
...the trick is to ask the right questions at the right time...Traditional iterative processes first hit on a solution and then try and refine it. Set based concurrent engineering methods emphasize the need to first target multiple alternatives... Concurrent Engineering is a business strategy which replaces the traditional product development process with one in which tasks are done in parallel and there is an early consideration for every aspect of a product’s development process...The trick in SBCE, is to realize what decisions you can defer and what you must take now as well as how risky or difficult would it be to change a decision later...

Many examples of modularity are based ! around the ideas of abstraction and of an interface: two parts! make a commitment to how they will “talk” to each other, but no commitment about what happens beyond the interface. Another approach that can help in not doing something wrong is simply not doing it. The implication is so profound and challenging probably because “not doing it” is not considered as an alternative at all. However, not doing it “now” could allow us to do it better when we really need it or have enough information about it.

Some of this thinking bears a resemblance to traditional agile views; building thin vertical slices all the way through a system rather than building horizontal "layers" sequentially, avoiding things until the "last responsible moment," and depending on an interface rather than an implementation.  The primary divergence comes from iterating on a fixed point solution as opposed to converging on a fixed point solution via iterating on several possible solutions as a set.

Putting ! the Evolution in Evolutionary Design

When people talk about evolutionary design, they typically talk about iterative fixed-point design.  The term "evolutionary" is used a lot in agile, but I have not seen much discussion about relevant metaphors from evolutionary biology.  A better word for what most people are calling evolutionary would be iterative or incremental.  I like incremental and iterative.  My point here is that evolution achieves incremental progress by a process that is more sophisticated than fixed-point iteration. 

Evolution is driven by mutation and select! ion.  Through mutation, randomness ensures divergence! the ind ividuals within a population.  Through selection, fitness ensures that populations (and randomly chosen individuals within populations), tend to be increasingly better suited to their environments.

In set-based triangulation, the mutation is driven by design sets arising from divergent use cases, and selection is driven by triangulating the design sets in order to converge on a design that can accommodate the divergent use cases.

When you apply these concepts from evolutionary biology to software, you can see that it results in a way of thinking that is a bit different from the kind of fixed-point iteration that people call "evolutionary design."  When you take the application of these concepts to the extreme, you end up in the very cool fields of Genetic Algorithms and Genetic Programming.

Using biological metaphors to think about computing systems is a very deep subject for me.  For now, I just want to point out that biological metaphor is how I think about evolutionary design, and this is why set-based triangulation is more important to me than fixed-point iteration. 

Set-Based Triangulation
The cheapest, fastest, and most reliable components are those that aren't there. -- Gordon Bell

I stay focused on something with a laser-like precision until I get something working end to end.  The results are simple, but often very naive and inefficient. I even take on a bit of technical ! debt.  Ironically, I find that trying to do too much ! design ( even evolutionary design by fixed-point iteration) before you have enough divergent use cases can be deceiving and yield counter-intuitive results. you might feel good, but in fact you might be taking on more technical debt than if you had deliberately taken on technical debt with the explicit intent to keep the structure of the system extremely flat.  The reason for this is related to my discussion about bad OO.  It is more expensive to unroll an inappropriate structure in order to refactor toward an appropriate structure than it is to refactor toward an appropriate structure starting from a flat structure. 

This is why I try to keep things very simple with a relatively flat structure as long as I can get away with it.  It can be quite difficult to determine what an appropriate structure looks like until you have a broad ! and deep enough set of divergent use cases to allow you to triangulate. 

Now, let's take a look at planar and non-planar graphs.
In graph theory, a planar graph is a graph which can be embedded in the plane, i.e., it can be drawn on the plane in such a way that its edges intersect only at their endpoints. A non-planar graph is the one which cannot be drawn in the plane without edge intersections.

This is analogous to the problem with design, even iterative evolutionary design that is based on refactoring and refining a fixed point solution.  Imagine that your divergent use cases represent vertices in the graph, and that fixed point solutions represent edges between these use cases.  Imagine further that non-planar graphs with edge intersections represent dreaded "spaghetti code."  If you can avoid fixed point solutions, ! then you can avoid connecting the graph with vertices.  I! f you ca n do that, then you may have a chance to shuffle the vertices around in such a way that you can connect your edges without causing intersection. 

This is not an exercise in imagination at all.  The dependency graphs of poorly designed software systems are in fact complex non-planar graphs with a spaghetti-like intersection of edges.


OK, so how do you keep the system unstructured enough for long enough to allow this sort of thing?  I mentioned earlier that I am comfortable with deliberately taking on a bit of technical debt in order to retain a flat structure.  Let's look more closely at the nature of that debt.  My version of taking on technical debt does not involve haphazard violation of basic programming etiquette that results in a brittle system.  I stick to the basics like dependency injection, encapsulation, and keeping things side effect free as much as possible.  If I see a lot of duplication, I create abstr! actions.  I don't shy away from low level abstractions in the bottom up spirit - I just don't have much in the way of higher level abstraction and structure in the system. 

I just keep building up enough working end to end vertical slices through the system until I have enough divergent use cases that I feel comfortable that I am going to be forced into a decent design.  I resist the desire to go off on tangents that might seem attractive.  I don't try to "evolve" the design much along the way.  Thinking about evolution as fixed-point iteration can be a slippery slope.  Ironically, the urge to constantly refactor, build a "rich domain model", and use a lot of design patterns is up the same alley as the Big Design Up Front, i.e. doing a lot while knowing a little.

One area I don't take on technical debt is testing.  I try to be pretty rigorou! s with testing, and I TDD most things.  TDD helps with ta! king the outside-in perspective, and it leaves you with a nice suite of regression tests once the time comes to triangulate.  This way, once I am able to triangulate, I am in good shape to make rapid and radical change in the code base. 

I am not espousing either a big-bang triangulation, a fly-by-night sort of design, or piling on technical debt until you reach default and eventual bankruptcy.  Most people freak about about this notion of revisiting things because most people never come back to revisit.

I'm not advocating that you avoid doing any deep thinking about design.  It is a matter of when that thinking occurs.  There is the Big Design Up Front approach, which focuses on doing a lot of analysis and design before you start coding.  This way of thinking is under constant attack from the Agile school of thought which espouses Evolutionary Design.  The approach I am talking about is evolutionary, but it has an up front element to it - namely, "Big Work Up Front."  There is the shared element of up front discovery; but it is discovery by doing, not discovery by talking.  This means that you actually do a lot of real work, and intentionally prioritize your work to solve for a variety of divergent use cases.  Until then, you avoid trying to think deeply about the design or even make any real concerted effort toward evolving a design.  This is where I think taking a triangulation based approach lies toward the very extreme ends of evolutionary design and even diverges in some respects.  You intentionally avoid doing much refactoring during this exploratory phase.  You have some duplica! tion.  You accumulate cruft in the code base.  There! is an e bb and flow.  At times, a lot of the system will seem brute force and inelegant.  As I like to say, you need to be comfortable with being uncomfortable.

The real win is that the up front discovery phase does not result in a ridiculous Software Requirements Specification; it results in a working end to end product with a nice suite of tests, but a design that leaves much to be desired.  Not only do you then have the information you need to stumble into a great design, you have also paved the way for yourself to achieve it on the back of your already working system.  In the language of my graph theory analogy, you now have the opportunity to shuffle vertices around and connect them in a way that is as close as possible to a planar graph.  Again, this is much easier to do with a flat structure where your vertices have very few clearly defined edges, otherwise you will pay the cost of untangling the intersecting edges of the spaghetti graph.

Three systems of Measuring Angles

No comments:

Post a Comment