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.
In IT the learning never stops. If you stop you're dead. Probably why I do support now the past decade because I was tired of keeping up and continuing to take classes and studying. I can't study anymore nearly 20 years removed from college.
Just to reinforce this. College will teach you how to code (at least somewhat) and some important concepts, but software is a life long education process. New languages arise, tools change, etc. Plus you need to always steadily improve your non-code skills, which means reading books on software culture, QA books so you understand how QA is going to test your stuff, design books so you can write code that actually has a plan behind it
Consider that almost all fields that morph like software require continuing education classes. Doctors, lawyers, all civil engineering, even insurance brokers, etc. And I mean require in the legal sense, as in your license to practice will get revoked if you don't fulfill the yearly continuing education criteria (which usually means attending training classes a few times a year).
Software doesn't legally require CE (continuing education), but not much stays put and if you're content to simply forget about keeping up to date then you'll find yourself unemployable in your chosen field after about 15 years.
At first I thought that this was clearly intended to go up somewhere else and got posted by mistake ;/
I thought so too. It's odd seeing this in an Elemental Dev journal. Ah well...Good read. Nice reminder on what a n00b I am when it comes to programming.
Thanks for the insights Cari. I might be able to find some time to digest this information before I have to go back to university this fall.
P.S. I'm really bad at #7. Is there any good tricks to avoid re-inventing the wheel? Its really a waste of my time to try to code something that already exists, and I can't seem to find.
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.
I'm a C# coder, not a C++ one, but some comments from my experience. This rule:
Renders this one useless:
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.
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..!)
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?".
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).
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.
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?
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.
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.
praise to the coders, i'd slit my wrists if i had to deal with this stuff daily.
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.
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
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.
Just wondering:
So in theory we can mod GalCiv 3 ourselves?
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".
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.
If you need the prefix before the variable name to remember that the variable can take values outside the signed int range you have a problem. That should be in the documentation, but not in the variable name. Maybe I read your code and I think you used unsigned because the variable can't be negative, which is a wrong assumption, so using the prefix to make assumptions about how something works is dangerous to say the least.
Continous integration is really good to have if you make the effort to have a good set of test (let it be because you use TDD or just because you test your code afterwards). Checking changes in really important places knowing that you have tests to give you a little more or "faith" in your changes is always nice.
Oh I completely agree that it's a horrible practice to reassign input objects, and I can't offhand think of any cases where it would make sense. It's no more safe than doing so in C++ though, unless you delete the object you point to it still wouldn't affect the outside code....not that that makes it a better idea.
I think unit testing is one of the more underestimated QA elements in programming. It's a pain to write good unit tests, but it's a godsend to have them. I recently had to refactor the types some of our database entities, and without seeing where the unit tests failed due to casting it would have ment clicking around inside our app for hours to test every possible query. Hibernate is my friend, but it is a very stubborn and demanding friend.
17) I'm eagerly waiting for the next C++ version to come up with its nice "auto it = pVector->iterator(); it != pVector.end()" instead of having to use typedefs.
18) Does that mean you don't have a compilation server compiling all the check ins in real time? Why would you test compilation AFTER committing?
And you have to rewrite all the variable names if you change the return type of a method
Yikes! Poor you.
All of that sounds like solid information, but if you don't remind him not to eat your Egg Salad... it's all going to end messy anyway...
There are many great features available to you once you register, including:
Sign in or Create Account