Think about the millions of hours that Americans waste on football. Now imagine a world where the same Americans spend even half that time studying history or economics, or reading a reputable newspaper, or doing any of the things it takes to be an effective voter.

Don’t wave that flag if you’re too lazy to do the fucking work.

Split Notation

Identifier naming convention — OOP development

Split Notation

Identifier naming convention for object-oriented software development
Developed by Jeremy Kelly

Version 5.0

This page includes a two-column print style. For best results, print in landscape, apply narrow margins, change the Scale setting in your browser’s print options to 70%, and enable background graphics. Firefox and Chrome each have their own set of printing bugs, but one of them usually works.



Split Notation is an identifier naming convention for object-oriented software development. It was designed for use with C++ and C#, but is largely applicable to other object-oriented languages. Its purpose is to:

  • Make identifiers more descriptive, easier to generate, and easier to read by separating low-level technical details from high-level concepts;
  • Avoid name collisions;
  • Avoid common programming mistakes by marking language features that require special attention.

It was previously known as ‘Iowan Notation’.

Separating concerns

Software development requires careful management of details from varied domains. Business concerns must be modeled, computing resources must be usefully abstracted, and the programming language — with its particular and sometimes perverse way of representing or transforming data — must be correctly applied.

Names that are confusing or incomplete are likely to produce errors, so variable names, function names, and other identifiers must document these details while remaining concise and legible. Identifier generation should also be easy, yet deterministic, so that different developers can hope to produce or predict the same name for a given element.

Parts of this problem are too difficult to solve in a general way, particularly business concerns and platform details, which vary too much to be categorized from afar. Language features are well-suited to categorization, however, because they are clearly defined and relatively few in number.

Split Notation honors this distinction by enforcing a separation of concerns within identifiers. It labels important language features with a range of one-character prefixes, leaving the identifier ‘root’ to document business concerns and other high-level concepts.

Consider an application with a custom stream type. The ‘stream’ concept might be represented by the root Stm. Throughout the program, streams could be used and referenced in many different ways. Applying some of the more common prefixes:

Identifier Referent
tStm Stream type
oStm Local stream instance
gpStm Global pointer to a stream instance
esStm Class-private static stream instance
arStm Function parameter passing a non-const reference to a stream instance

The prefixes allow a single root to produce a range of identifiers that are concise and descriptive, yet obviously related. They also prevent name collisions between these identifiers, and they do this without polluting the root with noisy modifiers like ‘My’, ‘Type’, or ‘Param’. The result is semantically dense but highly legible code.

Other benefits

Split Notation also helps developers by marking elements with special or unusual properties:

  • References, static objects, and union members are marked as a reminder that changes to such objects affect other objects or scopes;
  • Virtual functions are marked to show that their behavior may change when invoked by subclasses;
  • Macros are marked to warn against side-effects from duplicated parameter expressions.

Note that Split Notation does not document specific types. This is because:

  • Compilers for languages like C++ and C# already flag most type safety violations;
  • Types sometimes change during the course of development, and many such changes (replacing a number type with a larger equivalent, or replacing a class with another that implements the same interface) can be made to well-written code without detailed review. Changing the identifier in these cases would create unnecessary work.

Though types are not explicitly documented, the identifier root strongly hints at the object’s type by describing its role.

The Notation

In Split Notation, identifiers begin with zero or more lowercase prefix characters, with at most one prefix drawn from each of the following groups:

Prefix Criterion
z (none)
g Global element
y Element with internal linkage
c Protected class member
e Private class member
a Function parameter
o Local element
t Type
x Template parameter
f Interface
m Macro
n Namespace
s Static class member or local static object
v Virtual function
u Union member
r Non-const reference
q Object reference
p Data pointer
d Function pointer or delegate
b Managing object
h Handle
i Iterator

Non-static class-public objects and functions meet no criterion, and their identifiers are not prefixed. Other identifiers are prefixed at least once. Only qualities inherent to the identified object are labeled. Pointers to pointers, for instance, are prefixed with one p, not two. When multiple prefixes apply, they are affixed in the order specified above, with the exception of z, which may be placed anywhere within the prefix.

The identifier root follows the prefixes; it contains one or more words describing the business concern or other high-level concept that is being referenced. The first letter of every word within the root is capitalized, as in ‘CamelCase’:

string oNameLast;

It is acceptable and occasionally desirable to omit the root, producing an identifier of prefixes only, as in the loop index below:

for (int o = 0; o < eFlds.Ct(); ++o)
  cout << eFlds[o].Name() << endl;

Prefix reference


(no criterion)

The z prefix has no set meaning. It may be used to resolve name collisions with reserved words or third-party code, or for any other reason.



The t prefix applies to user-defined types and typedefs. It allows the same root to be shared by a type and an instance of that type:

tTbl oTbl;

It does not apply to template type parameters, for which the x prefix is used instead.


Template parameter

The x prefix applies to template parameters, both type and non-type. By distinguishing template parameters, it clarifies template design:

template<class xNum>
struct tPt {
  xNum X, Y;



In languages that support them, the f prefix applies to interfaces. It allows the same root to be shared by an interface and a type or instance implementing that interface.



The m prefix applies to macros. It warns against side-effects from duplicated parameter expressions, and other preprocessor oddities:

// Probably a bad idea:
int oMin = mMin(++oX, ++oY);



The n prefix applies to namespaces. It helps distinguish namespaces from containing types:

// tLog is a type:
tLog::tLine oLine;
// nStr is a namespace:
oText = nStr::gTrim(oLine.Text());


Global element

The g prefix applies to global objects and functions, and optionally, global types and interfaces. It also applies to global enumerators, which are essentially global constants:

enum tTurn {

In C#, the prefix can be omitted from enumerators, since these are qualified with the type name when used.


Element with internal linkage

The y prefix applies to objects, functions, and enumerators — and optionally types and interfaces — with internal linkage:

static int yIDCurr = 0;

void tView::Del(int aID) {
  if (aID == yIDCurr)


Protected class member

The c prefix applies to class-protected objects and functions, and optionally, protected types and interfaces. Outside of C#, it also applies to protected enumerators, which are essentially protected constants.


Private class member

The e prefix applies to class-private objects and functions, and optionally, private types and interfaces. Outside of C#, it also applies to private enumerators, which are essentially private constants.

Distinguishing protected from private entities is helpful when implementing parent classes.


Function parameter

The a prefix applies to function parameters. Though they are essentially local objects, it is useful to distinguish them because modifications to reference parameters change data outside the local scope.

This prefix does not apply to macro parameters, as these are not necessarily objects, and macros do not define scopes.


Local element

The o prefix applies to local objects. In languages that support them, it also applies to local functions, and optionally, types.


Static class member or local static object

The s prefix applies to static class members and local static objects. It warns that changes to such objects have effects outside the current invocation or instance. It also warns against static initialization and deinitialization order fiascos.

The prefix does not apply to elements declared static to create internal linkage.


Virtual function

The v prefix applies to virtual class functions. It warns that a function’s behavior may change in subclasses, and prevents virtual functions from being unknowingly called within constructors.

The prefix obviously cannot be applied to virtual destructors.


Union member

The u prefix applies to union members. It warns that changes to such objects overwrite other parts of the union.


Non-const reference

The r prefix applies to non-const references. It shows that modifications to such objects have effects outside the current scope. Because they cannot be modified, const references do not receive this prefix.

In C#, ref and out parameters include this prefix. ‘Class references’ or ‘object references’ — which are entirely distinct from C++ references — are prefixed with q instead.


Object reference

In languages that support them, the q prefix applies to ‘object references’ and object-referenced types. It shows that changes to a referenced instance persist outside the current scope. In C#, it also distinguishes classes, which use the prefix, from structures, which do not. This guards against unwanted copying and boxing.

Note that ‘references’ in C++ differ fundamentally from the ‘object references’ found in C#, Java, and Delphi. C# does offer C++-like references in the form of ref and out parameters, however.


Data pointer

The p prefix applies to data pointers and data pointer types:

typedef tFld* tpFld;
tpFld opFld = 0;


Function pointer or delegate

The d prefix applies to function pointers and function pointer types, including those referencing class functions. In languages that support them, it also applies to delegates. Distinguishing function pointers from data pointers allows the same root to be used by identifiers of both types:

typedef tTbl* (* tdTbl)(const string&);

tdTbl odTbl = &eTblFromFile;
tTbl* opTbl = odTbl("Cust");


Managing object

The b prefix applies to objects (like auto pointers) that are used to manage other objects. Distinguishing objects from managing objects allows the same root to be used by both varieties of element. It also avoids confusion about the element’s type:

tAuto<tPart> obPart(new tPart);

// Invokes tAuto::Ck:
bool oCkAlloc = obPart.Ck();
// Invokes tPart::Ck:
bool oCkPart = obPart->Ck();



The h prefix applies to handles and handle types. Though not a language feature, handles are a common means of address, and labeling them allows the same root to be used when a concept is referenced in distinct ways:

typedef int thRec;

tRec oRec(oTbl.First());
thRec ohRec = oRec.h();



The i prefix applies to iterators and iterator types. Though not a language feature, iterators are a common means of address, and labeling them allows the same root to be used when a concept is referenced in distinct ways:

tiRec oiRec(oTbl.First());
for (; !oiRec.EOT(); ++oiRec)
  cout << *oiRec << endl;

Design notes

Scope prefixes and types

Some versions of this notation have applied scope prefixes (like g, c, and e) to types and interfaces. Such elements can produce the same name-hiding problems that affect objects and functions, so it makes sense to label their scopes. On the other hand, types are defined less often than instances, somewhat limiting the chance of a type name collision. Longer prefix strings are also less legible.

The decision ultimately may be better left to the programmer. Those who define many nested types may prefer to document type scopes. Those who do not may prefer to prefix types with t alone.

Scope prefixes and overrides

It is possible to change the access level of a virtual function when overriding it, rendering the scope prefix assigned to that function invalid. There is no way for a naming convention to account for this, however, and the practice is questionable from a design perspective, so it seems best simply to avoid it.

Macro parameters

It would occasionally be helpful to label macro parameters, but the a prefix would be misleading here, and it seems wasteful to dedicate a new prefix to this concern. To prevent name collisions, macro parameters may be prefixed with z.

Return values

In earlier versions of the notation, functions were labeled with r or p if they returned non-const references or pointers. This clarified the effect of modifying such return values, but it made function identifiers somewhat difficult to interpret. The current version is simpler, and even without help, it seems unlikely that return value modification could cause much confusion. If the value is used right away, the effect is obvious:

IDNext() = oID;

Conversely, if the value is stored for later use, the object receiving the return value must bear the appropriate prefixes:

int* opID = IDNext();
*opID = oID;

Cleanup obligations

Most resource management can and should be handled with RAII; this approach is occasionally impractical, however, and it cannot be implemented in a meaningful sense within C# or Java. It therefore might be useful to label types and functions that incur cleanup obligations; this would go somewhat beyond documenting ‘language features’, however, and prefix strings in C# already seem too long. For now, cleanup obligations remain undocumented.