Coding Conventions
December 9, 2020 ยท View on GitHub
Unless otherwise specified, the Innoactive projects adhere to the MSDN C# Coding Conventions and Design Guidelines. In the following, the most important guidelines and custom decisions are listed.
Language
- Code, documentation, and comments are to be written in English.
Naming Conventions
Name
- Use meaningful but concise names.
- Do not use special characters in any names.
- Do not use abbreviations.
public class RuntimeStatistics
{
// very bad:
private int n;
// bad:
private int numFrms;
private int frameCnt;
// good:
private int numberOfFrames;
private int frameCount;
// too verbose:
private int numberOfElapsedFramesSinceApplicationStart;
}
- Keep the most specific descriptor left, e.g. pauseButton not buttonPause.
- Boolean variables and Properties should start with Is or Has, e.g. isActive.
Formatting
- Classes, properties, and public fields are written in PascalCase.
- Private fields are written in camelCase.
- Static and const fields (public, protected, or private) are formatted like non-const/static fields.
public class MyClass
{
public float MyProperty { get; private set; }
public bool MyPublicVariable;
private string myPrivateVariable;
public static string MyStaticVariable = "someValue";
protected const string MyConstVariable = "someOtherValue";
private const string myPrivateConstVariable = "yetAnotherValue";
}
- Functions are written in PascalCase.
- Function parameters are written in camelCase.
- Local variables are written in camelCase.
public bool MyFunction(bool myFunctionParameter)
{
float myLocalVariable = 0;
}
- Interfaces are written in PascalCase, but start with an I.
public interface IMyInterface
{
}
- Enums are written in PascalCase.
- Enum Values are written in PascalCase.
public enum MyEnum
{
FirstEnumValue,
SecondEnumValue,
ThirdEnumValue,
}
Bracing and Indentation
- Each opening and closing brace is placed in a separate line.
- Braces start at the indentation level of the outlying scope.
- Add Braces even for short single-line scopes.
// Do
if (condition == true)
{
return;
}
// Don't
if (condition == true)
return;
if (condition == true) return;
- Exception: Default properties are implemented in a single line. Encapsulating properties and properties with simple single-statement accessors place accessors on a single line together with the respective implementation. Complex accessors with multiple statements follow the general rule of placing braces and statements on separate lines.
public bool DefaultProperty { get; protected set; }
public bool EncapsulatingProperty
{
get { return wrappedValue; }
protected set { wrappedValue = value; }
}
public bool SimpleProperty
{
get { return entry == "MagicValue"; }
set { entry = string.Format("Bla{0}", value); }
}
public bool ComplexProperty
{
get
{
if(wrappedValue == null)
{
throw new OperationException();
}
wrappedValue;
}
protected set
{
wrappedValue = value;
if (implementation != null)
{
implementation.ForwardValue(value);
}
}
}
public bool MixedProperty
{
get
{
return wrappedValue;
}
protected set
{
wrappedValue = value;
if (implementation != null)
{
implementation.ForwardValue(value);
}
}
}
- Content of the braced scope is indented an additional level.
- Indent by 4 spaces.
- Indent every scope (namespaces, classes, loops, ...).
public class ClassInTopScope
{
public bool PropertyInClassScope { get; set; }
public float FunctionInClassScope()
{
public float functionVariable;
{
float scopedVariable = 3;
}
for (int i = 0; i < 100; ++i)
{
float scopedVariable = i;
}
}
}
- Do not indent compile
#ifand#pragma. - Indent
#regionspecifications. - Do not further indent code inside compile directives like
#ifor#regions.
namespace MyNamespace
{
public class MyClass1
{
public bool FirstFunction();
#pragma warning disable 0618 // temporarily disable obsolete usage warning
CallObsoleteFunction();
#pragma warning restore 0618
#region First Region
public bool SecondFunction();
#endregion
#region Second Region
public bool ThirdFunction();
#endregion
}
#if COMPILE_DEFINE
public class MyClass2
{
}
#endif
}
- When splitting a statement or declaration, indent the split line by one level.
- When defining local delegates, do not indent the opening and closing braces.
public float FunctionWithManyParameters()
{
bool expression = CheckConditionOne()
&& CheckConditionTwo()
&& CheckConditionThree()
&& CheckConditionFour();
Action localTask = () =>
{
// [...]
}
new AsyncTask(localTask).Execute();
new AsyncTask(() =>
{
// [...]
})
.Execute();
}
Code comments
- Enrich your code with meaningful comments.
- Use
//for inline code comments, and///for API docs. - Add xml API docs to all elements of the public API: classes, functions, properties, fields, and events.
- Comments should always start with a capital letter, be separated from the comment symbol by a space, and end with a period.
- All sentence-like parts of a comment (e.g.
<param> and <return>texts) should end with a period.
/// <summary>
/// Explain the general intent and typical usage of the class here.
/// </summary>
public class ExampleClass
{
/// <summary>
/// Provide additional detail here.
/// </summary>
public float ExampleProperty { get; private set; }
/// <summary>
/// Provide details about what the function does.
/// </summary>
/// <param name="exampleParameter">Explain parameter here.</param>
/// <returns>Explain return type here.</returns>
/// <exception cref="ExampleException">When is this exception thrown.</exception>
public int ExampleFunction(float exampleParameter)
{
// [...]
}
}
- Avoid empty xml tags components like
paramorreturn. Either add a valuable descriptions, or remove the tag.
public class ExampleClass
{
//Do
/// <summary>
/// This function computes the square root of a value.
/// </summary>
public float SquareRoot(float value)
{
// [...]
}
// Don't
/// <summary>
/// This function computes the square root of a value.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public float SquareRoot(float value)
{
// [...]
}
}
Whitespaces
- Use empty lines to separate classes/class members/blocks of statements.
- Do not use two or more consecutive empty lines (use regions instead to group blocks).
- Add a whitespace to separate a condition/iteration keyword (
if,while,for, ...) and its braces. - Do not use a whitespace to separate a function name and its brace.
if (CheckParameters(param1, param2) && param3)
{
// [...]
}
while (pi == 3)
{
// [...]
}
SomeFunction(param);
- Add whitespaces after commas and around operators.
if (CheckParameters(param1, param2) && param3)
{
return param1 + (param2 * param3);
}
Events
- Use C# events - avoid UnityEvents, Actions, or similar.
- Use EventArgs classes instead of delegates or actions for event definitions.
- Use immutable
EventArgsclasses, which provide public readonly fields. Initialize these fields from a constructor.
public class MyButton
{
// Do
public class ClickedEventArgs : System.EventArgs
{
public readonly string SomeData;
public ClickedEventArgs(string data)
{
SomeData = data;
}
}
public event EventHandler<ClickedEventArgs> Clicked;
// Don't
public delegate void ClickedEvent(string someData);
public event ClickedEvent Clicked;
// Don't
public event Action<string> Clicked;
}
- EventArgs classes end with
EventArgs, and are derived fromSystem.EventArgs. - Name Events with a verb describing the event (without pre- or suffix), Loaded, Finished.
- Event handling functions start with
On, followed by the scope and the event name. - If an event can be triggered by a child class, provide a function called
EmitEVENTNAME.
public class MyButton
{
public class ClickedEventArgs : System.EventArgs
{
public readonly string someData;
public ClickedEventArgs(string data)
{
someData = data;
}
}
public event EventHandler<ClickedEventArgs> Clicked;
protected void EmitClicked(string data)
{
Clicked.Invoke(this, new ClickedEventArgs() { someData = data });
}
}
public class ButtonObserver
{
public void Observe
{
new MyButton().Clicked += OnButtonClicked;
}
private void OnButtonClicked(object sender, MyButton.ClickedEventArgs args)
}
Logging
- Prefer format syntax over explicit string concatenation.
private void SomeFunction()
{
int numberOfThings = 42;
// Do
Debug.Log($"I know {numberOfThings} things");
// Or
Debug.LogFormat("I know {0} things", numberOfThings);
// Don't
Debug.Log("I know " + numberOfThings + " things");
}
Exceptions
- Exception names end with
Exception. - If exceptions are to be thrown by only one class, define them inside this class.
- Exceptions thrown by multiple sources are defined in a separate file, in the matching namespace (no Exceptions sub-namespace).
Access Modifiers
- Only use public variables or Properties with public setters for data objects.
- Always specify an Access Modifier.
public class MyClass
{
// Do
private float myPrivateVariable1;
// Don't
float myPrivateVariable2;
}
Unity-specific
- Prefer private variables marked with
[SerializeField]over public variables for Inspector fields. - When using a public variable in a MonoBehaviour, still add
[SerializeField]to ease detection of Inspector fields. - Use the
[Tooltip("...")}attribute in Inspector fields for documentation / usage hints. - Always initialize Inspector fields (to prevent never-assigned-warnings).
- Avoid the automatic conversion from
UnityEngine.Objectto bool. - When using Enum-typed Inspector fields, consider hard-wiring the enum values to prevent serialization errors when changing available enum values.
// Do
if (gameObject != null)
{
// [...]
}
// Don't
if (gameObject)
{
// [...]
}
Lambdas
- Prefer lambda-style expressions over delegate-style.
// Do
(float param1, string param2) => {}
// Don't
delegate(float param1, string param2) {}
- Prefer named functions over lambdas.
- Assign longer or more complex lambdas to named local
Action/Func. - If nested lambdas are required, assign each one to named local
Action/Func. - Avoid defining long lambdas directly as parameter of a function call.
Obsoletion
- Avoid changes in code that break existing APIs (i.e. Class Names, Function Argument types, Renaming of public members / properties, return types).
- Use
System.Obsoleteto mark outdated code that will be removed in newer versions. - When re-naming classes/properties/methods, try to maintain the old version tagged as obsolete.
- Obsolete code will be maintained for one (major) release, and is deleted afterwards.
Misc
- Define class-specific Exceptions, Enums, EventArgs, ... inside the corresponding class.
- For Interfaces, define Exceptions, Enums, EventArgs, ... in the same file as the interface.
- Do not use
var, always specify the full type name. - Use
Invoketo trigger actions or events. - Only use
this.before members where absolutely necessary. - Decide whether or not to design for inheritance.
- If not intended to be derived from, make all non-public entities
private. - If intended to be derived from, take care to mark necessary entities
protectedand/orvirtual.
- If not intended to be derived from, make all non-public entities
- Consider using early returns in functions to prevent deeply nested scopes.
- Use parentheses to group parts of complex statements. Especially, always group sub-clauses when combining different boolean operators.
- Do not omit the array type specifier when using array initialization lists.
// Do
string[] myList = new string[] { "a", "b", "c", "d" };
// Don't
string[] myList = new [] { "a", "b", "c", "d" };
- Do not prefix static method defined in the same or parent class with the class scope.