Organizing and managing code?

Started by
16 comments, last by snowmanZOMG 12 years, 1 month ago
Okay so I've been delving into C++ for a couple of months now, trying out everything, using SDL, openGL, box2D etc.. and I think I'm ready to start on the project I've had in mind, but there's one thing I really fear before starting.

What's the proper way of organizing and managing my game code?

I ask because I keep seeing how even the tiniest memory leak could lead to disaster and with thousands of lines of code and dozens of files, debugging is going to be a nightmare.

First, what should I keep in mind so that debugging isn't a complete mess?
So far with all my little tests, the compiler seemed to have erratic behavior with errors. Syntax errors would get pointed to normally, but a null error would cause the game window to close. Is there an easy way to find and catch null references without having to check manually for them before using any variable? Any other debugging tips?

Second, memory issues
I basically just want a general outline of how to make sure the code structure is optimized and how to deal with memory leaks should they arise. I wouldn't want to be programming only to find out my entire code is an un-fixable mess after 4 months.

Like, any common pitfalls or an article showing the proper way to structure your code, or if anyone has any suggestions about stuff to look out for or bad practices, that'd be of great help!

Also, just to give you an idea of what I mean in case I'm not making much sense, I've only been seriously programming in Actionscript 3, where I never really bothered with removing everything when the game was done, I'd just stack what I'm going to re-use outside of the screen. But with C++, I'm the one who needs to remove *everything* when the user closes the window, so that's something I wasn't really aware of at the start.
Advertisement
Folders.
Source control (like SVN, Git, Hg).
Properly named files.
Keep them relatively small and focused. A few thousand lines of code in a source file is OK, but not a great idea. For example, don't do this

Use Asserts, everywhere. They get removed during release builds, but can provide valuable checks for PROGRAMMER ERRORS in debug.

Additionally: DO not write code in asserts. An assert should be a boolean check, NOTHING MORE. Don't call functions and check their return result in an assert, because it'll be gone come release.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Wow, asserts, that's a really valuable tool there, thank you Washu!

Another question just popped into my head, about exiting and removing everything, do I have to keep track of everything on screen at all times in order to know what to remove? In case the user exits at the main menu, or at a specific level, where some assets will be there and others won't.

Wow, asserts, that's a really valuable tool there, thank you Washu!

Can't tell if you're being sarcastic here or not. But yes they are very valuable. An "assert(this);" can save you hours of debugging by simply pointing out that "this was null". An assert is not for USER ERROR, it is for the things that you are assuming are true. I.e. if you have a function that takes a string and the string must never be null... assert(string != null); can save you from going "FUUUUU". Especially since it tends to give you a nice stack trace, so you can find exactly where it's being called.
Another question just popped into my head, about exiting and removing everything, do I have to keep track of everything on screen at all times in order to know what to remove? In case the user exits at the main menu, or at a specific level, where some assets will be there and others won't.
[/quote]
Uh, asset tracking is rarely associated with the visibility of the asset, just its existence. When exiting your application you should attempt to do as little work as you have to, preferring just to clean up what needs to be cleaned up (i.e. resetting the resolution to default, maybe closing sockets, and saving any open files), and then just exit. Windows will tend to handle the rest.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.


Can't tell if you're being sarcastic here or not. But yes they are very valuable. An "assert(this);" can save you hours of debugging by simply pointing out that "this was null". An assert is not for USER ERROR, it is for the things that you are assuming are true. I.e. if you have a function that takes a string and the string must never be null... assert(string != null); can save you from going "FUUUUU". Especially since it tends to give you a nice stack trace, so you can find exactly where it's being called.


I was genuinely being grateful! I apologize if I came off as sarcastic.


Uh, asset tracking is rarely associated with the visibility of the asset, just its existence. When exiting your application you should attempt to do as little work as you have to, preferring just to clean up what needs to be cleaned up (i.e. resetting the resolution to default, maybe closing sockets, and saving any open files), and then just exit. Windows will tend to handle the rest.
[/quote]

That's actually a relief, so I don't need to remove every single variable and every single asset when closing.

Again, thanks for the speedy replies Washu! If you or anyone else still has any debugging and/or optimization "Gotcha's", feel free to post! Maybe others will benefit as well.
Everything Washu said. Expanding on source control: test your code before you commit. Don't commit anything that you know will break something else without having fixed that something else. Give useful, concise messages when you commit, so that 3 months down the road when you have to look back to why you made that change that introduced a bug, you have a good idea. Try to keep your commits to small units (i.e. commit things in groups, where each group makes logical sense; don't change a bunch of files and commit them all together if the changes in one file were completely unrelated to changes in another file).

For memory: use smart pointers. If you don't, you better have a dang good reason. And "speed" isn't a good enough reason unless you've proven with a profiler that it makes a significant, critical difference.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Wow..smart pointers. Is it just me or is C++ a very dense language? There are so many ways to do everything and so many things to take advantage of. And I thought having "pointers" was strange, as opposed to having everything just being a normal variable. What's wrong with using just regular variables anyways?

Also, another quick question. Right now, what I'm doing is putting the assets I'm using in the directory of all my source files, and in the bin/debug/ directory as well. But I imagine as the assets grow more and more, adding everything twice isn't the way everyone else does it. Or should I just keep having a copy of the assets in the bin/debug, and as well bin/release and my source directory?

Wow..smart pointers. Is it just me or is C++ a very dense language? There are so many ways to do everything and so many things to take advantage of. And I thought having "pointers" was strange, as opposed to having everything just being a normal variable. What's wrong with using just regular variables anyways?

Nothing is wrong with regular variables. There are just certain things you can't do with them. For example, say you want an array of integers somewhere, and you want to prompt the user how big this array should be. You can't just create a normal variable on the stack that has an arbitrary length that you can change depending on what the user says. You have to prompt the user how big the array should be, and then dynamically allocate that memory on the heap and access it through a pointer, which is exactly what std::vector does for you.


Also, another quick question. Right now, what I'm doing is putting the assets I'm using in the directory of all my source files, and in the bin/debug/ directory as well. But I imagine as the assets grow more and more, adding everything twice isn't the way everyone else does it. Or should I just keep having a copy of the assets in the bin/debug, and as well bin/release and my source directory?

Why are you putting them in multiple places? So that when you run the program they're in the same folder as your executable so it can find the resources? If so, you should change your IDEs settings so that the directory used when debugging is the directory in which your art assets are placed, and keep them in one place.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

First, what should I keep in mind so that debugging isn't a complete mess?


IMHO, debugging is a sign of failure. It's better to write correct code than to write buggy code.

Now that's easier said than done, and noone writes 100% bug-free code. But there are some ideas that are useful.

- Test Driven Development

For every piece of code, decide how you are going to test it. Design the tests *first* (could be a unit test, or some other kind of test). *Then* write the code, and rework it until the tests pass.

- Refactoring

There are two kinds of coding activities: refactoring existing code and adding new functionality. Only do one activity at a time. When refactoring, don't add new functionality, and test against existing tests. When adding new functionality, modify or design the tests first, then code (see above). Look for suitable refactorings both before and after adding new functionality.

- Profiling

Don't use the debugger as a profiler. Use proper profiling tools for tracking memory leaks and performance issues.

openwar - the real-time tactical war-game platform


IMHO, debugging is a sign of failure. It's better to write correct code than to write buggy code.

Now that's easier said than done, and noone writes 100% bug-free code. But there are some ideas that are useful.

I would not say that debugging (as in "using a debugger to find your bugs") is a sign of failure as such, but finding a bug without a clue where it originated with a debugger can be a real pain. And while nobody writes 100% bug-free code a good way to minimize bugs is to minimize your code, the less code the less bugs. Avoid code duplication under almost every circumstances except maybe for the simplest stuff. Try to find common patterns in your code and put it in a common place and don't reinvent the wheel all the time.


(could be a unit test, or some other kind of test).

Maybe have a look at Design by Contract (http://en.wikipedia....ign_by_contract) as an extension to simple Asserts to check for code correctness. But don't forget to compile the contracts out before you ship. smile.png


- Refactoring

There are two kinds of coding activities: refactoring existing code and adding new functionality. Only do one activity at a time. When refactoring, don't add new functionality, and test against existing tests. When adding new functionality, modify or design the tests first, then code (see above). Look for suitable refactorings both before and after adding new functionality.

I totally agree on that one and I like to add: Do not be afraid of refactoring. While it may be tedious to do it will lead to a cleaner code base and if it doesn't you're doing it wrong.

This topic is closed to new replies.

Advertisement