Logging functions have to be impure to be useful. If they don't change the state of the world around them by writing something somewhere, why would you use them? This makes any function that uses a logging function directly impure too. If that is something you want to avoid, you could inject a logging service and use that instead of the logging function. Let's do that and see what challenges we come across.
Consider the following function, which
(defn update-gift [{:keys [datasource]} request]
(let [{:keys [external-list-id external-gift-id]} (:path-params request)
{:keys [name ok price description]} (:params request)]
(when ok
(domain/update-gift! datasource external-gift-id name price description))
(response/redirect (str "/list/" external-list-id "/edit") :see-other)))
The function domain/update-gift!
persists the changes to the database. It has a side effect, which makes it an impure function. Because update-gift
uses domain/update-gift!
, it's impure too.
You could argue that this fact alone is a reason to refactor this code. Generally speaking, pure functions are easier to test and easier to reason about, which are both good reasons to prefer pure functions over impure ones.
When I used to work on front ends based on JavaScript or TypeScript, I usually had Karma running in watch mode while developing. Each time I saved a file, all (unit) tests would run. This would give me a short feedback loop, letting me know quickly when I was unintentionally breaking things and constantly indicating whether what I was creating matched its specifications as defined by the tests. In other words, tests were used to prevent regressions, but also as a tool to quickly see whether I was building the right things.
In the last few years, I've been using Clojure and ClojureScript to create prototypes and utilities at work as well as hobby projects and apps for personal use. Because of the size and nature of these applications, I wasn't too worried about regressions. Because Clojure and ClojureScript have excellent support for REPL-driven development, the need for tests as a means for quick feedback also disappeared. As a result, I wrote a few tests for these applications, but not nearly as many as I used to.
Deep down inside, however, I knew I would have to invest some time into learning more about testing Clojure and ClojureScript applications at some point. I wouldn't want to work in a team that produced software without decent test coverage. I should hold myself to the same standard. This week, I decided to sit down and take some time to look into different ways to execute tests for ClojureScript apps powered by shadow-cljs. As you may know, shadow-cljs is one of the two de facto standard tools for creating ClojureScript apps. The other is Figwheel.