When I got to this point of the series, I expected to be writing about some interaction testing - checking that our messages triggered the right transitions between screens.
But as I started writing the code, I found it wasn’t hanging together well.
In order to make new tests pass, I was adding new handlers to the existing reducers. Some messages required modifying the
ScreenReducer - and others required modifying the
Nothing wrong with this - it’s essentially what I expected when I made the split.
Except that I found two worrying problems.
The first was that it wasn’t always straightforward to predict which reducer needed modification in order to process a given message. Such a violation of the Principle of Least Surprise didn’t bode well for the future maintainability of the code.
The second was that I found some occasions where I needed to transplant a handler from one reducer to the other. In one case I found that a minor change to a handler required me to completely relocate it from the
ScreenReducer to the
WordTutorApplicationReducer. Needing to make major changes to accommodate minor improvements, well, again this doesn’t seem like a good situation.
To remedy these problems, I had to revisit the design of our reducers.
We still want to avoid having all the handlers written in a single reducer because that would be a maintenance nightmare. The current split between the two reducers was based on which part of the application state would be affected by the message. This seems to be the key error.
Therefore, I’ve changed things up to instead split the functionality based on the currently active screen. For each distinct screen, we’ll create a dedicated reducer. To make things predictable, we’ll name each one for the associated screen, with the suffix Reducer added.
AddVocabularyWordScreenReducer demonstrates the pattern. We first check to see if the current screen is the expected one, and we exit early if it isn’t. This serves to ensure our reducer only takes action when the expected screen is present.
By leaving this decision inside the Reducer, I’ve left the door open for future reducers that work across multiple screens, should we find a good reason to introduce them.
Next, we switch, based on the type of the message.
If we get a message type that we don’t expect, we just return the status quo.
The other reducer,
VocabularyBrowserScreenReducer follows the same structure.
Now the location of each message handler is quite predictable. No need for future maintainers (or us!) to go on a quest across the codebase, looking for the handler required. Instead we can go directly to the right reducer, based on knowledge of which screen is active.