I've written many post concerning writing code this way or another. Same applies to writing tests - all kind of tests you can possibly imagine. But there's one kind of software development process output I've been notoriously avoiding: documentation.

This topics is bit touchy - we all know the most popular statements / opinions regarding documentation these days:

  1. Clean code doesn't need documentation (AKA self-explanatory code)
  2. Interface(s) should serve as the formal specification & ...
  3. ... tests as its representative use cases (TDD/ATDD/BDD for different levels of abstraction)

Every one of you have also heard about typical flaws of hand-written, traditional documentation, namely (no worries, it's gonna be brief):

  • hard to maintain, quickly gets out-of-date
  • no means to force full-sync of code & docs
  • hard to meet information needs for different levels of abstraction without getting too voluminous & repetitive
  • etc.

Ok, so we're all on the same page here, but why have I brought you here then? To whine on how hopeless we are in terms of docs? Nah, actually to share some nice ideas for reviving the idea of documentation I've recently encountered :)

doctest

If you give automated tests a thought, one of the key enables to make them work & bring actual benefit was to treat them as the integral part of code - they are supposed to be kept in the same source control repo & even committed together with the actual, functional code. This way, changes in code are always accompanied by changes in tests - CI loop takes care of verifying whether they match each other.

Why can't we do the same with documentation then?

Actually, that's what doctest is trying to do (the original version is for Python, but Elixir has something similar already as well):

  • you write docs directly in code (Doxygen / Javadoc style, using special tags; each section is bound to code construct - in Elixir case: module, function, etc.)
  • to illustrate how particular code construct works, you put some examples with their expected output in documentation section (yes, again - in code) and ...
  • this sections gets executed as a normal automated test (in CI loop) & its output is validated against what has been put into documentation section as the expected outcome

Simple & effective - once it gets out-of-date (API changes, result changes, etc.) your test runner / CI loop will immediately let you know. Plus: you've got your documentation kept together with the code itself, so you don't have to look for it. Minus: the structure of your documentation is directly corresponding to low-level code constructs - that's useful but not in 100% of cases.

Example

  @doc """
    Just a basic sum, with some default params (1st & 3rd). Here go tests:

    iex> SomeModule.sum(1,2,3)
    6

    iex> SomeModule.sum(2)
    12

    iex> SomeModule.sum("A")
    ** (ArithmeticError) bad argument in arithmetic expression
  """
  @spec sum(number() | nil, number(), number() | nil) :: number()
  def sum( a \\ 3, b, c \\ 7) do
    a + b + c
  end

To make this spec run altogether with other tests, you just:

defmodule SomeModule.Test do
  use ExUnit.Case
  doctest SomeModule

  # here come your 'normal' tests

end

Go

The language with the worst name ever aka Go aka Golang goes even further :) I'm not an expert (my Go-fu is still weak, I have to admit ;/), but AFAIK (I still may have been told rubbish), using godoc is an obligatory part of the syntax. If you're interested in details, you can start digging here.

The benefit of such tools (Elixir's doctest, Go's godoc) is enormous - it's not that you're able to test your docs as automated tests & read formatted, linked documentation in human-readable form (usually HTML) -> the generated output fully integrates with platform's tooling to provide the seamless help experience. For example, in Elixir (IEx) documentation generated with doctest doesn't differ at all from standard library documentation: they are accessible together in the very same way.

λ iex.bat
Eshell V7.1  (abort with ^G)
Interactive Elixir (1.1.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c(".\\lib\\some_module.ex") 
[SomeModule]                        
iex(2)> h SomeModule                
* SomeModule                        
                                 
  This is documentation for SomeModule         
                                 
iex(3)> h SomeModule.my_sum           
* def my_sum(x \\ 1, y \\ 1)       
                                 
  This function calcs a sum ;P              
                                 
iex(4)>                          

Part II can be found here

Pic: © imagewell10 - Fotolia.com

Share this post