We always know less than we should. Creating products is a constant movement into a better understanding of our business needs, market demand, and technology constraints. We embark on a new project adventure to understand and solve a problem. We are so full of ambition and oh so naïve!
Our clients come in full of excitement, too. And maybe even bring some pain and baggage from previous (or current) experiences with technology and the creation of it and their ideas of how products are made. We bring to the table all of our ideas of how the client works, or how their industry operates.
But here’s the thing: we have an incomplete and inaccurate view of what we’re trying to express in very detailed instructions for a computer to execute. We work in languages and systems that are frought with side effects. Even if documentation was complete and perfect, our understanding is not. It is our understanding that is required to author the code and move the bits around to build these products.
The tools and processes and patterns we employ should embrace naïvety, changing requirements, and evolving understanding.
Declarative and Imperative UIs
I’m speaking primarily to the user interface of products because this is where I spend the majority of my time. I can’t speak with confidence to domain models and distributed systems, though I suspect that anyone with some expertise in these other worlds would echo these sentiments.
Imperative User Interfaces
Imperative programming—which is the most likely kind of programming that you do—does not generally take misjudgements with much grace. Imperative programming is when you painstakingly write each step of the process for the computer. In browser-UI land this might look something like the following:
- Render each of these email messages with this exact DOM structure.
- Stick that structure into a DOM node with the id
(2b. Hope there aren’t more than one DOM nodes with that id.)
- When you hear a click event on an email message, replace the DOM node with
email-messageswith the DOM structure for an email message that looks like this (pretend there is a complex HTML template here)
- Decrement the unread count on the sidebar.
Cake, right? Now at any given point in time—could be the same general time by another developer, or by yourself in a week when you return to this code—the backend now has an API to get the unread count that you use to populate that data. Without changing the previous code at all—maybe even completely unaware of its existence—the following instructions are added to the codebase:
Every 10 seconds fetch the unread count
Grab the unread count from the response and render it in the sidebar.
We could keep going with evolving requirements like showing the unread count in the favicon, or show the unread count alongside the username in the header bar, but all of these situations are only more examples of the current bugs in our simple system.
What guarantee do you have that the unread count in the left navigation is correct?
None. At all. The source of truth for this application is non-existent and arbitrary pieces of our application code can touch anything else. You may argue this problem is confined to hostile runtimes like the browser, but I would open up your bug tracker and point out the many scenarios where you have issues due to similar practices.
Declarative User Interfaces
Using a declarative programming model, the UI becomes a projection of the app state. I am of the opinion that a UI is the expression of your product, which is all of the business logic that culminates in a given application state.
Twitter’s core product is not twitter.com, or the Twitter iOS application. Their product is the network of connections and conversations you have and are able to tap into. Twitter.com, Twitter for iOS, etc. are interface expressions to their product.
Last year at feops conf, Seth Walker made a comment about the throwawayability of UI code. Etsy is a heavily data driven company that is running numerous experiments at all times. The ability to write a local UI experiment that doesn’t bleed into other aspects of the application are extremely important.
A global and stateful environment like the DOM and CSS actively work against you attempting to do this at scale. At Skookum, we found that React helps us tackle concerns at the appropriate level of abstraction. This is a topic you will see us talking about more in the future, and much has been written by others on the subject already.
With a declarative paradigm, the previous example becomes something more akin to this:
- App state -> UI
- Change app state due to that 10 second interval AJAX call -> UI
All state should flow through the same pipeline to generate a predictable result. React and Flux are not the first to these ideas, but they are modern implementations of these ideas; implementations that operate well in stateful environments and at scale.
Service-oriented architecture (SOA) is the approach we’ve (we referring to our industry, not only us at Skookum) been taking to build our back-end services that power our businesses. Marrying ourselves to one technology or one codebase has not panned out on the server, and it hasn’t worked out on the client, either.
Are the tools we’re creating and choosing to implement helping or hindering us from the goals we are aiming to achieve? While some frameworks and tools help us write less, do more, and move faster, other frameworks help us think more, consider more, and comprehend more.
Time and again React.js and kin have provided sane evolutionary paths forward from poor judgements, whereas every other tool I’ve used did nothing to protect me from myself.
For Humans, By Humans.
We create for humans. Our creations manifest themselves through computers. But we don’t write code for computers. We write code for humans. The humans who come after us to maintain systems. To evolve systems to new business requirements. To evolve systems to new technology requirements.
Let’s create and use tools that make us better, stronger, safer. Let’s not simply continue to make the same buggy software faster.