Coding Standards - Orhu/Summer2023Project GitHub Wiki
Overview
This will contain commenting rules, style guides, naming conventions, and best practices/tips. The goal of this guide is to make it so that we can easily use and read each other's code as easily as possible.
Table of Contents
Commenting
Comments should be added to every class, struct, enum, function, fields, and property. Comments should always be complete sentences ending in periods.
Classes, Structs, Delegates, & Enums
Comments for these should be formatted using the auto-generated ///
and should describe what the class(or whatever it is) is used for. ex:
Comments for these should always be added unless only used for a single private serialized variable to make the inspector view more user-friendly like this PlayTime enum:
Functions
Functions should always be commented using the auto-generated ///
comments. The summary should describe what the function does, but save explanations of parameters for the parameter section of the comment, or when and how the function is used. The parameter descriptions should describe how the parameter values affect what the function does, and any restrictions on the parameter values. Make sure to add spaces after the period on parameter descriptions and similar.
Fields, Properties, & Local Variables
Fields and properties should be commented using a single-line comment, and should describe the data stored. If it is serialized a tooltip attribute should be used instead of a comment. When possible any value restrictions should be specified using attributes instead of being described in comments.
If a property has a backing field, the comment should be on the backing field only.
In Your Code
In general, you do not need to comment the actual functional part of your code; however, there are a few scenarios where commenting can be helpful.
- Comments explaining your logic should be added when you have a particularly long line of code, or are using a complex math function.
- Comments explaining what a lambda does and its parameters should be added if the lambda is too complex to be inlined. (See Lambdas)
- Comments delineating blocks of related code within a single function. For example:
Style Guide
This guide is a set of general rules that are far from exhaustive or perfect, if a scenario comes up where these rules don't cover your scenario or would result in your code becoming less readable, use your best judgment and choose a new standard or exception, and drop that standard in the programming discord with an @Programming.
Fields, Properties, & Local Variables
Variable attributes should all be put on the line above the variable with [Tooltip("...")]
being listed first if present, and with the exception of [SerializeField]
, [NonSerialized]
, and [HideInInspector]
which are put on the same line as the variable before the access specifier.
If a property uses an auto-generated backing field it should be declared in a single line.
Otherwise the set and get functions should each be on their own lines with set coming first. Set and get functions should only be inlined if their value can be obtained through a simple function call or variable reading. When inlined set/get should use the =>
syntax.
If a property has a custom backing field it should be declared on the line directly above the property.
Class Layout
Classes should contain things in the following order with the given number of line breaks. Every element within each of the categories should have a single line break after it.
Regions should be used frequently and should be nested if that helps. If you are debating between creating a region and not, make one. There are two ways to structure your regions:
- To separate chunks of each of the below categories into related categories but without changing the order of categories. For example, if you have a bunch of variables relating to movement and physics on a projectile you should wrap them all in a region.
- To group functions and variables only used by those functions by grouping them together into what I'll call a functionality region. This will change the order of things; however, within a functionality region things will be organized as if they are within an inner class. An example of this could be creating a region for an interface implementation that requires you to make variables not used throughout the rest of the class, and putting those variables at the top of the region.
See the SaveManager.cs file for a good example.
Category |
---|
public const Fields |
private const Fields |
2 Line Breaks |
serialized public Vars |
serialized private Vars |
2 Line Breaks |
public static Vars |
private static Vars |
public Vars |
private Vars |
3 Line Breaks |
public Inner Classes |
private Inner Classes |
3 Line Breaks |
Constructors |
public Functions |
private Functions |
3 Line Breaks |
functionally #region 1 |
3 Line Breaks |
functionally #region 2 |
3 Line Breaks |
functionally #region 3 |
ect... |
Lambdas
Lambas in C# have no extra performance costs over normal functions and thus should be used over functions if the code is only called once like in a delegate binding or Linq functions. Lambda parameters should always be explicitly typed even if the parameter is never used. Lambdas should be inlined only if they are a simple function call, variable access, or single binary operator. The {} should never be excluded when inlined.
Misc.
Access Specifiers
Access specifiers should always be included for everything, including variables, classes, and delegates.
Brackets
Brackets should always be put on a new line, unless used to return a variable or 0 param function, continue, or break.
if (foo)
{
DoSomething();
DoSomethingElse();
}
if (foo) { return GetSomething(); }
Namespaces
Everything in the project should be wrapped in the Cardificer
namespace so that we avoid conflicts with Unity and .NET. Avoid using sub-namespaces unless absolutely necessary.
Naming Conventions
In our project, we use only use PascalCase
, camelCase
, _camelCase
, and SCREAMING_SNAKE_CASE
.
PascalCase
is the default, so if something is not explicitly mentioned here then it should be named usingPascalCase
.camelCase
is used for all properties, fields, and local variables by default._camelCase
is used only for backing fields. A backing field is a private field that is only ever used within the get and set methods of a single property.SCREAMING_SNAKE_CASE
is used only for const fields.
Things to Avoid
- When declaring variables do not use
var
as it makes things harder to read.
Best Practices & Tips
Useful C# Functions
Here is a list of useful C# things, if you stumble across a neat feature, please add it here and @programming on the discord.
if
Statements
Declaring Variables in In C# it is possible to create variables inside statements while checking for type and validity by using C#7's Pattern Matching (A very powerful tool with many other uses) using the following syntax:
if (varToTest is Type localVar)
{
localVar.DoSomething();
}
Ex:
Note: There are other syntaxes like if (varToTest is { } localVar)
and if (varToTest is var localVar)
, but I would avoid using them as they make this already somewhat hard-to-read pattern even harder to read.
Nullable Types
Nullable types are a feature of C# that allows any value type (int, bool, etc) to null. This is useful for cases when you may or may not return a valid value from a function. For example, a find index function could be written like this int? FindIndex(T item) { ... }
instead of saying that an arbitrary value like -1 is when nothing was found, instead you can use the HasValue function or the ?? operator. For more info see these pages:
Other resources
Here are some videos that very well explain some core concepts and give really good advice, and are the basis for how I code. Feel free to check out the other videos by this person, but they stray a bit from these coding standards.
Another good youtube channel that explains a lot of stuff especially low-level computer logic and shaders. Also, he has some really fun and cool projects. Though this content has less practical applications in your code.