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 5)
7 PagesFirst 3 4 5 6 7 
on Aug 04, 2009

Hi,

I'm quite interested to see that you guys still use a form of hungarian notation (variable and class prefixes, and scope prefixes).

We code in C#, and we haven't used hungarian (except for controls on a form: txt for textboxes and so forth) since the death of VB6. I used VB.NET for a few years before they finally convinced me to adopt C# (and truth be told, I still prefer VB, but that's a whole other debate), but I didn't use any form of hungarian notation even in VB.NET.

I've never coded in C or C++, but I find in .NET it's kind of redundant to give variables type and scope prefixes, what with IntelliSense and the ability to mouse over an identifier to get the declaration in a tooltip, not to mention F12 for "go to definition."

These days, I use camel-case for all variables, and I'll use something like int customerCount for a counter in a for loop, and so forth. If you don't know by "Count" that it's a counter (which in 90%+ of cases is an int), you can mouse over any reference of customerCount, and VS will display "private int customerCount" in a tooltip.

Thoughts?

Cheers
Graham 

on Aug 04, 2009

Tooltip will help you in most cases. The hungarian notation is good, if you print the code and work on paper or you are writing a book about programming.

Even with tooltips I think it is good, if you use "m_" (class members) and I used "sz" prefix for T strings (it is not good to use (char *) on windows platform, where str prefix is used). Starting every class name with "C" is bad due to the reasons already mentioned (if you are trying to access them in IDE all first letters are C).

I also much rather use "i", "j", and "k" variables for cycles, unless there is a very good reason not to (= lots of code between for () { and }) .

on Aug 04, 2009

For all programmers and folks who want to program. Read Code Complete by Steve McConnell. It's truly the bible for what it means to write readable, maintainable code. And it goes farther than just "I think this looks good and here's why" type explanations. It lays out different styles, points out + and - for each, even references actual code comprehension studies in some cases (specifically, I remember one referenced on indentation levels).

Even if you find parts of Code Complete you don't agree with, at a minimum you'll be exposed to alternatives that make you think about the style you've chosen.

on Aug 04, 2009

Wow, didn't expect a thread like this here!

I have until recently been a lapsed compSci major myself - and can definitely attest to the fact that a university education really doesn't make you ready to go out and work on a project like a game.  (Not only that, it can ALSO burn you out on programming in general for a few years... ugh)

It's really interesting to see a list like from a Real Life Game Studio (that makes games I like playing).  I've recently been at working trying to write my own engine and tools (with a far, far more limited scope than the Stardock codebase, obviously!), and after writing just about everything three times it really drives home the necessity of many of these rules.  (Except for, as everyone else seems to mention, Hungarian notation.  I just can't read the stuff!)  I just started trying to work with boost's shared_ptr (which at least in spirit follows from #4 here) after reading the relevant chapters in Meyer's book, and have finally decided that it's pretty neat.  (I only decided this AFTER I figured out that if I construct two seperate shared_ptrs from the same original pointer that things go horribly wrong...)

It's also interesting to see how many of these points are really about effective collaboration rather than actual code-writing.  I can only imagine the shift from working on a project where you wrote everything yourself to a massive collaboration...  It's terrifying, really.  It may just be me, but I've always thought that the single most difficult thing in programming is trying to look at a piece of code that somebody else wrote and work out how it does what it does.  I've always been absolutely abyssmal at it, actually.  I was recently trying to work with the Collada DOM and got so frustrated trying to understand why on earth it was designed the way that it was that I simply gave up and started rewriting a Collada loader from scratch using tinyxml (which, in retrospect, was actually a much better decision - raw XML is, if anything, easier that using the Collada DOM, and writing a loader/conversion interface for a format like Collada is really educational.).  The point (if I had one) being that working with other people on a complex project is REALLY HARD - and you can really tell that based on the amount of emphasis on communications stuff in the coding guidelines.

I'd love to see more along these lines.  I find the game programming side of things really fascinating.  (I mean, I find the game designing side of things fascinating too.  And I often find the game playing side of things to be much more fascinating than is good for me...) 

 

on Aug 04, 2009

I've found hungarian notation to be nice 15 years ago, but with modern environments (I use VS 2008 and DevExpress's CodeRush) hungarian is redundant. Wikipedia has an excellent article on the subject. http://en.wikipedia.org/wiki/Hungarian_notation

I'm surprised that I didn't see Knuth's 'Art of computer science' volumes there. There are three finalized volumes and some 'fasciles' out there for volume 4. If you want to understand the algorithms at a deep level, they're fantastic, although too deep for most people.

Another tool that's great for bringing someone up to speed on new code is one called Enterprise Architect. It's a UML modeling tool at a reasonable price. You can suck in a project and get an overall view of how the pieces are connected pretty quickly.

 

 

on Aug 04, 2009

This thread is a wealth of information. Thanks for posting it.

on Aug 05, 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.

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.
...

 

Hungarian notation has been misinterpreted as "prefix with data type" rather than "prefix with type".  It might have better been called "prefix by kind".  That is, it's not the physical data type, it's the logical data type.

There's a great overview at http://www.joelonsoftware.com/articles/Wrong.html.

on Aug 05, 2009

I really like to see these sort of things. Please publish more. 

I'm not as familiar with C/C++ environments as I'd like to be. I have been developing in a Lotus Notes/Domino environment for various companies for over ten years now and most of those concepts still resonate with what I do.

Couple of suggestions:

Document the WHY not the HOW.  Any programmer should be able to read the HOW in the code, but I need to know WHY this code was done this way. This goes double if you do something unusual.  I can't count the times I've tried to figure out WHY a function is sending emails to Nigeria, only to have the original developer gone, and the documentation consists of  //now we email Nigeria

Hardcoding should be grounds for termination, or at least the recipient of the cheque for the next company outing. Anything that can be externally configured should be moved out of the code. If a scripter or artist or playtester can tweak values (unit speeds for example) this frees up valuable programmer time.

The ability for the functions to optionally (command line switch?) output USEFUL logging. I don't know what sort of trace logs you can do in your environments, but if you can have the program tell you WTF it's doing, debugging gets so much easier.

on Aug 05, 2009

 

RalphT,

I agree that Enterprise Architect is a fantastic product for the cost.  You basically get similar functionality to Rational Rose at literally 1/50 the cost.

I think you have 3 types of people posting in this thread...those who are in the biz of software development, those who are in school and trying to learn, and those who are seeing their eyes glaze over from all this technical talk.  Great discussion though, keep it going!

on Aug 06, 2009

I'm a Java developer and I'm no more in C/C++ since ages and it has been funny reading these guidelines: some of them are best practices that no-one would break, and others are just not applicable to Java.

Let's make a comparison, just for fun!


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.

Ok, it's a good practice using "final" in Java.


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;

Java developers love using LOOOOOONG variable/function/class names!

But I never understood the reason to prefix variables with their datatype. Perhaps because in Java there are simplier (and fewer) native types, and it is common to use setters and getters and user-defined datatypes (beans).

m_ and p_ prefixes are quite useless since you can setup the IDE to display them in different styles.


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.

Not applicable to Java, I think. I don't know anything avout ref counting or STL


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.

I say... let your IDE help you with this.


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.

Never heard about TCHAR or _T()



16) Use this-> when calling member functions.

You can't directly call non-member functions in Java, so it's not a problem


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;

Interfaces come in help. If you know you need a sorted list you declare your variable as List and then you can choose any implementation of it and change it whenever you want.

And if you can't avoid the refactoring, just do it! It works very well and it makes you save up a lot of time!


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.

I absolutlely agree.

 

Assuming that coding guidelines like these are well recognized and followed by every good Java developer (there are also official guidelines about coding style) and assuming that it is the case for C/C++ too, I would shift the focus to something more subjective; a subject touched on in 19) but never developed more. Like the way to organize classes and packages, usage of inheritance and polymorphism, perhaps a little about cyclic and backward dependencies (DSM), usage of well known and useful patterns ...

 

I'm waiting for the time when AAA games will be developed in Java, I'll be there

on Aug 06, 2009

SoulSpirit2

16) Use this-> when calling member functions.


You can't directly call non-member functions in Java, so it's not a problem

I don't think that's the spirit of that rule. You can't call non-member functions direcly in C# either and that rule is enforced in StyleCop (SA1101 "Prefix Local Calls With This", under Readibility->Member Access). You can read about why to use that rule here:

http://www.thewayithink.co.uk/stylecop/sa1101.htm

on Aug 06, 2009

Document the WHY not the HOW. Any programmer should be able to read the HOW in the code, but I need to know WHY this code was done this way. This goes double if you do something unusual. I can't count the times I've tried to figure out WHY a function is sending emails to Nigeria, only to have the original developer gone, and the documentation consists of //now we email Nigeria

ROFLMAO

on Aug 07, 2009

Vicente

Quoting SoulSpirit2, reply 70
16) Use this-> when calling member functions.


You can't directly call non-member functions in Java, so it's not a problem


I don't think that's the spirit of that rule. You can't call non-member functions direcly in C# either and that rule is enforced in StyleCop (SA1101 "Prefix Local Calls With This", under Readibility->Member Access). You can read about why to use that rule here:

http://www.thewayithink.co.uk/stylecop/sa1101.htm

I think in java or C# this is not a useful rule at all. If you see f() you know it cannot be something different from this.f(), so adding this is just typing more characters and making the code less readable imo.

In C++, f() can be this->f() or ::f() so it's interesting to make the difference. In fact, prefixing globals with :: (or namespace) is usually a good idea and maybe better than requiring this-> in terms of ease to read.

on Aug 07, 2009

LDiCesare

I think in java or C# this is not a useful rule at all. If you see f() you know it cannot be something different from this.f(), so adding this is just typing more characters and making the code less readable imo.

In C++, f() can be this->f() or ::f() so it's interesting to make the difference. In fact, prefixing globals with :: (or namespace) is usually a good idea and maybe better than requiring this-> in terms of ease to read.

Yes "f()" can be something different than "this.f()": it can be a static call to a static method of the class. Putting "this." you are saying very clearly that what comes after "this." is instance usage (method, property, attribute,...), not static or local.

That's what makes that rule interesting for readibility.

on Aug 08, 2009

SoulSprit, I wouldn't go overboard with the 'final' in Java, as it can clutter your code. It's also just a bit too defensive for my tastes: you need to think first do you really mean that no-one should ever be able to extend this class? Having said that, it's very useful if you want your objects to be immutable for thread safety purposes (data that can't be changed doesn't need to be locked).

Reference counting doesn't apply to Java. Java has garbage collection to clean up unused objects. In C and C++, people have to do it manually using free(*object). Ref counting is a method whereby if you hold a reference to an object then you let it know, so it can automatically free itself when nothing holds a reference to it.

STL algorithms I assume means standard library algorithms: don't reimplement them yourself because the standard library version will probably be better. I think it is also applicable in Java to prefer using foreach or an iterator object, because a manual for loop is more prone to mistakes. There was a lot of hand waving about performance when these were introduced, but really an iterator object is a tiny cost, and by using a foreach loop you are declaring your intent to the compiler more clearly. And the JVM compiler is spookily clever, so you definitely want to be working with it and not against. In .Net languages for loops are very rarely used: mostly it is better to use Linq. Similiarly, in most scripting or dynamic languages you should usually be doing this with list comprehensions. These are usually called stuff like 'select', 'map', 'where', 'filter', 'aggregate', 'reduce'.

On the 'this' keyword, in Java and C# 'this' is often worth using it in your constructor, as it's an easy mistake to assign an input parameter to itself.

As for long names in Java, it reminded me of this: http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html . Although there is room for stupidly long named ManagerControllerExecutor types in .Net too: WPF value converters are far and away the worst named items I have encountered so far.

7 PagesFirst 3 4 5 6 7