Create A C Style Guide Write Cleaner Code That Scales
Create A C Style Guide Write Cleaner Code That Scales
Create A C Style Guide Write Cleaner Code That Scales
C R E AT E A C #
STYLE GUIDE
Write cleaner code that scales
Contents
Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Developing as a team. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Be consistent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
It takes a village. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Naming conventions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Identifier names. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Casing terminology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Camel case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Pascal case. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Snake case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Kebab case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Hungarian notation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Enums. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Classes and interfaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Namespaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
What is EditorConfig?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Horizontal spacing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Vertical spacing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Regions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Class organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Single-responsibility principle. . . . . . . . . . . . . . . . . . . . . . . . . 36
Refactoring example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Extension methods. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Common pitfalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
I N TRODUCTION
Creativity can be messy.
Once your logic is functional, then the process of refactoring and cleaning up
begins.
This guide compiles advice from industry experts on how to create a code
style guide. Establishing ssuch a guide for each member of your team to follow
will help ensure your codebase can grow your project to a commercial-scale
production.
These tips and tricks will help your development process in the long term, even
if they cost you extra effort up front. A cleaner, more scalable codebase also
facilitates the efficient onboarding of new developers as you expand your team.
Keep your code clean to make life easier for yourself and everyone involved in
the project.
Contributors
This guide was written by Wilmer Lin, a 3D and visual effects artist with over
15 years of industry experience in film and television, who now works as an
independent game developer and educator. Significant contributions were also
made by senior technical content marketing manager Thomas Krogh-Jacobsen
and senior Unity engineers Peter Andreasen, Scott Bilas, and Robert LaCruise.
Most game developers would agree that clean code is any code that’s easy to
read and maintain.
There’s good reasons for this congruence. Something that might be obvious to
you as the original author might be less apparent to another developer. By the
same token, when you implement some logic now, you might not remember
what that same code snippet does three months later.
Clean code aims to make development more scalable and conform to a set of
production standards, including:
Your future teammates – and your future self – will be thankful for that.
Before looking into how to create the style guide, let’s go over some general
rules to help you scale up your Unity development.
Let’s face it: Engineers and developers can overcomplicate things, even though
computing and programming are hard enough. Use the KISS principle of “keep
it simple, stupid” as a guide for finding the simplest solution to the problem
at hand.
There’s no need to reinvent the wheel if a proven and simple technique solves
your challenge. Why use a fancy new technology just for the sake of using it?
Unity already includes numerous solutions in its Scripting API. For example, if
the existing Hexagonal Tilemap works for your strategy game, skip writing your
own. The best code you can write is no code at all.
– Leonardo da Vinci
– Bjarne Stroustrup
– Edsger W. Dijkstra
“Everything should be made as simple as possible, but no simpler.”
– Albert Einstein
The related YAGNI principle (“you aren’t gonna need it”) instructs you to
implement features only as you need them. Don’t worry about features that you
might need once the stars align. Build the simplest thing that you need now and
build it to work.
The first step of software development is to understand what you are trying to
solve. This idea might seem like common sense, but too often developers get
bogged down in implementing code without understanding the actual problem,
or they'll modify the code until it works without fully grasping why.
What if, for example, you fixed a Null Reference Exception with a quick if-null
statement at the top of a method. Are you sure that was the real culprit, or was
the problem a call to another method deeper inside?
Instead of adding code to fix a problem, investigate the root cause. Ask yourself
why it’s happening rather than applying a band-aid solution.
Making clean code is a fluid and ongoing process. Get the whole team into this
mindset. Expect code cleanup to be part of your day-to-day life as a developer.
Most people don’t intend to write broken code. It just evolves that way over
time. Your codebase needs constant maintenance and upkeep. Budget time for
that and make sure it happens.
On the flip side, don’t strive for perfection. When your code meets production
standards, it’s time to commit it and move on.
In The Pragmatic Programmer, Andy Hunt and Dave Thomas write, “Rather than
construction, programming is more like gardening.” Software engineering is an
organic process. Be prepared if everything does not go according to plan.
Even if you make the most elaborate drawing, designing a garden on paper will
not guarantee results. Your plants may bloom differently than you expected.
You’ll need to prune, transplant, and replace parts of your code to make this
garden successful.
Software design isn’t quite like an architect drawing blueprints because it's more
malleable and less mechanical. You’ll need to react as your codebase grows.
Once you decide how to tackle a problem, approach similar things the same
way. It’s not difficult but will take constant effort. Apply this principle to
everything from naming (classes and methods, casing, etc.) to organizing
project folders and resources.
Above all, have your team agree on a style guide and then follow it.
It takes a village
Although keeping code clean and simple is in everyone’s best interest, “clean
and simple” is not the same as “easy.” Clean and simple takes effort and is hard
work for beginners and experienced developers alike.
Your project will become messy if left unchecked. It’s a natural consequence of
so many people working on different parts of a project. Everyone is responsible
for pitching in and preventing code clutter, and each team member will need to
read and follow the style guide. Cleanup is a group effort.
This guide focuses on the most common coding conventions you’ll encounter
during Unity development. These are a subset of the Microsoft Framework
Design Guidelines, which include an extensive number of rules beyond what is
presented here.
These guidelines are recommendations, not hard and fast rules. Customize
them according to your team’s preferences. Pick a style that suits everyone,
and ensure they apply it.
Consistency is king. If you follow these suggestions and need to modify your
style guide in the future, a few find-and-replace operations can migrate your
codebase quickly.
When your style guide conflicts with this document or the Microsoft Framework
Design Guidelines, it should take precedence over them because this will allow
your team to maintain a uniform style throughout your project.
These are excellent starting points for managing your Unity development. Each
guide offers solutions for naming, formatting, and commenting. If you’re a solo
developer, this might feel like a constraint at first, but following a style guide is
essential when working in teams.
Think of a style guide as an initial investment that will pay dividends later.
Maintaining a single set of standards can reduce the time spent on relearning if
you move anyone onto another project.
Style guides take the guesswork out of coding conventions and formatting.
Consistent style then becomes a matter of following directions.
We created an example C# style sheet that you can also use as a reference as
you assemble your own guide. Feel free to copy and tweak it as needed.
Naming conventions
The names of your variables, classes, and methods aren’t mere labels. They
carry weight and meaning. Good naming style impacts how someone reading
your program can comprehend the idea you’re trying to convey.
Identifier names
An identifier is any name you assign to a type (class, interface, struct, delegate,
or enum), member, variable, or namespace. Identifiers must begin with a letter
or an underscore (_).
You can’t define variables with spaces in the name because C# uses the space
character to separate identifiers. Casing schemes can alleviate the problem of
using compound names or phrases in source code. There are several well-
known naming and casing conventions.
Camel case
Also known as camel caps, camel case is the practice of writing phrases
without spaces or punctuation, separating words with a single capitalized
letter. The very first letter is lowercase. Local variables and method parameters
are camel case.
For example:
examplePlayerController
maxHealthPoints
endOfFile
Pascal case
Pascal case is a variation of camel case, where the initial letter is capitalized.
Use this for class and method names in Unity development. Public fields can
be pascal case as well. For example:
ExamplePlayerController
MaxHealthPoints
EndOfFile
Snake case
In this case, spaces between words are replaced with an underscore character.
For example:
example_player_controller
max_health_points
end_of_file
Kebab case
Here, spaces between words are replaced with dashes. The words appear on a
“skewer” of dash characters. For example:
example-player-controller
Max-health-points
end-of-file
naming-conventions-methodology
Hungarian notation
The variable or function name often indicates its intention or type. For example:
int iCounter
string strPlayerName
— Use nouns for variable names: Variable names must be descriptive, clear,
and unambiguous because they represent a thing or state. So use a noun
when naming them except when the variable is of the type bool (see
below).
— Prefix Booleans with a verb: These variables indicate a true or false value.
Often they are the answer to a question, such as – is the player running?
Is the game over? Prefix them with a verb to make their meaning more
apparent. Often this is paired with a description or condition, e.g.,
isDead, isWalking, hasDamageMultiplier, etc.
— Use meaningful names. Don’t abbreviate (unless it’s math): Your variable
names will reveal their intent. Choose names that are easy to pronounce
and search for.
— Single letter variables are fine for loops and math expressions, but
otherwise, don’t abbreviate. Clarity is more important than any time saved
from omitting a few vowels.
— When doing quick prototyping, you can use short “junk” names and then
refactor to meaningful names later.
— Use pascal case for public fields. Use camel case for private variables:
For an alternative to public fields, use Properties with a public getter (see
Formatting below).
— Avoid too many prefixes or special encoding: You can prefix private
member variables with an underscore (_) to differentiate them from local
variables.
Some style guides use prefixes for private member variables (m_),
constants (k_), or static variables (s_), so the name can reveal more about
the variable at a glance.
Many developers eschew these and rely on the editor instead. However,
not all IDEs support highlighting and color coding, and some tools can’t
show rich context at all. Consider this when deciding how (or if) you will
apply prefixes together as a team.
— Specify (or omit) access level modifiers consistently: If you leave off the
access modifier, the compiler will assume the access level to be private.
This works well, but be consistent in how you omit the default access
modifier. Remember that you’ll need to use protected if you want this in a
subclass later.
The code snippets in this guide are non-functional and abbreviated. They’re
presented here to show style and formatting.
You can also reference this example C# style sheet for Unity developers,
based on a modified version of Microsoft’s Framework Design Guidelines. This
represents just one example of how you can set up your team’s style guide.
— Private member variables are camel case and use underscores (_) as a
prefix.
Review each rule in the example style guide and customize it to your team’s
preferences. The specifics of an individual rule are less important than having
everyone agree to follow it consistently. When in doubt, rely on your team’s own
guide to settle any style disagreements.
// parameters
public void InflictDamage(float damage, bool isSpecialDamage)
{
// local variable
int totalDamage = damage;
— Avoid redundant names: If your class is called Player, you don’t need
to create member variables called PlayerScore or PlayerTarget. Trim
them down to Score or Target.
— Avoid jokes or puns: While they might elicit a chuckle now, the
infiniteMonkeys or dudeWheresMyChar variables won’t hold up after a
few dozen reads.
— Use the var keyword for implicitly typed local variables if it helps
readability and the type is obvious: Specify when to use var in your
style guide. For example, many developers avoid var when it obscures the
variable’s type or with primitive types outside a loop.
Generally, use var when it makes the code easier to read (e.g., with long
type names) and the type is not ambiguous.
Enums
Enums are special value types defined by a set of named constants. By default,
the constants are integers, counting up from 0.
Use pascal case for enum names and values. You can place public enums
outside of a class to make them global. Use a singular noun for the enum name.
Note: Bitwise enums marked with the System.FlagsAttribute attribute are the
exception to this rule. You typically pluralize these as they represent more than
one type.
[Flags]
public enum AttackModes
{
// Decimal // Binary
None = 0, // 000000
Melee = 1, // 000001
Ranged = 2, // 000010
Special = 4, // 000100
Follow these standard rules when naming your classes and interfaces:
— Prefix interface names with a capital I: Follow this with an adjective that
describes the functionality.
}
}
// EXAMPLE: Interfaces
public interface IKillable
{
void Kill();
}
Methods
.
Note: “function” and “method” are often used interchangeably in Unity
development. However, because you can’t write a function without incorporating
it into a class in C#, “method” is the accepted term.
— Use camel case for parameters: Format parameters passed into the
method like local variables.
Several naming schemes exist for events and their related methods in the
subject and observers. Try these practices:
— Name the event with a verb phrase: Choose a name that communicates
the state change accurately. Use the present or past participle to indicate
events “before” or “after.” For example, specify “OpeningDoor” for an event
before opening a door or “DoorOpened” for an event afterward.
— Use the System.Action delegate for events: In most cases, the Action<T>
delegate can handle the events needed for gameplay. You may pass
anywhere from 0 to 16 input parameters of different types with a return
type of void. Using the predefined delegate saves code.
— Prefix the event handling method (in the observer) with the subject’s
name and underscore (_): If the subject is named “GameEvents,” your
observers can have a method called “GameEvents_OpeningDoor” or
“GameEvents_DoorOpened.”
Note that this is called the “event handling method”, not to be confused
with the EventHandler delegate.
Decide a consistent naming scheme for your team and implement those
rules in your style guide.
— Add a using directive at the top of the file to avoid repeated typing of the
namespace prefix.
When the compiler finds the class names Controller1 and Controller2, it
understands you mean Enemy.Controller1 and Enemy.Controller2.
If the script needs to refer to classes with the same name from different
namespaces, use the prefix to differentiate them. For instance, if you have a
Controller1 and Controller2 class in the Player namespace, you can write
out Player.Controller1 and Player.Controller2 to avoid any conflicts.
Otherwise, the compiler will report an error.
When constructing a style guide, personalize how your team will format your
code. Consider each of the following code formatting suggestions when setting
up your Unity dev style guide. Omit, expand, or modify these example rules to fit
your team’s needs.
In all cases, consider how your team will implement each formatting rule and
then have everyone apply it uniformly. Refer back to your team’s style to resolve
any discrepancies. The less you think about formatting, the more you can work
on something else.
Properties
In this way, the property encapsulates the data, hiding it from unwanted
changes by the user or external objects. The getter and setter each have their
own access modifier, allowing your property to be read-write, read-only, or
write-only.
You can also use the accessors to validate or convert the data (e.g., verify that
the data fits your preferred format or change a value to a particular unit).
The syntax for properties can vary, so your style guide should define how to
format them. Use these tips to keep properties consistent in your code:
// equivalent to:
// public int MaxHealth { get; private set; }
}
Apply the expression-bodied syntax for the set and get accessors.
Remember to make the setter private if you don’t want to give write
access. Align the closing with the opening brace for multi-line code
blocks.
Serialization
Serialized fields appear in the Inspector, but you cannot serialize static,
constant, or read-only fields. They must be either public or tagged with the
[SerializeField] attribute. Unity only serializes certain field types, so refer
to the documentation page for the complete set of serialization rules.
using System;
using UnityEngine;
[SerializeField]
private PlayerStats _stats;
}
Reference this serializable class from another class. The resulting variables
appear within collapsible units in the Inspector.
— The Allman style places the opening curly braces on a new line, also
known as the BSD style (from BSD Unix).
— The K&R style, or “one true brace style,” keeps the opening brace on the
same line as the previous header.
< // EXAMPLE: Allman or BSD style puts opening brace on a new line.
There are variations on these indentation styles as well. The examples in this
guide use the Allman style from the Microsoft Framework Design Guidelines.
Regardless of which one you choose as a team, make sure everyone follows the
same indentation and brace style.
In Visual Studio (Windows), navigate to Tools > Options > Text Editor > .
C# > Tabs.
On Visual Studio for Mac, navigate to Preferences > Source Code > C# Source
Code. Select the Text Style to adjust the settings.
— Where possible, don’t omit braces, even for single-line statements: This
increases consistency, keeping your code easier to read and maintain. In
this example, the braces clearly separate the action, DoSomething, from
the loop.
Do you have multiple developers working on the same project with different
editors and IDEs? Consider using an EditorConfig file.
The EditorConfig file can help you define a coding style that works across your
entire team. Many IDEs, like Visual Studio and Rider, come bundled with native
support and do not require a separate plugin.
EditorConfig files are easily readable and work with version control systems. You
can see an example file here. The code styling from EditorConfig travels with
your code and can enforce coding styles even outside of Visual Studio.
EditorConfig settings take precedence over the global Visual Studio text editor
settings. Your personal editor preferences still apply whenever you’re working in
a codebase without a .editorconfig file, or when the .editorconfig file doesn’t
override a particular setting.
Horizontal spacing
— Add spaces to decrease code density: The extra whitespace can give a
sense of visual separation between parts of a line.
// AVOID: no spaces
for(inti=0;i<100;i++){DoSomething(i);}
// AVOID:
CollectItem(myObject,0,1);
//AVOID:
DropPowerUp( myPrefab, 0, 1 );
// AVOID
DoSomething ()
// AVOID
x = dataArray[ index ];
— Use a single space before flow control conditions: Add a space between
the flow comparison operator and the parentheses.
// AVOID
while(x==y)
// AVOID
if (x==y)
— Don’t use column alignment unless needed for readability: This type of
spacing aligns the variables but can make it difficult to pair the type with
the name.
Vertical spacing
You can use the vertical spacing to your advantage as well. Keep related
parts of the script together and use blank lines to your advantage. Try these
suggestions to organize your code from top to bottom:
Keep this to a minimum and note on your style guide where applicable.
The #region directive enables you to collapse and hide sections of code in C#
files, making large files more manageable and easier to read.
However, if you follow the general advice for Classes from this guide, your class
size should be manageable and the #region directive superfluous. Break your
code into smaller classes instead of hiding code blocks behind regions. You will
be less inclined to add a region if the source file is short.
Don’t despair if these formatting rules seem overwhelming. Modern IDEs make
it efficient to set up and enforce them. You can create a template of formatting
rules and then convert your project files at once.
Use the settings to modify the General, Indentation, New Lines, Spacing,
and Wrapping options.
Select the Policy at the top. Then set your spacing and indentation in the
Text Style tab. In the C# Format tab, adjust the Indentation, New Lines,
Spacing, and Wrapping settings.
If at any time you want to force your script file to conform to the style guide:
— In Visual Studio for Mac, go to Edit > Format Document (Ctrl + I hotkey)
On Windows, you can also share your editor settings from Tools > Import and
Export Settings. Export a file with the style guide’s C# code formatting and then
have every team member import that file.
Visual Studio makes it easy to follow the style guide. Formatting then becomes
as simple as using a hotkey.
Note: You can configure an EditorConfig file (see above) instead of importing
and exporting Visual Studio settings. Doing this allows you to share formatting
more easily across different IDEs, and it has the added benefit of working with
version control. See the .NET code style rule options for more information.
Though this isn’t specific to clean code, be sure to check out 10 ways to speed
up your programming workflow in Unity with Visual Studio. Clean code is much
easier to format and refactor if you apply these productivity tips.
“ NO ON E I N TH E BRI EF H ISTORY OF
COM P UTI NG HAS E VER WRIT TEN A
PI ECE OF P ERFECT SOFT WARE . IT ’S
U N LI KELY THAT YOU ’LL BE TH E FI RST.”
– Andy Hunt, author of The Pragmatic Programmer
According to Robert C. Martin’s Clean Code, the first rule of classes is that they
should be small. The second rule is they should be even smaller than that.
Limiting the size of each class makes it more focused and cohesive. It’s easy to
keep adding on top of an existing class until it overextends with functionality.
Instead make a conscious effort to keep the classes short. Big, bloated classes
become difficult to read and troubleshoot.
Imagine the source code of a class as a news article. You start reading from the
top, where the headline and byline catch your eye. The lead-in paragraph gives
you a rough summary, then you glean more details as you continue downward.
Journalists call this the inverted pyramid. The broad strokes of most
newsworthy items appear at the beginning. You only get the story’s nuances as
you read to the end.
Your class should also follow this basic pattern. Organize top-down and think of
your functions as forming a hierarchy. Some methods serve a higher-level and
lay the groundwork for the big picture. Put these first, then, place lower-level
functions with implementation details later.
For example, you might make a method called ThrowBall that references other
methods, SetInitialVelocity and CalculateTrajectory. Keep ThrowBall
first, since that describes the main action. Then, add the supporting methods
below it.
Though each news article is short, a newspaper or news website will have many
such collected stories. When taken together, the articles comprise a unified,
functional whole. Think of your Unity project in the same way. It has numerous
classes that must come together to form a larger, yet coherent, application.
Class organization
Each class will need some standardization. Group class members into sections
to organize them:
— Fields
— Properties
— Events / Delegates
— Monobehaviour Methods (Awake, Start, OnEnable, OnDisable, OnDestroy,
etc.)
— Public Methods
— Private Methods
Recall the recommended class naming rules in Unity: The source file name must
match the name of the Monobehaviour in the file. You might have other internal
classes in the file, but only one Monobehaviour should exist per file.
Remember the goal is to keep each class short. In software design, the single-
responsibility principle guides you toward simplicity.
The idea is that each module, class, or function is responsible for one thing.
Suppose you want to build a game of Pong. You might start with classes for a
paddle, a ball, and a wall.
Because the game design is simple, you can incorporate all of these things into
a basic Paddle class. In fact, it’s entirely possible to create one Monobehaviour
that does everything you need.
Instead, break your Paddle class into smaller classes, each with a single
responsibility. Separate data into its own PaddleData class or use a
ScriptableObject. Then refactor everything else into a PaddleInput class,
a PaddleMovement class, and a PaddleAudio class.
A PaddleLogic class can process the input from the PaddleInput. Applying
the speed information from the PaddleData, it can shift the paddle using the
PaddleMovement. Finally, the PaddleLogic can notify the PaddleAudio to play a
sound when the ball collides with the paddle.
Each class does one thing in this redesign and fits into small, digestible pieces.
You don’t need to scroll through several screens to follow the code.
You’ll still require a Paddle script but its sole job is to tie these other classes
together. The bulk of the functionality is split into the other classes.
Note that clean code is not always the most compact code. Even when you use
shorter classes, the total number of lines may increase during refactoring. However,
each individual class becomes easier to read. When the time comes to debug or
add new features, this simplified structure helps keep everything in its place.
Refactoring example
For a more in-depth look at refactoring a simple project, see How to architect
code as your project scales. This article demonstrates how to break down larger
Monobehaviours into smaller pieces using the single-responsibility principle.
You can also watch Mikael Kalms’s original presentation, “From Pong to
15-person project,” from Unite Berlin.
Try the following suggestions when you create methods for your custom
classes:
— Avoid side effects: A method only needs to do what its name advertises.
Avoid modifying anything outside of its scope. Pass in arguments by value
instead of by reference when possible. If sending back results via the out
or ref keyword, make sure that’s the one thing you intend the method to
accomplish.
Though side effects are useful for certain tasks, they can lead to
unintended consequences. Write a method without side effects to cut
down on unexpected behavior.
While the Boolean flag as an argument seems innocuous, it can lead to tangled
implementation or broken single-responsibility.
Extension methods
To create an extension method, make a static method and use the this keyword
before the first argument, which will be the type you want to extend.
Then, when you want to use it, invoke the ResetTransformation method. The
ResetOnStart class calls it on the current Transform during Start.
Extension methods can build many useful utilities without the need to create
more Monobehaviours. See Unity Learn: Extension Methods to add them to your
gamedev bag of tricks.
In The Pragmatic Programmer, Andy Hunt and Dave Thomas formulated the
DRY principle, or, “don’t repeat yourself.” This oft-spoken mantra in software
engineering advises programmers to avoid duplicate or repetitious logic.
In doing so, you can ease bug fixing and maintenance costs. If you follow the
single-responsibility principle, you shouldn’t need to change an unrelated piece
of code whenever you modify a class or a method. Quashing a logical bug in a
DRY program stops it everywhere.
The opposite of DRY is WET (“we enjoy typing” or “write everything twice”).
Programming is WET when there are unnecessary repetitions in the code.
Imagine there are two ParticleSystems (explosionA and explosionB) and two
AudioClips (soundA and soundB). Each ParticleSystem needs to play with its
respective sound, which you can achieve with simple methods like this.
AudioSource.PlayClipAtPoint(soundA, hitPosition);
}
AudioSource.PlayClipAtPoint(soundB, hitPosition);
}
Here each method takes a Vector3 position to move the ParticleSystem into
place for playback. First, stop the particles (in case they are already playing)
and play the simulation. The AudioSource’s static PlayClipAtPoint method then
creates a sound effect at the same location.
AudioSource.PlayClipAtPoint(clip, hitPosition);
}
Add more ParticleSystems and AudioClips, and you can continue using this
same method to play them in concert.
Note that it’s possible to duplicate code without violating the DRY principle. It’s
more important that you don’t duplicate logic.
Here, we’ve extracted the core functionality into the PlayFXWithSound method.
If you need to adjust the logic, you only need to change it in one method rather
than in both PlayExplosionA and PlayExplosionB.
Most of your code won’t need comments if you follow KISS principles and break
your code into easy-to-digest logical parts. Well-named variables and functions
will explain themselves.
Rather than answering “what,” useful comments fill in the gaps and tell you
“why.” Did you make specific decisions that are not immediately obvious? Is
there a tricky bit of logic that needs clarification? Useful comments reveal
information not gleaned from the code itself.
— Don’t add comments to replace bad code: If you need to add a comment
to explain a convoluted tangle of logic, restructure your code to be more
obvious. Then you won’t need the comment.
— Place the comment on a separate line when possible, not at the end of a
line of code: In most cases, keep each one on its own line for clarity.
— Use the double slash (//) comment tag in most situations: Keep the
comment near the code that it explains rather than using a large multi-
line at the beginning. Keeping it close helps the reader connect the
explanation with the logic.
— You can also use a summary XML tag in front of public methods or
functions: Visual Studio can provide IntelliSense for many common XML-
style comments.
< // EXAMPLES:
// This is a common comment.
// Use them to show intent, logical flow, and approach.
You can add a name and date to a TODO for more accountability and
context.
Also, be realistic. That TODO you left in the code five years ago? You’re
never going to get to it. Remember YAGNI. Delete the TODO comment until
you need to implement it.
— Avoid journals: The comments are not a place for your dev diary. There’s
no need to log everything you’re doing in a comment when you start a new
class. Proper use of source control makes this redundant.
— Avoid attributions: You don’t need to add bylines, e.g., // added by devA
or devB, especially if you use source control.
“ I F DEBUGGI NG IS TH E PROCESS
OF REMOVI NG SOFT WARE BUGS,
TH EN PROGR AM M I NG M UST BE TH E
PROCESS OF P UT TI NG TH EM I N.”
— Edsger W. Dijkstra, computer science pioneer
Clean code isn’t an accident. It’s the deliberate work of individuals trying to think
and code like a team.
A code smell is a telltale sign you might have troublesome code lurking in the
project. Though the following symptoms don’t necessarily point to underlying
problems, they are worth investigating when they appear:
When you give something more than one responsibility, it breaks more
easily because it’s harder to anticipate everything. If you update a method
that is doing one thing, and the updated logic still works, you expect the
rest of your code to continue to work afterward.
— Fragility: If you make a minor change and everything stops working, this
often indicates a problem.
— Duplicate code: If it’s noticeable that you’ve cut and pasted code, it’s time
to refactor. Extract the core logic into its own function and call that from the
other functions. Copy-and-paste code is difficult to maintain because you
need to update the logic in multiple locations each time there is a change.
The techniques presented here are less a specific set of rules than a set of habits,
and like all habits, you’ll need to discover them yourself through daily application.
As mentioned earlier in the guide, feel free to copy this C# style sheet for Unity
developers, to use as a starting point for your own guide.
When you code as a group, game development becomes less of a long solo race
and more akin to a relay. You have teammates to share the workload with and
split up the entire course.
Remember to stay in your lane and pass the baton and together, you will make it
across the finish line.
If you’re looking for help on how to clean up your code, reach out to Unity’s
professional services team, Accelerate Solutions. The team is made up of Unity’s
most senior software developers. Specializing in performance optimization,
development acceleration, game planning, innovation, and much more,
Accelerate Solutions offers custom consulting and development solutions for
game studios of all sizes.
One of the services offered by Accelerate Solutions is CAP (Code, Assets and
Performance). This two-week consulting engagement begins with a three-day
deep dive into your code and assets to uncover the root causes of performance
issues. This will come with an actionable and detailed report with best practice
recommendations. To learn more about this or other services Unity Accelerate
Solutions offers, speak to a Unity representative today.
References
This guide is a short list of best practices used in computing. For more information, refer to the
Microsoft Framework Design Guidelines, which serve as an overarching style guide for this document.
You can also learn more from the comprehensive volumes already written about clean code. Here are a
few of our favorite books to consider to further your understanding:
Clean Code: A Handbook of Agile Software Craftsmanship. Robert C. Martin, 2008. Prentice Hall. ISBN
978-0132350884.
The Pragmatic Programmer, 20th Anniversary Edition. David Thomas and Andrew Hunt, 2019, Addison
Wesley, ISBN 978-0135957059.
“ TALK IS CH E AP.
SHOW M E TH E CODE.”
— Linus Torvalds, creator of Linux and Git
Once you establish formatting rules for your style guide, configure your script
templates. These templates generate the blank starting files for scripted assets
like C# scripts, shaders, or materials.
Mac: /Applications/Unity/Unity.app/Contents/Resources/ScriptTemplates
81-C# Script-NewBehaviourScript.cs.txt
82-Javascript-NewBehaviourScript.js.txt
84-Shader__Unlit Shader-NewUnlitShader.shader.txt
Whenever you make a new scripted asset in the Project window from the Create
menu, Unity uses one of these templates.
Script templates are customizable. For example, you can add a namespace or
remove the default Update method. Modifying the template can save you a few
keystrokes every time you create one of these scripted assets.
PriorityNumber–MenuPath–DefaultName.FileExtension.txt
— PriorityNumber is the order that the script appears in, in the Create menu.
Lower numbers have higher priority.
— MenuPath allows you to customize how the file appears in the Create
menu. You can create categories with the double underscore(__).
— DefaultName is the default name given to the asset if you don’t specify
one.
Also, note that each script template also has a .txt appended to the
FileExtension.
If you want to apply a script template to a specific Unity project, copy and paste
the entire ScriptTemplates folder directly under the project’s Assets.
For example, you could create a blank script template for ScriptableObjects.
Make a new text file under the ScriptTemplates folder called:
80-ScriptableObject-NewScriptableObject.cs.txt
Restart the Editor after you save the script template. Next time you should see
an extra option in the Create menu.
A custom script template adds a new menu item in the Create menu.
Be sure to back up both the customized script templates and the originals. You
will need to restore any files if Unity fails to recognize a modified template.
Once you have a set of script templates you like, copy your ScriptTemplates
folder to a new project and customize them to your specific needs. You can also
change the original script templates in the application resources but exercise
caution. That affects all projects using that version of Unity.
See this support article for more information about customizing your script templates.
Also, check the attached project for a few additional script template examples.
“ DEBUGGI NG IS LI KE BEI NG A
DETECTIVE I N A CRI M E MOVI E
WH ERE YO U ARE ALSO TH E
M U RDERER.”
— Filipe Fortes
Automated testing is an effective tool for improving the quality of your code
and reducing the time spent on bug fixes. Test-driven development (TDD) is a
development methodology where you create unit tests while you develop the
software. In fact, you’ll routinely write each test case before making a specific
feature function.
As you develop the software, you’ll repeatedly run it against this whole test suite
of automated processes. This is in stark contrast to writing the software first
and building the test cases later. In TDD, coding, testing, and refactoring are
interwoven.
1. Add a single unit test: This describes one new feature you want to add to
your application; spec out what needs to be done, either from your team
or your user base.
2. Run the test: The test should fail since you haven’t implemented the new
feature into your program. Additionally, this verifies whether or not the test
itself is valid. It should not always pass by default.
3. Write the simplest code that passes the new test: Write just enough logic
to make it pass the new unit test. This doesn’t have to be clean code at
this point. It can use inelegant structure, hard-coded magic numbers, and
so on, as long as it passes the unit test.
4. Confirm that all tests pass: Run the full automated test suite. Your
previous unit tests should all pass. The new code meets your new testing
requirements and the old requirements as well.
If not, modify your new code – and only your new code – until all tests
pass.
5. Refactor: Go back and clean up your new code. Use your style guide and
make sure everything conforms.
6. Repeat: Go through this process every time you add a new feature. Each
step is a small, incremental change. Make frequent commits under source
control. When debugging, you only have to examine a small amount of new
code for each unit test. This simplifies the scope of your work. If all else
fails, roll back to the previous commit and begin again.
That’s the gist of it. If you develop software using this methodology, you tend
to follow the KISS principle by necessity. Add one feature at a time, testing as
you go. Refactor continuously with each test, so cleaning your code becomes a
constant ritual.
Like most of the tenets of clean code, TDD takes extra work in the short-term
but often results in the improvement of long-term maintenance and readability.
The Unity Test Framework (UTF), formerly known as the Unity Test Runner,
provides a standard test framework for Unity developers. UTF uses NUnit, an
open-source testing library for .NET languages.
The Unity Test Framework can perform unit tests in the Editor (either using Edit
Mode or Play Mode) and on target platforms (e.g., Standalone, Android, iOS).
Install UTF via the Package Manager. The online documentation will help you get
started.
— Create a new test suite, called a Test Assembly: The Test Runner UI
simplifies this process and creates a folder in your project.
— Create a test: The Test Runner UI helps you manage the C# scripts that
you will create as unit tests. Select aTest Assembly folder and navigate to
Assets > Create > Testing > C# Test Script. Edit this script and add logic
for your test.
— Run a test: Use the Test Runner UI to run all of the unit tests or run a
selected one. Using JetBrains Rider, you can also run UTF directly from the
script editor.
— Add Play mode tests in the Editor or as standalone: The default Test
Assembly works in Edit Mode. If you want unit tests to work at runtime,
create a separate assembly in Play Mode. Configure this for your
standalone build as well (with the results of the test displayed in the
Editor).
The Test Framework displays the results of a standalone build within the Editor.
See the Test Framework microsite for more information about getting up and
running with UTF.