在没有代码规范的情况下,一个多人合作的项目中很可能会出现多种代码风格,就像是一栋房子的装修中同时出现中式古典风格、现代简约风格、欧式奢华风格以及亚马逊原始风格。抛开美观不谈,这对项目成员间的协作以及后续维护会造成较大的阻碍,所以统一的代码规范是必要的。
本规范综合参考Godot C# Style Guide、微软的C# Coding Style与Unity Code Style Guide,可作为Godot、Unity、.NET等C#项目代码规范。
文中未特别说明部分优先遵循Godot C# Style Guide,其次遵循微软的C# Coding Style。
格式
本地文件中的换行符在提交到git后应转换为LF,而不是CRLF或CR,一般情况下git默认开启此功能。
使用UTF-8无BOM编码,如果使用Visual Studio需要注意设置。
使用4个空格作为tab键缩进,一般这是Unity、VSCode、Rider的默认设置,Godot需要在Editor Settings -> Text Editor -> Behavior -> Indent
中修改。
花括号换行使用Allman风格,而非K&R风格:
while (x == y)
{
DoSomething();
DoSomethingElse();
}
if (x > 0)
{
DoSomething();
}
内容只有一行时,可省略花括号:
while (x == y)
DoSomething();
if (x > 0)
DoSomething();
属性的get/set方法,以及方法体较为简单时,可写成一行:
public interface MyInterface
{
int MyProperty { get; set; }
}
public class MyClass : ParentClass
{
public int Value
{
get { return 0; }
set
{
ArrayValue = new [] {value};
}
}
public int Foo() { return GetSomeValue(); }
public void Bar() => DoSomething();
}
字段、属性、方法之间的空行不要超过2行。
方法内语句之间的空行不要超过2行。
保持代码紧凑易阅读,不要无意义空行。
命名
基础
C#代码文件使用PascalCase命名,例如MyClass.cs
。
不使用默认命名空间,即必须指定类所在的命名空间,命名空间使用PascalCase。
namespace Game
{
public class MyClass
{
}
}
C# 10.0以上可使用文件范围的命名空间。
namespace Game;
public class MyClass
{
}
命名空间与文件夹结构尽量保持一致,例如namespace Game.Combat.Characters
,则其文件夹结构为Game/Combat/Characters
。
接口使用PascalCase命名,加“I”前缀。
接口成员的访问修饰符没有要求,其他地方使用显式访问修饰符。
public interface ITouchable
{
// 接口成员可省略访问修饰符
void Interact();
}
枚举使用PascalCase命名,不加任何前缀。
public enum DrinkType
{
None,
Soft,
Hard
}
类与结构体使用PascalCase命名,不加任何前缀,基类可用“BaseXxx”命名但不是硬性要求。
public class SomeClass
{
}
所有常量使用PascalCase命名,无论其访问修饰符是什么,包括局部常量,且不使用任何前缀如“k_”。
public class SomeClass
{
public const float DefaultSpeed = 10f;
private const string LogTag = "MyClass";
}
所有private字段,无论是否为static,均使用camelCase命名,加下划线前缀,除此之外不使用其他前缀如“m_”、“s_”、“t_”等。
public class SomeClass
{
private static T _instance;
private static readonly object _lockObj = new();
private Vector3 _aimingAt;
private Vector3 _velocity;
}
非private字段使用PascalCase命名。
public class SomeClass
{
protected int HitPoints;
internal int State;
public string Name;
}
所有属性使用PascalCase命名,无论其访问修饰符是什么。
public class SomeClass
{
private bool IsAlive => HitPoints > 0;
protected float MyProperty { get; set; }
public float AnotherProperty
{
get { return MyProperty; }
}
}
所有方法使用PascalCase命名。
public class SomeClass
{
public void MyMethod()
{
}
}
局部变量及方法参数使用camelCase命名,不加任何前缀。局部常量使用PascalCase命名。
public float SomeMethod(float someValue)
{
const float Increment = 1.2f;
var result = someValue + Increment;
return result;
}
可读性
含有三个字母及以上的缩写词时,遵循当前命名约定,例如“APIHandler”在PascalCase时写作“ApiHandler”,camelCase时写作“apiHandler”。
两个字母的缩写词为特例,例如“UIUtil”在PascalCase时写作“UIUtil”,camelCase时写作“uiUtil”,仅限首字母缩写词,例如“Id”不是首字母缩写。
命名应该尽量表达清晰,尽量达到自解释,缩写不要影响可读性,例如:
FindNearbyEnemy()?.Damage(weaponDamage); √
FindNode()?.Change(wpnDmg); ×
bool
变量不加任何固定的前缀,例如“b”。但推荐使用“is”、“has”等词来表明其含义,例如“isDead”, “isWalking”, "hasDamageMultiplier"。
尽量使用动词短语为事件命名,用动词时态区分事件发生的时机。事件不加任何前缀或后缀。
public event Action OpeningDoor; // 开门事件发生之前
public event Action DoorOpened; // 开门事件发生之后
事件的接收方法以“On事件名”命名。
public void OnOpeningDoor()
{
}
public void OnDoorOpened()
{
}
非特殊情况不使用拼音命名。
避免拼写错误,很多时候拼写错误会造成一些让人一时摸不着头脑的Bug(例如JSON序列化、服务端传值错误等等),建议开启编辑器的拼写检查功能。
示例
namespace MyGame;
// 类与结构体使用PascalCase命名,不加任何前缀
public class MyClass<T, R> : Parent<T, R> where T : class, new()
{
// 所有常量使用PascalCase命名
public const float DefaultSpeed = 10f;
private const string LogTag = "MyClass";
// 所有private字段使用camelCase命名,加下划线前缀
// 使用显式访问修饰符,不省略private
private static T _instance;
private static readonly object _lockObj = new();
private Vector3 _aimingAt;
private Vector3 _velocity;
// 非private字段使用PascalCase命名
protected int HitPoints;
internal int State;
public string Name;
// 所有属性使用PascalCase命名
private bool IsAlive => HitPoints > 0;
protected float MyProperty { get; set; }
public float AnotherProperty
{
get { return MyProperty; }
}
public static T Instance
{
get
{
if (null == _instance)
{
lock (_lockObj)
{
_instance ??= new T();
}
}
return _instance;
}
}
// 所有方法使用PascalCase命名
public void MyMethod()
{
int[] values = {1, 2, 3, 4};
int sum = 0;
for (int i = 0; i < values.Length; i++)
{
switch (i)
{
case 3: return;
default:
sum += i > 2 ? 0 : 1;
break;
}
}
i += (int)MyProperty;
}
// 使用显式访问修饰符,不省略private
private int MyMethod2()
{
return 0;
}
}