Maintaining a common code style will boost our productivity by reducing the amount of noise in Code Reviews, improving code navigation and allowing new members to on-board faster.
When working with the Unity Project these guidelines must be followed.
- For all our style needs we are using an
.editorconfig
file. - It's recommended to use a
Format On Save
extension on your IDE of choice so we avoid styling feedback noise on pull requests. - We don't use the
.resharper
extensions for.editorconfig
. So if you useresharper
plugin orrider
you must set theresharper
specific style settings to match those ofVS Code
andVS Community
.
You can find a settings export file in the root of the project called "rider_codeStyleDCLSetting". Bear in mind that the conversion between VS and Rider is not 1 on 1 but it's good enough.
Microsoftβs guidelines are used as a baseline. Here you will find short summary of its adaptation to the current project, highlights and specifics related to Unity, which we suggest to follow.
Use
PascalCase
- Namespace, Class, Struct, Interface, Enumeration and its Enumerators, Method, Delegate declaration, Event, public Property, and public Field;camelCase
- non-public Property, non-public Field, methods Parameter, local Variable;ALL_UPPER_SNAKE_CASE
- Constants, static read-only fields;I
prefix in front of Interface name;- Asynchronous methods end with
Async
- Events name is in past tens and without
On
prefix _
,__
,___
for unused parameters of the method (for example, for events subscribers, interface implementation and inheritance overriden methods)
namespace MyProject // Namespace -> PascalCase
{
public enum Side { Left, Right } // Enumeration and Enumerators -> PascalCase
public delegate void Interaction<T> (T current); // Delegate declaration -> PascalCase
public interface IInitializable { } // Interface -> PascalCase, starts with 'I'
public class MyClass : IInitializable // Class/Struct -> PascalCase.
{
public const string ASSET_PATH = "AvatarUI"; // Constant -> ALL_UPPER_SNAKE_CASE
public int PublicField; // public Field -> PascalCase
private bool isVisible; // non-public Field -> camelCase
public bool IsInitialized { get; set; } // public Property -> PascalCase
private bool isVisitble { get; } // non-public Property -> camelCase
public event Interaction<bool> Interacted; // event -> PascalCase, without On prefix
public void Play(float speed, int _) // Method -> PascalCase. Method parameters -> camelCase. Not-used parameter -> underscore
{
var velocity = speed; // local variable -> camelCase
Interacted += OnInteracted; // for event subscribers 'On' prefix can be used
}
public async void PlayAsync(float speed) {..} // Asynchronous methods end with `Async`
}
}
Interface
- try to name it with adjective phrases (IDamageable
). If it is difficult, then use descriptive noun (IBaseVariable
) or noun phrase (IAssetProvider
).Class
/Struct
- name with nouns (Health
) or noun phrases (InputService
).Delegate type
- try to use nouns or noun phrases (take example from .NET built-in delegate types -Action
/Function
/Predicate
)
using
go first and placed alphabetically- Class members grouped and appears in the following order
- Enums, delegates declarations
- Fields (
const
andstatic readonly
goes first) - Properties
- Events (and
UnityEvent
's) - Methods
- Nested classes
public
public
with[PublicApi]
and[UsedImplicitly]
attributesinternal
protected internal
protected
private
- methods has additional rule for ordering
Const
,static
andreadonly
goes first (see example below)public
fields with[HideInInspector]
and[NonSerialized]
attribute goes afterpublic
's[SerializedFields]
attribute fields goes after allpublic
fields and beforeinternal
-private
-protected
)
public const string ASSET_PATH = "AvatarPrefab"; // Constants
internal const float MIN_TIME = 0.1f;
public static readonly int WAVE_ANIM_HASH = Animator.StringToHash("Wave"); // Static Read-only
private static readonly int DANCE_ANIM_HASH = Animator.StringToHash("Dance");
public static bool IsOpen; // Static
internal static bool isAvtive;
public readonly List<int> CachedNumbers = new List<int>(); // Read-only
protected readonly List<Section> sections = new List<Section>();
public int PublicFieldA; // Public
[HideInInspector] public int SomeField02;
[NonSerialized] public int SomeField03;
[SerializedField] internal Animator animator; // [SerializedField]'s
[SerializedField] private AnimationClip[] clips;
protected float cooldown; // internal-protected-private's
private bool isVisible;
static
andreadonly
goes first (similar as for fields, see example above π)get
-only andset
-only properties goes last.- Access modifiers for
set
considered to have higher priority thanget
(considered to be more exposed).- For example, property with
public set
andprivate get
goes beforeget
-only Property or Property withprivate set
andpublic get
- For example, property with
public bool Property1 { get; set; } // get-set order
public bool Property2 { private get; set; }
public bool Property3 { get; private set; }
public bool Property4 { get; }
protected bool Property5 { get; set; }
private bool Property6 { get; set; }
- Helper and supplementary methods which called from another method should be placed after method that calls it (in most cases it is
private
functions).
// Note: indentaion for helper methods is used only for clarity. It is not a part of our formating style
public void Test1() // called by other class
{
Test1Helper1();
Test1Helper2();
}
private void Test1Helper1() => A(); // called by Test1()
internal void A() { } // called by Test1Helper1()
private void Test1Helper2() => B(); // called by Test1()
public void B() { } // called by Test1Helper2()
private void Awake() { } // called by Unity
- Not-helper methods should follow the order (but its helper methods follows previous rule and allowed to be placed in between of this order):
- entry-point/creation/setup methods, like
constuctor
andinitialize
- exit-point methods, like
destructor
anddispose
public
public
with[PublicApi]
and[UsedImplicitly]
attributes- Unity-callbacks
Awake
,Start
,OnEnable
,OnDisable
,OnDestroy
,- other callbacks (with respect to
Enter
-Stay
-Exit
order)
internal
protected
- entry-point/creation/setup methods, like
- consider using local function for your helper method if it is small
- do not use local functions inside local functions
- for more detailed example on the methods ordering rules see methods organization in
CodeStyleExample.cs
file
Most of this formatting conventions will be known by your IDE thanks to the .EditorConfig and applied via auto-formatting on the fly. So there is no need of remembering it.
- keep each
public
type (likeclass
/enum
/struct
/interface
) in a separate file with the name equal to the type name - access modifiers are obligatory to use: π’
private void Awake() { }
π΄void Awake() { }
- use
var
only when it is evident. In all other cases specify the variable type explicitly. - don't omit comparison to
null
: π’if (character == null)
/if (character != null)
π΄if (character)
/if (!character)
- use
nameof
where it is possible - use string interpolation instead of concatenation π’
$"url = {url} / userid = {userId}"
π΄"url" + url + " / userid=" + userId
; - Use
Action
/Func
delegate in most cases- when the amount of
event
/delegate
parameters is higher than 3 then define a customEventArg
(eitherSystem.EventArgs
or a customstruct
) or define a customdelegate
.
- when the amount of
- one line - one statement. Split chain methods (like LINQ) in several lines starting with
.
on each line
List<string> filteredWords = new FilterLogic(listWords).
FilterOutShortWords().
FilterOnlyWordsContainingLetterC().
FilterArbitraryComplexItems().
FilterSomeMoreArbitraryComplexItems().
GetWords();
- namespaces are obligatory
- Each type (
class
,enum
,interface
) should be inside a namespace.
- Each type (
- namespace name should be meaningful
- π΄ avoid too abstract namespaces like
Scripts
,Components
,Contexts
, etc. - π’ it should reflect the Domain, area to which the script is belonging -
DCL.UI
,DCL.NPC
,DCL.Social.Chat
- π΄ avoid too abstract namespaces like
- π΄ avoid very deep levels of namespaces
- consider using plural namespace names where appropriate
- folder structure should be aligned with the namespaces
- π΄ not every folder should be namespace provider, especially folders like
Scripts
,MainScripts
,Assets
- π΄ folders that are deep in the folders hierarchy should be without namespace.
- π΄ not every folder should be namespace provider, especially folders like
Most of this rules are saved in the .editorconfig
and will be applied automatically on formating.
- Horizontal spaces
- indentation =
4 spaces
(notab
) - only one space between code elements is allowed
- space after a comma between function arguments - π’
CollectItem(myObject, 0, 1);
π΄CollectItem(myObject,0,1);
- no space before flow control conditions - π’
while(x < y)
π΄while (x < y)
- space before and after comparison operators - π’
if(x == y)
π΄if(x==y)
- indentation =
- Vertical spaces
- one blank line is used for vertical separation and grouping
- one blank line is always used to separate groups (field, properties, method definitions)
- π΄ two and more blank lines successively are not allowed
- Line breaks and curly braces
{}
- Curly brace is always placed on a new line (Allman/BSD style):
- line break before the opening brace
{
- line break between the closing brace
}
andelse
- line break before the opening brace
- avoid brackets when the body is a one line
- put the body on the new line in most cases
- it is allowed to put the body in the same line when it contains only one simple interruption (like
return
/break
/continue
)
- Curly brace is always placed on a new line (Allman/BSD style):
if(!IsInRange()) return; // interruptions are in the same line
if(IsInRange()) // body on the new line; no brackets
Fire()
else
{
CalculateDistance();
MoveToEnemy();
}
- Expression-bodies
- remove brackets in most cases where it is possible (for loops,
if
-else
, methods, and properties)β οΈ exception: always use brackets for Unity callbacks
- Properties - placed on the same line
- Methods - placed on the new line
- remove brackets in most cases where it is possible (for loops,
public bool IsInitialized => isInitialized; // Property - body placed on the same line =>
private void Awake() // Unity-callback - use brackets even when expression-body is possible
{
var collider = GetComponent<Collider>;
}
public void Initialize() => // Methods - body placed on the new line after =>
SubrcribeToEvents();
private void TrimAll()
{
foreach (string device in InputDevices) // Braces removed
device.Trim();
}
- Class
- Use
[RequireComponent(typeof(MyComponent))]
attribute when you cache components viaGetComponents
onAwake
andStart
callbacks - Use
[DisallowMultipleComponent]
attribute when there should be only oneComponent
of such type on theGameObject
- Use
- Methods
- Use
[PublicAPI]
attribute ifpublic method
is exposed to be called from outside of the solution - Use
[UsedImplicitly]
attribute for implicitly called methods, such as calls from Unity animation events, via UnityGameObject.SendMessage()
,GameObject.BroadcastMessage()
and similar. - Use
[Button]
attribute fromEasyButtons
(requires reference in.asmdef
file) instead of[ContextMenu]
attribute. Consider usingMode
parameter of this attribute forEditor
/PlayMode
-only methods.
- Use
- Variables (in Inspector)
- Use
[SerializedField]
for exposing variable in the inspector instead of converting it topublic
variable - Use CAPITAL letters for
[Header]
attribute - Use
[Space]
attribute for better grouping of exposed variables - Use
[Tooltip]
instead of comments for exposed in inspector variables
- Use
- try to avoid using Scripting Symbols. Often it can be solved via polymorphism
- when using it, try to decorate as less code as possible
- No tabs when starting Scripting Symbols
- No line breaks between Scripting Symbols and its body
#if UNITY_WEBGL
// Here goes WEBGL code
#elif UNITY_STANDALONE
// Here goes Standalone code
#endif
- always use XML comments
/// <summary>
before thepublic
class declaration providing appropriate descriptions of their behavior
/// <summary>
/// This is the InitialScene entry point.
/// Most of the application subsystems should be initialized from this class Awake() event.
/// </summary>
public class Main: MonoBehaviour
{
/// ...
}
- only use comments
//
inside of the class when it is necessary and there is a need to provide additional information which cannot be covered by good naming:- description of not obvious hidden logic behind the solution (such as for complex logic or mathematical algorithms)
- reference to the bug-ticket
- "why" description of the hack
- XML comments
/// <summary>
for not obvious public methods
- each comment starts with an uppercase letter and end with a period
- insert one space between the comment delimiter (
//
) and the comment text - don't use asterix syntax for comments π΄
/* comment */
- remove commented out code
- Class name should contain Tests postfix after the feature name to the class that tests the current feature
- Test method name
- try to reflect core of the arrange/act/assert part of the test in the test name (especially assert)
- consider using word
Should
to describe assert part and wordWhen
to describe act and arrange part of the test
- The method body should be split by
// Arrange
-// Act
-// Assert
comments to respective blocks.Arrange
comment could be omitted if there is no arrangement or it coincide with the acting.
public class NavmapTests
{
[Test]
public void CorectSceneDataShouldBeDisplayedWhenPlayerCoordinatesChanged()
{
// Arrange
var navmapView = Object.FindObjectOfType<NavmapView>();
MinimapMetadata.GetMetadata();
// Act
CommonScriptableObjects.playerCoords.Set(new Vector2Int(-77, -77));
// Assert
Assert.AreEqual("SCENE_NAME", navmapView.currentSceneNameText.text);
Assert.AreEqual("-77,-77", navmapView.currentSceneCoordsText.text);
}
}