Creating a code style guide

illustrations illustrations illustrations illustrations illustrations illustrations illustrations
post-thumb

Published on 20 October 2022 by Andrew Owen (5 minutes)

I’ve written previously about the importance of a style guide for written content. The same is true for code. Arguably, it’s more important because code is much harder to understand. When it comes to code style, you’ll never find two developers who agree on everything. So my advice would be to get the input of everyone who touches the code and come to a consensus on what the style should be. And remember that it’s a guide, not a rigid set of rules.

Because I spent nearly 15 years working as a technical writer, most of the coding I’ve done in my life has been for my own amusement. And that means I’ve been free to do what I like. But in that time I’ve seen some bad habits that I’ve tried to avoid. In particular, I’ve dealt a lot with Z80 assembly language source code. Most of it is horrible. My gripes include:

  • Single monolithic assembly files.
  • Written in CAPITALS.
  • Needless decoration on comments.
  • Too few comments.
  • Meaningless labels
  • Full of magic numbers
  • Pointless gaps between instructions and parameters
  • Enough whitespace to fill the Albert Hall
  • No API docs.
  • Numbers written as hex that should be written as decimal.
  • Bitwise instructions written as hex that should be written as binary.
  • Undocumented pseudo-op codes.

My own code style is essentially a reaction against this. I was also influenced by a document published by Nikos Drakos and Ken Clowes at the Computer Based Learning Unit of the University of Leeds, and the fact that I used to write SDK documentation for C++ developers. I’ll now attempt to formally document it. At some point in the future, I should extend it. Where information is missing, I defer to the C++ Core Guidelines.

Introduction

This document describes coding standards for Z80 assembly language programs. The following plug-ins are recommended for Visual Studio Code:

Assuming you’re using something more modern than Z80, I also recommend these plug-ins:

API

API calls should use a vector table so that relocation of the target code won’t break existing applications. For example:

org $04c4;

    ;;
    ; open a file for appending if it exists
    ; @param IX - pointer to ASCIIZ file path
    ; @throws sets carry flag on error
    ;;
    SEFileAppend:
    	jp v_open_w_append;					// $00

Characters

When comparing ASCII characters, use the character, not the code point. For example:

    	cp '(';								// opening parenthesis?

Comments

In Z80, the semicolon ( ; ) denotes that a comment follows. However, it is most commonly used as a line terminator. Modern integrated development environments are set up to expect this. Therefore, every line should end with a semicolon. Comments should be preceded with a double slash ( // ) for visibility. Standalone comments should be written as:

    ; // this is a standalone comment

Inline comments should start at the tenth tab stop (column 40), for example:

    	di;									// interrupts off

Comments should be as short as possible to convey what the code is doing. Where an instruction can only have one meaning, it’s not necessary to comment the code.

Use capitals for register names in comments. For example:

    	ld d, e;							// E to D

Data

So far as possible, data should be stored separately from code.

Headers

Use a definitions file to declare:

  • constants
  • macros
  • index register offsets

Index registers

If you’re using a large range of index registers, for example as a set of variables, define the offset with the name of the variable and a leading underscore. For example: _kstate.

Line length

Lines should be no more than 80 characters in length for display on a standard terminal. In-line comments should occur at the 40 character point so that they will appear on the next line on a 40-column display.

Macros

Use macros sparingly. Different assemblers use different syntax, and converting them can be time-consuming. They work best for pseudo op codes.

Numbers

Define meaningful names for constants and variables. Avoid magic numbers.

Use decimal wherever possible.

Always use binary for bitwise operations (AND, OR, XOR). For example, and %00001111.

Write hexadecimal with lower case letters and a leading dollar sign. For example, $801c.

Pseudo op codes

Never use pseudo op codes. Support is inconsistent between assemblers. If you must, define a pseudo-op code as a macro.

Public documentation

Use the asmdoc Perl script to generate API docs for public routines.

    ;;
    ; Short description.
    ; @author Name of contributing author.
    ; @deprecated Version deprecated in and replacement module.
    ; @param HL - Register contents.
    ; @return Result in register, such as <code>HL</code>.
    ; @see <a href="www.example.com">External reference</a>.
    ; @since Version in which module was introduced.
    ; @throws Error number and description handled by RST 8 routine.
    ; @version Version in which the module was last udpated. 
    ;;

Routines

A routine is somewhat analogous to a function in C. A public routine is preceded by an asmdoc definition. Routine and subroutine names are terminated by a colon. Instructions in the routine or subroutine are indented by one tab. For example:

     joystick:
    	in a, (stick);						// read joystick
    	ld (jstate), a;						// store in system variable
    	ret;								// end of subroutine

Self-modifying code

Self-modifying code should be avoided with two exceptions:

  • When it is critical for speed of operation.
  • When it is critical for code compactness.

Source

Break source code into sets of related modules and include those modules in the main assembly file. Modules should start with a special comment. For example:

    ;;
    ;	// --- ARITHMETIC ROUTINES -------------------------------------------------
    ;;
   :

Strings

User lowercase wherever possible. Use camelCase in preference to underscores ( _ ). Reserve CAPITALS for substitutions in comments. For example:

    	xor a;								// LD A, 0

Whitespace

Uses tabs (one tab is four characters). This speeds up search and compile times.

Image: Detail from Rodnay Zaks’ “Programming the Z80” (Sybex)