The Blog

Style Guidelines

Overview

I have been seeing a lot of questions lately about style.  Ironically, a lot of people have in the past asked me to make a style guide.  The reason I have refused for so long is because people can’t handle a style different from their own.  It is such a personal subject for some people for some reason.  But I decided to finally write one now because it can still be of help to people who are not set in their own ways (yet) and looking for ideas on their own styles.

If you already have your own style and consider other styles to be offensive, you have no obligation to finish reading this article.  This article is only to give out ideas for people pondering their new personal styles.  Feel free to take from it what you will and reject what you dislike.  That’s what makes your personal style personal.

 

Why Conventions/Styles?

When working alone on a hobby project, one may ask why he or she needs to even consider sticking to one style or another.  “Why should I use a naming convention?  I know what I wrote and I can read it fine,” young Akiko said.  Well perhaps you can, but can anyone else?  Will you still be able to read it 2 years from now after you have completely moved on to many new projects?  I have personally been using naming conventions from the start, so I can still read a lot of my code from 14 years ago, but it is still very sloppy and definitely disorganized.  I was inconsistent with spacing, and headers are #include’ed in the order in which I needed them.  I declared and defined class functions at the same time often in class declarations.  Namespaces?  Waste of time, I thought.  Well I was wrong.  All these things were added to C++ to help with organization, and organization is key to a solid project.

Not only that, but it is probably a disservice to yourself to show a potential employer code that does not follow any particular convention or style.  Most companies have styles they use and you will be expected to follow that style exactly.  If you have never tempered yourself even a little in following some style or another, you are unlikely to be able to fit into the team.

 

Naming Conventions

Naming conventions are one of the most important things to consider when taking on a new style.  Without naming conventions, clashes are more likely to happen, and the actual purpose of the variable is less clear.  Some say the variable name itself should indicate what its purpose is.  While that is also true, naming conventions only add more information to the variable, and that information can be quite helpful to others browsing your code.  Pop quiz: Why does the program freeze here occasionally?

for ( count = 0; count < total: ++count ) {}

Perhaps it would be clearer if put this way:

for ( ui16Count = 0; ui16Count < ui32Total; ++ui16Count ) {}

count” and “total” were declared previously in the function.  Figuring out their types isn’t too difficult.  You could hover your mouse over them and wait for a little yellow bubble, or maybe scan up the code in the function and hope they were not too far up there.  But that is assuming you knew to check their types in the first place.  In the initial version, it looks like a harmless loop.  You are probably going to study the insides of the loop for a while, check to see if count or total are changed inside the loop, etc., etc., wasting time.

Hungarian Notation is widely used.  Apps Hungarian is used to indicate how the variable will be used, whereas Systems Hungarian indicates the actual type of the variable.  I personally prefer Systems Hungarian because I already know how the variable will be used.  That is the purpose in giving it a descriptive name.  The quiz above is an example of why it is helpful to have metric information about a variable along with a descriptive name.  In essence, without metric information about every variable, you are going about your debugging session by studying the algorithm, rather than the implementation of that algorithm.

Here is my system.  Again, take or reject as you please.  These are only ideas you can use to mold your own style. Lower priority means they go in front of higher priorities.

  • Variables
    • Priority 1 (origin):
      m_: A class member.
      _: A function parameter.
    • Priority 2 (pointer depth):
      lp: A long pointer. One p for every pointer level.
      p: A pointer. One p for every pointer level.
    • Priority 3 (sign):
      u: Unsigned.
    • Priority 4 (type):
      c: Char.
      s: Short.
      i: Integer.
      l: Long. One l for each long.
      f: Float.
      d: Double.
      sz: 0-terminated character array (String with a Zero).
    • Priority 5 (size):
      8: 8 bits.
      16: 16 bits.
      32: 32 bits.
      64: 64 bits.
      128: 128 bits.
    • Remember not to use sizes on types that do not have a size defined. For example, it is wrong to label an int variable as i32MyHappy. int is not fixed to 32 bits.
    • Priority 4 has additional rules for identifying classes, structures, and enumerations. For these, I use the significant letters and numbers of the class, structure, or enumeration name. Significant letters are typically start of words within the name, but you will have to use your gut sometimes.
  • Classes
    • I put a C before class names, and the name of the class has significant letters (typically start of words) capitalized.  When significant letters follow and they are not the result of a new word, they are lower-cased except for the first one.  For example, in COpenGL, the GL is an acronym, so it is treated as a single word, and only the first letter capitalized.  It becomes COpenGl.  For some reason I feel that I must now reiterate that you can take, reject, and modify what you want to make your style more suitable for you.
  • Structures/Unions
    • Structures and unions are fully upper-cased and begin with the upper-case version of the namespace in which they reside.  Underscores live between the namespace name and the structure name, and between significant breaks in the structure name that could make it easier to read.  For example, in a namespace called lsg, one possible structure name could be LSG_RENDER_QUEUE_ITEM.  Structures and unions are always typedef’ed to have a pointer version and a constant pointer version of themselves.  For the above example, these would be LPLSG_RENDER_QUEUE_ITEM and LPCLSG_RENDER_QUEUE_ITEM respectively.
  • Enumerations
    • Enumerations follow the same rules as those for structures and unions, but are not typedef’ed.
    • Each enumerated item within the enumeration also begins with the upper-cased namespace and underscore, but the following characters are the significant letters of the enumeration name.  For example, within LSG_RENDER_STATES, one enumerated value could be LSG_RS_LIGHTING.  This helps to avoid name clashes and helps inform the viewers of the type or intention of the enumerated value.  Everywhere I see the RS, I will know that this value is one of many render states, and I will have a good idea as to what the code is doing.  It also helps you eye-scan the code better.  If you are searching for all the render states used within a function, you can scan only for “LSG_RS_”.  They should appear to you very quickly.  The alternative is that you have to read each enumeration in the function fully and consider for each one whether it is a known render state or not.  If it is not your own code, you may find yourself jumping back and forth to the LSG_RENDER_STATES definition frequently.
    • An example of where it helps to avoid name clashes is when you have two different objects that are not necessarily related but that both accept similar flags upon creation. No one would suggest that a texture class and a vertex buffer class have much in common. And since they don’t, it is not wise for them to share a common set of creation flags, even if they both have a flag for “set-only”. Giving them a common set of flags causes clutter, since it is not readily clear which flags are used by which class. For example, LSG_SET_ONLY and LSG_RENDER_TARGET would be 2 flags under the same enumeration, but only one of them is applicable to both vertex buffers and textures. Instead, each should define their own flags separately, even if 1 or 2 of them are redundant. This is where a naming conflict would occur without a bit of additional naming rules for enumerated items. Thus LSG_SET_ONLY becomes LSG_TF_SET_ONLY and LSG_VBF_SET_ONLY, and I don’t need to bother saying which flag is meant for which class.
  • Macros
    • Macros follow the same rules as enumerations.
  • Functions
    • Functions have each word capitalized.
  • Namespaces
    • Namespaces are short and fully lower-cased.
  • Files
    • Files begin with an upper-case version of the namespace in which they live. After that, each word in the file name is capitalized. Every .CPP file has exactly one .H file with exactly the same name, and they are in the same folder. Some people put headers and source files into two different directory trees, each with the same structure. After having done so for years I certainly will never do it again. It is nothing but a pain to maintain. Files are allowed to have one class only (though nested classes are allowed), and that class must be named exactly the same as the file itself, but with a C prefix instead of a namespace name.

 

Structure

A consistent structure for your code will help its readability tremendously.  The term structure can cover a wide variety of things, and this is a subject where you can grow forever.  There is always one tiny detail you could do better in your next project.  Here are some ways in which I structure my code.

  • Headers
    • Each module has one main header which defines things such as the primitive types that the rest of the module will use, etc. All other header files in the library/project #include this file first. The only exceptions are for other headers that are intended as helpers to the main header. For example, in my math library, I want my fixed-function template class to be exposed as one of the primitive types declared in the main library header. In this case the template class includes nothing, and the main header includes it.
    • After including the main header, the following #include’s are alphabetized. This ensures ease in confirming the existence of a given #include. Alphabetization is strict. It is not by only the file name, but the actual text inside the include quotes. That is, all #include’s that begin with “../” will be before those that begin with letters. The compare is case insensitive. Here is an example of some #include’s I happened to randomly have in my code while I type this article (I am debugging in the background):
    • #include "../LSIImageLib.h"
      #include "../Bmp/LSIBmp.h"
      #include "../Gif/LSIGif.h"
      #include "../Png/LSIPng.h"
      #include "../Resampler/LSIResampler.h"
      #include "../Tga/LSITga.h"
      #include "Misc/LSCMisc.h"
      #include "Vector/LSTLVectorPoD.h"
    • This is my L. Spiro Image library as can be seen from the first header. By the header file names, it is clear that the image library depends on the L. Spiro Compression and the L. Spiro
      Template Library libraries.
    • .CPP files always include their .H files first, and then again the rest are alphabetized.
    • <>-style #include’s are grouped separately from the “”-style #include’s, but are still alphabetical.
  • Spacing
    • Class functions look much nicer when they are all indented the same amount. Decide for yourself (comments removed):
    • LSVOID LSE_CALL SetAllocator( CAllocator * _paAllocator );
      LSVOID LSE_CALL Reset();
      LSE_INLINE LSUINT32 LSE_CALL GetWidth() const;
      LSE_INLINE LSUINT32 LSE_CALL GetHeight() const;
      LSE_INLINE LSI_PIXEL_FORMAT LSE_CALL GetFormat() const;
      LSE_INLINE const LSVOID * LSE_CALL GetBufferData() const;
    • LSVOID LSE_CALL                        SetAllocator( CAllocator * _paAllocator );
      LSVOID LSE_CALL                        Reset();
      LSE_INLINE LSUINT32 LSE_CALL           GetWidth() const;
      LSE_INLINE LSUINT32 LSE_CALL           GetHeight() const;
      LSE_INLINE LSI_PIXEL_FORMAT LSE_CALL   GetFormat() const;
      LSE_INLINE const LSVOID * LSE_CALL     GetBufferData() const;
    • This is not just for eyecandy.  When people are viewing your code, 80% of the time they are examining the functions they can call.  Usually they are looking at function names more than anything, so it is very helpful to keep the names on the same level.
    • And if your functions are all going to be indented the same amount, your members should be as well.
    • There should also be a specific amount of indention for any comments you want to put to the right of function declarations.
    • When enumerated values are assigned via =, the = should be at the same indentation level as everything else as well.
    • You may find that you have written 4,000 functions/members/enumerations with the same level of indentation, and suddenly you have to declare a function that has so many characters before the function name that it cannot be indented at the same level.  One option is to modify all 4,000 previous indentions to suit to new one, but my solution is to allow the declaration to exceed the indention limit, and then put the function name on a new line, at the same level as every other function name.  Remember, people are scanning your header file for function names, so the priority is to keep the name on the same indention level.
  • public, protected, private
    • In this order. People viewing your header files only care about the public interface that is exposed to them. They want to see the functions they can call, etc.
  • Order of Declaration
    • Enumerations first, then types, then operators, then members, then functions. Inside the public section, swap the order of members and functions. Repeat this order for each of the visibility zones (public, protected, private).
    • Enumerations come first because functions, members, and types can rely on them, but they cannot rely on functions, members, or types.
    • Types follow because they might be needed by functions or members, but never vice-versa.
    • Operators follow because they are not so common, and they can cause problems if the users of the class do not know about them.
    • In the public section, functions come next because again in the public section people are more likely interested in the functions they can call. In the other sections, the author of the class will likely be more interested in the class members rather than the hidden functions on the class. The reason is that you are unlikely to need to type the names of your protected functions as often as you are the names of your class members.
  • Inlining
    • Never inline functions within the class declaration (with one exception). This creates clutter. Put inlined functions at the bottom of the header file or tucked away in a .INL file.
    • Templates are the exception to this. Thanks to the nature of templates, separating definitions and declarations unfortunately provides little gain in readability compared to the amount of jumping back and forth up and down the file. A fixed-function template usually has hundreds of methods—most of which will be operators which do not have decent (or any) IDE support for jumping between declarations and definitions via right-click—and that is not something you want to make if you keep declarations and definitions separate.
  • File Structure
    • For a given project or library within a project, there is a Src folder. Beneath this will be found the main include file for that project/library and a bunch of folders. No other source files may live in the main include folder. This way, when you visit the folder, you can more easily find what you want. If there are a lot of code files in the same directory, you will find yourself scanning with your eyes for a moment, and it may not even be there, instead choosing to hide in one of the folders. So the upper level stays clear of clutter. Each folder should be aptly named and allow files to be grouped logically. There may only be one sub-level of folders. That is, below the Src folder, each folder contains only source files and no nested folders. This reduces the complexity of the tree structure and avoids some hassles I have encountered in the past, but which I currently can’t remember because I need to remember for my article.
  • Comment Flags
    • Mentioned before, enumerations should be declared before types, etc. For each of these groups, it wouldn’t hurt to put a small comment header. I use // == Enumerations., followed by all of my enumerations, then // == Types., etc. I would have gone with a more noticeable header style for my groups, but these are things I type often, and I want to be able to make sure I can type them quickly, easily, and consistently.
  • Spacing 2 (New Lines)
    • Consistent spacing techniques improve the appearance of your code. I have actually defined for myself exactly how many blank lines shall follow the last #include before a non-empty line may appear (1 in .H files, 2 in .CPP files), but in order to avoid writing an entire book I will focus on only a few I think are more important.
      • Functions, members, types, and enumerations within their respective groups have 1 empty line between them. Never more and never fewer. This type of consistency is more aesthetically pleasing than having some functions with no empty lines between them, some with 2, etc.
      • Between these groups, I put two (2) empty lines. Again, no more and no fewer.
    • You can decide how anal you wish to be about this, just as you can decide everything about your own style that you wish. I only mention it here because it is one of the things coders tend to consider the least, but it is one worth considering.
  • Function Order
    • I order functions in source files in the exact same order in which they appear in header files. The same applies to inlined functions at the bottom of header files.
    • During declaration inside class bodies, functions are ordered based on priority, usually with the create and reset functions appearing first. Consider what people want to see about your class while they examine your header files to decide the order of functions.
  • Namespaces
    • Names of namespaces should be as short as possible, and lower-case.
    • It really helps to have the closing brace of a namespace marked with the namespace itself via a comment. For example:
      namespace lse {
      } // namespace lse
  • #ifdef’s and friends
    • Each #ifdef and #if must be matched with a closing #endif by the standard. However it helps to use a comment to label that #endif with the appropriate #ifdef or #if. For example:
      #ifdef LSG_OGL
      #endif // #ifdef LSG_OGL

      #if defined( LSE_WINDOWS ) || defined( LSE_APPLE )
      #endif // #if defined( LSE_WINDOWS ) || defined( LSE_APPLE )

 

 

Conclusion

The process of refining your style is never truly complete.  I hope to provide a few things for consideration when developing your own style, and I reiterate that you are entirely free to take from it what you desire.  I tried to post my reasons for each point so that you can understand why I chose that way and decide if it suits you.

 

 

L. Spiro

About L. Spiro

L. Spiro is veteran of the gaming industry and currently makes video games in Tokyo, Japan, as an R&D programmer for tri-Ace (http://research.tri-ace.com/). L. Spiro has worked on Ghost Recon 2 Online, 187 Ride or Die, Catz 5, Dogz 5, Imagine Happy Cooking, Ready Steady Cook, Leisure Suit Larry Beach Volley, HOT PIXEL, After Dark: Flying Toaster and more, for Ubisoft, Atari, Lucas Arts, Eidos Entertainment, Vivendi Universal, Konami, and more.

2 Awesome Comments So Far

Don't be a stranger, join the discussion by leaving your own comment
  1. __SKYe
    January 26, 2013 at 6:20 AM #

    Great article.

    Also, there is a typo in then “#ifdef’s and friends” section, where it says “…by the stanard” instead of standard.

    • L. Spiro
      January 26, 2013 at 9:20 AM #

      Thank you; fixed.

      L. Spiro

Leave a Comment

Remember to play nicely folks, nobody likes a troll.