The easiest way to create a non-simple system is to not understand the problem when starting to write the solution.
Rather than become a theory of that problem, the system becomes the history taken towards solving that problem. It documents every path, every side-path and every intersection taken.
A Jack Rusher tweet suggests “when creating something new, create it off to the side”.
We do need to create solutions in order to understand problems. However, we can choose not to set the early solutions in stone, and make our system depend on their flaws. If we choose to create the new thing separately, we can choose which lesson to take from it. Did we just need the problem understanding? Or can we take the solution?
By being explicit in how we increase the weight of the system, we avoid accidental weight, accidental complexity.
This slower form of system building aims to keep a system simple rather than refactor the system towards simplicity.
But there’s still a tension. We need to get started! We need to get version one off the ground! How shall we manage that?
In short, we can either
first encode bad theories of problems, then fix them. This is the technical debt → refactor pattern.
or first aim to understand problems, then, later, choose which parts of our understanding to set in stone. This is the explore → understand → engineer pattern.
I feel torn between the two. The desire to make it good gets the better of me. I want to make it really good! But there’s also plain implementation work.
Anyway, we’ve got two options,
Choose either, but please choose explicitly.
I prefer simplicity from the start, but simplicity is hard. Stopping, pondering. Seeing that I add complexity, coupling and inessential details. It pains me! The critic judges, and prevents me from finishing.
We can live in ideals, or live in reality. Not finishing is not good. Not landing on something real is not good.
Theory exists abstracted from practice. Judging whether a theory is useful requires interpretation. But not all that is useful is theory.
I’ve been meddling with Datastar in Clojure for more than six months, and I’m conflicted. I want to make something simple and beautiful. But I can’t just see that beautiful result. I’ve tried! I’ve caught glimpses of it, but not seen its whole. That’s where I am right now. I have a start. It’s data driven and REPL friendly, but incomplete. It lacks machinery.
Perhaps that’s just it. I’ve experienced, and found aspects of the good theory. Now, it’s time to put the theory to the side and build machinery. Deliver.
To deliver, constraints are helpful. Part of the difficulty is how to make multi-page setups REPL friendly. And I have chosen to delay the multi page part. Just make the single page work! Just append details to the router, for now. Just append details to the html body, for now.
The index page as been subject to details-appending already. Have a look;
(defn index-page []
(hiccup/html5 {}
[:head
[:title "Steinars tekster"]
[:meta {:charset "utf-8"}]
(assets/id->hiccup :assets.css/vars)
(assets/id->hiccup :assets.css/style)
(assets/id->hiccup :assets.js/datastar)]
[:body
[:h1 "Steinars tekster"]
[:p "Å være er å lære."]
[:count-control
[:div [:button (commands/on-click :commands/dec) "dec"]]
[:div (str @!count)]
[:div [:button (commands/on-click :commands/inc) "inc"]]]]))It does not have a single responsibility!
It does it all! And my judgement? Just add more stuff. Heh. What a world.
but there’s only one. Only one page. So there’s no cluttering of assumption-tentacles all around the code base.
In that sense, it is off to the side. Referenced only one place, from the router. The router (http request handler) and the command handler are the rest of this namespace,
(defn handle-cmd [cmd _req]
(case (:kind cmd)
:commands/dec
(swap! !count dec)
:commands/inc
(swap! !count inc)
nil)
{:status 202})
(defn handle-req [req]
(let [{:keys [uri request-method]} req]
(cond
(= uri "/")
{:status 200
:headers {"Content-Type" "text/html; charset=utf-8"}
:body (index-page)}
(contains? assets/by-uri uri)
(assets/serve (assets/by-uri uri))
(and (= request-method :post)
(contains? commands/by-uri uri))
(handle-cmd (commands/by-uri uri) req)
:else
{:status 404
:body "Not found"}))), which I’d say isn’t too bad.
Simplicity is good, but not always the answer. Building theory is part of knowledge work. Sometimes it’s the right way to spend time.
But we still need to explore. Exploration is messy. But exploration teaches us things we don’t know. It exposes us to new skills we may learn.
Because, in the end, you can’t derive an “ought” from an “is”. Yeah, it’s simple, but how ought you spend your time? Simplicity is not the end. Simplicity is the beginning of your next move. Simplicity is The Beginning of Infinity.