Skip to end of metadata
Go to start of metadata

Disclaimer:

  • Rules are made to be broken. Know the standards, but do not treat them as absolutes.

The Standards:

  • Get the name and signature right. Rich strongly respects Java's commitment to not break existing code. In practice, that means we can tweak the implementation forever, but once we publish a name and signature we need to stick with it. (In practice I think this means that we want many people to review the name and sig, even if they don't review the implementation details.)
  • Use type hints for functions that are likely to be on critical code; otherwise keep code simple and hint-free.
    • Only use type hints that matter. If you are not certain a type hint helps, don't add it.
  • Use good names, and don't be afraid to collide with names in other namespaces. That's what the flexible namespace support is there for.
    • OTOH, using the same name with a different signature or semantics begs the question as to whether one of them is less than ideal.
  • Be explicit and minimalist about dependencies on other packages. (Prefer :require :refer in 1.4+ or :use :only in 1.0-1.3).
  • Don't use a macro when a function can do the job. If a macro is important for ease-of-use, expose the function version as well.
    • Open question: should there be a naming convention here? A lot of places use "foo" and "foo*" or "bar" and "bar-fn".)
  • If you are sure you have all the information at compile time, use a macro where it would improve performance sensitive code. (The discussion on a macro for logging is instructive. We end up needing both versions because the information is only sometimes available at compile time.)
  • Provide a library-level docstring.
  • Provide automated tests with full branch coverage.
  • Unroll optional named arguments. Callers should not have to wrap optional named arguments in a map literal:

  • Use '?' suffix for predicates.
    • N.B. - predicates return booleans
  • Use '_' for destructuring targets and formal arguments names whose value will be ignored by the code at hand.
  • Include a docstring. Except: If your function is so self-explanatory that the docstring cannot do anything more than repeat the name and arglist, then do not include a docstring.
    • Personally I would remove some of the docstrings already in place.)
      • Will this hamper the HTML doc and other tools?
  • When in doubt, expose the performant version. Clojure goes to great lengths to enable performance when you need it, and lib should too. (That's why we don't have multimethod + in core, for instance.) Users can always create more polymorphic APIs on their own, hijacking symbols if they want to.
  • If you take a good name that collides with core, make sure your semantics are parallel (possibly minus laziness). Good example of this is string functions that shadow core seq functions.
  • Make liberal use of assert and pre- and post- conditions. This is not idiomatic today, but it should be going forward.
  • Be lazy where possible.
  • Follow clojure.core's example for idiomatic names like pred and coll.
    • in fns
      • f, g, h - function input
      • n - integer input usually a size
      • index - integer index
      • x, y - numbers
      • s - string input
      • coll - a collection
      • pred - a predicate closure
      • & more - variadic input
    • in macros
      • expr - an expression
      • body - a macro body
      • binding - a macro binding vector
  • Do NOT follow idioms from clojure.core's preamble code. That code runs in a limited environment because Clojure is not bootstrapped yet.
  • Decompose the pieces. If your name isn't Rich, don't write a form as long as, say, the definition of doseq.
  • Use keyword-first syntax to access properties on objects:

  • Use collection-first syntax to extract values from a collection (or use get if the collection might be nil).

    Note that not all collections are keyed by keyword.

  • Idiomatic code uses destructuring a lot. However, you should only destructure in the arg list if you want to communicate the substructure as part of the caller contract. Otherwise, destructure in a first-line let. Example: my snake code from the book fails this test, doing too much destructuring in arg lists.
  • Prefer updating over setting. Many reasons: the unified update model provides a simple standard way to do this. Helps you discover commutative operations. Reduces the surface area of assumptions you are making about the object you are updating.
  • Don't support operations on the wrong collection type. If your algorithm is only performant with random access, then require an arg that has random access.
  • Use *earmuffs* only for things intended for rebinding. Don't use a special notation for constants; everything is assumed a constant unless specified otherwise.
  • Use the bang! only for things not safe in an STM transaction.
  • Prefer sequence-library composition over explicit loop/recur.
  • Rebindable vars should be paired with scoping macros, e.g. in and with-in-str.
  • Lazy seqs should be exposed as functions that hold only the minimum state needed, a.k.a. "let go of your head." Let the caller decide how much local memory they want to use.
  • Use Klass/staticField, (Klass/staticMethod), (Klass.) and (.method obj) interop styles with the only exception being in code-generating-code where the older (. obj method) style may be easier to produce.
  • If you present an interface that implicitly passes a parameter via dynamic binding (e.g. db in sql), also provide an identical interface but with the parameter passed explicitly.
  • When providing a default case for cond, use the keyword :else as a condition instead of true
  • To access a private var (e.g. for testing), use the @#'some.ns/var form
  • Protocols:
    • One should only extend a protocol to a type if he owns either the type or the protocol.
    • If one breaks the previous rule, he should be prepared to withdraw, should the implementor of either provide a definition
    • If a protocol comes with Clojure itself, avoid extending it to types you don't own, especially e.g. java.lang.String and other core Java interfaces. Rest assured if a protocol should extend to it, it will, else lobby for it.
      • The motive is, as stated by Rich Hickey, to prevent "people extend protocols to types for which they don't make sense, e.g. for which the protocol authors considered but rejected an implementation due to a semantic mismatch.". "No extension will be there (by design), and people without sufficient understanding/skills might fill the void with broken ideas."
Labels: