首页 > 代码库 > .NET开发规范

.NET开发规范

一、命名规则

1.1 命名法说明

(1)    Pascal命名法:将标识符的首字母和后面连接的每个单词的首字母都大写。

例如:BackColor

(2)    Camel命名法:标识符的首字母小写,而每个后面连接的单词的首字母都大写。

例如:backColor

(3)    变量、类名、成员变量、方法等命名,禁止采用:英文和拼音组合、拼音命名、非规范的英文简写命名、汉字命名(特殊情况除外:如部分枚举采用汉字);

说明:名字定义不怕长,力求准确,并且可读性友好;

反例:decimal jiage = 0M;//价格

1.2 常见命名规范

(1)    C#方法参数、内部变量命名:应采用Camel命名法,即,首字母小写;

正例:int userId = 0;

(2)    C#类内成员属性:应采用Pascal命名法,即,首字母大写;

正例:public int UserId { get; set; }

(3)    C#枚举(enum)命名:应采用Pascal命名法,即,首字母大写;

说明:应该为英文单词的名词单数形式,不应该增加 Enum前缀或者后缀;根据实际业务定义即可;

正例:UserType

反例:UserTypeEnum|UserTypes

(4)    C#接口(interface)命名:应采用Pascal命名法,即,首字母大写;

说明:应该统一以I字母开头,实现类则去掉对应的I字母;

(5)    C#实体对象(Model)命名:采用Pascal命名法,即,首字母大写;

说明:

数据库表实体应命名为ModelInfo,Model为数据库表名;

数据模型(DataModel)应该定义为DModelInfo,D为前缀,Info为后缀,Model为相关业务名称;

视图模型(ViewModel)应该定义为VModelInfo,V为前缀,Info为后缀,Model为页面或相关业务名称;

正例:TUserInfo|DUserLogInfo|VUserListInfo

(6)    C#数据层(DAL)类命名:应采用Pascal命名法,即,首字母大写;

说明:应统一采用DAL后缀,ModelDAL,Model为实体名称(去掉Info后缀);

正例:TUserDAL

(7)    C#持久层(Persistence,iBatisNet)命名:map文件名一般保持和文件内的namespace一致,并且和数据库实体表名一致;方法名一般和DAL层的调用方法保持一致,方便阅读;

(8)    C#业务层(BLL)类命名:应采用Pascal命名法,即,首字母大写;

说明:应统一采用BLL后缀,ModelBLL,Model为实体名称(去掉Info后缀);

正例:TUserBLL|DUserLogBLL

(9)  对象类型成员的命名规则:满足类内成员属性的规则;

说明:应当以完整的类名去掉前缀和后缀之后的实体名作为属性名;

正例:public staticTUserInfo TUser { get; set; }

反例:public staticTUserInfo TUserInfo{ get; set; }

(10)  常量命名规范:全部字母大写,每个单词用下划线分割;

正例:///<summary>渠道ID(Value:175)</summary>

private const int CHANNEL_ID= 175;

(11)  js变量和方法名规则:均应该采用Camel命名法,首字母小写;

(12)  数据库对象和字段的命名尽量采用Pascal命名法,即,首字母大写;

说明:常见数据库对象的命名除符合命名规范之外,应尽量使用前缀来区分对象的类型。

正例:表:TUser|视图:VUserLevel|自定义函数:GetMaxId()|存储过程:ProcSetUserState

(13)  WCF引用的服务命名规范,建议引用的命名空间添加WCFService前缀;

1.3 代码文件命名

(1)    文件名遵从Pascal命名法,无特殊情况,扩展名小写;

(2)    使用统一而又通用的文件扩展名:C# 类  .cs ;

正例:TUserInfo.cs

 

二、代码格式化外观

2.1 代码行列展示

为了方便阅读,单行代码应尽量控制在一屏之内,否则应按照实际情况调整换行显示:

(1)    在代码或者表达式的逗号之后换行;

(2)    在代码或者表达式的操作符之前换行;

(3)    以上情况,可酌情处理,尽量保证方便阅读即可;

2.2 代码文件排版

(1)    Visual Studio 可使用默认快捷键Ctrl +K,Ctrl +D  格式cs文件或cshtml等文件代码;提交代码之前应该格式化;

(2)    过于长的代码块,可按照业务逻辑相关,使用外侧代码#region...#endregion将代码分块折叠;

正例:

#region 测试方法

#endregion End 测试方法

 

三、程序代码注释规范

3.1 代码行内注释

(1)    因任何原因修改单行代码时,应加以注释,说明修改原因、时间和作者,如有相关需求或BUG应该同时标记需求或BUG编号;

(2)    如有必要,应该在文件最顶部增加如下格式的注释说明;并在每次重要的变更之后增加详细的变更记录说明;

3.2 文档型注释

(1)    所有供外部调用的类、方法、成员属性、枚举等都应该添加此类型的注释(summary),调用者可直接通过工具提示查阅相关说明,无需转入定义阅读代码和注释;

(2)    文档注释一般对所注释的对象的用途,使用方式,参数列表和返回值等做出详细说明,方便调用者查阅;

(3)    封装的通用js方法也可使用文档注释规范,语法应以js的注释规范为准;

说明:在Visual Studio内,js的文档注释同样具有工具提示的效果;在VS的编辑器内为js方法添加summary注释的快捷操作步骤:方法内第一行点击右键,选择“插入代码段”—选择“XMLComments”之后即可添加param或summary节点;

 

四、常用.NET编程规范

4.1 类和类内成员修饰符的使用

(1)    类和类内成员注意修饰符,不要滥用public关键字,依据实际情况定义;

(2)    常驻内存的常量,建议使用const修饰;

正例:

///<summary>渠道ID(Value:175)</summary>

private const int CHANNEL_ID = 175;

(3)    单例模式的实现,除使用static修饰之外,应考虑是否需要volatile修饰;

(4)    partial(部分类)的使用:当一个类内代码块过多,或业务模块过于复杂时,可考虑将类拆分为多个文件保存;文件的命名应考虑指明当前类下的业务模块名称。

正例:当TUserBLL使用了partial修饰时,拆分为登录相关,后台管理相关2个部分类文件,应分别命名为:TUserBLL.Login.cs|TUserBLL.Management.cs

(5)    常见修饰符的说明:

public  公有的,类和成员的访问修饰符,无访问限制;

internal 内部的,类和成员的访问修饰符,限制为程序集内访问;

protected 受保护的,成员访问修饰符,限制为类内或派生类访问;

protected internal 受保护或内部的,成员访问修饰符,限制为:程序集内可访问、其他程序集内的派生类可访问;

private 私有的,成员访问修饰符,限制为类内访问;

说明:任何类、方法、参数、变量,严控访问范围,过于宽泛的访问范围,不利于模块解耦;

(6)    其他C#关键字的使用规范和说明,参照微软官方文档:

C# 关键字| Microsoft Docs

https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/index

 

4.2 循环代码块的使用

(1)    循环体一定要掌握循环次数,尤其是用户级应用,避免超时;

(2)    循环体内不允许直接声明任何类型的变量(包括值类型、对象和集合类型);

说明:应该将变量定义到循环体之外,在循环内赋值;循环体内调用的其他方法也要考虑期内部变量声明的情况;

(3)    js的循环体同样应该考虑性能问题;

4.3 实体模型的使用

(1)    数据库的实体模型中,增加的非数据库字段属性,应在文档型注释中标记“[虚拟字段]”,方便调用者查阅;

(2)    尽量避免匿名类的使用(包括但不限于Action的JsonResult);

说明:可使用数据表的Model、ViewModel、DataModel等通用对象作为被序列化的返回值对象;

(3)    实体中的枚举类型字段,不要定义为int获其他值类型,建议直接定义枚举类型;

正例:public static UserType Type { get; set; }

(4)    配置参数的值为Json或表中的字段为Json结构时,也应该考虑为其定义单独的数据模型,方便各应用的解析;

4.4 变量的合理赋值

(1)    字符串拼接尽量避免使用“+”或“+=”,少量拼接或格式化采用string.Format()方法,循环或大量拼接必须使用StringBuilder 对象;

说明:StringBuilder中拼接格式化的信息,可直接采用AppendFormat方法;

(2)    常数数字的赋值,非int值的情况,应在数值末尾加以类型字母标识,减少隐式的类型转换;

正例:

decimal decValue = http://www.mamicode.com/108.21M;//M表示decimal类型数值声明

float fltValue = http://www.mamicode.com/108.21F;//F表示float类型数值声明

long lgValue = http://www.mamicode.com/108L;//L表示long类型数值声明

(3)    Hashtable的使用,在根据不同的业务条件加入不同的Key时,建议使用Key索引的形式赋值,不要采用Add()方法;

说明:

当因为业务判断导致添加了重复的Key,Add方法会抛出异常;

直接使用Key索引赋值,将会覆盖重复的Key和Value;

正例:

int userId=1;

Hashtable param =new Hashtable();

param["UserId"] =userId;

 

4.5 集合(IList和List)的使用

(1)    尽量减少IList对象的ToList()方法调用(尤其是集合元素较多时);

(2)    从数据源(缓存或数据库)取出的数据集合,不应该一次性取出过多数据;用户级别应用建议一次性获取不要超过100个元素;windows服务可适量增加但不建议超过1000个;业务需要时,应考虑分页处理;

4.6 MVC和Razor视图语法的规范

(1)    所有的Controller和Action都应该考虑过滤器和用户登录状态、权限的限定;

(2)    业务代码尽量后置,视图的主要目的是展示和回传数据,尽量不要出现业务代码模块;控制器主要负责和业务层交互数据,复杂业务处理尽量后置到业务层(BLL);

(3)    视图数据的展示,尽量将ViewBag等数据转换为强类型的实体,方便开发和阅读;dynamic等动态类型,无法识别和显示实体下的具体属性和文档型注释的工具提示;

(4)    通用的视图应当通过部分页或其他方式封装为通用组件;

正例:用户提交订单、用户地址管理等多处会用到省市县行政区划联动选择的代码块,应当封装一个行政区划的下拉框联动组件;

(5)    视图中某些js代码,或html代码动态加载的控制,应当尽量采用Razor的写法实现,放弃使用js的控制方式;减少页面的呈现代码量;

(6)    不建议在Action内拼接HTML字符串输出视图上的html内容,可维护和扩展性较弱;

 

五、持久层(基于iBatisNet)和数据库规范

(1)   所有数据库访问必须在数据层(DAL)进行,禁止在其他层访问数据库;

(2)    所有脚本只能存在于存储过程和持久层的map文件中,禁止出现在DAL或其他层;

(3)    为了方便阅读,请尽量将脚本规范化书写:

SQL SERVER关键字全部大写;

表名、字段名、视图、自定义函数等数据库对象尽量书写时和定义的大小写一致;

(4)    所有查询禁止出现“SELECT * FROM ”的写法,即便是需要全部字段也要逐个列出;如果数据库字段出现和Model不一致,“SELECT * FROM ”会导致ORM映射抛出异常;

说明:尽量缩减查询的字段数量,尤其是大文本数据的字段,非必要时不要查询返回;

(5)    查询尽量使用无锁查询 WITH ( NOLOCK );

说明:实时性极强(如账户余额、或更新后立即查询等操作)应该考虑放弃使用无锁查询;

(6)    map文件的SQL脚本可根据实际参数实现动态组装,并有缓存效果,但不建议业务关联性不太强的脚本共用同一个map方法;

说明:如果书写的查询脚本存在复杂查询,JOIN级联查询等,此类脚本只能适用于确实需要JOIN或复杂查询的方法调用,若调用者不需要复杂查询,应当创建新的查询脚本方法;不可共用;

正例:已存在的脚本使用LEFT JOIN连接了多个表,当调用者不需要关联这些表时,应考虑选择其他map方法或者新创建方法,不能共用;

(7)   COUNT计数时,推荐使用COUNT(1)或者COUNT(Id),不建议直接使用COUNT(*);

(8)    通过SQL脚本判断满足指定条件的记录是否存在时,推荐使用语法EXISTS;

说明:使用关键字EXISTS和IF组合,通过判断返回值是1或0来判断,要查询的数据是否存在;

IN的写法也应该使用EXISTS替代;

正例:

IF (EXISTS(SELECT 1 FROM dbo.TUser WITH ( NOLOCK )  WHERE   UserId = 1 ))

SELECT  1

ELSE SELECT  0

反例:禁止直接使用以下写法:

SELECT  * FROM    dbo.TUser WHERE   UserId = 1

SELECT TOP 1  * FROM   dbo.TUser WHERE   UserId =1

SELECT  COUNT(1) FROM    dbo.TUser WHERE   UserId = 1

(9)    使用iBatisNet的Update方法,不要轻易更新整个Model的所有字段,浪费资源,并且可能会导致bug;尽量只更新已修改的字段;

说明:尽量只传入需要修改的字段,必要时可定义为Hashtable等字典类型参数;

(10)  SQL SERVER 2012起,建议使用新的分页方法(OFFSET…FETCH);

说明:从执行计划来看,FETCH语法,比ROW_NUMBER()分页性能好很多,语法如下,

OFFSET { integer_constant | offset_row_count_expression } { ROW | ROWS }

FETCH { FIRST |NEXT} { integer_constant |fetch_row_count_expression } { ROW | ROWS } ONLY

(11)  使用DELETE删除数据,务必谨慎考虑;

说明:数据无价,谨慎删除!必要情况下应考虑使用逻辑删除(通过字段标识为删除状态);

(12)  事务的使用:如需使用事务,请务必确保事务有结束点;事务中只负责数据的更新操作,不应该将不必要的查询放入事务中进行;

说明:必须保证所有路径都有Commit或者RollBack,事务的使用,必须使用try…catch模块捕获异常,并在异常时RollBack;

(13)  所有的数据库脚本禁止跨库访问;

说明:所有的T-SQL语句,不要添加数据库名称前缀的形式直接跨库访问;如确切需要跨库,应使用链接服务器(LINKEDSERVER)方式,或考虑其他解决方案;

(14)  复杂的数据处理业务,应考虑创建存储过程;

(15)  生产环境一般不建议手工修改数据,如必要,请务必谨慎检查脚本的正确性之后将脚本和相关需求发送给DBA执行

 

六、分布式缓存使用规范

(1)    推荐使用Redis或Memcached缓存;

(2)    缓存只允许在BLL(业务层)直接引用,不可在其他层出现直接调用;

(3)    所有缓存的使用,要确保缓存数据的刷新及时性,否则会引起数据不一致的bug;

(4)    缓存的KEY命名,必须按照既定的规则定义;建议使用树形结构定义KEY节点;

(5)    使用缓存读取数据之前,务必确保配置了正确的缓存KEY,否则会导致读取数据失败;

(6)    不可对已配置的缓存中的数据私自更换数据模型,会导致其他应用无法解析到正确的数据,出现未处理的异常;

(7)    更新数据时,涉及展示数据、获取数据判断时,不可直接取缓存数据作为修改前的展示数据,防止将旧数据覆盖写入到数据库中;

(8)   合理使用缓存:相对静态的数据、访问频率较高的数据应酌情使用缓存来读取,降低数据库访问压力;并应该根据实际的缓存命中率情况考虑设定缓存的时长;

(9)    Redis消息队列的使用:插入到消息队列的数据应保证正确性、并且简洁;队列由服务读取后操作,有一定的时间延迟,并且操作结果不可反馈到应用,因此要考虑实际应用场景;

 

七、WCF和WebAPI使用规范

7.1 封装WCF和WebAPI的原则

(1)    通用业务方法、模块应该考虑封装为WCF或WebAPI;

7.2 WCF的配置和引用

(1)    生产环境的WCF,均应该采用TCP协议部署和引用(必须要外网引用的WCF除外);

(2)    测试环境的WCF部署域名,建议使用和生产环境相同的设定(可通过hosts指向来访问),能保证调试和生产环境的一致性;

(3)    禁止通过WCF传入和返回超大量的数据;

(4)    WCF的引用,应添加到BLL(业务层),不可引用至其他层;

说明:WCF虽然引用至BLL层,但配置节点应该手动添加到各个应用的Web.config或App.config中;

7.3 WCF和WebAPI的开发规范

(1)    传入的参数和返回值,尽量采用Json格式,并使用数据模型来定义,避免直接使用值类型或对象类型;

说明:值类型和对象类型的参数扩展性不好,如遇到增加返回值字段或者参数字段,不利于扩展;

(2)    如果返回的数据为集合,并且量过大,应考虑使用分页方式返回数据,每页数据建议不超过100条;

(3)    开放性的WCF接口或WebAPI务必考虑安全性控制;

说明:可通过私钥加时间戳的签名验证方式控制安全性;签名可采用MD5加密算法等实现;特殊接口如无法增加签名验证可考虑不增加该限制;有开发能力应该实现完善的OAuth2.0授权机制;

(4)    调用WCF的方法,使用完毕后务必显式的直接调用WCF的关闭方法;

说明:开启WCF的连接会消耗大量服务器资源,务必及时关闭;也不可循环多次开启连接;

(5)    WCF的Service层、WebAPI的Action层尽量减少业务代码的耦合,将业务逻辑后置到BLL业务层;

(6)    通用的WCF或者WebAPI接口,建议形成开发手册(开发规范文档);

 

八、Visual Studio解决方案使用

(1)    所有解决方案下不得私自引入前端组件、服务端组件,不得私自变更已有的组件或框架;

(2)    通用工具层不得私自引入第三方组件,或变更、删除、新增通用方法;

(3)    所有的资源文件(CSS文件,JS文件,图片文件)不得私自修改,必须以UI提供的文件为准;

(4)    解决方案的调试,如果设置了本地IIS调试或其他特殊VS设置,请不要将配置提交到svn(或其他源代码管理工具),建议设置到user文件,并将user文件加入到svn的忽略列表;因为其他开发者肯定没有一致的IIS站点,会导致调试异常;

(5)    不得擅自升级任何解决方案下任何项目的.NETFramework版本;

(6)    不得擅自升级引入的所有第三方组件和dll文件的版本;

(7)    所有项目下引入的第三方dll文件,应该放到对应的解决方案的指定文件夹下,并分类放置、提交到svn;防止其他开发者无法获取到引入的文件,导致编译失败;

(8)    复杂项目的开发,建议先画对应的业务流程图;流程路径更严谨,开发思路更清晰,维护更方便;

(9)    项目开发过程中建议记录下发布时需要上线的文件列表,防止发布时遗漏;

 

技术分享

 

.NET开发规范