Advertisement

Advice for writing good code

Started by March 12, 2017 05:56 AM
5 comments, last by jamespetts 7 years, 8 months ago

The thing that scares me most is the the aspect of indie game development is providing support for code bugs after releasing a game. I see games that have a boatload of bugs reported on Steam. I'm not willing to put myself in that position.

That got me thinking that the best way to minimize the amount of bugs created would be to:

1. Write code that's less likely to break.

2. Bring in as many play testers as possible to before the release.

I would also need to have a method to efficiently identify the cause of bugs.

I have the following questions:

What books can the community recommend for writing efficient code that minimizes potential bugs? There's a well reviewed one on Amazon, but it's 900 pages. 'Clean Code' was printed poorly according to multiple reviews, so I'm not interested in that one either.

Are there any tools or techniques the development studios use to identify the cause of bugs efficiently?

The book Game Programming Patters explained the Command pattern can be used to re-play a game. Would this be feasible to implement in a shipped game by recording commands executed after a save in order to identify the source of potential defects?

The thing that scares me most is the the aspect of indie game development is providing support for code bugs after releasing a game. I see games that have a boatload of bugs reported on Steam. I'm not willing to put myself in that position.


Harsh reality check time: if you aren't willing to support your work, don't release it to the public.

Supporting your product is a necessary and integral part of shipping something. It's the price to play the game, as it were. If you don't want to pay that price, you shouldn't let people play your games.

To put it another way: bugs are inevitable. Very, very few software products in the history of computing have been delivered with zero bugs. And players will find things you didn't even begin to imagine.


That got me thinking that the best way to minimize the amount of bugs created would be to:
1. Write code that's less likely to break.
2. Bring in as many play testers as possible to before the release.


Writing robust code and playtesting are valuable tools for shipping better software. These are good things to look into.

You should also fail fast. If you detect anything that might be an error condition, immediately dump the memory of the game to disk and crash hard. This philosophy is great for ensuring bugs are easy to spot.

I would also need to have a method to efficiently identify the cause of bugs.


By nature the bugs you don't catch pre-release are likely to be harder to fix. Some won't be worth the effort. Others will be emergencies.

Such is life when shipping products.

But there are tools you can use: crash dumps ("minidumps" on Windows) are one of the best.


I have the following questions:
What books can the community recommend for writing efficient code that minimizes potential bugs? There's a well reviewed one on Amazon, but it's 900 pages. 'Clean Code' was printed poorly according to multiple reviews, so I'm not interested in that one either.


If you can't be bothered to read a moderately long book, you're not going to develop the discipline to write truly robust code, and absolutely not if you're learning skills at the same time. Buy the good book.

Are there any tools or techniques the development studios use to identify the cause of bugs efficiently?
The book Game Programming Patters explained the Command pattern can be used to re-play a game. Would this be feasible to implement in a shipped game by recording commands executed after a save in order to identify the source of potential defects?


Deterministic replay is a powerful tool but also an expensive one in terms of development effort required.

Other tools you might consider include logging, inspection consoles, and internal debug visualizations for things like AI, pathing, physics, etc.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Advertisement

Are there any tools or techniques the development studios use to identify the cause of bugs efficiently?


Things you NEED to know:
- All of the features of your debugger.
- All of the features of your profiler.
- Everything related to unit testing.
- Everything related to integration testing.

Programming guidelines:
- Do the simplest thing that could possibly work.
- Don't write code that you aren't going to use NOW.
- Delete code which is not being used.
- Delete code which is not covered by a unit test.
- If you KNOW something will perform terribly, optimize it now. Otherwise, profile it later.
- Keep variables as short-lived (or encapsulated) as possible (locals > parameters > closures > private members >>>> public members >>>>>> thread statics >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> globals)
- Keep variables in valid states at all times (don't leave things null, set them to invalid values, or use types which even allow invalid values in the first place).
- Avoid keeping variables which mean the same thing in multiple places.
- Use immutable variables until you're *sure* you need mutable ones.
- Make constants for your magic numbers now, not later.
- Keep your functions as short as possible. If you find yourself writing a big function, split it up.
- Avoid inheritance until you are absolutely sure you need it.
- Prefer simple functions and functional style over classes. Don't use classes until you need them.
- When you have classes, ensure that their instances are always in a valid state.
- Refactor frequently, and use unit tests to catch refactoring mistakes.
- Don't use threads until you ABSOLUTELY know what you're doing.
- Never use linked lists.
- Never concatenate strings using the + operator in .NET.

Are there any tools or techniques the development studios use to identify the cause of bugs efficiently?


Things you NEED to know:
- All of the features of your debugger.
- All of the features of your profiler.
- Everything related to unit testing.
- Everything related to integration testing.

Programming guidelines:
- Do the simplest thing that could possibly work.
- Don't write code that you aren't going to use NOW.
- Delete code which is not being used.
- Delete code which is not covered by a unit test.
- If you KNOW something will perform terribly, optimize it now. Otherwise, profile it later.
- Keep variables as short-lived (or encapsulated) as possible (locals > parameters > closures > private members >>>> public members >>>>>> thread statics >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> globals)
- Keep variables in valid states at all times (don't leave things null, set them to invalid values, or use types which even allow invalid values in the first place).
- Avoid keeping variables which mean the same thing in multiple places.
- Use immutable variables until you're *sure* you need mutable ones.
- Make constants for your magic numbers now, not later.
- Keep your functions as short as possible. If you find yourself writing a big function, split it up.
- Avoid inheritance until you are absolutely sure you need it.
- Prefer simple functions and functional style over classes. Don't use classes until you need them.
- When you have classes, ensure that their instances are always in a valid state.
- Refactor frequently, and use unit tests to catch refactoring mistakes.
- Don't use threads until you ABSOLUTELY know what you're doing.
- Never use linked lists.
- Never concatenate strings using the + operator in .NET.

Some of these are fantastic, especially magic numbers, it's so easy to make a rectangle draw at 32,32 but if you ever change that, Going through all the code is a ballache.

However can you explain the last one? Not sure why that would be an issue.

However can you explain the last one? Not sure why that would be an issue.

Strings are immutable reference types. Concatenating strings with the + operator creates an entirely new string, and then copies the content of the two operand strings into it. This is somewhat less fast than other methods of building up a string dynamically, and it also produces significantly more garbage objects that are short-lived and thus more likely to force a gen0 collection (which thus pushes all the uncollectable gen0 stuff into gen1, increasing its size, and thus increase the cost of a gen1 collection. Et cetera for gen2.

"Never" using operator+ for strings is harsh -- it's probably okay to do it once or twice in parts of the code that aren't anywhere near the time-sensitive inner loops. But, on the other hand, "never" helps establish the habit of using other string formatting mechanisms which don't have quite the same probems (or avoiding string formatting altogether). And it's not like the alternative mechanisms are really tedious, so it's certainly a reasonable habit to develop.

Outside of the performance and technical issues, concatenation of strings that will be displayed (and thus localized) makes that text harder (or impossible) to localize correctly. The component strings lose their context, which is important for other languages (some languages have gendered words, for example), and the use of the + enforces an order which is not universal for all languages.

However can you explain the last one? Not sure why that would be an issue.

Strings are immutable reference types. Concatenating strings with the + operator creates an entirely new string, and then copies the content of the two operand strings into it. This is somewhat less fast than other methods of building up a string dynamically, and it also produces significantly more garbage objects that are short-lived and thus more likely to force a gen0 collection (which thus pushes all the uncollectable gen0 stuff into gen1, increasing its size, and thus increase the cost of a gen1 collection. Et cetera for gen2.
"Never" using operator+ for strings is harsh -- it's probably okay to do it once or twice in parts of the code that aren't anywhere near the time-sensitive inner loops. But, on the other hand, "never" helps establish the habit of using other string formatting mechanisms which don't have quite the same probems (or avoiding string formatting altogether). And it's not like the alternative mechanisms are really tedious, so it's certainly a reasonable habit to develop.


Yes, exactly. It's an exaggeration meant to be a reminder about the dangers of certain, seemingly trivial operations. Using + is fine if you're gluing together a few small strings (like for a debug message or a ("Lives: "+numLives) expression for your HUD).

A few weeks ago I was investigating some slow C# code and found that a roughly 100,000 character string was being built one character at a time using a "string += char" expression in a loop.

I ended up calculating that the amount of memory access performed just for concatenations was somewhere in the ballpark of 20 gigabytes of reads/writes. And that's just for the string data. Obviously the garbage collector had to clean up all those temporary strings, and who KNOWS how much overhead was incurred from that.

The entire operation took ~6 seconds. In a game, 6 seconds is an unacceptably long amount of time to wait for anything. And hell, even in command line tools that you normally expect to take a while, there's no excuse to write code that slow. Changing to a StringBuilder used far less memory accesses, was unnoticable (i.e. EXTREMELY fast), and did not trigger the garbage collector.
Advertisement

If you do not like fixing bugs, then I am afraid that software development is not for you. In any sort of software development, a majority of your time will be spent fixing bugs. Some will be easy to find; others difficult; some easy to fix, others difficult.

Fixing bugs in my project can take anything from about 2 minutes to 4 months per bug. Subtler bugs can lay dormant undiscovered for years until somebody does a very specific combination of things that even the best testers could not imagine.

Writing good code can certainly greatly reduce the number of bugs (and make them easier to find when they do exist), but you will never be able to eliminate them entirely. Nypyren has given some useful tips: another is to make your code as modular as possible so that it is easy to track down and fix a bug in a specific part of the code (which I think is what a lot of Nypyren's advice amounts to in any event).

This topic is closed to new replies.

Advertisement