Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Status
colourRed
titledraft
This standard uses Unity’s internal coding standard as it’s its foundation and extends Microsoft's Framework Design Guidelines, which defines a number of rules not covered by this document.

Definitions

  • camelCase: *words capitalized, except the first

  • PascalCase: all *words capitalized

  • UPPERCASE: all letters in all *words capitalized

*A “word” may only contain letters and numbers (no underscores or other symbols)

Encoding

  • Text file encoding should use UTF8 with no BOM, using LF (unix) line endings

  • Tabs should use 4-wide tab stops, using spaces only (no tab characters)

  • Lines should not contain any trailing whitespace

  • End of files should always include a single newline

...

  • Use PascalCase for all symbol names, except where noted.

  • No ‘Hungarian notation’ or other prefixes, except where noted.

  • Spell words using correct US-English spelling.

  • Use descriptive and accurate names, even if it makes them longer. Favor readability over brevity.

  • Avoid abbreviations when possible unless the abbreviation is commonly accepted.

  • Acronyms are PascalCase, unless they are exactly two letters, in which case they are UPPERCASE. (ex. htmlText, GetCpuCycles(), IOStream)

  • Do not capitalize each word in so-called closed-form compound words. (Microsoft’s FDG example)

  • Use semantically interesting names rather than language-specific keywords for type names (i.e. GetLength > GetInt).

  • Use a common name, such as value/item/element, rather than repeating the type name, in the rare cases when an identifier has no semantic meaning and the type is not important (i.e. newElements > newInts)

...

  • When a method takes one or more arguments that imply a binary condition, consider the following options:

    • If the method name clearly conveys a binary condition, then use a bool argument (e.g., void SetActive(bool active))

    • If the method name conveys a condition that could conceivably have more than two states:

      • You can create multiple named method variants (e.g., void Repaint() and void RepaintImmediately(), T GetComponent() and T GetComponentReadOnly()).

        • (+) Avoids adding new enumerated type to the API surface.

        • (-) Increases number of named methods (and thus number of search points in API reference).

        • (-) API surface will grow if support for new modes is added later.

      • You can create a single method and add an enum for the condition (e.g., void ApplyChanges(InteractionMode mode = InteractionMode.UserAction)).

        • (+) More robust to future support of additional modes.

        • (+) Single named API point communicates common intent, parameter conveys implementation/execution details.

        • (-) Adds more enumerated types to the API, which may not be widely used.

      • In most cases, prefer overloads rather than additional methods with different names (e.g., void GetItems(List<T> items) and void GetItems(T[] items, int length) instead of void GetItemsDynamic(List<T> items) and void GetItemsPreAllocated(T[] items, int length)).

...

  • camelCase: *words capitalized, except the first

  • PascalCase: all *words capitalized

  • UPPERCASE: all letters in all *words capitalized

*A “word” may only contain letters and numbers (no underscores or other symbols)

Readability Examples

  • HorizontalAlignment instead of AlignmentHorizontal (more English-readable)

  • CanScrollHorizontally instead of ScrollableX ('x' is somewhat obscure reference to the x axis)

  • DirectionalVector instead of DirVec (unnecessary and use of nonstandard abbreviation)

...

  • Documenting the 'why' is far more important than the 'what' or 'how'.

  • Document anything that would surprise another engineer (or yourself in six months when you've forgotten it).

  • /*C-Style comments*/ are not permitted. They are reserved for commenting out big hunks of code locally (never to be committed).

  • No "divider" comments (i.e. long ----- and ////// comments just to break up code).

  • No "category" comments (i.e. // Functions // Private Data // Globals etc.).

  • Only use /// (triple slash) comments if you are writing xmldoc, and never for ordinary comments that you want to stand out

...

Code Block
languagec#
public event Action<ThingHappenedEventArgs> thingHappened;

        [Description("I do things"), DebuggerNonUserCode]                                           // | Attributes always go on a line separate from what they apply to (unless a parameter), and joining them is encouraged if they are short
        public void DoThings(IEnumerable<IThingAgent> thingsToDo, string propertyDescription)       // | For types that are already internal (like class Example), use public instead of internal for members and nested types
        {
            varList<IThingAgent> doneThings = new List<IThingAgent>();                                 
             // | 'var' required on any 'new' where the type we want is the same as what is being constructed
            var string indent = new string(' ', 4);                                                        //
| ...even primitive types                                                                                                     // | When appropriate, separate code blocks by a single empty line
            IList<string> doneDescriptions = new List<string>();                                    // | (This is a case where 'var' not required because the types of the variable vs the ctor are different)

            foreach (varIThingAgent thingToDo in thingsToDo)       in thingsToDo)                                             // | 'var' required in all foreach
            {
                if (!thingToDo.DoThing(propertyDescription, m_CurrentCount))
                    break;                                                                          // | Braces not required for single statements under if or else, but that single statement must be on its own line

                using (File.CreateText(@"path\to\something.txt"))                                   // | Use @"" style string literal for paths with backslashes and regular expression patterns
                using (new ComputeBuffer(10, 20))                                                   // | Don't use braces for directly nested using's
                {                                                                                   // | Braces required for deepest level of nested using's
                    doneThings.Add(thingToDo);
                }
            }

            foreach (varIThingAgent doneThing in doneThings)                                                   // | Dirty details about allocs at https://q.unity3d.com/questions/1465/when-does-using-foreach-in-c-cause-an-allocation.html
            {                                                                                       // | Braces are required for loops (foreach, for, while, do) as well as 'fixed' and 'lock'
                doneDescriptions.Add(doneThing.operationDescription);
                Debug.Log(indent + "Doing thing: " + doneThing.operationDescription);               // | Prefer a + b + c over string.Concat(a, b, c)
            }

            Debug.Log("System Object is " + typeof(object));                                        // | Always use lowercase `object` for the System.Object class.
            Debug.Log("Unity Object is " + typeof(UnityEngine.Object));                             // | Always use a fully qualified name for Unity's Object type, and never 'Object'
        }

        public void ControlFlow(string message, object someFoo, WindingOrder windingOrder)          // | Use c# aliases of System types (e.g. object instead of Object, float instead of Single, etc.)
        {
            for (int i = 0; i < k_MaxCount; ++i)                                                    // | Using i and j for trivial local iterators is encouraged
            {
                // all of this is nonsense, and is just meant to demonstrate formatting             // | Place comments about multiple lines of code directly above them, with one empty line above the comment to visually group it with its code
                if ((i % -3) - 1 == 0)                                                              // | Wrap parens around subexpressions is optional but recommended to make operator precedence clear
                {
                    ++m_CurrentCount;
                    s_SharedCount *= (int)k_DefaultLength.x + totalCount;

                    do                                                                              // | 'while', 'do', 'for', 'foreach', 'switch' are always on a separate line from the code block they control
                    {
                        i += s_SharedCount;
                    }
                    while (i < m_CurrentCount);
                }
                else                                                                                // | 'else' always at same indentation level as its 'if'
                {
                    Debug.LogWarning("Skipping over " + i);                                         // | Drop 'ToString()' when not required by compiler
                    goto skip;                                                                      // | Goto's not necessarily considered harmful, not disallowed, but should be scrutinized for utility before usage
                }
            }

        skip:                                                                                       // | Goto label targets un-indented from parent scope one tab stop

            // more nonsense code for demo purposes
            switch (windingOrder)
            {
                case WindingOrder.Clockwise:                                                        // | Case labels indented under switch
                case WindingOrder.CounterClockwise:                                                 // | Braces optional if not needed for scope (but note indentation of braces and contents)
                    if (s_SharedCount == DisplayData.MaxItems)                                      // | Constants go on the right in comparisons (do not follow 'yoda' style)
                    {
                        varstring warningDetails = someFoo.ToString();                                    // | 'var'
for the result of assignments is optional (either way, good variable naming is most important)                         for (varint i = 0; i < s_SharedCount; ++i)
                        {
                            Debug.LogWarning("Spinning a " + warningDetails);
                        }
                    }
                    break;                                                                          // | 'break' inside case braces, if any

                case WindingOrder.Charm:
                    Debug.LogWarning("Check quark");                                                // | Indentation is the same, with or without scope braces
                    break;

                case WindingOrder.Singularity:
                {
                    varstring warningDetails = message;                                                   // | (this seemingly pointless variable is here solely to require braces on the case statements and show the required formatting)

                    if (message == Registry.ClassesRoot.ToString())
                    {
                        // Already correct so we don't need to do anything here                     // | Empty blocks should (a) only be used when it helps readability, (b) always use empty braces (never a standalone semicolon), and (c) be commented as to why the empty block is there
                    }
                    else if (m_CurrentCount > 3)
                    {
                        if (s_SharedCount < 10)                                                     // | Braces can only be omitted at the deepest level of nested code
                            Debug.LogWarning("Singularity! (" + warningDetails + ")");
                    }
                    else if (s_SharedCount > 5)                                                     // | 'else if' always on same line together
                        throw new IndexOutOfRangeException();
                    else if ((s_SharedCount > 7 && m_CurrentCount != 0) || message == null)         // | Always wrap subexpressions in parens when peer precedence is close enough to be ambiguous (e.g. && and || are commonly confused)
                        throw new NotImplementedException();

                    break;
                }

                default:
                    throw new InvalidOperationException("What's a " + windingOrder + "?");
            }
        }

...

  • When only a single parameterized type is used, naming it 'T' is acceptable.

  • For more than one parameterized type, use descriptive names prefixed with 'T'.

  • Consider indicating constraints placed on a type parameter in the name of the parameter.

    Code Block
    languagec#
      public static TResult Transmogrify<TResult, TComponent>(                                    // | When wrapping params, do not leave any on line with function name
                TComponent component, Func<TComponent, TResult> converter)                              // | When wrapping, only indent one stop (do not line up with paren)
    
               where TComponent : Component
       
        {
     
              return converter(component);
            }
       
    }
    

Structs

  • Name classes and structs with nouns or noun phrases.

  • No prefix on class names (no 'C' or 'S' etc.).

  • Structs may be mutable, but consider immutability when appropriate. [FDG Exception]

...