Being A Better Programmer by (06 August 2001) |
Return to The Archives |
Introduction
|
I always had this fantasy that game programmers were the cream of the crop.
Anyone can write a word processor or a Visual Basic application, and anyone can
hack a save file, but game programmers have to push the limits of the machine!
They have to get every aspect of an environment on the screen and somehow not
have a frame rate hit. I always thought game programmers were the cream of the
crop, until I actually joined the industry. The harsh reality hit me almost
instantly and I began to think -even say aloud, I'm amazed our game even runs. Traditionally, game programmers worked in their basement or garage, piecing together assembly to put pixels on the screen. Back when the programmer was the artist because the resolution was so bad actually paying an artist would be ludicrous. However a lot has changed since then, games aren't made by two buddies in a garage, they are made by teams of people over many months of development. In this day and age, we are not individuals who work on the entire project. We are a piece of a living breathing company that must work together to achieve success. Now there are Game Developer Conferences ( http://www.gdconf.com/ ), Online Tutorials ( http://www.flipcode.com/ , http://www.gamedev.net/ ), Articles ( http://www.gamasutra.com/ ), Message Boards ( http://www.thedc.com/ ), Magazines ( http://www.gdmag.com/ ), and Books ( http://www.satori.org/gamegems/ ) that allow us to share new ideas and techniques. Yet somehow, with all this teamwork, and all these wonderful new resources at our disposal, we still manage to code ourselves into corners. If there was an approach to programming that dodged all obstacles and bugs while somehow avoiding backtracking or rewriting code, then I'm sure we'd all be using that technique. Since there is not a perfect way to program, all we can do is keep our eyes and ears open, observe the programmers around us and see what they are doing right and wrong. Here are a few basic programming techniques that I've observed first hand which greatly improved my own programming habits. The most important thing to remember when reading them is that although some of them may sound simple and elementary, knowing a technique is worthless unless you practice it. Many programmers effortlessly shrug off things like error checking and commenting conventions as a waste of time. So I'd just like to point out that these suggestions are intended to improve the amount of time and effort needed to complete tasks. After practicing only a few of these programming techniques, I have noticed that my own code writes itself much faster and more efficiently than it ever did before. |
Plan For The Worst
|
Okay, let's just assume that you are a super human programmer, and you're code is never buggy and never breaks. Let me be the first to congratulate you on writing the perfect code. However, what will happen when your perfect code is not given perfect data. Will the code assume a pointer is valid? Will it try to use a sound file as a texture map? As basic as it may sound, code should not assume anything. The C language has a brilliant standard function called "assert," which was designed to trap errors. Every time your code is given outside data, get in the habit of making sure that data is what you expected it to be. If it's not, assert AND print a message that explains exactly what is wrong and why the program asserted. It is important to give a message, this way anyone can read it and instantly say to themselves "oops, I see the mistake". Ninety percent of the time that's what a bug is, a simple mistake. So instead of letting it cripple your game, and waste everyone's time as people try to trace a bug backward in an effort to pinpoint it's cause, why not just tell them the moment something goes wrong. Nine times out of ten it can be painlessly fixed. The other ten percent of the time the team will be made aware of a major coding issue long before it becomes an unfixable problem that everyone will just have to work around. Regardless of what language you're working in. The first subroutine you should ever write should be a simple error printing function. Make it work like the language's basic print function, so it's simple and painless for other programmers to remember and use. Then when the program asserts in a case that is not obvious from the error message, you can place a break point inside the error printing function and the program will break exactly when the problem occurs. This will save time on locating the first occurrence of the problem while making it simple to debug. |
Expect To Forget
|
Don't expect to remember what your code is doing. Projects these days last for months and even years. You will not remember what you programmed at the beginning of the project. In fact, it's probably the only real guarantee you'll ever get. The sky is blue (if it's not raining, and not dusk, or night time), the grass is green (if it's watered regularly and it's not winter ), and you will always forget the details of a bunch of text you wrote months ago (always). So comment everything as if you're teaching it to someone who doesn't know it at all --someone like yourself six months from now. Think about a team member telling you there is a bug somewhere in your old code, and you have to revisit it and fix it. If you can pop open your file and understand what you were doing, without having to care about variables or parameters. Finding and fixing the bug will be quick and almost painless. The technique is simple, when ever you use parentheses, have a comment to read what the line is doing. Just write it as if you were reading the line out loud. If someone else read it, they'd speak it letter for letter not knowing what the variables and crazy calculations were doing. If you read it, you would speak it after your mind converts all those calculation to what they really mean. For example, if a line of code that should only be executed if "(frmp>10)", "(plist[i].bdown & x03)", and "(plist[i].y > pond.y)". Why not have a comment that says "if( ten frames have passed AND button 3 is still down AND player not underwater )". When you comment this way, you will benefit twice. First, anyone will be able to read exactly what the function is supposed to be doing without even knowing how to program in that particular language. This makes checking logic much easier. Second, if the logic is sound, it's easy to spot errors because a line of code won't do what it says it should be doing. In the previous example, if the actual code was running when the player was underwater, it would be easy to spot what part of the code the bug was in because the comment tells us "AND player not underwater." If the line wasn't commented, you may look at that if statement a hundred times thinking it was right before it hits you, "wait a minute, is that greater than, or less than pond.y?" Comments have to be one of the most powerful tools a programmer has. They are universal because all programming languages have a way to write comments. It's a shame that being simple and common also makes them a prime target to be taken for granted. This actually leads into our next point. |
Documentation
|
At one time I wrote documentation. I remember writing a few dozen pages on various systems and modules. The documentation itself was sound, but ultimately counter-productive. No one, including myself, ever used it. Many people would forget it even existed, and if they came to me and asked about the system I could explain to them exactly what they needed to know in a fraction of the time it would have taken for them to read the documentation. Not to mention, there's a chance someone might read this so-called "documentation" from start to finish and get absolutely no insight on what they wanted to know. The time it took me to write those pages of text was completely wasted. Worse yet, every time we changed one of those various systems or modules, we had to change the document as well. So I effectively doubled our work load. However, the solution is most definitely NOT to skip documentation altogether. Instead, the solution is to make your code files the documentation. Before every function, make a comment block that explains what it does, how to use it, and what to watch out for. If it's a complex piece of code, explain the approach your about to take to the reader (maybe even yourself six months from now) so they will understand what they are about to see before they jump into the actual code. If things need to be used in a specific order, say so. If a function only works in one obscure case tell people right there -as plain as day. Now, instead of having to open a separate document and search for what you need to know, the documentation will be right there in the code. The things you'd normally have to make an effort to learn will be exactly where you need them, when you need them, and everyone will be saved the time of looking for them. Other programmers will be more likely to use your code correctly if they find that you've laid out all of the issues for them to see. Also, unlike having a separate document, other programmers will unconsciously proof read your self documenting code. When anyone approaches you because they do not understand a piece of code, you can safely conclude that the documenting comments in that area are lacking. This is something you probably would never have been aware of if they hadn't just unintentionally told you. So while you're clarifying what they need to know, comment that piece of code further. Now the next person won't have to ask you. |
Human Error and Monkey Business
|
I read an article on Gamasutra entitled "Postmortem: Angel Studios' Smuggler's Run" (by Charles Eubacks) and it introduced me to a concept that was so simple, yet so incredibly effective -maybe even brilliant. In this postmortem, they gave an entire "What Went Right" point to something they called a "buildmonkey." The buildmonkey was a tool that automatically compiled the entire game every morning at 3:00 am and logged the process. This way, at the start of each day, the programmers would have a one hundred percent freshly compiled version of their project. If for any reason the project didn't compile, they would have a log with errors to tell what went wrong. It even sent emails to key people to inform them if the built was successful or if it had problems. Although I'm not proposing everyone go out and code a buildmonkey, I am pointing out the time saved in writing a tool that's purpose is to perform a repetitive, mundane task and prevent/catch human error. I've already touched on using error checking devices in your function calls, and how much time is saved when mistakes are caught and corrected as they are made. Now I'd like to point out the exact same advantage in data outside the code base itself. Most games these days rely heavily on scripts, ini files, and other data sources to make the game run as desired. However many times, while in development, a script writer will make a mistake, or an exporter will dump a command incorrectly. Instead of waiting until this data is actually loaded into the game to see if it will work. Why not have a simple DOS program that could check the syntax of such files on the designer and artist's end. This program could flag mistakes before the data is ever loaded into the game and would be a huge time saver. Now I'm sure many of you probably just thought to yourselves "that would be great, but nobody would actually use it." Well, it's simple. The next time someone comes to your desk asking you why the game is crashing, you're first response can always be "did you run the syntax monkey?" Another great example of a monkey would be a function that prints out the latest known script commands every time the game is run (on a build flag of course). This would also eliminate the need to constantly update documentation, and answer questions like "can you tell me again, what was the syntax for an AIPoint?" Humans make mistakes, some more than others. However there are these nifty new fangled things call "computers" that are made to do mind numbing, mundane tasks over and over again. It's about time we start putting them to good use, and save ourselves some time and effort. |
An Engine Has Belts And Rods
|
The best metaphor for a game engine that I've ever heard came from one of my coworkers, Jerry, someone who isn't even a programmer. He actually posed the question to me, "What is an engine? Why make one? What's the big deal?" I did my best to explain, and when I was done he paraphrased to say that a game engine is a lot like a car's engine. Without it, the car could never move, but at the same time, a running engine without the chassis (tires, etc.) was just as useless. I thought this was a good example and listened as he continued to clarify. Jerry explained that when a piece of your car's engine breaks, you can replace it. If a belt breaks, you insert a new one and tweak the tension to optimal performance. At the same time, however, that engine has a transmission, a block, and rods. They are vital to the engine's functionality, but if you throw a rod, you can't just pop in a new one without disassembling half the engine. Then he concluded that if you were going to build a game engine from the ground up, you'd want to build it with a lot of belts and no rods, so you could constantly swap parts out and tune performance without bringing the entire engine to a screeching halt. Brilliant. Not knowing a thing about programming, he hit the nail right on the head. One of the biggest things that separates good programmers from great programmers is writing code that will fit in the engine like a belt, rather than a rod. The concept of writing flexible, reusable, and replaceable code can actually be quite simple. The key to writing general case code is to not let you're sub-routines become more than a screen long. The way to do this is NOT to write as much compact, cryptic crap as you can squeeze on one line and NOT to use global variables. The solution is to break down everything you can into tight, reusable functions that either perform one task, or call the necessary function to complete one task. For example: VectorAdd() would contain a small piece of code to add two vectors together, while SceneDisplay() would contain calls to PrepRender(), Render3dObjects(), RenderHud(), RenderDebugText(), and SwapBuffers(). However both functions would only be a few lines long. As you break the code into smaller pieces, you will start spotting functions that can be reused in other places without making much effort at all. In the previous example, RenderHud() and RenderDebugText() will probably share some core function calls since they both draw objects on top of everything else on the screen. Many believe that there must be some grand design before you can make general case code. When writing modules and systems, design is an important step, but that is not the point I'm making. I'm saying, make it a habit to write everything you program as general and reusable as possible. Then, when you start designing new systems and modules, you will have much of the functionality already available, and you need only call those functions in an organized manner, instead of reinventing the wheel a couple hundred more times. |
A Shiny New Hammer
|
As I stated in the beginning of this article, there is no single approach to programming that will handle the many hurdles you'll be put up against. Many consider programming a kind of black art form all it's own. I like to think of us as being more like carpenters than wizards. Carpenters build structures from raw materials using the knowledge they've gained through first and second hand experience. Early in their career, maybe they built a deck in their own backyard with a hammer, nails, saw, and timber. As they gained experience, doing more advanced things, like adding an extra room to a house, became more feasible. Before long many tasks like setting cement, constructing an internal frame, laying insulation, putting up sheetrock, and shingling the roof all become second nature to them. When a carpenter reaches that point, perhaps they will get the chance to actually build an entire house with a team of people such as plumbers, electricians, and so on. These carpenters are valued not because they have a lot of tools, but because they know how to use them --and more importantly, when to use them. Give a carpenter a hammer and ask him to hang a door on it's hinges and he will ask you for a drill and a screw driver. Give an ordinary man a hammer and he might assume the door was meant to be hung with nails. Give a child a shiny new hammer and suddenly everything becomes a nail, just begging to be hit. It's important to learn patience and good judgment when approaching a problem instead of charging at it and unconditionally using our latest technique. Just because you finally read a quaternion tutorial, doesn't mean their useful in all cases. Use your tools and techniques responsibly and if you don't have the correct knowledge to make the call on what would be the best approach, admit it. |
Admit Your Own Limits
|
We're all only human, and although it would be nice to know everything, we don't. The danger in situations where we have to complete an unfamiliar task is not our limited experience or knowledge, it's how we deal with it. It's very tempting to accept a task you know nothing about and not tell anyone it may be problematic because you don't want to loose value in management's eyes. In cases like this, some people follow the reasoning that it would be better not to say anything than to have someone think you might not know what you're doing. Sometimes this approach may work. You might keep your mouth shut, work some extra hours, read a book and a few on-line articles and get the task completed on time. More often than not, you keep your mouth shut, you work lots of extra hours, read anything you can find and the task still gets completed a week to a month late. You did it, but at what cost? If you don't have the correct knowledge for the task at hand, get over yourself and admit it. Don't hide or lie about it. If you express that you are unfamiliar with or simply don't know how to solve a problem, then something can be done about it. A phrase as simple as "I've done Regardless, don't be afraid to say something. When you do, someone can teach you; you will be allocated time to learn the correct techniques; or someone else can do it. The important thing is not to let the project suffer, and not admitting your own limits is the best way to do just that. No one will know you can't handle it if you don't tell them, and it's better to let them know before hand, than when you're in way over you're head. |
Fix It, Don't Hack It
|
Far too often prototype code is written and it ends up becoming game code. Many times people (management) will walk by and see something on the screen and assume it's done when it's not. So that programmer gets push onto another task since they are "done," and a hack gets left behind to plague the project some day down the line. Don't let this happen to you. Prototype code is fine so long as it stays prototype code. Once you understand the process, take the time to write code that will work right, instead of only working "for now." Further more, if there is code that is causing a problem, don't hack over it. Fix it. If you can add a line of code that will hack it into a state that works, why not spend another five minutes to understand why that works. After that you can make it work in all cases. Do it right and you won't have to deal with it again. Then there will be one less thing to cause problems in the future. |
Step Softly and Carry Nothing
|
Many of us also get caught in the trap of "figuring things out for ourselves." Some times we will see nice code that we would like to apply to another aspect of our programs, however we need to understand what exactly it's doing to use it properly. Prime examples of this are online tutorials. When I stumbled onto Jeff Molofee's OpenGL tutorials, I thought they were heaven sent. With his help, I was able to jumpstart my use of OpenGL, since those first couple of steps are the toughest. Excited about the API, I set out to write "my 3d engine" (as I'm sure many before me have). Before long, I found that I was relying on Jeff's code as a crutch rather than for what it was intended to be used as. I'd spend countless hours fiddling with enum values and commands that I really didn't know, then looking back to the tutorials to see how he was doing it. Finally it occurred to me just how much time I was wasting because I had to guess what things were doing, rather than just knowing. Jeff's tutorials were great, but trying to mimic what he did shouldn't be our goal. Now I have an OpenGL book, and instead of guessing at the details of the API, I began to understand them, and in turn I've come to appreciate Jeff's code in a whole new light. Choose your stepping stones wisely, but remember they exist only to get you where you're going and will only slow you down if you try to pick them up and carry them with you along the way. It also helps to leave a few stepping stones behind for those to come. Others will benefit as you have, but above all, you'd be amazed just how much you yourself will learn in the process of trying to teach others. |
Don't Get Personal
|
This is, by far, the hardest thing to do. Many of us can get very protective of our code on an unconscious level. Programming is not an easy thing to do, and it's nice to be able to take pride in our work and our accomplishments. It feels good to have someone point at the screen and say "wow that's really cool," and to know that you did that. However there is always more to learn. If someone has a different approach to something you've written, don't take it personal and don't dismiss them or get angry with them. Talk with them and one of two things will result. Either you'll learn how to program better or you can explain to them what you've concluded and hopefully they will become a better programmer. Being good at programming is nice and all, but working with others who are good at programming is an honor that is far too often taken for granted. Be professional and learn from each other. |
Summarize
|
I've touched on a lot of topics in this article. So before I sign off, here's a quick recap. Plan For The Worst Regardless of what language you're working in. The first subroutine you should ever write should be a simple error printing function. Expect To Forget Comment everything as if you're teaching it to someone who doesn't know it at all --someone like yourself six months from now. Documentation Make your program files contain the documentation, put it right there in the code, exactly where you will need it, and you will ultimately save everyone time. Human Error and Monkey Business Humans make mistakes, some more than others. However computers were made to do mind numbing, mundane tasks over and over again. It's about time we start putting them to good use, and save ourselves some time and effort. An Engine Has Belts And Rods Writing code that will fit in the engine like a belt, rather than a rod using concept of flexible, reusable, and replaceable general case code. A Shiny New Hammer Give a child a shiny new hammer and suddenly everything becomes a nail, just begging to be hit. It's important to learn patience and good judgment when approaching a problem instead of charging at it and unconditionally using our latest technique. Admit Your Own Limits No one will know you can't handle a task if you don't tell them, and it's better to tell them before hand, than when you're in way over you're head. Fix It, Don't Hack It Do it right and you won't have to deal with it again. Then there will be one less thing to cause problems in the future. Step Softly and Carry Nothing Choose your stepping stones wisely, but remember they exist only to get you where you're going and will only slow you down if you try to pick them up and carry them with you along the way. Don't Get Personal Being good at programming is nice and all, but working with others who are good at programming is an honor that is far too often taken for granted. Be professional and learn from each other. |
|