REPL driven development is the foundation of working with Clojure.
The REPL is an instant feedback workflow that continually runs your code without the need to manually run a complete compile-build-run cycle.
The REPL contains your live application to which you interact with by calling (evaluating) code. A single expression can be called to focus in on its behavior and see the results. Even when evaluating a whole namespace, each expression is evaluated by itself in turn, within the REPL process.
Coding with a REPL provides fast feedback as design decisions are encoded, giving every opportunity to testing assumptions driving those design choices.
A REPL is typically used from an editor with files that compose the project code base. A Clojure aware editor can evaluate code from the source code files to and display the results inline.
{% youtube %} https://youtu.be/rQ802kSaip4 {% endyoutube %}
The (comment ,,,)
function is used to included code that is only run by the developer directly. Unlike ;;
comments, specific expressions inside a comment block can be evaluated in a Clojure aware editor to help the developer work with a project.
Rich comment blocks are very useful for rapidly iterating over different design decisions by including the same function but with different implementations. Hide clj-kondo linter warnings for redefined vars (def
, defn
) when using this approach.
;; Rich comment block with redefined vars ignored
#_{:clj-kondo/ignore [:redefined-var]}
(comment
) ;; End of rich comment block
The expressions can represent example function for using the project, such as starting/restarting the system, updating the database, etc.
Expressions in rich comment blocks can also represent how to use a namespace API, providing examples of arguments to supply to further convey meaning to the code.
These rich comment blocks make a project more accessible and easier to use.
The "Rich" in the name also refers to Rich Hickey, the author and benevolent dictator of Clojure design.
It is highly recommended to journal your design process to make your code easier to understand and maintain. Journals avoid the need for long hand-over or developer on-boarding processes as the journey through design decisions are already documented.
It is recommended to create a Design Journal section at the bottom of each namespace. This journal should cover
- All the important REPL experiments used to create the resulting namespace code.
- Discussions of design choices, including those not taken and why.
- Expressions that can be evaluated to explain how a function or parts of a function works
The design journal can be used to create meaningful documentation for the project very easily and should prevent time spent on repeating exactly the same conversations.
Design Journal for TicTacToe game using reagent, clojurescript and scalable vector graphics
Use Pretty Print to view data structures that are the result of evaluating your code. This makes those data structures easier to parse as a developer and more likely to notice incorrect results.
Clojure Data Browsers (cider-inspect, REBL, Reveal, Portal) provide effective ways to navigate through a nested data structures and large data sets.
Your editor should automatically apply formatting that follows the Clojure Style guide.
Continuous linting with clj-kondo significantly reduces a wide range of bugs and syntax errors as they happen, speeding up the development process.
Eastwood also provides linting and is typically used as a batch process before a code commit or as part of continuous integration.
Kibit provides suggestions to help ensure idiomatic Clojure.
Test Driven Development (TDD) and REPL Driven Development (RDD) complement each other as they both encourage incremental changes and continuous feedback.
RDD supports rapid design as different approaches can easily be explored and evaluated. and tests focus the results of those experiments to guide delivery of the correct outcomes.
Tests provide an simple tool to define and test your assumptions from the evolving design and give you feedback when changes break that design.
Unit tests should support the public API of each namespace in a project to help prevent regressions in the code. Its far more efficient in terms of thinking time to define unit tests as the design starts to stabilize that as an after thought.
clojure.test
library is part of the Clojure standard library that provides a simple way to start writing unit tests.
Clojure has a number of test runners available.
Wire up a continuous integration service that runs tests and builds code on every shared commit (or every commit if you run a CI server locally).
CircleCI provides a simple to use service that supports Clojure projects.
Defining a deployment pipeline provides an efficient way to deploy applications and also get fast feedback from a wider range of stakeholders and users, especially when spin up testable deployments of your application based on commits (i.e. push to shared develop or feature branch).
Ideally the deployment should run via continuous integration service to ensure all tests pass before deployment.
There are few novel features of programming languages, but each combination has different properties. The combination of dynamic, hosted, functional, extended Lisp in Clojure gives developers the tools for making effective programs. Less well understood are the ways in which Clojure's unique combination of features can yield a highly effective development process.
Over more than a decade, we have developed an effective approach to writing code in Clojure whose power comes from composing many of its key features. As different as Clojure programs are from e.g. Java programs, so to can and should be the development experience. You are not in Kansas anymore!
This talk presents a demonstration of the leverage you can get when writing programs in Clojure, with examples, based on my experiences as a core developer of Clojure and Datomic.
{% youtube %} https://youtu.be/Qx0-pViyIDU {% endyoutube %}