In Cognitive Load During Problem Solving: Effects on Learning, John Sweller explores the way we think about solving problems and some ways in which we can optimise the process.
Forward vs Backward working
As a starting point, Sweller notes that for many domains, problem solving can be considered a transformation between an “initial state” and a “goal state”.
For example, consider the following initial and goal states:
In the paper, there is a distinction between “backward-working” and “forward-working” approaches to problem solving.
Backward-working problem solving techniques are characterised by working “backward from the goal setting subgoals”:
In contrast, a forward-working approach means choosing a way to proceed from the Initial state toward the stated goal:
This way of working is preferred by experts who already know the paths they will need to take to reach the answer. Sweller states that:
Experts are able to work forward immediately by choosing appropriate equations leading to the goal because they recognize each problem and each problem state from previous experience and know which moves are appropriate.
Forward-thinking in Programming
A naïve interpretation of “forward” vs “backwards” problem solving technique in programming might describe “forwards” working as e.g. proceeding from Database to Business Logic to UI. However, this would probably be incorrect.
Our task in programming is to solve a problem for users (or dependent machine actors). Our initial state is likely to be a shallow understanding of the problem and our task is to discover the problem and encode a solution as best we can.
Further complicating our “Initial state” is that our users often have yet to fully discover the problem space for themselves.
Sweller further categorises “forward-working” strategies into three:
- Already knowing how to solve the problem end-to-end based on prior experience (“schema-driven”)
- Already knowing how to solve part of the problem, getting us closer to our goal, after which the remaining parts can be reconsidered (“reduce differences”)
- Exploring the problem space to see what is possible (“uncontrolled search”)
When we encounter a typical programming problem, approach (1) is traditionally preferred. But, more agile approaches often use strategy (2). Strategy (3) is more chaotic and hard to justify, at least in a business context. As Sweller puts it:
The practicality of the third strategy is dependent on the problem structure. Many problems have extensive state spaces. An uncontrolled search of a space containing thousands of paths is clearly unlikely to be productive.
However, all three approaches appear to ignore the existence of frameworks, patterns, conventions and heuristics which we often apply in a programming context. For example, DDD, hexagonal architectures, service topologies and platforms.
Domain Driven Design
One popular technique for discovering the design of a problem space is Domain Driven Design. Which aspects of DDD would we consider to be “Forward-working”?
At the core of DDD, we usually concentrate on discovering a core domain and a ubiquitous language to describe it. We often drive design from this core domain outwards towards the boundaries of the systems — for example UI, state persistence, or interaction with external services.
We have established patterns such as Bounded Context or Anti-Corruption Layer which are available to “discover” in our design process.
We might enhance our domain discovery process using a technique such as Event Storming.
These techniques feel like they support a Forward-working strategy by assisting us “borrow” the expertise of the community which has grown up around them.
It feels to me that much of our discovery process, whether it’s DDD, Event Storming, or just TDD, often falls into the “uncontrolled search” strategy which Sweller considers to be unfeasible.
It’s certainly true that the problem spaces we work in contain “thousands of paths”, and yet somehow, we are able to effectively solve problems this way.
My theory is that although our problems spaces contain thousands of paths, we programmers also work in a field where there is no single correct path. A large percentage of the paths we may follow via “uncontrolled search” will result in a good solution which reduces the risk of waste.
Furthermore, in software we can work from a “good” solution to an “optimal” solution due to the fluidity of our working material.