Choose names for classes and structs that tell us what they represent. Use PascalCase for these names.
public class CalculateCost { ... }
public struct VehicleModel { ... }
public enum VehicleStates { ... }Begin interface names with I and then use PascalCase for the rest of the name. Make sure the name conveys what the interface does.
public interface IFileManager { ... }
Use names for methods that explain what they do. These should also be in PascalCase.
public void CalculateTotalPrice(Order order) { ... }Pick meaningful names for variables that describe what they hold. Use camelCase for these names.
// YES
int itemCount = 10;
// NO
int a = 10;Private variables should have an underscore (_) prefix attached to them. To save additional keystrokes, avoid using the keyword private as private int _a; as it is the same as int _a;
// NO
private int a;
int a;
// YES
int _a;
float _rotationY;How about the access modifiers between public and private? Do I have to use a _ as well?
public int a;
protected internal int _b;
protected int _c
internal int _d;
private protected int _e;
// Don't use `private` in this case
int _f;For constants, use SCREAMING_CAPS which are uppercase letters separated by underscores. Name them so that it's clear what they represent.
const int MAX_RETRY_COUNT = 3;Give parameters descriptive names. These should also use camelCase.
public void UpdateCustomerInfo(int customerId, CustomerData newData) { ... }
#2. Formatting:
Use one tab for each indentation level. Start curly braces on the line after the corresponding statement or declaration.
if (condition)
{
// code here
}
else
{
// code here
}Although we don’t enforce line lengths its best to keep your lines around 100-120 characters maximum. If a line is too long, break it into multiple lines.
var longVariableName = someObject.SomeMethodWithLongName(
parameter1,
parameter2,
parameter3);Use XML comments to document public APIs, parameters, return values, and exceptions.
NOTE: When you change the parameters or functionality of the method or function, remember to update the XML comment for that function
/// <summary>
/// Calculates the total price for the given order.
/// </summary>
/// <param name="order">The order to calculate the total for.</param>
/// <returns>The total price of the order.</returns>
public decimal CalculateTotal(Order order) { ... }
type three forward slashes - /// to generate the above template in VS and VS code.When you want to add some notes about what your code does and why it is done in this way, add inline comments to clearly state your purpose.
public decimal CalculateTotal(Order order)
{
// GST value based on the location of the order
order += GST();
}Insert blank lines to separate different logical sections within a method or class. Example:
public void ProcessOrder(Order order)
{
// Logic for preparation...
// Blank line for separation
ProcessItems(order.Items);
// Logic for finalization...
}Put your using directives at the top of the file, outside of any namespaces. Group related directives together.
using System;
using System.Collections.Generic;
namespace MyNamespace
{
// Class and namespace definitions...
}Format your directives in this order:
-
C# Directives
-
Unity Directives
-
3rd Party Directives
-
Local Directives / Static Namespace Directives
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using DO.Tween;
using TMPro;
using CustomLibrary.Runtime.SubFolder.ClassName;
using static NamespaceName.Runtime.ClassName;Organizing the order of variables within a class is essential for readability. Follow a consistent order to enhance code clarity.
Place enum declarations, sub-classes, and struct declarations at the beginning of the class to provide a clear overview of the types involved.
public class MyClass
{
public enum Status
{
...
}
public struct TestStruct
{
...
}
// Constants and Variables will follow...
// ...
// Other members and methods will follow...
}Group constants and static variable declarations at the top of the class, right below any enum/sub-class/struct declarations.
public class MyClass
{
// enum/sub-class/struct declarations go here
const int MAX_ITERATIONS = 5;
public static readonly float PI_APPROX;
int _count;
string _name;
}Make sure your code follows the style guide everywhere. If you're working on an existing project, stick to the existing style of the project unless judgement says otherwise.
Use try-catch blocks to handle exceptions properly. Catch specific exceptions first and then more general ones.
try
{
// Code that might throw an exception...
}
catch (SpecificException ex)
{
// Handle specific exception...
}
catch (Exception ex)
{
// Handle other exceptions...
}Aim for each method and class to do one thing really well. Avoid having methods that handle too many tasks and break down large methods into smaller ones that each have a clear purpose.
Instead of using raw numbers or strings in your code, create constants with descriptive names.
const int MAX_RETRY_COUNT = 3;
int remainingRetries = MAX_RETRY_COUNT;Use explicit type declarations when the variable’s type isn’t immediately clear, enhancing code clarity and maintainability
// YES
List<string> names = GetNames(); // Clear type declaration
//NO
var age = 30; // We now that the type is evident from the assignmentInstead of nesting if conditions, it's a good practice to return early when a condition is not met. This approach makes the code easier to follow and reduces the indentation level.
Additionally, consider using guard clauses to check preconditions at the beginning of a function. If a precondition is not met, you can either return early or throw an exception, depending on the severity of the violation.
public void ProcessData(Data data)
{
if (data == null)
{
return;
}
// Process data...
}
public void ProcessData(Data data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
// Process data...
}In summary, the choice between switch and if-else if depends on the nature of your conditions and your coding convention. switch is ideal for comparing values against constants, improving code readability, and streamlining maintenance. For complex conditions, if-else if remains a valuable option.
Use switch for comparing a value against constants to enhance readability. We usually favor switch statements for optimized execution
switch (day)
{
case 1: return "Sunday";
case 2: return "Monday";
// ... (other days)
default: return "Invalid Day";
}
// C# 8.0 Switch Expression
public static string GetDayOfWeek(DayOfWeek day)
{
return day switch
{
DayOfWeek.Monday => "It's Monday!",
DayOfWeek.Tuesday => "It's Tuesday!",
DayOfWeek.Wednesday => "It's Wednesday!",
DayOfWeek.Thursday => "It's Thursday!",
DayOfWeek.Friday => "It's Friday!",
DayOfWeek.Saturday => "It's Saturday!",
DayOfWeek.Sunday => "It's Sunday!",
_ => "Invalid day of the week"
};
}For complex conditions, consider using if-else if structures. Try and put the most likely cases at the top for performance reasons to avoid extra condition checks.
if (order.TotalPrice > 1000 && order.CustomerType == CustomerType.Premium)
{
/* ... */
}
else if (order.TotalPrice > 500)
{
/* ... */
}
else
{
/* ... */
}Use ternary operators for simple conditional assignments or expressions; they're concise and replace if conditions effectively. But remember, never, ever use nested ternary operators – they seriously mess up code clarity and should be avoided at all costs.
// YES
string status = (age >= 18)? "Adult" : "Minor";
// NO
string result = (score >= 90) ? "A" : (score >= 80) ? "B" : (score >= 70) ? "C" : "F";Classes and functions should be written to limit the amount of similar or identical code. If there is lots of copy-pasted code or code that looks very similar, it will be brittle and will be riddled with bugs over the course of a project. Try and unify the code to make it as short as possible; this will also make it easier to read and debug.
Magic numbers are hard-coded numeric values lacking any context. They reduce code readability and make updates error-prone. To remedy this, replace magic numbers with named constants and comment why a constant was needed.
Avoid the use global variables to prevent polluting the namespace with variables. Use variables in the appropriate scope or use gets/set methods to access them explicitly.
Excessively nested code makes it hard to read and follow. No code should be more than 3 indents deep. To address this, refactor by reduce nesting by using early returns, and breaking down complex logic into smaller functions.
If a single change in one file requires many files or places in files to be changed to accommodate, it may be that the code is too rigid.
A class that is over 1000 lines long is probably too complex to be able to manage properly. Break the functionality out into other classes to make the class easier to manage and understand.
No method/function should be longer than 150 lines long, and ideally under 100 lines. If there are long functions, their scope is too large and they should be refactored or broken into smaller functions.
If the code has too many branches or loops, it needs to be simplified/refactored or needs to be broken up into smaller functions.
Having too many input parameters makes it hard to understand and use. To address this, refactor by grouping related parameters into objects or structuring the code to minimize the number of inputs, promoting cleaner and more concise functions.
Is when a class has dependencies on implementation details of another class. Classes should generally not be built with assumptions on how other classes are implemented. Use interfaces or redesign the system to encapsulate your code appropriately.
Granting excessive permissions, access, or privileges that go beyond what is necessary may cause potential security vulnerability or misconfiguration. Review and reduce the level of access and permissions granted to the code or component so that you mitigate the risk of unauthorized access or misuse of resources.
Code that is deprecated or no longer used is dead code. It should be deleted as you replace it with newer code or systems by that programmer.