Using spec for Function Contracts and Instrumentation

Use `clojure.spec` to describe function shapes and enable instrumentation where it improves development feedback.

spec (clojure.spec.alpha): A Clojure library for describing the shape of values and functions, generating sample data, and instrumenting code during development and testing.

Using spec for function contracts is one of the clearest ways to make expectations explicit in a data-oriented Clojure system. But it also needs realistic framing: spec is not a static type system, and instrumentation is not something you blindly leave enabled everywhere in production.

The value comes from writing clearer contracts and getting better feedback in development, tests, and exploratory work.

What Function Specs Are Good For

Function specs help describe:

  • the expected arguments
  • the shape of the return value
  • relationships between inputs and outputs when needed

That makes them useful for:

  • catching bad calls early in development
  • documenting function expectations in executable form
  • generating data for tests
  • making data-heavy boundaries easier to reason about

A Small Example

 1(ns app.discount
 2  (:require [clojure.spec.alpha :as s]
 3            [clojure.spec.test.alpha :as stest]))
 4
 5(defn apply-discount [amount percent]
 6  (* amount (- 1 percent)))
 7
 8(s/fdef apply-discount
 9  :args (s/cat :amount pos-number?
10               :percent (s/and number? #(<= 0 % 1)))
11  :ret number?)

This already gives you more than prose comments do:

  • the argument contract is machine-readable
  • tooling can inspect it
  • instrumentation can enforce it during runtime checks in dev/test

What Instrumentation Does

Instrumentation wraps function calls so spec checks whether the arguments satisfy the declared contract.

That is especially useful when:

  • APIs are changing quickly
  • data enters from many sources
  • tests should fail early on bad inputs
  • the REPL is being used heavily for exploration

It is less useful when developers start treating it as a full replacement for careful domain modeling or targeted validation logic.

What spec Does Not Give You

It is important to be precise:

  • it does not replace all runtime validation needs
  • it does not give full static guarantees like a typed language
  • it does not automatically make bad data modeling good

This keeps the lesson grounded. spec is a powerful descriptive and checking tool, but it still sits inside a broader design discipline.

When To Use It

spec is especially helpful at boundaries:

  • public functions in a library
  • data ingestion edges
  • protocol adapters
  • core business functions with meaningful contracts

It is often less valuable on every tiny private helper. Over-specifying everything can produce a lot of maintenance noise without proportionate clarity.

Common Mistakes

The first mistake is treating function specs as if they were free documentation. If the spec is vague or stale, it becomes another source of confusion.

The second mistake is instrumenting everything without considering overhead or developer workflow.

The third mistake is using spec where a smaller local predicate or explicit validation function would be clearer.

Design Review Questions

Ask these when reviewing spec usage:

  • Is the function important enough to deserve an explicit contract?
  • Does the spec actually explain the shape better than prose would?
  • Should instrumentation run in this context, or only in dev/test?
  • Is spec clarifying the model, or compensating for a weak one?

Good spec usage sharpens contracts. Bad spec usage adds ceremony without improving understanding.

Loading quiz…
Revised on Thursday, April 23, 2026