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.
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.
Clean coding is great!
Help you find things easy, helps your colleagues when trying to make sense of what you wrote. Still it also makes it easier to fire you... something to keep in mind
1. Invest in a continuous build tool. I've used Bamboo and Cruise Control and either are effective - they monitor your source repository and when someone commits, they can fire off whatever you want...typically a compile and then any nunit tests. They can send emails if someone breaks the build or a unit test. Bamboo is particularly good about keeping tracking unit test results.
2. I just have to go "uggh" at the Hungarian notation. A good IDE can tell you a variable's declared type. Hungarian is painful if you change the underlying data-type. When you start trying to writing type-neutral code (ie working with templates), it becomes difficult to come up with a good prefix anyway. I especially dislike putting "C" in front of my class names. Just capitalizing the first letter sets it apart from functions and variables. Having a C in front makes it hard on me when I am browsing classes and want to get to the class called WalkAnimator. Without the C, I can just hit the W key and the list of classes will scroll down to the W's (most gui lists have this sort of functionality). But when called CWalkAnimator there is no such short-cut. But I guess this is a personal choice and people have warred over the "right way" for years
3. When you do not care about the result, use the prefix increment/decrement operator instead of the postfix increment/decrement operator. It is faster, considerably so for the case of classes. ie write:
++it
instead of:
it++
I really like using const. Since I've been using c# for the past few years instead of c++, I've really missed being able to use const. I wish they would bring it to the language. I was surprised to see the comment about std::for_each() being faster than writing a for() loop yourself. Unless c++ compilers have changed in recent history, the best you can hope for is that the compiler will inline the for loop and also the functor call and you will end up with code that is as fast as a for() loop. More likely your functor code will be too complex to be inlined and you will end up with slightly slower code (an extra function call each iteration).
As an aside, does C++ have anonymous methods yet? Do they plan on adding them in any fashion? Being able to write something like :
std::for_each (a.begin(), a.end(), f(x) { x.DoSomething(); });
Is so much easier when writing your code than breaking your stream of thought to go create a new class or function somewhere then come back to what where you wanted to use it.
Yes, clean coding is great. However I have not seen it since I started programming. Well not quite true, some simple tasks at the university were clean. I am very happy, that I could move from C++ with MFC to Java. It has compatibilty issues too, but not as much as the mentioned deadly combination.
Yep, we do that too. I forgot to put interfaces in there.
I totally agree. I touched on this a bit with the one about functions.
I think that was the book I learned C from in college.
Oh, yeah, that's a good one. We use CTRL+F8 a lot.
The STL and boost both have a lot of useful algorithms and classes to help with stuff. CodeProject often has really good stuff too. That's where we foung PUGXML and a helper class for threads that's come in really useful. Other than that, Google it. For game related programming resources, Gamasutra has a list on it site and you can also ask at gamedev.net. I also meant code that exists in our engine already, though, so in that case it's simply a matter of checking with another developer before writing another xml parser or whatever.
We store data in classes whose data members are private or protected and accessible only by functions (we try to avoid using friends). Rather than trying to make the class generic, we actually have specfic varaibles for specific members. We had a programmer on GalCiv2 who introduced us to the concept of property buckets, which map a string ID to another string which holds the value of the field. While this is useful for temporary storage while reading in for XML, using it for regular classes makes it much harder to debug and adds overhead. So rather than having a std::map<std::string, std::string> to hold all of the class' data members, we actually declare them normally, like FLOAT m_fStrength, FLOAT m_fSensorRange, etc. The most generic we get is to have an array of abilities, where the index of the array is defined in an enum. That way, it's quick and easy to add new abilities but we can still easily view the values in the debugger. Other than that, I'd have to think about what we do more.
Heh, I should have thought of this one. I complain about it all the time.
Yes, but why are you changing the type? Changing type should not be done lightly. Things like population, which cannot be negative, should be expressed as ULONGs. Changing it to LONG would be meaningless, and changing it to a FLOAT or DOUBLE would be equally silly. If you're making a switch from unsigned to signed, you're going to have to make a lot of changes and make certain that everywhere that deals with that variable is still using correct behavior.
I had been certain that this was in Effective STL, but maybe I was thinking of the std::find function. Now I'm going to have to re-read Effective STL.
Has there been any talk about Python programming style? I would strongly suggest using PEP8 (http://www.python.org/dev/peps/pep-0008/), as this is something every Python programmer should know. And it makes things consistent if you are using some third party code in mods.
By the way, reading through that PEP is quite informative even if it will not be used for Elemental. Lots of good advices in there!
Unfortunately, PEP8 is in conflict with the style of most major languages (C, C++, Java, C#, etc). Specifically the 79 character line limits and the _ for naming conventions rather than camel case.
Given Stardock is a C++ shop, they'd probably be better suited to maintaining a C++ style in their Python code for overall consistency and avoiding a brain mode-switch when switching code bases.
A std::foreach will be better than a naive for( int i = 0; i < list.size(); i++ ) doSomethingWith( list[i] ); unless the compiler sees the code is stupid and should rewrites it inself into C-like: for (ptr = list[0]; ptr< endadress; ++ptr) doSomethingWith( ptr );
I personally find the std::foreach ugly and unreadable and prefer the old for, which is fine with iterators except std::vector<whatever>::const_iterator is a pain to write and often requires one or two typedefs.
Because the guy before you picked a bad type. A short instead of an int because he thought it was big enough, for instance. Or conversely, an enum when a bool would have been better. Or you template your method and the float argument is now a <T> which may be int, bool or whatever. There are many reasons, including the most common one: We make mistakes and shouldn't shoot ourselves in the foot for having made mistakes. Of course, a game code base has a short life expectancy, but in code which lasts tens of years, prefixing the type is a really bad idea . Examples there: http://mindprod.com/jgloss/unmainnaming.html (item number 31, but the whole site is a must-read).
Everyone everywhere is always supposed to check in code that compiles. An integration server does the check automatically and warns people about their mistake without requiring them to manually do a compilation on another machine/clean environment. CVS, SVN, Perforce and other versioning systems I know are all equally unable to guess when a file should be added to a project and when not.
I just finished reading through that. Can I get a couple of Tylenol, please?
"Don't use single letter variables."
As someone who has programmed a simple genetic algorithm on my TI-84 calculator, I can attest to how important this is. By the end I needed to write down all the variables and what they were for on a peice of paper, and had ended up using all the letters of the alphabet, some used more than once in the code. Thinking about doing it in more complex code is cringeworthy at the very least.
We are good at it in my company. Especially at points 3, 6, 11, 14 and 15
for the link.
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?
CheersGraham
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 }) .
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.
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...)
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.
This thread is a wealth of information. Thanks for posting it.
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.
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.
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!
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!
Ok, it's a good practice using "final" in Java.
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.
Not applicable to Java, I think. I don't know anything avout ref counting or STL
I say... let your IDE help you with this.
Never heard about TCHAR or _T()
You can't directly call non-member functions in Java, so it's not a problem
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!
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
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
ROFLMAO
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.
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.
There are many great features available to you once you register, including:
Sign in or Create Account