Preferring repetitive Action notifications over reuse

Have you ever noticed yourself going to unnecessary lengths to avoid repetition in your code despite the fact that requirements actually represent a complicated world? I swear the longer I write code in the industry the more DRY feels like a leaky ideological imposition that drifts from “knowledge” duplication into code/documentation – ie text artifact – duplication. Sigh. This is a nuance we lose too easily by the ad nauseam peddling of the shorthand DRY; which I suppose also reveals the danger of the supremacist tendency of the axiomatic; axioms all-to-easily claim ontological root. Especially in the minds of early career folks or folks emerging from industry founding fathers cargo cults.

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

The Pragmatic Programmer, [emphasis mine]

As we’re tapping along in an editor, the code that bears some resemblance to other code is, in fact, the precise nuance our applications need to create the richness of these membranes we build between people and machines. Here’s an example slightly altered.

Let’s say we are working with an events management system where a new feature is being built to allow event organizers to administer marketing campaigns on their events. In the current world, an event organizer can do two things to a campaign once it’s added to the event:

  1. Activate the campaign.
  2. Select the campaign to be “featured” – this places the campaign into a larger sitewide collection of campaigns that are featured in certain areas of the product.

A sketch of the stateless component in our graph might look like so (just relevant JSX and event handler):

Then Product decides to add a new nicety whereby an event organizer, when activating a marketing campaign, will automatically trigger marking that campaign with the special “featured” flag if that campaign happens to be the only active marketing campaign on the event. This is effectively a third flow; of course to the user it’s just a list item with a couple check boxes (simple stuff, right?).

As the developer I might think: this doesn’t seem all that hard! I already had the foresight to implement a flexible indirection – a consolidated event handler to coordinate the store notifications for user interactions in one place. This means all I have to do is flavor the conditional will another branch; and I can reuse the current dispatchers. I’m a fucking oracle.

Whereby I try:

I mean, that’s the right logic. All my test mocks are reusable. We are sailing along.

Sort of.

Then something nags at me looking at this code. It’s subtle, but there’s a risky assumption embedded here. Inside the interior if-then branch under the conditional expression hasNoActivated, we are sending a sequence of two distinct mutative notifications to our data store; moreover, the second depends on the first to complete successfully. Now, we might test this code in the browser and it works every time. Our tests pass.

But.

For how long?

This code lives downstream of our next indirection – the container – that shepherds these messages to the store. So this code is trying to coordinate a subroutine of effectful functions (Actions) three (perhaps more) layers away from the store itself.

Which begs some questions. For example, although the action notifiers I’m calling are synchronous, how confident am I in the inner workings of my component framework’s render engine and algorithm – particularly it’s collaboration with my data layer (ie Redux)? I might be forcing additional renders here. More importantly, how much can I trust other developers to maintain the correct sequencing in the container? Will a test help? Will a comment help? Will either of these prevent mistakes when additional behavior needs to be added.

100% maybe.

Software is weird. Code like this could become a productive, yet forgotten, corner of entropy. But silent risks, if recognized, must be avoided. We can do better, although part of reaching this better is yielding our mental model to become more reactive – to adapt, to embrace nuance, to become loose as the requirements become more complicated. My observation is that the code is not lack for defensive guards and documentation, but lack for an expanded set – single, unambiguous, authoritative – of blessed possibilities with distinct articulations. In other words, an expanded model – expressed in code – that models more precisely the desired possibilities of what a user can do.

Let’s try rewriting our component-level event handler to incorporate the new happy path into our domain’s lexicon: the Action notifiers, the names of things “that depend on when they are called or how many times they are called” (see Grokking Simplicity).

The modifications in this third example add a new Action notifier (handler) that represents the distinct type of mutation we want to effect against the store. In other words, a function that satisfies the new Product case that narrows attention – of the developer and program – on the new case by leveraging a semantic, structural, change to the number of container handlers. (Side note: I fantasize that Zachary Tellman might describe the new handler name as a too “natural” yet still “consistent” name (see Elements of Clojure).

Furthermore, let’s observe that the payload semantics remain the same. Which means our data layer comprised of the container and action dispatching, only requires similarly modest adjustments to shepherd the same data. Lastly, though quite importantly, our container is now capable of dispatching messages to the store and effecting a mutation in just a single pass if we wish it. For example:


updateCampaignAndUpdateFeaturedCampaigns(data) {
    dispatch(updateCampaigns(data));
}

// Whereby our reducer might return the state mutations composed as:

{
    campaigns: {
        ...state.campaigns,
        {
            id: payload.id,
            activated: true
            // other fields...
        }
    },
    featuredCampaigns: [
        ...state.featuredCampaigns,
        payload.id
    ],
}

Overall this design provides increased confidence that this display component, our presentational leaf node, is more precisely responsible for render and event notification. Perhaps the subtweet here is keeping events and messages 1:1, even if you wind up with similar or duplicate lines of code.

Ross Chapman @internetross