3、代码约定

概述

一般情况下,代码使用以下格式:

  • 使用四个空格缩进。 不要使用选项卡。
  • 一致地对齐代码以提高可读性。
  • 方法函数代码量应尽量少,当函数因为逻辑过长时应考虑提取封装
  • 禁止 为了便利大量的复制代码,仅仅是为了小幅度更改。
  • 适当情况下,可将长语句分解为多行以提高清晰度。
  • 大括号 { } 都单独占用一行,大括号与当前缩进级别对齐,包括 if-else try-catch 方法 命名空间
  • 遵循 Visual Studio 工具默认的 Format 格式化即可,写完代码记得 ctrl k d
正例
public Sample()
{
    public Sample()
    {
        if(true)
		{

		}
		else
		{

		}
    }
}

变量声明

不要在同一行内放置一句以上的代码语句。 这会使得调试器的单步调试变得更为困难。

正例
var a = 1;
var b = 2;

代码分块

当代码过于复杂有多个逻辑步骤时,应该使用 #region...#endregion 标记单个逻辑步骤,方便折叠,便于阅读。

空行

空行是为了将逻辑上相关联的代码分块,以便提高代码的可阅读性。 在以下情况下使用一个空行:

  • 当接口和类定义在同一文件中时,接口和类的定义之间。
  • 当枚举和类定义在同一文件中时,枚举和类的定义之间。
  • 当多个类定义在同一文件中时,类与类的定义之间。
  • 方法与方法之间。
  • 方法中变量声明与语句之间。
  • 方法中不同的逻辑块之间。
  • 方法中的返回语句与其他的语句之间。
  • 属性与方法、属性与字段、方法与字段之间。
  • 语句控制块之后,如if、for、while、switch。
  • 注释与它注释的语句间不空行,但与其他的语句间空一行。

if语句

  • 条件语句判断不应层次过深,比如超过3层嵌套,就应该利用面向对象特性、设计模式等方法进行抽象
  • 对于简单的业务逻辑,应优先使用三目运算,禁止简单判断也使用大量 if...
  • 应使用防御式编程,即优先判断错误条件,穷举完逻辑上可能错误以后直接 return应尽量少的出现 else 逻辑块),再书写正确的业务逻辑,禁止 returnelse 同时出现
正例
 //判断num 是否等于 3
 var num = 2;
 if(num == 3)
 {
	return num;
 }

 throw new Exception("num不等于3");
  • 所有的if 语句都应该有 大括号 {},不管单行,还是多行
正例
 if(true)
 {
	return true;
 }

for、foreach语句

  • 循环优势在于计算,而不是处理数据(涉及IO的操作,一般情况不建议写在循环中,例如文件读取、数据读取等等)
for (int i = 0; i < 100; i++)
{
	//Code
}
foreach (var item in itemList)
{
	//Code
}

switch语句

  • 最后的 default 必须存在
switch (Codition)
	{
		case 1:
			//Code
			break;
		default://不可缺失
			break;
	}

using语句

  • 对于封装的类一般都需要继承 IDispose 接口,有利于垃圾回收器的及时回收,使用 Using 是良好习惯。
  • 通过使用 using 语句简化你的代码。 如果具有 try-finally 语句(该语句中 finally 块的唯一代码是对 Dispose 方法的调用),请使用 using 语句代替。
正例
using (Font arial = new Font("Arial", 10.0f))
{
    byte charset2 = arial.GdiCharSet;
}
  • 不要使用新的 using 语法,即去掉大括号 {} 的写法
正例
using (Font arial = new Font("Arial", 10.0f))
{
    byte charset2 = arial.GdiCharSet;
}

new 运算符

  • 使用对象实例化的简洁形式之一,如以下声明中所示。
var firstExample = new ExampleClass();
  • 使用对象初始值设定项简化对象创建,如以下示例中所示。
正例
 var thirdExample = new ExampleClass
 {
     Name = "Desktop",
     ID = 37414,
     Location = "Redmond",
     Age = 2.3
 };

LINQ

  • 所有的 LINQ 查询参数统一使用 Lambda 表达式,禁止使用 from 子句
  • Lambda 表达式的传入参数为方法首字母,如 customers.Where(w=>w.City == "Seattle").Select(s=>s.Name).OrderBy(o=>o.City); 里面的 w s o
  • Linq 表达式中的方法过多时,应适当按照方法换行,一个方法独占一行
正例
var seattleCustomers = customers.Where(w=>w.City == "Seattle").Select(s=>s.Name);

字符串

  • 使用字符串内插来连接短字符串,如下面的代码所示。
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
  • 若要在循环中追加字符串,尤其是在使用大量文本时,请使用 System.Text.StringBuilder 对象。
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
    manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);

事件处理

  • 使用 lambda 表达式定义稍后无需移除的事件处理程序:
正例
public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(((MouseEventArgs)e).Location.ToString());
        };
}

异常处理

  • 避免空异常(Null Reference Exception): 在访问对象的属性或方法之前,应始终检查对象是否为 null,以避免空引用异常。
  • 不要捕获通用异常:避免捕获通用的 Exception 类型,而是捕获特定的异常类型,以便更好地理解和处理异常情况,对于整个代码调用链路不管在哪里,至少需要有一个异常捕获,来记录明确的错误日志或者处理成友好的报错信息等。
try
{
    // 一些可能引发异常的代码
}
catch (SpecificException ex)
{
    // 处理特定异常
}
  • 使用 finally 块:在 try-catch 块中,如果需要无论是否发生异常都要执行某些代码,可以使用 finally 块。
try
{
    // 一些可能引发异常的代码
}
catch (SpecificException ex)
{
    // 处理特定异常
}
finally
{
    // 这里的代码会在try或catch块完成后执行
}
  • 不要滥用异常: 异常处理是相对昂贵的操作,因此不应该用于正常的控制流。只有在真正发生错误时才应该引发异常。

编程技巧(重要!)

  • 充分发挥面向对象的思想,封装、继承、多态等特性
  • 禁止使用面向过程的编程思想,这会导致函数方法因为逻辑复杂性代码量变得过长,不利于阅读和重构(通常称为猪大肠代码)
  • 禁止在没有思考清楚逻辑之前,进行代码书写
  • 代码书写过程中时刻谨记变量、对象等Null引用问题,该校验校验并抛出异常等信息,禁止使用可能为Null的变量进行逻辑判断和处理。
  • 对于不再使用的方法和接口不应删除,应标记过时并写明替代方法,举例
  • 禁止程序中使用已过时的方法,特别是Framework中的API方法。
  • 禁止在类的构造方法中写业务逻辑,如有对象初始化什么的,应在此类中添加类名+Init的方法,并且进行异常捕获记录日志,再在构造方法中调用。
  • 禁止在使用共享变量/缓存集合进行非线程安全操作,即要加锁才可以进行更新操作,对于只读的可以不考虑。特别是做后端服务的一定要考虑多线程问题。
  • 针对多线程中加锁问题,如果可以不加就不加,如果需要加锁应该控制最小粒度,即如果加锁变量可以完成的,绝不加锁方法,加锁意味着性能损耗问题。

推荐

  • 应尽量以安全的代码实现需求,即应该是书写在编译时就可以发现问题的代码并修正(硬编码),而不是在运行时再去发现(软编码,反射等)
  • 在需求分析时尽量考虑全部可能情况,但不要为了有可能存在的需求进行过度的设计和编码实现。往往过度的设计会增加软件复杂度和实现周期,并且有可最后一点作用都没有。
  • 工具类方法即可能会复用的,业务无关性的方法封装,推荐使用扩展方法定义并实现。
  • 通用业务即可能会复用的业务方法,应提取抽象成类和方法,进行拆分归类,目的就是解耦。软件工程中解耦做的越好,不管是以后扩展还是重构都会省时省力,方便调整。

以下是摘录网络上的推荐:

类成员与方法访问控制从严:

  • 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private 。
  • 工具类不允许有 public 或 default 构造方法。
  • 类非 static 成员变量并且与子类共享,必须是 protected 。
  • 类非 static 成员变量并且仅在本类使用,必须是 private 。
  • 类 static 成员变量如果仅在本类使用,必须是 private 。
  • 若是 static 成员变量,必须考虑是否为 const。
  • 类成员方法只供类内部调用,必须是 private 。
  • 类成员方法只对继承类公开,那么限制为 protected 。

说明: 任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 Service 方法,或者一个 public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的

日志记录

  • 禁止混乱无意义的日志输出,日志记录,应在入口,出口,有标志性的地方,如果是调试程序的临时日志记录,在调整完毕以后必须删除,应充分使用日志级别进行隔离。大量日志记录非常会损耗服务的整体性能,拖慢服务并且带来日志占用磁盘空间,日志清理的问题。
  • 不允许个性化的日志记录,应统一格式,对于第三方接口调用记录建议格式为:
    • 本程序名->调用程序名:接口名
    • 本程序名->调用程序名:URL地址和入参
    • 本程序名->调用程序名:出参
    • 本程序名->调用程序名:异常(应记录堆栈信息,会标明具体发生异常时的详细信息)
  • 应理解 AOP 面向切面编程的设计模式,对于封装代码、解耦模块非常有好处。
Last Updated:
Contributors: heyuan