There's a lot of teamwork and ingenuity that goes into making a game like
Destiny. We have talented people across all disciplines working together to
make the best game that we can. However, achieving the level of coordination
needed to make Destiny isn’t easy.
It's like giving a bunch of people paintbrushes but only one canvas to share between them and expecting a high-quality portrait at the end. In order to make something that isn't pure chaos, some ground rules need to be agreed upon. Like deciding on the color palette, what size brushes to use in what situations, or what the heck you’re trying to paint in the first place. Getting that alignment amongst a team is incredibly important.
One of the ways that we achieve that alignment over in engineering land is through coding guidelines: rules that our engineers follow to help keep the codebase maintainable. Today, I'm going to share how we decide what guidelines we should have, and how they help address the challenges we face in a large studio.
The focus of this post will be on the game development side of things, using the C++ programming language, but even if you don't know C++ or aren't an engineer, I think you'll still find it interesting.
Notice how there’s an exception called out in that second guideline? Guidelines are expected to be followed most of the time, but there's always room to go against one if it results in better code. The reasoning for that exception must be compelling though, such as producing objectively clearer code or sidestepping a particular system edge case that can't otherwise be worked around. If it’s a common occurrence, and the situation for it is well-defined, then we’ll add it as an official exception within the guideline.
To further ground the qualities of a guideline, let’s look at an example of one from everyday life. In the USA, the most common rule you follow when driving is to drive on the right side of the road. You're pretty much always doing that. But on a small country road where there's light traffic, you'll likely find a dashed road divider that indicates that you're allowed to move onto the left side of the road to pass a slow-moving car. An exception to the rule. (Check with your state/county/city to see if passing is right for you. Please do not take driving advice from a tech blog post.)
Now, even if you have a lot of well-written, thought-out guidelines, how do you make sure people follow them? At Bungie, our primary tool for enforcing our guidelines is through code reviews. A code review is where you show your code change to fellow engineers, and they’ll provide feedback on it before you share it with the rest of the team. Kind of like how this post was reviewed by other people to spot grammar mistakes or funky sentences I’d written before it was shared with all of you. Code reviews are great for maintaining guideline compliance, spreading knowledge of a system, and giving reviewers/reviewees the opportunity to spot bugs before they happen, making them indispensable for the health of the codebase and team.
You can also have a tool check and potentially auto-fix your code for any easily identifiable guideline violations, usually for ones around formatting or proper usage of the programming language. We don't have this setup for our C++ codebase yet unfortunately, since we have some special markup that we use for type reflection and metadata annotation that the tool can't understand out-of-the-box, but we're working on it!
Ok, that pretty much sums up the mechanics of writing and working with guidelines. But we haven't covered the most important part yet: making sure that guidelines provide value to the team and codebase. So how do we go about figuring out what's valuable? Well, let's first look at some of the challenges that can make development difficult and then go from there.
Our codebase is also fairly large now, at about 5.1 million lines of C++ code for the game solution. Some of that is freshly written code, like the code to support Cross Play in Destiny. Some of it is 20 years old, such as the code to check gamepad button presses. Some of it is platform-specific to support all the environments we ship on. And some of it is cruft that needs to be deleted. Changes to long-standing guidelines can introduce inconsistency between old and new code (unless we can pay the cost of global fixup), so we need to balance any guideline changes we want to make against the weight of the code that already exists.
Not only do we have all of that code, but we're working on multiple versions of that code in parallel! For example, the development branch for Season of the Splicer is called v520, and the one for our latest Season content is called v530. v600 is where major changes are taking place to support The Witch Queen, our next major expansion. Changes made in v520 automatically integrate into the downstream branches, to v530 and then onto v600, so that the developers in those branches are working against the most up-to-date version of those files. This integration process can cause issues, though, when the same code location is modified in multiple branches and a conflict needs to be manually resolved. Or worse, something merges cleanly but causes a logic change that introduces a bug. Our guidelines need to have practices that help reduce the odds of these issues occurring.
Finally, Bungie is a large company; much larger than a couple college students hacking away at games in a dorm room back in 1991. We're 150+ engineers strong at this point, with about 75 regularly working on the C++ game client. Each one is a smart, hardworking individual, with their own experiences and perspectives to share. That diversity is a major strength of ours, and we need to take full advantage of it by making sure code written by each person is accessible and clear to everyone else.
Now that we know the challenges that we face, we can derive a set of principles to focus our guidelines on tackling them. At Bungie, we call those principles our C++ Coding Guideline Razors.
I'll walk you through each of the razors that Bungie has arrived at and explain the rationale behind each one, along with a few example guidelines that support the razor.
When we make changes to the codebase, most of the time we're taking time to understand the surrounding systems to make sure our change fits well within them before we write new code or make a modification. The author of the surrounding code could've been a teammate, a former coworker, or you from three years ago, but you've lost all the context you originally had. No matter who it was, it's a better productivity aid to all the future readers for the code to be clear and explanative when it was originally written, even if that means it takes a little longer to type things out or find the right words.
Some Bungie guidelines that support this razor are:
It's like giving a bunch of people paintbrushes but only one canvas to share between them and expecting a high-quality portrait at the end. In order to make something that isn't pure chaos, some ground rules need to be agreed upon. Like deciding on the color palette, what size brushes to use in what situations, or what the heck you’re trying to paint in the first place. Getting that alignment amongst a team is incredibly important.
One of the ways that we achieve that alignment over in engineering land is through coding guidelines: rules that our engineers follow to help keep the codebase maintainable. Today, I'm going to share how we decide what guidelines we should have, and how they help address the challenges we face in a large studio.
The focus of this post will be on the game development side of things, using the C++ programming language, but even if you don't know C++ or aren't an engineer, I think you'll still find it interesting.
What's a Coding Guideline?
A coding guideline is a rule that our engineers follow while they're writing code. They're commonly used to mandate a particular format style, to ensure proper usage of a system, and to prevent common issues from occurring. A well-written guideline is clearly actionable in its wording, along the lines of "Do X" or "Don't do Y" and explains the rationale for its inclusion as a guideline. To demonstrate, here’s a couple examples from our C++ guidelines:Don't use the static keyword directly
The "static" keyword performs a bunch of different jobs in C++, including declaring incredibly dangerous static function-local variables.
You should use the more specific wrapper keywords in cseries_declarations.h, such as static_global, static_local, etc. This allows us to audit dangerous static function-locals efficiently.
Braces On Their Own Lines
Braces are always placed on a line by themselves.
There is an exception permitted for single-line inline function definitions.
Notice how there’s an exception called out in that second guideline? Guidelines are expected to be followed most of the time, but there's always room to go against one if it results in better code. The reasoning for that exception must be compelling though, such as producing objectively clearer code or sidestepping a particular system edge case that can't otherwise be worked around. If it’s a common occurrence, and the situation for it is well-defined, then we’ll add it as an official exception within the guideline.
To further ground the qualities of a guideline, let’s look at an example of one from everyday life. In the USA, the most common rule you follow when driving is to drive on the right side of the road. You're pretty much always doing that. But on a small country road where there's light traffic, you'll likely find a dashed road divider that indicates that you're allowed to move onto the left side of the road to pass a slow-moving car. An exception to the rule. (Check with your state/county/city to see if passing is right for you. Please do not take driving advice from a tech blog post.)
Now, even if you have a lot of well-written, thought-out guidelines, how do you make sure people follow them? At Bungie, our primary tool for enforcing our guidelines is through code reviews. A code review is where you show your code change to fellow engineers, and they’ll provide feedback on it before you share it with the rest of the team. Kind of like how this post was reviewed by other people to spot grammar mistakes or funky sentences I’d written before it was shared with all of you. Code reviews are great for maintaining guideline compliance, spreading knowledge of a system, and giving reviewers/reviewees the opportunity to spot bugs before they happen, making them indispensable for the health of the codebase and team.
You can also have a tool check and potentially auto-fix your code for any easily identifiable guideline violations, usually for ones around formatting or proper usage of the programming language. We don't have this setup for our C++ codebase yet unfortunately, since we have some special markup that we use for type reflection and metadata annotation that the tool can't understand out-of-the-box, but we're working on it!
Ok, that pretty much sums up the mechanics of writing and working with guidelines. But we haven't covered the most important part yet: making sure that guidelines provide value to the team and codebase. So how do we go about figuring out what's valuable? Well, let's first look at some of the challenges that can make development difficult and then go from there.
Challenges, you say?
The first challenge is the programming language that we’re using for game development: C++. This is a powerful high-performance language that straddles the line between modern concepts and old school principles. It’s one of the most common choices for AAA game development to pack the most computations in the smallest amount of time. That performance is mainly achieved by giving developers more control over low-level resources that they need to manually manage. All of this (great) power means that engineers need to take (great) responsibility, to make sure resources are managed correctly and arcane parts of the language are handled appropriately.Our codebase is also fairly large now, at about 5.1 million lines of C++ code for the game solution. Some of that is freshly written code, like the code to support Cross Play in Destiny. Some of it is 20 years old, such as the code to check gamepad button presses. Some of it is platform-specific to support all the environments we ship on. And some of it is cruft that needs to be deleted. Changes to long-standing guidelines can introduce inconsistency between old and new code (unless we can pay the cost of global fixup), so we need to balance any guideline changes we want to make against the weight of the code that already exists.
Not only do we have all of that code, but we're working on multiple versions of that code in parallel! For example, the development branch for Season of the Splicer is called v520, and the one for our latest Season content is called v530. v600 is where major changes are taking place to support The Witch Queen, our next major expansion. Changes made in v520 automatically integrate into the downstream branches, to v530 and then onto v600, so that the developers in those branches are working against the most up-to-date version of those files. This integration process can cause issues, though, when the same code location is modified in multiple branches and a conflict needs to be manually resolved. Or worse, something merges cleanly but causes a logic change that introduces a bug. Our guidelines need to have practices that help reduce the odds of these issues occurring.
Finally, Bungie is a large company; much larger than a couple college students hacking away at games in a dorm room back in 1991. We're 150+ engineers strong at this point, with about 75 regularly working on the C++ game client. Each one is a smart, hardworking individual, with their own experiences and perspectives to share. That diversity is a major strength of ours, and we need to take full advantage of it by making sure code written by each person is accessible and clear to everyone else.
Now that we know the challenges that we face, we can derive a set of principles to focus our guidelines on tackling them. At Bungie, we call those principles our C++ Coding Guideline Razors.
Razors? Like for shaving?
Well, yes. But no. The idea behind the term razor here is that you use them to "shave off" complexity and provide a sharp focus for your goals (addressing the challenges we went through above). Any guidelines that we author are expected to align with one or more of these razors, and ones that don't are either harmful or just not worth the mental overhead for the team to follow.I'll walk you through each of the razors that Bungie has arrived at and explain the rationale behind each one, along with a few example guidelines that support the razor.
#1 Favor understandability at the expense of time-to-write
Every line of code will be read many times by many people of varying backgrounds for every time an expert edits it, so prefer explicit-but-verbose to concise-but-implicit.
When we make changes to the codebase, most of the time we're taking time to understand the surrounding systems to make sure our change fits well within them before we write new code or make a modification. The author of the surrounding code could've been a teammate, a former coworker, or you from three years ago, but you've lost all the context you originally had. No matter who it was, it's a better productivity aid to all the future readers for the code to be clear and explanative when it was originally written, even if that means it takes a little longer to type things out or find the right words.
Some Bungie guidelines that support this razor are:
- snake_case as our naming convention.
- Avoiding abbreviation (eg screen_manager instead of scrn_mngr)
- Encouraging the addition of helpful inline comments.