Skip to content

Private or not to private #63

Open
@practicalli-johnny

Description

@practicalli-johnny

Braindump of thoughts on using private vars, private functions and names of data.

Consider why you wish to make things private.
As Clojure uses immutable data, there is less reason to hide data. So the private concept is not as relevant in Clojure.
It is mostly used as documentation rather than a constraint on accessing certain parts of code (which can still be accessed when private)
Metadata forms part of the function documentation, so is more useful that creating a macro (e.g. defn- macro) to wrap each of the many ways to create a var.

I tend to just define helper functions that contain code that would otherwise be shared by several functions. This is the main reason I would consider using private.
If there are several helper functions, these can then be moved into a separate namespace.
I test shared functions through the functions that use them, minimising the number of tests whilst still getting effective test coverage.

defining things as private can be worked around by specifying the var name, typically done when writing tests for private functions (something that can be avoided by only testing public functions).

One approach is to have a naming scheme to denote names that should only be used within the current namespace. A subtle naming is to simply add - at the end of the name. This also shows up in autocomplete popups (as it's part of the name). Although this is simple, I didnt find it that useful in the long run

I also organise my code in sections using comment blocks, e.g system, helper functions, domain model, public API functions. An additional benefit of this is it makes separating code into different namespaces a trivial thing, as you have already demarcated sections of your code. It's easy to visually see when a section has evolved enough to merit its own namespce.

Linters will highlight unused helper functions when defining them with defn
Sean Corfield:
I've gone back and forth on this. I've found that if you use a linter like Joker in real time in your editor, having defn- for implementation functions is very useful when you're refactoring since unused functions get highlighted immediately. But I like being able to easily test (some) of those functions and sometimes they become useful to other namespaces.
So my baseline tends to be: in a library, make everything public by default, organize by namespaces to indicate "implementation" details and either use ^:no-doc or explicit wording in the docstring to discourage use of functions you do not consider part of the API. But in application code, use private by default and make things public as needed over time (and pretty much only ever test public functions still).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions