Many people have praised re-frame, with good reason: it can really help in developing rich browser apps, by providing a principled approach to state management (and documenting it extremely well).
I’d like to do something different here: discussing how the design of re-frame can encourage some forms of accidental complexity, and how to mitigate them.
Let me be very explicit: this is not another “is re-frame good?” discussion.
Here are some ‘threats’ of complexity which I’ve identified:
Proliferation of names. In addition to UI components, re-frame makes you name subscriptions and state transitions. As is well articulated in Elements of Clojure, naming too many things is an often-overlooked cause of complexity: choosing good names is difficult, and we should avoid it when we can.
Over-specificity. It’s not very straightforward to make generic UI components or state management logic in re-frame.
AFAICT, one reason for that is that we are naturally inclined to hard-code specific app-db paths in components and subscriptions: one mitigation strategy is to turn these paths to parameters.
Another potential reason for that issue is the following:
Lack of expressiveness of data-oriented state management. Subscriptions and state transitions are expressed in a data-oriented language, which more predictable but less expressive than Clojure: less expressiveness can result in less composability, less reusability, and less abstraction power.
For example, I’ve often wished for (and guiltily implemented) an
:re-frame.effect/update-in handler, which you call with an anonymous function.
The way I see it, one mitigation for that is too just give up on some of the data-orientation, as in the above example: parameterize your subscriptions and effects with non-data objects such as functions. You will use some transparency / predictability benefits of your effect, but the result is that it prevents the implementation of 10 effects that all do almost the same thing, it’s probably a win.
Global environmental coupling. The
app-db in re-frame is a global singleton. This can make it hard to implement components with isolated effects, e.g for testing or pre-rendering.
The forms of complexity that re-frame combats are well-known: re-frame encourages an approach to state management that is very functional, centralized, and data-oriented, through guidelines that are easy to follow.
Several of the pitfalls I’ve identified (“proliferation of names”, “over-specificity”, “lack of expressiveness”) might sound familiar: they are well-known issues with class-based programming languages.
And indeed, as had happened with class-based languages, I fear that re-frame users who embrace its principles too readily might succumb to unanticipated forms of accidental complexity, through what I’d call the "fallacy of ceremony": the illusion that, because you’re continuously using constructs that stem from a principled design, you are necessarily writing a well-designed program.
The way I see it, that’s the fault of the user more than that of the tool, and the solution is to keep an alert and critical eye for these complexity pitfalls… and occasionally make the required transgression to re-frame’s principles. I hope such discussions can improve the awareness of such pitfalls.
What do you think? What have I missed?