I've been thinking about why debugging can be hard and some higher level design heuristics that can help. One thing that really gets me down is invalid states. This problem has a slightly different face in functional programming versus object oriented programming. Sometimes it's not clear cut that a state is invalid, but rather we may have interim states that have no real business meaning.
Lets start with the classic list processing style of functional programming. It's pretty common to take a list of elements conforming to some design, they represent things in the domain in some given state, and the initial states are normally pretty sensible. Then the list is processed via a series of list processing stages, maps and filters, reductions, and so forth. The problem is that these primitives don't distinguish convenient intermediate steps that are just implementation details from stages that are really significant and meaningful states from the domain perspective, valid business states.
The solution is chunking together list processing primitives to make higher order list processing functions take lists of valid items and produce similarly valid results, hiding the intermediate workings, (valid here means representing a state that is meaningful in the business domain).
Failing to break the list at the important points leaves any future developer puzzling over the meaning and correctness of the intermediate lists, particularly if they want to change the process by perhaps adding additional intermediate states or shifting process so that subsequent states are different, or if they are wondering why an object arrives at a particularly stage of processing in a given state.
An analogous situation plagues object oriented programs. A method transforms an object, it does so via a series of intermediate steps. If the method makes those changes on the object directly then the object goes through a series of intermediate states. This can cause all sorts of confusion if you're trying to modify a transformation, it leaves the developer puzzling over what precise intermediate state the object might be in if they what to make change at some given point, effectively the developer has to run the whole program in their head to understand the context of any given sub part of the process. I'll just mention parallel programming, no need to say more I hope.
The solution here is to avoid changing parts of the object, instead, as much as possible calculate the new state in local variables and method parameters, then change the object in a single step. Two phases, one of data crouching and then another of state changing.
Too many months or maybe even years of my working life have been spent trying to understand the state of programs that allow wide access to intermediate states and don't distinguish intermediate states from significant stable states with real meaning in the business domain.
So, some rules of thumb:
- Don't go tweaking state ad hoc, (getters and setters are for tools not programmers), state changes should correspond to meaningful business events.
- Aim to hide, even from other methods, the intermediate states that come about as you implement a method.
- Don't let raw list processing primitives become exposed in your business abstraction layer.
- Chunk list processing primitives together into higher order operations which take you from one business state to another.
- Know what constitutes a valid state, and don't let your objects ever be in invalid or intermediate states.
No comments:
Post a Comment