后端编码规范
要作为一名合格的开发者,最基本的素质就是要做到编码规范。从小我们就接受教导"字如其人",而写代码亦是如此。良好的代码 风格,彰显了个人的工作素养。良好的代码规范,能够帮助我们进行更好的团队协作:
- 方便代码的交流和维护
- 不影响编码的效率,不与大众习惯冲突
- 使代码更美观、阅读更方便
- 使代码的逻辑更清晰、更易于理解
规范目的
一个软件的生命周期中,80%的花费在于维护。几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护。
编码规范的核心价值在于:
- 改善软件的可读性,让程序员尽快而彻底地理解新的代码
- 使应用程序的结构和编码风格标准化,便于团队协作和知识传承
- 使源代码严谨、可读性强且意义清楚
- 与其它语言约定相一致,并且尽可能直观
为了充分发挥编码规范的价值,每个软件开发人员必须一致遵守编码规范。
高质量的代码特质
高质量的代码应该具备以下核心特质:
- 易懂性:代码必须易读且简单明确,能够展示出重点所在。代码应该易于重用,不包含多余代码,并附带相应文档说明。
- 正确性:代码必须正确实现其预期功能,经过充分测试,可以按照文档描述进行编译和正确运行。
- 一致性:代码应该遵循统一的编程风格和设计原则,确保不同代码之间风格一致,便于团队协作和维护。一致性 体现了对细节的追求,传递了代码库的优良品质。
- 流行性:代码应当采用现代编程实践,如使用Unicode、错误处理、防御式编程以及可移植性设计。使用当下推荐的运行时库、API函数和项目设置。
- 可靠性:代码必须符合法律法规、隐私政策和安全标准。避免展示入侵性或低质量的编程实践,确保安装和执行过程可撤销,不永久改变机器状态。
- 安全性:代码应该展示安全编程实践,如遵循最低权限原则、使用运行时库函数的安全版本,以及采用SDL(安全开发生命周期)推荐的项目设置。
合理运用编程实践、设计模式和语言特性,是实现上述代码特质的关键。
基本代码格式
缩进
规则:所有代码都应使用4个空格来表示缩进,严禁使用制表符。
原因:不同的编辑器会使用不同数量的空格来显示制表符,这会导致代码格式混乱,降低可读性。
建议:配置Visual Studio文字编辑器,启用"以空格代替制表符"选项。
花括号
花括号⼀定独占⼀⾏。关于花括号的格式问题,也是很多⼈争论的点,有些⼈习惯于Java的左花括号跟在圆括号后⾯,但微软官⽅推荐的是花括号应该独占⼀⾏,不与任何语句并列⼀⾏。
左花括号 "{" 放于关键字或⽅法名的下⼀⾏并与之对⻬。左花括号 "{" 要与相应的右花括号 "}"对⻬。
错误的示范:
public static void Main(string[] args) { }
正确示例:
public static void Main(string[] args)
{
}
特殊要求:if、while、do语句后必须使用 {},即使 {} 中为空或只有一条语句。
示例:
if (somevalue == 1)
{
somevalue = 2;
}
建议:右花括号 } 后建议添加注释,便于找到与之对应的左花括号。
示例:
{
while(1)
{
if(valid)
{
} // if valid
else
{
} // not valid
} // end forever
}
例外情况:类的自动属性花括号可与代码合占一行
示例:
public string Name { get; set; }
using排序
引⼊的命名空间应该按照字⺟⾳序排列,这样做的⽬的在于⽅便在引⼊的多个命名空间中直接快速的找到命名空间。
换⾏
当表达式超出或即将超出显示器⼀⾏显示范 围的时候,遵循以下规则进⾏换⾏:
- 在逗号后换⾏。
- 在操作符前换⾏。
规则1优先于规则2。
当以上规则会导致代码混乱的时候⾃⼰采取更灵活的换⾏规则。
空⾏
目的:空行用于将逻辑上相关联的代码分块,提高代码的可阅读性。
使用两个空行的情况:
- 当接口和类定义在同一文件中时,接口和类的定义之间
- 当枚举和类定义在同一文件中时,枚举和类的定义之间
- 当多个类定义在同一文件中时,类与类的定义之间
使用一个空行的情况:
- 方法与方法、属性与属性之间
- 方法中变量声明与语句之间
- 方法中不同的逻辑块之间
- 方法中的返回语句与其他语句之间
- 属性与方法、属性与字段、方法与字段之间
- 语句控制块之后,如if、for、while、switch
- 注释与它注释的语句间不换行,但与其他语句间空一行
空格
规则:在以下情况中需要使用空格:
-
关键字和左括符
(之间应使用空格隔开。如:while (true)注意:方法名和左括符
(之间不要使用空格,这样有助于辨认代码中的方法调用与关键字。 -
多个参数用逗号隔开时,每个逗号后都应加一个空格
-
除了
.之外,所有的二元操作符都应使用空格与它们的操 作数隔开- 一元操作符、
++及--与操作数间不需要空格
- 一元操作符、
-
语句中的表达式之间用空格隔开。如:
for (expr1; expr2; expr3)
示例:
a += c + d;
a = (a + b) / (c * d);
while (d++ == s++)
{
n++;
}
PrintSize("size is " + size + "\n");
文 件定义
规则:
- 通常情况下,一个.cs文件只能定义一个类、接口、枚举或结构体
- 特殊情况可将多个类定义在同一.cs文件,如代码生成器生成的代码或紧密关联的两个类
- 类名应该与.cs文件名保持一致,以便于通过文件名查找类名
示例:UserInfo类应该在UserInfo.cs文件中
语句
规则:不要在同一行内放置一句以上的代码语句。
原因:多行语句会使得调试器的单步调试变得更为困难。
错误示例:
a = 1; b = 2;
正确示例:
a = 1;
b = 2;
命名规范
基本命名规范
核心原则:为各种类型、函数、变量、特性和数据结构选取有意义的命名,使其能直接反映其作用。自注释的代码就是好代码。
具体规范:
- 名称应该说明"什么"而不是"如何",避免使用公开基础实现的名称,保留简化复杂性的抽象层
- 示例:使用
GetNextStudent(),而不是GetNextArrayElement()
- 示例:使用
- 不要在标识符名中使用不常见的或有歧义的缩短或缩略形式的词
- 示例:使用
GetTemperature而不是GetTemp(Temp可能是Temperature或Temporary的缩写)
- 示例:使用
- 公共类型或大家都知道的缩写可以使用缩略词
- 示例:线程过程(ThreadProc)、窗口过程(WndProc)、对话框过程函数(DialogProc)
- 不要使用下划线、连字号或其他任何非字母数字的字符
- 不要使用计算机领域中未被普遍接受的缩写
- 在适当的时候,使用众所周知的缩写替换冗长的词组名称
- 示例:UI(User Interface)、OLAP(On-line Analytical Processing)
- 在使用缩写时,遵循以下规则:
- 超过两个字符长度的缩写请使用Pascal命名法或驼峰命名法(如HtmlButton或HTMLButton)
- 仅有两个字符的缩写应大写(如System.IO,而不是System.Io)
命名原则总结:
- 选择正确名称时的困难可能表明需要进一步分析或定义项的目的
- 使名称足够长以便有一定的意义,并且足够短以避免冗长
- 唯一名称在编程上仅用于将各项区分开
- 表现力强的名称是为了帮助人们阅读,提供人们可以理解的名称是有意义的
- 确保选择的名称符合适用语言的规则和标准
推荐的命名法
Pascal命名法
- 定义:将标识符的首字母和后面连接的每 个单词的首字母都大写
- 适用范围:可以对三字符或更多字符的标识符使用Pascal命名法
- 示例:
BackColor、UserInfo、GetData
驼峰命名法
- 定义:标识符的首字母小写,而每个后面连接的单词的首字母都大写
- 示例:
backColor、userInfo、getData
不推荐的命名法
匈牙利命名法
- 定义:匈牙利命名法是一名匈牙利程序员发明的命名规范,基本原则是:变量名=属性+类型+对象描述
- 示例:
m_bFlag(m表示成员变量,b表示布尔,合起来为:"某个类的成员变量,布尔型,是一个状态标志") - 要求:严禁使用匈牙利命名法(不要在变量名称内带有其类型指示符)
常用的命名规范
1. 语言规范
- 规则:严禁使用拼音与英文混合的方式命名,更不允许直接使用中文
- 原因:正确的英语拼写和语法可以让阅读者易于理解,避免歧义
- 注意:即使纯拼音命名的方式也要避免采用
- 正例:
name、order、baidu、alibaba等 国际通用的名称可视为英文 - 反例:
zhekou(折扣)、Shuliang(数量)、int 变量=1
2. 类名规范
- 规则:类名使用Pascal命名法
- 例外情况:DTO/UID等模块功能缩写或接口定义(如IInterface)
- 正例:
UserDTO、XmlService、TFlowInfo、TTouchInfo、IUserService - 反例:
userDto、XMLService、tflowInfo、ttouchInfo
3. 方法与变量命名规范
- 规则:方法名、参数名、成员变量、局部变量都统一使用驼峰命名法
- 正例:
name、getUserInfo()、userId
4. 常量命名规范
- 规则:常量的命名使用Pascal命名法
- 建议:单词力求语义表达要完整,不要嫌名字长
- 正例:
MaxStockCount - 反例:
Max_Count
5. 特殊类命名规范
- 抽象类:推荐使用Base结尾
- 异常类:使用Exception结尾
- 测试类:以它要测试的类的名称开始,以Test结尾
- 要求:杜绝不规范的缩写,避免望文不知义
- 反例:将
NotFoundException缩写命名为NotFoundEx
6. 自定义元素命名原则
- 目的:为了达到代码自注释的目的
- 要求:任何自定义编程元素在命名时,尽量使用完整的单词组合来表达其意思
- 反例:
int a的随意命名方式
7. 设计模式命名规范
- 要求:如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式
- 目的:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念
- 正例:
public class OrderFactory、public class LoginProxy
8. 接口和实现类命名规范
- 规则1:对于Service和DAO类,暴露出来的服务一定是接口
- 正例:
CacheService实现自ICacheService接口
- 正例:
- 规则2:如果形容能力的接口名称,取对应的形容词为接口名(一般为-able结尾)
- 正例:
IDisposeable接口
- 正例:
9. 枚举类命名规范
- 规则:枚举类的成员名称使用Pascal命名法
- 说明:枚举为特殊的类,成员均为常量
- 建议:
- 为枚举成员显式指定枚举值,防止将来在中间插入枚举变量导致枚举值混乱
- 最好为每个枚举值打上Description标签
- 示例:
public enum PaymentStatus : sbyte
{
/// <summary>
/// 进⾏中
/// </summary>
[Description("进⾏中")]Processing = 1,
/// <summary>
/// 成功
/// </summary>
[Description("成功")]Succeed = 2
}
各层命名规约
A) Service/DAO 层方法命名规约
- 获取单个对象:方法用
Get做前缀 - 获取多个对象:方法用
List做后缀,如:GetOrdersList - 获取统计值:方法用
Count做后缀 - 添加或更新:方法用
Save或Add - 删除:方法用
Remove或Delete - 修改:方法用
Update
B) 领域模型命名规约
- 实体对象:如
UserInfo,实体名称即为数据库表名 - 数据传输对象:
xxxDTO,xxx 为业务领域相关的名称 - 展示对象:
xxxViewModel,xxx 一般为网页名称
命名空间和程序集命名
- 命名规则:命名空间名称采用Pascal命名法,且首字符大写
- 命名格式:命名空间名称尽量反映其内容所提供的整体功能,一般以"域名.项目名.模块名"的命名方式
- 示例:
Masuit.MyBlogs.Models
- 示例:
- 文件夹结构一致性:命名空间应该与文件夹层级结构保持一致
- 示例:在项目
Masuit.MyBlogs.Core中,Masuit.MyBlogs.Core.Infrastructure.Services命名空间对应项目的Masuit.MyBlogs.Core/Infrastructure/Services文件夹
- 示例:在项目
常见标识符 的命名规范
| 标识符 | 规范 | 命名结构 | 示例 |
|---|---|---|---|
| 类,结构体 | Pascal命名法 | 名词 | public class ComplexNumber {...}public struct ComplextStruct {...} |
| 命名空间 | Pascal命名法 一定不要以相同的名称来命名命名空间和其内部的类型。 | 名词 | namespace Microsoft.Sample.Windows7 |
| 枚举 | Pascal命名法 一定要以复数名词或名词短语来命名标志枚举,以单数名词或名词短语来命名简单枚举。 | 名词 | [Flags]public enum ConsoleModifiers { Alt, Control } |
| 方法 | Pascal命名法 | 动词或动词短语 | public void Print() {...}public void ProcessItem() {...} |
| Public属性 | Pascal命名法 一定要以集合中项目的复数形式命名该集合,或者单数名词后面跟 "List" 或者 "Collection"。 一定要以表肯定的短语来命名布尔属性,(CanSeek,而不是CantSeek)。当以 "Is" "Can" 或 "Has" 作布尔属性的前缀有意义时,也可以这样做。 | 名词或形容词 | public string CustomerNamepublic ItemCollection Itemspublic bool CanRead |
| 非Public属性 | 驼峰命名法 一定要使用'_' 前缀,保持代码一致性。 | 名词或形容词 | private string _name; |
| 事件 | Pascal命名法 一定要用现在式或过去式来表明事件之前或是之后的概念。 一定不要使用 "Before"或者"After" 前缀或后缀来指明事件的先后。 | 动词或动词短语 | // 关闭窗口后引发的关闭事件。public event WindowClosed// 在关闭窗口之前引发的关闭事件。public event WindowClosing |
| 委托 | Pascal命名法 ⼀定要为⽤于事件的委托增加'EventHandler'后缀。 ⼀定要为除了⽤于事件处理程序之外的委托增加'Callback'后缀。 ⼀定不要为委托增加"Delegate"后缀。 | public delegate WindowClosedEventHandler | |
| 接口 | Pascal命名法,并带有'I' 前缀 | 名词 | public interface IDictionary |
| 常量 | Pascal命名法用于Public常量; 驼峰命名法用于Internal常量; 只有1或2个字符的缩写需全部字符大写。 | 名词 | public const string MessageText = "A";private const string messageText = "B";public const double PI = 3.14159...; |
| 参数,变量 | 驼峰命名法 | 名词 | int customerID; |
| 泛型参数 | Pascal命名法,带有'T' 前缀 一定以描述性名称命名泛型参数,除非单字符名称已有足够描述性。 一定以T作为描述性类型参数的前缀。 应该使用 T 作为单字符类型参数的名称。 | 名词 | T,TItem, TPolicy |
| 资源 | Pascal命名法 一定要提供描述性强的标识符。同时,尽可能保持简洁,但是不应该因空间而牺牲可读性。 一定要为命名资源使用字母数字字符和下划线。 | 名词 | ArgumentExceptionInvalidName |
变量的使用
全局变量
- 原则:尽量少用全局变量
- 使用规范:为了正确使用全局变量,一般是将它们作为参数传入函数
- 注意事项:
- 永远不要在函数或类内部直接引用全局变量,因为这会引起副作用:在调用者不知情的情况下改变了全局变量的状态
- 这一原则同样适用于静态变量
- 修改规范:如果需要修改全局变量,应该将其作为一个输出参数,或返回其一份全局变量的拷贝
变量的声明和初始化
-
作用域原则:一定是在最小的、包含该局部变量的作用域块内声明它
-
声明时机:
- 如果语言允许,就仅在使用前声明它们
- 否则就在作用域块的顶端声明
-
正确示例:
void MyMethod()
{
int int1 = 0;
if (condition)
{
int int2 = 0;
...
}
}
-
避免变量重名:避免在不同层次作用域间使用相同的变量名
int count;
...
void MyMethod()
{
if (condition)
{
int count = 0; // 避免:与外部作用域变量重名
...
}
...
} -
初始化默认值:一定要在声明变量时初始化它们的默认值,降低被抛空引用异常的概率
-
声明与初始化合并:在 语言允许的情况下,将局部变量的声明和初始化或赋值置于同一行代码内
- 好处:减少代码的垂直空间,确保变量不会处在未初始化的状态
-
错误示范:
string str;
str="123";
- 正确示范:
string str="123";
- 单行声明规则:一行只建议作一个声明,并按字母顺序排列
int level; // 推荐
int size; // 推荐
int x, y; // 不推荐