Lead Developer, Stardock Entertainment
Published on July 31, 2009 By CariElf In Elemental Dev Journals

We've got a new coder starting on Monday and in order to help him out, my co-workers and I have been compiling a list of guidelines about our coding practices.  Up until now, I've just sat down with new coders and given them a tour of our codebase and tried to convey our coding practices at that point, but new people always have a lot to remember and having a document to refer to might help.  I thought that I'd share our list with you and see if you have any suggestions to add.

                                          Stardock Dev Guidelines

Read Effective C++ 3rd Edition (More Effective C++ is redundant as it's older than the 3rd edition, which compiled the earlier edition of Effective C++ and More Effective C++) and Effective STL.  You'll recognize some of Scott Meyer's tips in the list below, and it will help you write better code. 

1) Make Get functions (functions that just return a variable) const, unless it's returning a pointer or reference to a class that needs to be modified. Also use const correctness when passing variables.

2) Name variables so that they tell something about the variable.

Don't use single letter variables. Even if it's x y and z for coordinates, use the prefix to indicate what type it is.

Try to avoid generic variable names; instead of ulIndex, use ulShipIndex when iterating through a list of ships.

Class names should have a C in front of them unless they're functor, e.g. CBasicGameObject

Basic types should have a prefix before the variable name:

- b for BOOL,

- n for INT,

- l for LONG,

- ul for ULONG,

- f for FLOAT,

- d for DOUBLE,

- sz for null terminated strings,

- str for STL strings,

- c for CHAR (or TCHAR),

- by for BYTE (unsigned char)

Class member variables should have m_ as a prefix, e.g. BOOL m_bInitialized;

Pointers should have a p in the name, e.g. CMapData * m_pMapData;

References can have an r in the name, e.g. CMapData & rMapData;

Global variables should have g_ as a prefix, e.g. CResourceManager * g_pResourceManager;

3) Use the typedefs for the basic types; if we have to port our code to another platform, it will be easier to add in a header with the #defines. Try to avoid using ints as the size of ints can vary based on the OS, use LONGs instead. Use BOOL instead of bool when possible.

4) Game objects, data classes, and graphic resources should be ref counted. So derive them from CCoreRefCount.

5) STL algorithms make your life easier and can be faster than doing the same work manually. It's faster to call std::for_each on a vector and pass it a functor than to loop through the vector yourself. If you do need to loop through a vector yourself, use iterators instead of accessing the elements with the [] operator, particularly if you're going to delete items.

6) #include "Kumquat3D.h" in every header file.

Try to avoid using #include for other files in your header file. Use forward declarations if possible in the .h, then use #include in the .cpp. It helps with compile times.

7) Check for existing code before you re-invent the wheel, and ask about code you're not familiar with before changing it.

8) Use Skype + IRC. If you don't need an immediate answer to a question, this is less disruptive. It takes at least 15 minutes to get back in the zone once you get out of it. Also, you can seach chat logs for conversations that happened over IM and you can't for verbal conversations.

9) Get a Google e-mail for Google docs, and a Windows Live ID for Mesh.

10) When designing your code, think about if this is code that may be used again. If so, it may belong in Kumquat3D rather than the game project, or write a base class in Kumquat3D and inherit it in the game project so that you can use app specific code.

11) Write detailed change log entries. A text file to be used for the change log is saved with each project.

12) Commit the entire module at once so that there's less chance of code being left out, and so that people who are trying to update while you're comitting your changes won't get partial changes and have to update again when you're done.

13) Don't hard-code strings, use the string file for screens and data files for strings. Also try to avoid hardcoding filenames.

14) Use TCHAR types and their functions, and use _T() for literal strings. Not all of our code uses TCHARs, but we're converting it as we go.

15) Write wrapper functions instead of accessing screen code directly from game code, if it's absolutely necessary that the code be done in the screen code rather than game code.

16) Use this-> when calling member functions.

17) It's often useful to create typedefs for container classes, so that if you change from using a vector to a deque, you don't have to search and replace in your code, and even if you don't, it makes the code more readable.

e.g. typedef std::vector<CBasicGameObject*> BasicGameObjectArray;

18) Try to remember to get someone to update to grab your changes after you've commited them so that you can make sure that you've checked everything in and that everything works on someone else's computer. Or check it out yourself on your testbox and make sure that it compiles.

19) Function names should indicate what the function does. Get means that it's returning a value; if you're calculating the variable, use Calculate or Convert or something of that sort in the title. If you're doing more than one thing in the function, consider whether you should be breaking it up into smaller functions, particularly if you find yourself copying and pasting code in the same function. Breaking the functions into smaller functions may also help you make the task more efficient.


Comments (Page 3)
on Jul 31, 2009

One this you might want to do to save yourself huge amounts of time is to have every function have an explanatory comment section preceeding it that has some string of characters common to all function descriptors.

An example of this would be something like:

//**WAJFFXCD**Function SpellStoneRain(ply [casting player entity]. bFirstCast [boolean true if never cast before], target [target enemy player entity])

//Casts the spell Stone Rain on target player.

//*Explain the effects of the spell and how this function does that*

//Returns (sSuccess [short 0=Spell failed, 1=Spell partially blocked, 2 = complete success])

//End Function Descriptor

Now clearly I probably have some syntax specific things you guys won't be doing, but the idea is there.

You can then automate the creation of a wiki or modding manual for the game by having a program extract all these comments that have **WAJFFXCD** in front of them.

The amount of time modders waste on trying to understand functions and features is huge. That's time that could be spent being productive and adding content. The problems that modders have are often things that developers can resolve in one sentence or wouldn't even be a problem if there were proper function descriptions. Keeping a manual on an ever changing game is hard to do, but when it is automatically extracted from the code it's much easier.

Having a repository like http://luabin.foszor.com/ code that updates with every patch is extremely useful. Having a wiki similar to http://wiki.garrysmod.com/?title=Entity.GetNetworkedEntity is also a must. Things like those two sites prevent thousands of wasteful forum posts.

 

on Jul 31, 2009

I'm a C# coder, not a C++ one, but some comments from my experience. This rule:

Name variables so that they tell something about the variable.

Renders this one useless:

Basic types should have a prefix before the variable name:

If you choose the correct name for the variable so it clearly shows what is the variable about, you don't need to specify the type of it in the name too.

In general I like more C# casing and naming rules than hungarian notation and most of the C++ conventions. Although if you are using code (3rd party libraries and so on) that already follows those conventions then you may be forced to use them to make all the code look uniform.

Also, I have found that in projects with a lot of people involved there can be nearly holy wars about coding conventions like: spaces or tabs, putting { at the end of the line or in the next line,...

For those things is nice using a tool that enforces those conventions (in .NET we have StyleCop). No one is going to like all the rules, and it takes a little time to get used to them, but you can get a big team writting code in the same way, which is great for maintenance and reviews.

on Jul 31, 2009

I just spent a good 20min trying to find a pic which says "I don't understand" or "I don't get it" but no luck....

 

I've done a JavaScript class which I flunked so I get variables but the rest is....well we can't all be geniuses on computerprogramming can we??

 

(Now if this was about computer knowledge THEN..!)

on Jul 31, 2009

Hey CariElf,

As long as you are giving your new hire study materials, a quick read of Design Patterns is good.  Many of the basic patterns like Factories, Strategy, Command patterns, etc have become mainstays in good Enterprise development. 

I left C++ in 2000, but I believe that there are now tools to generate documentation from markup comments similiar to Javadoc, or Sandcastle(.NET).  If everyone documents their methods, you can generate a pretty nice set of HTML docs from the markup.

I don't like the Hungarian notation either because I think it hampers the readability of the code.  However, you bring a good point about the searchability.  Though, I would counter that most modern IDEs allow you to trace variable and function references with a couple of simple clicks.  But if your folks are already used to reading the Hungarian, it can save some time.

Another thing that I've found useful for onboarding new hires is a new hire cheatsheet and checklist.  Which includes the dev tools and apps they need, any network access that needs to be setup, and any useful information like your servernames, dbnames, etc.  All on one page.  It helps with their setup and keeps them from asking basic questions like, "How do I login to your source repository?".

on Jul 31, 2009

Vicente
I'm a C# coder, not a C++ one, but some comments from my experience. This rule:


Name variables so that they tell something about the variable.
Renders this one useless:


Basic types should have a prefix before the variable name:
If you choose the correct name for the variable so it clearly shows what is the variable about, you don't need to specify the type of it in the name too.

Not necessarily; there have been occasions when I could have used an int or an unsigned int, and had both work.  I went with unsigned in some cases because I wanted the extra 'range' to my counting; I went with signed in others because it was useful for debugging (or just because there was no point in using unsigned).

on Jul 31, 2009

Totally agreeing with Ron here. You might not also know that they decided to use a int vs long vs double. It also takes nearly no effort or space including that one descriptive character there.

on Jul 31, 2009

ckessel
Good stuff. Well, aside from hungarian notation. Ack, eww, yuck.

http://www.joelonsoftware.com/articles/Wrong.html

The whole article is about creating good code, but nearer the bottom Joel talks specifically about Hugarian notation, it's original intent versus how it typically ends up being used. The original intent looks pretty good.

 

Best line in here: "Don’t use macros to create your own personal programming language."  LOL

CariElf's comment on lack of good enterprise experience in the classroom is right on.  A lot of computer science programs are really just glorified Java (or C++, or whatever) certifications.  The CS program I went through used Ada95.  Ostensibly it was because at the time it was the "official" language of the U.S. DoD, but in retrospect I wonder if it was done to ensure we focused on mathematics, algorithms, boolean logic, and other language-agnostic concepts.

Good point, too, re: hard-coded strings and localization.  I've ended up needing to use really ugly data structures to store "translation" services for custom reporting.  It gets the job done for light localization work, but would never scale.

 

Any chance of getting Stardock guidelines for data architecture?

on Aug 01, 2009

I think recent graduates frequently don't understand what it means to write code that will be used/maintained for a long time. In particular, it is easy to write a bunch of code that makes perfect sense today, but in two years you are trying to find a bug in it, and can't remember just how it works... and it's even worse if you are trying to pick up someone else's code.

 

Shortly after college, a more experienced colleage told me that he writes comments as if he were trying to describe his code to a complete idiot. When he came back to the code six months later, he found this was about the level he could follow... I have found this to be all too true.

 

on Aug 01, 2009

but in retrospect I wonder if it was done to ensure we focused on mathematics, algorithms, boolean logic, and other language-agnostic concepts.

 

Sounds, to a large degree, like my entire CS program.  Sure, we have to learn C++, C (I skipped this because I transferred schools, and as a result I'm SOL when I have to do stuff in it), C# (I'm told), and there's a class specifically to expose us to ML (OK), Prologue (ICK!) and Dr.Scheme (Lots of Irritating Stupid Parenthesis is right!  LISP).  But most of the courses focus more on the ideas behind those than the languages themselves.

on Aug 01, 2009

praise to the coders, i'd slit my wrists if i had to deal with this stuff daily.

on Aug 01, 2009

I would tentatively suggest:

Don't use magic numbers: significant values should not be typed inline, but rather defined somewhere obvious.

Program to the interface, not the implementation. If you rely on the internals of someone else's class then you have no guarantee that it won't arbitrarily change.

Write unit tests for any core functionality, for components which you intend to share or reuse, and for complex state machines.

Small commits, frequently. Don't keep personal copies of code outside of source control. No-one likes doing mega-merges.

The thing about not talking to people because it brings them out of the zone thing is interesting. It's something I've been thinking about a bit lately because where I work we do a lot of pair programming, which is a very different experience, and because I personally am very bad at doing things and talking at the same time.

on Aug 01, 2009

Interesting post, but some of your practices would make me cry out in horror. For instance the much abused Hungarian notation, works fine as long as you don't change your type. But what happens when someone changes the type of lShipCount to unsigned long and forgets to update the variable name? Not terribly fond of the m_ prefix either, but that one doesn't have the same potential of producing Coding Horror.

I saw some good points as to why it might help in the other posts, but I'm a Java developer and Eclipse does a pretty decent job of telling me exactly what the variable is and colour codes it for me if it's a class member as opposed to a local variable.

The mention of the STL is very nice though, from my brief experience with C++ back in College both students and professors neglected to use/mention it all that often. Some of the worst code I've ever seen, excepting my own of course, was actually Java code where the developer had re-invented pretty much everything from List to Map and Queue. He was an old C++ programmer

ckessel
I know distinguishing between a pointer to something and the thing itself is a big deal, where as in Java everything except primitives is effectively an immutable pointer (ie. everything except primitives is pass by reference).

That's actually not true. All function calls in Java are pass by value not pass by reference, the difference lies in what value it passes. For a primitive it passes the assigned value, but for an Object is passes the value of the pointer. Basically a Java function which has the signature

public void foo( Object myObject ) {} equals the C++ notation of

public void foo( Object *myPointer ) {}

You can reassign it in the function, and when the call returns the original reference won't have changed.  But I think I moved a bit off the topic now so I'll stop before I wander into the hinterlands or Java trivia.

on Aug 01, 2009

Just wondering:

Yeah, I used GalCiv names because they're a bit more recognizeable. The Elemental engine is a cleaned up and updated version of the GalCiv2 engine. Before a new major project we tend to go through and do an overhaul on the engine because we learn a lot over the course of a project, and technology updates.

So in theory we can mod GalCiv 3 ourselves?  

on Aug 01, 2009

That's actually not true. All function calls in Java are pass by value not pass by reference, the difference lies in what value it passes. For a primitive it passes the assigned value, but for an Object is passes the value of the pointer. Basically a Java function which has the signature

I didn't see much point in getting into the details of the difference between a pointer and an Object Reference and how that's handled... Seemed sufficient to compare it to pass by reference, especially since it's horribly bad practice to assign new objects to the input object reference anyway. Even in Java where it'd be "safe".

on Aug 01, 2009

Zakath
Interesting post, but some of your practices would make me cry out in horror. For instance the much abused Hungarian notation, works fine as long as you don't change your type. But what happens when someone changes the type of lShipCount to unsigned long and forgets to update the variable name? Not terribly fond of the m_ prefix either, but that one doesn't have the same potential of producing Coding Horror.

The link that was posted back on page 1 a few posts in has a good explanation of how Hungarian notation is supposed to be used, and it's not that. You don't specify in the variable name that it's a long, because you're right, any modern environment can figure that out pretty easily.

Instead it's supposed to tell you something about what the variable is logically.