Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put specs for Clojure.Spec?

So, I'm diving deeper and deeper into Clojure.Spec.

One thing I stumbled upon is, where to put my specs. I see three options:

Global Spec File

In most examples, I found online, there is one big spec.clj file, that gets required in the main namespace. It has all the (s/def) and (s/fdef) for all the "data types" and functions.

Pro:

  • One file to rule them all

Contra:

  • This file can be big
  • Single Responsibliy Principle violated?

Specs in production namespaces

You could put your (s/def) and (s/fdef) right next to your production code. So that, the implementation and the spec co-exist in the same namespace.

Pro:

  • co-location of implementation and spec
  • one namespace - one concern?

Contra:

  • production code could get messy
  • one namespace - two concerns?

Dedicated spec namespace structure

Then I thought, maybe Specs are a third kind of code (next to production and test). So maybe they deserve their own structure of namespaces, like this:

├─ src
│  └─ package
│     ├─ a.clj
│     └─ b.clj
├─ test
│  └─ package
│     ├─ a_test.clj
│     └─ b_test.clj
└─ spec
   └─ package
      ├─ a_spec.clj
      └─ b_spec.clj

Pro:

  • dedicated (but related) namespaces for specs

Contra:

  • you have to source and require the correct namespaces

Who has experience with one of the approaches?
Is there another option?
What do you think about the different options?

like image 282
DiegoFrings Avatar asked Jun 21 '16 10:06

DiegoFrings


3 Answers

I usually put specs in their own namespace, alongside the namespace that they are describing. It doesn't particularly matter what they're named, as long as they use some consistent naming convention. For example, if my code is in my.app.foo, I'll put specs in my.app.foo.specs.

It is preferable for spec key names to be in the namespace of the code, however, not the namespace of the spec. This is still easy to do by using a namespace alias on the keyword:

(ns my.app.foo.specs
  (:require [my.app.foo :as f]))

(s/def ::f/name string?)

I'd stay away from trying to put everything in one giant spec namespace (what a nightmare.) While I certainly could put them right alongside the spec'd code in the same file, that hurts readability IMO.

You could put all your spec namespaces in a separate source path, but there's no real benefit to doing so unless you're in a situation where you want to distribute the code but not the specs or vice versa... hard to imagine what that'd be though.

like image 163
levand Avatar answered Nov 18 '22 02:11

levand


In my opinion the specs should be in the same ns as the code.

My main consideration is my personal philosophy, and technically it works out well. My philosophy is that the spec of a function is an integral part of it. It isn't an extra thing. It's definitely not "two concerns" as put in the OP. Actually, it's what the function is, because in terms of correctness: who cares about the implementation? who cares what you wrote in your defn? who cares about the identifier? who cares about anything but the spec?
I think it's weird because not only clojure.spec came later, also most languages will not let you have specs as an integral thing even if you wanted it to be, and anything close (tests in the code perhaps) is usually frowned upon, so of course it seems weird. But give it some thought, and you might reach a conclusion like I did (or you may not, this part is opinionated).

The only good reasons I can think of as to why you wouldn't want specs in the same ns are these two reasons:

  1. It clutters your code.
  2. You want to support Clojure versions pre Clojure 1.9.0.

As for the first reason, well, again, I think it's an integral part of your functions. If you find that it really is too much, my advice would be the same as if your ns would be too cluttered up regardless of spec: see if you can split it up.

As for the second reason, if you truly care, you can check in code if the clojure.spec ns is available, if not then shadow the names with functions/macros that are NOP. Another option is to use clojure-future-spec but I haven't tried it myself so I don't know how well it works.

Another way this works out well technically is that sometimes there's a cyclic dependency that you cannot handle with different namespaces, e.g. when your specs depend on your code (for function specs) and your code depends on your specs for parsing (see this question).

like image 29
MasterMastic Avatar answered Nov 18 '22 02:11

MasterMastic


One other consideration depending on your use case - putting the specs alongside your main code limits use of your code to Clojure 1.9 clients, which may or may not be what you want. Like @levand, I would recommend a parallel namespace for each of your code namespaces.

like image 8
Colin Avatar answered Nov 18 '22 02:11

Colin