>  Blog

Really Smart Comments in Your Code


(Rather, Documentation Generation using Docco)






Pradyumn Sharma

June 13, 2017



Comments: DOs and DONTs in Brief

First of all, a disclaimer: I have never been fond of writing extensive comments in source code. Nor do I expect others to write too many comments. If you write really clean code, your code should not require too much explaining to do.

For example:

  • If you have to explain what a class, a method, or a variable does, perhaps a better name would be more valuable than a comment.
  • If you have a function that is hard to understand because it does too many things, splitting it into smaller functions, with each function doing only one thing, is a better way than having to write plenty of comments.

Having said that, I am not against writing comments either. It would be naive to suggest that things like writing good names (for classes, functions, variables, etc) and writing small functions are enough to make code understandable.

Comments are valuable

  • to provide a high-level overview of key steps and workflow in your code
  • to explain inherent complexity of your implementation
  • to explain strategic choices made by you


Introducing Docco

The real intention of this article is to introduce docco, a really cool tool for generating documentation from the comments in your source code. It's so simple to install and run, that you can start using it in less than 15 minutes. I promise. And chances are that you will love it.

Docco is a free tool, originally developed for Javascript, and powered by Node.js. However, it works well with other programming languages too.

Docco extracts comments in your source code, and generates web pages from your source file, placing the comments in a readable manner alongside the code. Docco supports markdown to allow text formatting when building documentation.



An Example

In one of my earlier blogs on the Eight Queens Problem, I had described an algorithm and presented the Java code for finding the 12 distinct solutions to the problem, and printing those along with the derived solutions (90, 180 and 270 degree rotations; mirror image of the original and its 90, 180 and 270 degree rotations).

I reproduce the code from that post here, but embellished with comments that I believe make it easier to understand.

class: EightQueens.java

package app;

import java.util.ArrayList;

public class EightQueens {

	// Although the chess board is an 8 x 8 grid, we choose to represent it as a
	// unidirectional array of size 8. Each position in the array represents one of the rows,
	// and the value in each position represents the column within the row where we are
	// placing a queen. Thus, if the array has the following values: 
	//    [0, 4, 2, 1, 7, 5, 3, 6] 
	// then it means that 
	//    * in the 0-th row, a queen is placed in column 0, 
	//    * in the 1st row, a queen is placed in column 4, and so on.
	// Note: The above is not a valid solution; it is shown for illustration only.
	
	private static int [] board = new int [8];
	private static int distinctSolutionsCount = 0;
	private static ArrayList <SolutionRecord> allSolutions = new ArrayList <SolutionRecord> ();
	
	// We start by trying to place a queen somewhere in 0-th row.
	
	public static void solution () {
		tryQueenInRow (0);
	}

	private static void tryQueenInRow(int row) {

		// We consider every column from 0 to 7
		// If a queen in the position under consideration does not attack any queen in the earlier rows,
		// then we successfully place a queen there; further, if we are already in the 7-th row,
		// it means that we have found a solution, else we repeat the same process for the next row.
		
		for (int col = 0; col < 8; col++) {
			if (! attacksQueen (row, col)) {
				board [row] = col;
				if (row == 7)
					foundASolution (board);
				else
					tryQueenInRow (row + 1);
			}
		}
	}

	// **Does a position under consideration attack any existing queen?**
	// To determine that, check whether there is already any queen in the same column,
	// or in the left diagonal, or the right diagonal.
	
	private static boolean attacksQueen(int row, int col) {
		if (attacksInSameColumn (row, col) ||
				attacksInLeftDiagonal (row, col) ||
				attacksInRightDiagonal (row, col))
			return true;
		else
			return false;
	}

	// **Any existing queen in the same column?**
	// Check in all the rows above the current one.
	
	private static boolean attacksInSameColumn(int row, int col) {
		for (int i = 0; i < row; i++) {
			if (board [i] == col)
				return true;
		}
		return false;
	}

	// **Any existing queen in the left diagonal?**
	// Again, check in all the rows above the current one.
	
	private static boolean attacksInLeftDiagonal(int row, int col) {
		for (int i = 1; i <= row && i <= col; i++) {
			if (board [row - i] == col - i)
				return true;
		}
		return false;
	}

	// And finally, **any queen in the right diagonal?**
	// check in all the rows above the current one.
	
	private static boolean attacksInRightDiagonal(int row, int col) {
		for (int i = 1; i <= row && i <= 7 - col; i++) {
			if (board [row - i] == col + i)
				return true;
		}
		return false;
	}

	// **We have a solution**.
	// If this does not match an earlier solution, save and print the solution as well as all its variants.
	
	private static void foundASolution(int [] board) {
		if (!matchesAnEarlierSolution (board)) {
			saveAndPrintSolutionAndVariations (board);
		}
	}

	// **Does this solution match an earlier solution?**
	// Check in the collection allSolutions.
	
	private static boolean matchesAnEarlierSolution(int [] board) {
		for (int i = 0; i < allSolutions.size(); i++) {
			if (haveSameContents(allSolutions.get(i).solution, board))
				return true;
		}
		return false;
	}

	private static boolean haveSameContents(int[] a, int[] b) {
		if (a.length != b.length)
			return false;
		for (int i = 0; i < a.length; i++)
			if (a[i] != b[i])
				return false;
		return true;
	}

	// All right, so **it is a new distinct solution**.
	// Add a copy of the solution to the collection allSolutions, and print it.

	private static void saveAndPrintSolutionAndVariations(int [] board) {
		distinctSolutionsCount ++;
		addSolutionRecordToAllSolutions (board.clone(), "Original solution " + distinctSolutionsCount);

		System.out.print("Distinct solution " + distinctSolutionsCount + " : ");
		printBoard(board);
		System.out.println();

		// Generate the seven derivations from the original solution, in order to save them in the
		// collection allSolutions (provided they are not duplicates) and print them (regardless).
		
		int [] variation;
		String description;
		
		variation = rotate90Degrees(board);
		description = "90 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);
		
		variation = rotate180Degrees(board);
		description = "180 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);
		
		variation = rotate270Degrees(board);
		description = "270 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);
		
		variation = mirror(board);
		description = "mirror of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);
		
		variation = mirrorAndRotate90Degrees(board);
		description = "mirror, 90 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);

		variation = mirrorAndRotate180Degrees(board);
		description = "mirror, 180 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);

		variation = mirrorAndRotate270Degrees(board);
		description = "mirror, 270 degrees rotation of solution " + Integer.toString(distinctSolutionsCount);
		saveAndPrintVariation (variation, description);

		System.out.println("+++++++++++++++++++++++++++++++");
		System.out.println();
		
	}

	private static void addSolutionRecordToAllSolutions(int[] board, String description) {
		SolutionRecord record = new SolutionRecord ();
		record.solution = board;
		record.description = description;
		allSolutions.add(record);
	}

	// The derived solution itself may be a duplicate of another derived solution of the same original.
	// For example, the 180 degree rotation of one solution turns out to be the same as its original solution.
	// So, we add the derived solution (variation) to the collection allSolutions conditional (not being a 
	// duplicate), but we print it anyway.
	
	private static void saveAndPrintVariation(int[] variation, String description) {
		if (!matchesAnEarlierSolution(variation))
			addSolutionRecordToAllSolutions (variation, description);
		System.out.print(description + " : ");
		printBoard (variation);
		System.out.println();
	}

	private static void printBoard(int [] board) {
		for (int i = 0; i <= 7; i++)
			System.out.print(board [i] + " ");
	}

	// Rotate the board 90 degrees, **clockwise**. It is difficult to visualize this rotation, but here is
	// an example. If the board is {0, 4, 7, 5, 2, 6, 1, 3}, its 90 degree clockwise rotation would be
	// {7, 1, 3, 0, 6, 4, 2, 5}.
	// See http://www.pragatisoftware.com/blog/eight-queens-problem-part-3 for illustration.
	
	public static int [] rotate90Degrees(int[] board) {
		int [] newBoard = new int [8];
		for (int i = 0; i < 8; i++) {
			newBoard [board [i]] = 7 - i;
		}
		return newBoard;
	}

	public static int[] rotate180Degrees(int[] board) {
		return rotate90Degrees(rotate90Degrees(board));
	}

	public static int[] rotate270Degrees(int[] board) {
		return rotate180Degrees(rotate90Degrees(board));
	}

	// Mirroring a board is simple: just reverse it.
	
	public static int[] mirror(int[] board) {
		int [] newBoard = new int [8];
		for (int i = 0; i < 8; i++) {
			newBoard [i] = board [7 - i];
		}
		return newBoard;
	}

	public static int[] mirrorAndRotate90Degrees(int[] board) {
		return rotate90Degrees (mirror (board));
	}

	public static int[] mirrorAndRotate180Degrees(int[] board) {
		return rotate180Degrees (mirror (board));
	}

	public static int[] mirrorAndRotate270Degrees(int[] board) {
		return rotate270Degrees (mirror (board));
	}
}


Installing Docco

You must have the following installed on your machine:

  • Python and the python package called ‘Pygments’. The easiest way is to install a pre-built Python distribution like ‘Anaconda’ which comes with Pygments (https://www.continuum.io/downloads)
  • Nodejs (https://nodejs.org/en/), NPM (The Node Package Manager) comes bundled with a standard Nodejs installation.

Docco uses these runtimes to parse your code and provide syntax highlights.

To install docco globally, run the following on the command prompt:

npm install -g docco



Running docco to Generate Documentation

Go to the command prompt in the folder where you have the source code to comment, and then run the following command:

docco EightQueens.java

Docco creates a subfolder called "docs" under the current folder. In that folder, you will find a file called "EightQueens.html". Open that file in your browser, and you will find the page appearing as shown below:





Closing Remarks

Docco is a first step towards the ‘literate programming’ approach for many languages. Literate programming (https://en.wikipedia.org/wiki/Literate_programming) is a paradigm introduced by Donald Knuth (http://www.literateprogramming.com/knuthweb.pdf). In a nutshell, it advocates that together the code (clean code!), comments and macros (if needed) must all be used to make the source lucid and easy to read, it must provide natural, continuous ‘flow’ (or exposition) of logic for the programmer reading the source code.

The experience of reading code should be as close to the experience of reading the text of an essay as possible. This, needless to say, contributes to better understanding of the system, resulting in fewer bugs and improves the maintainability of the code.

I hope this whets your appetite for docco. If you find docco interesting, you can find out all about it at https://jashkenas.github.io/docco/ , including the various ports for multiple languages, markdown support for formatting text with effects like bold, underline, list of bullet points, hyperlinks, etc.