C Language Style for Scalability
The C Language Style for Scalability (CLASS) defines a consistent style and organization for scalable C library and application code built on modern C compilers and operating systems. CLASS aims to collect industry best practice into one reusable standard.
CLASS was created by Pieter Hintjens, originally for the ZeroMQ project, and is available as a ZeroMQ RFC ( CLASS (C Language Style for Scalability) ).
This version of CLASS amends the original linked above in a few points:
-
clarification of ZeroMQ vs. IETF "RFC"s;
-
added Unix shared libraries, pkg-config declarations, and service management definitions for daemons to the list of things that a project SHOULD deliver;
-
revised "verbatim" markup in-lined in the text, and fixed a few typos;
-
requirement by default of
asciidoc
compatible markup for text files and source comments, unless a different markup is not easily avoidable (this and other aspects of documentation style, format and tooling were since relocated into a separate document: Documentation Recommendation); -
added standard text-processing programs among "dependencies" for the projects which aim to deliver rendered end-user documentation formats;
-
the template README file markup made compatible with
asciidoc
.
License
Copyright (c) 2009-2013 iMatix Corporation
This Specification is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This Specification is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, see http://www.gnu.org/licenses.
Change Process
This Specification is a free and open standard (see "Definition of a Free and Open Standard") and is governed by the Digital Standards Organization’s Consensus-Oriented Specification System (COSS) (see "Consensus Oriented Specification System").
Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119 (see "Key words for use in RFCs to Indicate Requirement Levels").
Goals
CLASS aims to be a complete and reusable style and organization guide for scalable C projects. CLASS collects best practice to solve a set of specific and well-known quality problems with C projects. It evolved from the style guide of the ZeroMQ CZMQ project. CLASS is plug compatible with the "coding style guide" requirements of the C4 process (see "Collective Code Construction Contract") and its revisions.
The specific goals for this Specification are to:
-
Give project maintainers and contributors a tool for measuring the quality of patches to a project.
-
Give the code the appearance of a single perfect author writing at one moment in time.
-
Reduce the number of lines of code and complexity of C projects.
-
Ensure total consistency of every line of code, across a large number of projects.
-
Reduce the risk of writing C code by solving common C style problems in a minimal fashion.
-
Provide a vehicle to collect best practice over time.
Quality Aspirations
The goal of CLASS is to help programmers write high-quality C code in a consistent fashion. We define "high quality" primarily thus:
-
The code is easy to read and understand even if you know little or nothing about its specific context.
-
The code is easy to reuse and remix, in whole or in fragments.
-
The code is easy to write correctly, and errors are rapidly clear.
Our main tool for achieving this is to identify and apply the best patterns and styles. The reader can learn these once, and then see them repeatedly across a wide body of code. Deviations from familiar patterns can then be taken as a sign of low-quality code that is more likely to contain errors and less remixable.
Common Problems
With this Specification we aim to address a specific set of problems that hurt the quality, remixability, and economics of many C projects:
-
If there is no style guide for a project, the maintainers must either rewrite contributions by hand (which costs extra work), or must abandon any attempt to enforce a consistent "voice" across a project (which hurts code quality and reusability). Thus maintainers need a written style guide to which they can point contributors.
-
If there is no reusable style guide, each project founder will improvise their own ad hoc style. This wastes previous experience, and creates unnecessary work for project founders. It also means each project will end up with a different style.
-
If a set of projects each have a different style, they cannot share code easily, which damages the economics of remixing. The more projects that use a single style, the lower the cost of code reuse. This is especially important in open source but also applies to projects built internally in an organization.
-
With no vehicle for collecting and sharing best practices in C programming, most projects will have mediocre structuring and style, leading to unnecessary complexity, excess lines of code, accumulation of unused code, and other quality problems.
-
When project founders create a new project without help, they often make mistakes such as forgetting to define a license for their code. This can create legal problems.
While these problems can affect all software projects, we solve them specifically for C projects.
General Style
Definitions
The overall unit of work is a project that builds as a library with a set of executable test programs and supplemental command-line tools.
The project is composed of classes, where each class covers one area of work and is implemented as a source file with matching header file.
Each class implements a series of methods, where each method performs one task and is implemented as a C function.
Language
The language for all names and comments SHALL be English.
Naming
Names SHOULD be chosen for ease of readability, and consistency. Unless otherwise specified, the following style rules apply to all given names:
-
Names SHALL be short words that are simple, clear, and obvious to the reader.
-
Names SHALL be used consistently for any given semantics.
-
Names SHOULD NOT be invented words or acronyms.
-
Names MAY be abbreviations if used widely.
-
Names SHALL NOT be reserved C or C++ keywords.
Project Style
Project Focus
The project SHALL focus on one identifiable problem space, which SHALL
be stated explicitly in the project README
.
Project Name
The project SHALL have these short names and abbreviations:
-
A project short name used in paths and URLs that identify the project. This would be used for instance in the GitHub project name. In this Specification we will use
myproject
as the example. -
A project prefix used for project files, output libraries, and method names. This would be used for instance in the library produced for the project. The prefix MAY be an acronym. In this Specification we will use
myp
as the example.
These names SHALL be noted in the project README
.
General Layout
The project SHALL contain at least these files and directories:
-
A
README
file that refers to this Specification and provides other necessary information about the project. -
A license file (e.g.,
COPYING
orLICENSE
) that specifies the terms of distribution for the project. -
An
include
directory for all header files. -
A
src
directory for all library source files. -
The public header file (
include/myproject.h
). -
Scripts and makefiles to build and test the project on at least one platform.
The project MAY contain these files and directories which MUST have these names if present at all:
-
An
AUTHORS
file listing all contributors to the project. -
A
doc
directory containing documentation. -
The internal header file (
src/myp_classes.h
).
The project SHOULD install these files:
-
The project header files and all class header files that form part of the public API.
-
The project library, named with the project prefix (
libmyp.a
and/orlibmyp.so(.*)
on POSIX platforms andlibmyp.pc
declarations for thepkg-config
, andmyp.dll
on Windows). -
Command-line tools, if present.
-
Distribution-dependent service files for programs intended to run as daemons (e.g. classic init-scripts, systemd units, Solaris SMF manifests).
Dependencies
The project SHALL depend at least on CZMQ (libczmq
), which imports
ZeroMQ (libzmq
), to provide portable APIs around networking, threads,
file systems, and other aspects.
The project SHALL depend at least on asciidoc
to render end-user formats
of documentation. It MAY depend on use of xmlto
, docbook
, fo
, a2x
,
ghostscript
and other common backends and tools for rendering of specific
final formats. The project SHALL NOT depend on tools not available freely
to anybody as required part of its documentation processing recipes (e.g.
as a component of the make distcheck
recipe chain).
Project Header Files
The project SHALL provide two services via header files:
-
A set of internal definitions to class source files, which a class
source file can access with a single
#include
statement. -
A public API that calling applications can access with a single
include
statement.
These two services MAY be combined into one project header file
(myproject.h
), or MAY be split into an public header file
(include/myproject.h
) and an internal header file (src/myp_classes.h
).
The project MAY further break down these header files if necessary.
The public header file SHALL define a version number for the project as follows:
// MYPROJECT version macros for compile-time API detection #define MYPROJECT_VERSION_MAJOR 1 #define MYPROJECT_VERSION_MINOR 0 #define MYPROJECT_VERSION_PATCH 0 #define MYPROJECT_MAKE_VERSION(major, minor, patch) \ ((major) * 10000 + (minor) * 100 + (patch)) #define MYPROJECT_VERSION \ MYPROJECT_MAKE_VERSION(MYPROJECT_VERSION_MAJOR, \ MYPROJECT_VERSION_MINOR, \ MYPROJECT_VERSION_PATCH)
The project header file SHALL assert the required version numbers for any dependencies immediately after including their respective header files, like this:
#include <czmq.h> #if CZMQ_VERSION < 10203 1. error "myproject needs CZMQ/1.2.3 or later" #endif
Definitions in the public header file are visible to calling
applications as well as class source code. The public header file SHALL
#include
all class header files that form part of the public API for the
project.
Definitions in the internal header file are visible only to class source
code. The internal header file, if present, SHALL include the public
header file, all class header files, and all system and library header
files needed by the project. The primary goal here is to keep delicate
system-dependent #include
chains in a single place, and away from class
source code.
Template README File
README-CLASS-template.asciidoc
.Project Title ============= Maintainter Name <user@domain.org> == Project Title <One-paragraph statement of the goals of the project, and the problems it aims to solve>. == References * Contribution policy is defined by C4 (http://rfc.zeromq.org/spec:42). * Project style guide is defined by CLASS (http://rfc.zeromq.org/spec:21). ** short name: <shortname> ** prefix: <prefix> * Licensed under <license name>, see COPYING * Language level: C99
Language Level
The project SHOULD use the C99 language for best clarity, but MAY use the C89 language for compatibility with older platforms. The language level SHALL be noted in the project README and all source code SHALL conform to it.
Use of the Preprocessor
Project source code SHOULD NOT include any header files except the project header file. This ensures that all class source code compiles in exactly the same environment.
Project source code SHALL NOT define "magic numbers" (numeric constants); these SHALL be defined in the external or internal header file, as appropriate.
Projects MAY use the preprocessor for these purposes:
-
To create backwards compatibility with older code.
-
To improve portability by e.g., mapping non-portable system calls into more portable ones.
-
To create precise, small macros with high usability.
Projects SHOULD NOT use the preprocessor for other work except when it significantly reduces the complexity of code.
Macro names SHALL be uppercase when they represent constants, and lowercase when they act as functions.
Class Styles
File Organization
Each class SHALL be written as two files:
-
A header file:
include/myp_myclass.h
-
A source file:
src/myp_myclass.c
These two files SHALL be the original documentation for the class. Specifically, the class header SHALL define the API for the class, and the class source file SHALL define the implementation of each method.
Class names SHALL follow the General Style for Naming. We will use
myclass
in examples.
Every source and header file SHALL start with an appropriate file header that states at least:
-
The name of the class or file and its purpose
-
The copyright statement for the class
-
The name of the project and a URL if relevant
-
The summary license statement
Here is a template file header for an MPLv2 open source project:
/* ========================================================================= <name> - <description> Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of MYPROJ, see https://github.com/MYORG/MYPROJ. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. ========================================================================= */
Class Types
We define two types of class:
-
Stateful classes, where the class provides methods working on instances, which are like "objects" in an object-oriented language.
-
Stateless classes, where the class provides methods that work purely on data provided by the caller or system.
A stateful class SHALL provide these methods:
-
A constructor method
myp_myclass_new ()
-
A destructor method
myp_myclass_destroy ()
-
A self-test method
myp_myclass_test ()
A stateful class MAY provide these methods, and SHALL use these names when providing such functionality:
-
A duplicator method
myp_myclass_dup ()
-
A set of list navigation methods
myp_myclass_first ()
andmyp_myclass_next ()
. -
Print methods
myp_myclass_print ()
andmyp_myclass_fprint ()
.
A stateless class SHALL provide at least this method:
-
A self-test method
myp_myclass_test ()
.
Method Names
Method names SHALL follow the General Style for Naming. Method names SHOULD be verbs ("destroy", "insert", "lookup") or adjectives ("ready", "empty", "new"). The method name SHOULD imply the method return type, where verbs return a success/failure indicator, if anything, and adjectives return a value or instance.
Class Header File
The class header file SHALL have this layout:
-
The file header
-
An outer
#ifndef
that makes it safe to include the header file multiple times -
Calling conventions for C++
-
A forward reference to the class type, for stateful classes
-
Prototypes for the class methods
Here is a template header file for stateful classes, not showing the file header:
#ifndef __MYMOD_H_INCLUDED__ #define __MYMOD_H_INCLUDED__ #ifdef __cplusplus extern "C" { #endif // Opaque class structure typedef struct _myp_myclass_t myp_myclass_t; // Create a new <class name> instance CZMQ_EXPORT myp_myclass_t * myp_myclass_new (void); // Destroy a <class name> instance CZMQ_EXPORT void myp_myclass_destroy (myp_myclass_t **self_p); // Self test of this class void myp_myclass_test (bool verbose); #ifdef __cplusplus } #endif #endif
Here is a similar template header file for stateless classes:
#ifndef __MYMOD_H_INCLUDED__ #define __MYMOD_H_INCLUDED__ #ifdef __cplusplus extern "C" { #endif // Self test of this class int myp_myclass_test (bool verbose); #ifdef __cplusplus } #endif #endif
All public methods SHALL be declared with CZMQ_EXPORT
in the class
header file so that these methods are properly exported on operating
systems that require it.
Class Source File
The class source file SHALL define:
-
The class structure, for stateful classes. This structure SHALL be opaque and known only to code in the class source file.
-
The class methods, in the same order as defined in the class header: constructor, destructor, other methods, and finally self test.
-
Any static functions used in the class methods.
-
Any global or static variables needed.
Class Properties
For stateful classes, the class structure has one or more properties defined as a private C structure in the class source file.
This SHOULD be defined as follows:
// Structure of our class struct _myclass_t { <type> <name>; // <description> };
Property names SHALL follow the General Style for Naming. Property names
SHOULD be nouns or adjectives (typically used for Boolean properties).
We will use myprop
in examples.
Method Styles
General Rules
Argument Names
Argument names SHALL be consistent with property names.
Return Values
Success/failure SHALL be indicated by returning an int
, with values
0
or -1
respectively.
Strings SHALL be returned as char *
when they are passed to the
caller, who must free them.
Strings SHALL be returned as const char *
when the caller may not
modify or free them.
Compound return values, e.g. a size-specified buffer, SHOULD be returned as fresh objects of a suitable class. The API SHOULD NOT return compound values via multiple routes, e.g. data via an argument and size via the return code.
The Self Test Method
In stateless classes, the only standard method is myp_myclass_test ()
,
which SHALL conduct a self test of the class, returning silently on
success, and asserting on failure.
The self test method shall take this general form:
// -------------------------------------------------------------------------- // Runs selftest of class void myp_myclass_test (int verbose) { printf (" * myp_myclass: "); // Conduct tests of every method printf ("OK\n"); }
-
The self test method SHALL be a primary source of example code for users of the class.
-
The self test method SHOULD cover every other method in the class.
Stateful Classes
The Constructor Method
The constructor SHALL take this general form:
// Create a new myp_myclass instance myp_myclass_t * myp_myclass_new (<arguments>) { myp_myclass_t *self = (myp_myclass_t *) zmalloc (sizeof (myp_myclass_t)); assert (self); self->someprop = someprop_new (); assert (self->someprop); return self; }
-
The constructor SHALL initialize all properties in new class instances. Properties SHALL either get a suitable initial value, or be set to zero. Very large properties MAY exceptionally be left uninitialized for performance reasons; such behavior MUST be explicitly noted in the constructor body.
-
Any properties that are dynamically allocated SHOULD be allocated in the constructor but MAY be left as null.
-
The constructor MAY take one or more arguments, which SHALL correspond to properties to be initialized.
-
The constructor SHALL return either a new instance reference, or null, if construction failed.
The Destructor Method
The destructor SHALL take this general form:
// Destroy a myp_myclass instance void myp_myclass_destroy (myp_myclass_t **self_p) { assert (self_p); if (*self_p) { myp_myclass_t *self = *self_p; someprop_destroy (&self->someprop); anotherprop_destroy (&self->anotherprop); lastprop_destroy (&self->lastprop); free (self); *self_p = NULL; } }
-
The destructor SHALL
null
-ify the provided instance reference. -
The destructor SHALL be idempotent, i.e. it can be called safely on the same instance reference more than once.
-
The destructor SHALL safely free properties and child class instances that are not
null
.
The Duplicator Method
The class MAY offer a duplicator method which creates a full copy of an
instance; if it offers such semantics, the method MUST be called
myp_myclass_dup ()
and take this general form:
// Create a copy of a myp_myclass instance myp_myclass_t * myp_myclass_dup (myp_myclass_t *self) { if (self) { assert (self); myp_myclass_t *copy = myp_myclass_new (...); if (copy) { // Initialize copy } return copy; } else return NULL; }
-
The duplicator SHALL return either a new instance reference, or
null
if construction failed, in the same manner as the constructor. -
The duplicator SHALL accept a
null
instance reference, and then returnnull
. -
A duplicated instance SHALL be entirely independent of the original instance (i.e. all properties SHALL also be duplicated).
List Navigation Methods
A class MAY act as a list container for other items, which may be child class instances, strings, memory blocks, or other structures.
Such a container class SHALL keep the list cursor position in the instance, and provide the following methods for navigating the list:
// Return first item in the list or null if the list is empty item_t * myp_myclass_first (myp_myclass_t *self) { assert (self); // Reset cursor to first item in list return item; } // Return next item in the list or null if there are no more items item_t * myp_myclass_next (myp_myclass_t *self) { assert (self); // Move cursor to next item in list return item; }
-
The navigation methods SHALL return
null
to indicate "no more items". -
The navigation methods SHALL be idempotent, and specifically, calling
myp_myclass_next ()
when at the end of the list SHALL return null each time. -
The class MAY offer
myp_myclass_last ()
andmyp_myclass_prev ()
methods. -
The class MAY offer
myp_myclass_size ()
which returns the list size. -
If the class offers methods to create list items, these SHALL be called
myp_myclass_append ()
(to add to the end of the list) andmyp_myclass_insert ()
(to add to the start of the list). -
If the class offers a method to remove a list item, this SHALL be called
myp_myclass_delete ()
; it SHALL take the item reference as argument, and it SHALL delete the first matching item in the list, if any. -
If the class maintains multiple lists, it SHALL create unique method names for each list by adding a list name, e.g.,
myp_myclass_myitem_first ()
.
Accessor Methods
The class MAY expose instance properties via its API, in which case this SHALL be done through accessor methods.
To return the value of a property the class SHALL define an accessor method like this:
// Return the value of myprop <type> myp_myclass_myprop (myp_myclass_t *self) { assert (self); return self->myprop; }
To write the value of a property, if this is permitted, the class SHALL define an accessor method like this:
// Set the value of myprop void myp_myclass_set_myprop (myp_myclass_t *self, <type> myprop) { assert (self); self->myprop = myprop; }
-
Properties exposed by accessor methods MAY not actually exist as such in the instance; they may be calculated rather than simply copied to/from the instance structure.
Formatted String Arguments
When a method (such as an accessor method) accepts a string argument as
primary argument, it SHOULD use a variable argument list and perform
vsnprintf
formatting on that string argument.
General Methods
The class MAY offer any number of other methods that operate on the instance. These methods shall take this general form:
-
The first argument to the method SHALL be the instance reference.
-
Other arguments may follow.
A method may take ownership of an object instance and then act as a destructor of the object instance at some later stage. In that case the method SHALL use the same style as the destructor.
Return Values
Methods SHOULD use one of the following patterns for returning values to the caller:
-
Returning nothing, if no return value is expected.
-
Returning a property value, on an accessor method.
-
Returning an object instance, on a constructor or duplicator.
-
Returning a child value, on a list navigation method.
-
Returning zero on success, -1 on failure.
-
Returning a freshly-allocated string.
Code Style
Thread Safety
-
All methods SHALL be thread safe.
-
Class instances SHOULD NOT generally be thread safe; a class instance will be owned by a single calling thread.
-
In exceptional cases class instances MAY be made thread safe by the addition of mutexes or locks inside methods.
Heap Use
One of the goals of CLASS is to hide heap use as far as possible within
classes. Application programs SHOULD use the heap only through
constructors and duplicators (including the library strdup ()
function).
Class methods MAY use the heap with care, but follow these rules:
-
When a class instance has been destroyed, all heap memory it used MUST be freed. Classes SHALL NOT leak memory under any conditions except during abnormal termination (e.g., on a failed assertion).
-
Non-atomic properties SHOULD be re-allocated (i.e., freed and allocated) in accessor functions that modify them, as needed.
-
The instance structure MAY use
char[]
arrays instead of heap allocatedchar*
pointers. -
When freeing a non-atomic property outside the destructor, a method MUST set the property to
null
if it does not allocate a new value immediately.
Static Variables
Classes SHOULD NOT use static variables except in exceptional cases, such as for global variables.
Static variables are not thread safe and they are therefore considered poor practice.
Particularly for representing any temporary state inside a class body, stack variables SHALL be used in place of static variables.
Static Functions
Functions that are not exported by a class are defined as static
and
named s_functionname ()
with no use of the project prefix or class name.
Static functions MAY be defined before first use, or MAY be prototyped and defined immediately after first use.
Static functions SHOULD NOT be collected at the end of the class source code.
Code Style
Indentation
Indentation SHALL be 4 spaces per level. Tab characters SHALL NOT be used in code.
Declarations
Functions SHALL be prototyped as follows:
<type> <name> (<arguments>);
Functions SHALL be defined as follows:
<type> <name> (<arguments>) { <body> }
When the project uses C99, stack variables SHALL be defined in-line, as close as possible to their first use, and initialized. For example:
myp_myclass_t *myclass = myp_myclass_new (); char *comma = strchr (surname, '.');
When the project uses C89, stack variables SHALL all be defined and initialized at the start of the function or method where they are used.
-
Variables and functions SHALL use lower-case names.
-
Where necessary, underlines SHALL be used to separate parts of a name.
-
Variable names like
i
andtemp
that carry no information SHALL NOT be used.
Statements
Code lines of more than 80-100 characters SHOULD be folded for readability.
Single-statement blocks SHALL NOT be enclosed in brackets.
This is the form of a single-statement block:
if (comma == NULL) comma = surname;
In else
statements, the else
SHALL be put on a line by itself.
Multiple if
/else
tests SHALL be stacked vertically to indicate that
the order is arbitrary.
This is the form of a stacked if
statement block:
if (command == CMD_HELLO) puts ("hello"); else if (command == CMD_GOODBYE) puts ("goodbye"); else if (command == CMD_ERROR) puts ("error");
With multi-statement conditional blocks, the closing bracket SHALL be put on a line by itself, aligned with the opening keyword.
This is the form of a stacked if
statement block with brackets around
each conditional block:
if (command == CMD_HELLO) { puts ("hello"); myp_peer_reply (peer, CMD_GOODBYE); } else if (command == CMD_GOODBYE) { puts ("goodbye"); myp_peer_reply (peer, CMD_DISCONNECT); } else if (command == CMD_ERROR) { puts ("error"); myp_peer_close (peer); }
This is the form of a while
statement:
char *comma = strchr (surname, ','); while (comma) { *comma = ' '; comma = strchr (surname, ','); }
Comments
Comments on code SHALL be used lightly and where necessary.
In C99 projects the syntax for comments is:
-
In-line comments SHALL use the C++
//
style. -
Multi-line comments MAY use the C
/* ... */
style or MAY use the C++//
style.
In C89 projects the syntax for all comments SHALL be the C /* ... */
style.
-
When possible in-line comments shall start at column 33.
-
In in-line comments, the
//
or/*
shall be followed by two spaces. -
Every function shall have a multi-line comment header that briefly explains its purpose.
-
Method comment headers SHALL be preceded by a line of hyphens ending at column 78.
-
Suitably-marked-up comments before a function MAY be used as source material for reference documentation.
This is the general template for a method comment header:
// -------------------------------------------------------------------------- // Finds the first item in the list, returns null if the list is empty. myp_myclass_t * myp_myclass_first (myp_myclass_t *self) { ...
-
Every property in a class structure SHALL have a 1-line in-line comment that describes its purpose.
-
Comments SHALL NOT be used to compensate for illegible code.
-
Code that cannot be reasonably read and understood by the casual reader SHOULD be rewritten, not annotated.
-
Properties and functions whose semantics are not clear from their names SHOULD be renamed, not annotated.
Blank Lines
Blank lines SHALL be used to separate blocks of code to improve readability, in these cases:
-
After the closing bracket of a function body and before the comment header for a function.
-
To break up blocks of code that exceed 6-8 lines.
-
After assertions at the start of a class body.
-
After an
if
statement with a single-statement block. -
After multi-line
case
blocks inside aswitch
statement. -
After multi-line comment blocks.
Blank lines SHALL NOT be used in these cases:
-
After the closing bracket of a conditional block.
-
To separate individual lines of code that could better be grouped together.
Vertical Alignment
Code SHALL NOT use extra spacing to create vertical alignment.
char *comma = strchr (surname, ','); while (comma) { *comma = ' '; comma = strchr (surname, ','); }
Punctuation
Punctuation SHALL follow English rules as far as possible.
This is the style for unary operators, with a space after but not before the operator:
char_nbr++;
This is the style for binary operators, with a space before and after the operator:
comma = comma + 1;
This is the style for the ?: operator:
comma = comma? comma + 1: strchr (name, '.');
This is the style for semi-colons, with a space after but not before:
for (char_nbr = 0; *char_nbr; char_nbr++) char_nbr++;
This is the style for parentheses, with a space before the opening, and after the closing parenthesis, with multiple opening or closing parentheses joined together without spaces:
node = (node_t *) zmalloc (sizeof (node_t)); if (!node) return -1;
This is the style for square brackets:
comma = name [char_nbr];
This is the style for pointer dereferences, with no space before or
after the ->
:
self->name = strdup (name);
Assertions
Classes SHOULD check the validity of arguments using assertions. That is, misuse of the API is considered a programming error, not a run-time error.
-
Assertions SHALL be used for their documentary value, for example to warn the reader, "this argument SHALL NOT be null".
-
Assertions on arguments SHALL come at the start of the class body and SHALL follow the order of the arguments.
-
Assertions MAY be used on return values from function calls if such failures cannot safely be handled by the code.
-
Assertions MAY be used on internal state (e.g., instance properties) to assert a mandatory condition for continuing.
-
Assertions SHALL NOT be used to trap errors on external conditions, e.g., bad user input, invalid protocol messages, etc.
-
Assertions SHOULD be used to trap errors on internal APIs, e.g. invalid messages sent from one thread to another.
-
Assertions SHALL NOT have side-effects since the entire statement may be removed by an optimizing compiler.
Exiting Functions and Goto Statements
The return
statement MAY be used at any point in a function to return
to the caller.
If the function needs to do clean-up (e.g. to free a number of
properties), the code MAY use goto
and a single clean-up block at the
end of the function. Such a clean-up block SHALL follow the last
"normal" return
.
A void
function SHALL NOT end in an empty return
statement.
Recommended Patterns
-
The recommended pattern for an open-ended loop is
while (true) {}
, withbreak
statements as needed to exit the loop. -
The recommended pattern for array iteration is:
for (array_index = 0; array_index < array_size; array_index++) { // Access element [array_index] }
-
The recommended pattern for list iteration is:
myp_myclass_t *myclass = (myp_myclass_t *) myp_myclass_first (myclass); while (myclass) { // Do something myclass = (myp_myclass_t *) myp_myclass_next (myclass); }
Portability
Portable Versus Native Classes
All projects SHALL depend at least on ZeroMQ (libzmq
) and CZMQ
(libczmq
), which provide portable APIs around networking, threads, file
systems, and other aspects.
-
A class SHALL be either "portable" or "native".
-
A portable class SHALL NOT use the preprocessor to compile differently on different systems.
-
A native class SHALL export a properly abstracted API that hides system differences, and SHALL use the preprocessor to compile differently on different systems.
-
A native class SHALL use the preprocessor macros defined in
czmq_prelude.h
, and specifically theWINDOWS
,UNIX
, and__UTYPE_ABC
macros. -
A native class SHALL NOT use preprocessor macros supplied by any specific build system. If the CZMQ-supplied macros are not sufficient these can be improved and extended.
-
The project architect SHOULD aim to fully separate portable and native classes, so that application developers see and write only portable classes.
This example shows the general style of native code:
#if (defined (__UNIX__)) pid = GetCurrentProcessId (); #elif (defined (__WINDOWS__)) pid = getpid (); #else pid = 0; #endif
Portable Language
The following types and macros are defined by CZMQ and may be used safely in all code:
-
bool
,true
,false
: Boolean data type and constants. -
byte
,dbyte
,qbyte
: unsigned 1-, 2-, and 4-octet integers. -
uint
,ulong
: unsigned integers and longs. -
int32_t
,int64_t
: signed 32-bit and 64-bit integers. -
uint32_t
,uint64_t
: unsigned 32-bit and 64-bit integers. -
streq (s1, s2)
: preferred overstrcmp (s1, s2) == 0
. -
strneq (s1, s2)
: preferred overstrcmp (s1, s2) != 0
. -
randof (number)
: return random integer in range0
..number - 1
. -
srandom
: typically used like this:srandom ((unsigned) time (NULL));
-
inline
,snprintf
,vsnprintf
: Windows uses non-POSIX variants with underscores.
Compiler Warnings
Compiler warnings SHOULD always be treated as fatal. The following is a list of constructs known to cause warnings on some but not all compilers:
-
Assigning a void pointer to a typed pointer without a cast. Always cast a
void *
before assigning it to a typed pointer. -
Failing to return a value in a non-void function. Always end a non-void function with a
return
statement.
Code Generation
Code generation MAY be used to produce classes mechanically when there is compelling benefit.
-
The code generator SHOULD be GSL, from https://github.com/imatix/gsl.
-
All code generation scripts SHALL be in the project
src
subdirectory. -
All model data (XML files) SHALL be in the project
src
directory. -
If only parts of a class are generated, these parts SHALL have the extension
.inc
and SHALL be generated into the projectsrc
directory, and SHALL be included in the class source file using an#include
statement. -
Code generation SHALL be done as a manual step. For example,
make code
. All generated code SHALL be committed into the project as for hand-written files. -
Code generation SHALL be fully idempotent, that is, generated code SHALL NOT contain any date or time stamps.
-
Code generation SHALL be treated as a form of dangerous abstraction that creates significant barriers to readers. A good rule of thumb is that for code generation to be profitable, it should reduce the lines of code written by hand by 80-90%.
-
Generated code SHALL contain a warning of this form at the start:
GENERATED SOURCE CODE, DO NOT EDIT
. -
Generated code SHALL otherwise conform to this Specification so that it is indistinguishable from hand-written code.
Security Aspects
Thread Safety
The use of opaque data structures that are accessed via references is thread safe. However:
-
Code SHALL NOT share state between threads except in exceptional and limited cases. Threads SHALL communicate by passing 0MQ messages.
-
Classes SHALL not use static variables since this is not re-entrant, thus not thread safe.
-
Class instances SHALL NOT be passed between threads except in "hand-off" cases.
-
Code SHOULD NOT use mutexes, locks, or other mechanisms to share state between threads.
-
Code MUST NOT use non-thread safe system calls such as
basename ()
.
Buffer Overflows
-
Code MUST always truncate over-long data.
-
Code MUST NOT use unsafe system calls such as
gets ()
.
Known Weaknesses
-
The heavy reliance on heap memory means that CLASS applications are vulnerable to denial-of-service attacks. Applications can mitigate this risk by enforcing limits on the number of class instances they create.
-
The heavy reliance on heap memory makes CLASS unsuitable for embedded systems where all memory use must be static.
-
In most CLASS applications it is difficult to handle an "out of memory" error in any way except to abort.