ASP.NET网站开发规范
目 录
1 .概述.... 2
1.1 背景... 2
1.2 目的... 2
2 .软件开发规范.... 3
2.1 开发环境... 3
2.2 代码组织... 3
2.2.1 文件创建、修改记录及版权信息... 3
2.2.2 代码内容的组织... 4
a. {,},(,),if,else,do,while,for,case等要对应整齐。..... 4
b. 少用空格,缩进全部用Tab键。..... 4
c. 变量的定义要集中。..... 4
d. 函数间要有空行分开,一个程序中的空行数目最好占8%-16%。..... 4
e. 多态函数和功能相近的函数集中放在一起。..... 4
2.2.3 命名规范... 4
2.2.4 注释编写... 5
2.2.3 常用编码习惯和技巧... 6
3 .软件测试规范.... 10
3.1 单元测试... 10
1 .概述
1.1 背景
光伏项目代码编写工作已全面展开,由于项目开发团队由三个部门抽调人员构成,故有必要在团队内部执行统一的开发规范。
1.2 目的
从软件开发环境、命名规范、注释编写、代码组织、常用编码习惯和技巧等方面对项目成果进行规范。
2 .软件开发规范
2.1 开发环境
开发语言为C#,选用ASP.NET架构。IDE选用VS2010,代码管理工具选用SVN并安装AnkhSVN插件,单元测试工具选用NUnit并安装TestDriven插件。
注意:如果你有些本地代码不想上传到svn的话,可以通过AnkhSVN插件排除这些文件。
2.2 代码组织
2.2.1 文件创建、修改记录及版权信息
形如
//+-------------------------------------------------------------------+
//+ FileName: JSONHelper
//+ Copyright (C): 合肥恒卓科技有限责任公司
//+ CLR版本: 4.0.30319.296
//+ File Created: 2013-1-18 10:24:37
//+-------------------------------------------------------------------+
//+ Purpose:
//+-------------------------------------------------------------------+
//+ History: 2013-1-18 10:24:37 by JingXiao
//+-------------------------------------------------------------------+
//+ Comment:
//+-------------------------------------------------------------------+
//+ Creator JingXiao
//+-------------------------------------------------------------------+
添加方法:
找到VS安装路径,形如D:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Code\2052,打开Class.zip修改其中的Class.cs,添加
//+-------------------------------------------------------------------+
//+ FileName: $itemname$
//+ Copyright (C): 合肥恒卓科技有限责任公司
//+ CLR版本: $clrversion$
//+ File Created: $time$
//+-------------------------------------------------------------------+
//+ Purpose:
//+-------------------------------------------------------------------+
//+ History: $time$ by JingXiao
//+-------------------------------------------------------------------+
//+ Comment:
//+-------------------------------------------------------------------+
//+ Creator JingXiao
//+-------------------------------------------------------------------+
同样修改CodeFile.zip中的CodeFile.cs文件以及Interface.zip中的Interface.cs文件。
2.2.2 代码内容的组织
a. {,},(,),if,else,do,while,for,case等要对应整齐。
b. 少用空格,缩进全部用Tab键。
c. 变量的定义要集中。
d. 函数间要有空行分开,一个程序中的空行数目最好占8%-16%。
e. 多态函数和功能相近的函数集中放在一起。
2.2.3 命名规范
2.2.3.1 变量命名规范
采用小驼峰式命名法,形如
private static JsonSerializerSettings setting = new JsonSerializerSettings();
2.2.3.1 方法命名规范
采用大驼峰式命名法,形如
public static string JsonSerializer<T>(T t)
2.2.4 注释编写
2.2.3.1 变量注释
用”///”开始,分3行,中间行填写注释内容。形如
/// <summary>
/// 错误列表
/// </summary>
private static List<Exception> errors = new List<Exception>();
2.2.3.1 函数注释
用”///”开始,分3行,中间行填写注释内容。形如
/// <summary>
/// JSON序列化,注意:日期只支持ISO格式序列化
/// </summary>
public static string JsonSerializer<T>(T t)
2.2.3.1 函数内部注释
用”//”开始,注释内容直接填写到同一行中。形如
//异常处理
setting.Error = errorhandler;
2.2.3 常用编码习惯和技巧
(1) 进行“等于”判断时将常量写在“==”号前面,形如If(null==strSample)这样能够有效防止将“==”号写成“=”号造成if判断永远为真的错误。
(2) 将单条语句用“{}”号括起来形成完整的代码块,这样能有效避免维护人员在此条语句下添加代码时没有将新代码用“{}”号括起来造成的逻辑错误。
(3) 如果可能的话,尽量在方法开始处进行参数检查,如果参数错误,抛出一个参数错误异常往往比直接return更好。
(4) 如果在函数里使用了try catch,最好不要把异常吃掉,尽量往上层抛出,在最外层方法里统一处理。
(5) 多使用const定义常量,或者在配置文件中设置常量,比硬编码好多了。
(6) 虽然C#主要操作托管资源,但是如果使用了非托管资源,比如数据库连接等,最好在构造函数中初始化,在析构函数中销毁。
(7) 尽量抽出些时间重构代码,将公用的变量抽象到父类中,将公有的方法抽象到接口中。
(8) 尽量使用泛型IList< 类型名/接口名>而不是ArrayList,因为泛型由编译器限定了容器内部装载对象的类型。
(9) 注意变量/属性/方法的访问级别,public不是所有问题的解决方案。有时候为了阻止手动初始化,我们甚至可以将类的默认构造函数重写为private的。
(10) 注意关键字及属性的应用,比如abstract、sealed,和serializable等。
(11) 用好static关键字,多个实例共享的资源可设置为static以节约内存同时提高效率;但是必须注意,声明为static的资源必须考虑多线程同步的问题。下面是一个线程安全的单例模式示例:
//单例
private static readonly DataManager instance = new DataManager();
//对外公开的属性
public static DataManager Instance
{
get
{
return instance;
}
}
(12) 尽量使用编译器提供的名字而不是硬编码,比如说使用string.Empty而不是””;还有使用System.Environmernt.NewLine而不是”\r\n”。
(13) 在使用类的属性/字段前记得判断类的实例是否为null,巧用”&&”和”||”能将多次判断压缩在一个语句之内,防止出现锯齿形的代码。
(14) 反射是一种很好的技术,用了你就会知道。
(15) 一个类最好不要超过5000行,一个方法不要超过1000行,一大堆if/else是坏味道,很多重复的相似的代码也是坏味道,应考虑代码重构。就ASP.NET来说,一个页面就是一个类,就该做一件事,复杂的内容可以用页面嵌套来拆分成多个类解决。
(16) 记得使用$(document).ready,而不是直接把要执行的js脚本函数写在页末,因为各页面元素加载速度有差异,写在页末的脚本执行时页面并不一定已经加载完毕。
(17) 巧用编译开关,比如可以在#if DEBUG #endif内部添加调试期特有的代码,我在Login页面上就在DEBUG模式下给登录名输入框设置了默认值以方便平时调试时登录所用,在发布网站时此特性会自动消失。
(18) 高级程序语言写的代码是给人看而不是给机器看的,不管从变量取名或者排版格式来讲,简洁易懂才是王道。
最后是一些设计模式的原则:
- 单一职责原则(Single Responsibility Principle),简称SRP:
应该有且仅有一个原因引起类的变更。
它的意义主要体现在接口上,每个接口应对应某一项或某一类功能,互不干扰,这样,当新增需求时,只需要修改相对应的接口即可。
- 里氏置换原则(Liskov Substitution Principle),简称LSP:
所有引用基类的地方必须能够透明的使用其子类对象。
也就是说:只要父类出现的地方子类就能够出现,而且替换为子类不会产生任何错误或异常。但是反过来,子类出现的地方,替换为父类就可能出现问题了。这个原则是为良好的继承定义一个规范,简单的讲,有4层含义:
(1) 子类必须完全实现父类的方法
(2) 子类可以有自己的特性
(3) 覆盖或者实现父类的方法时输入参数可以被放大
(4) 覆写或者实现父类的方法时输出结果可以被缩小
- 依赖倒置原则(Dependence Inversion Principle),简称DIP:
(1) 高层模块不应该依赖低层模块,两者都应该依赖于抽象(抽象类或接口)
(2) 抽象(抽象类或接口)不应该依赖于细节(具体实现类)
(3) 细节(具体实现类)应该依赖抽象
- 接口隔离原则:
要求的是在一个模块应该只依赖它需要的接口,以保证接口的小纯洁。而且需要保证接口应该尽量小,即设计接口的时候应该让接口尽量细化。
接口隔离原则与单一职责原则有些相似,不过不同在于:单一职责原则要求的是类和接口职责单一,注重的是职责,是业务逻辑上的划分。而接口隔离原则要求的是接口的方法尽量少,尽量有用(针对一个模块)
(1) 接口尽量小
接口尽量小主要是为了保证一个接口只服务一个子模块或者业务逻辑
(2) 接口高内聚
接口高内聚是对内高度依赖,对外尽可能隔离。即一个接口内部的声明的方法相互之间都与某一个子模块相关,且是这个子模块必须的。
(3) 接口设计是有限度的
但是如果完全遵循接口隔离原则的话,会出现一个问题。即接口的设计力度会越来越小,这样就造成了接口数量剧增,系统复杂度一下子增加了,而这不是真实项目所需要的,所以在使用这个原则的时候还要在特定的项目,根据经验或者尝试判断,不过没有一个固定的标准。
- 迪米特法则(Law of Demeter)又叫最少知道原则(Least Knowledge Principle):
(1) 一个软件实体应当尽可能少地与其他实体发生相互作用。
(2) 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
也就是要求各模块之间应该是松耦合的关系。
- 合成复用原则(Composite Reuse Principle):
尽量使用合成/聚合,而不是使用继承。
3 .软件测试规范
3.1 单元测试
单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码。举个例说明怎样进行单元测试:
#region 单元测试类
#if DEBUG
[TestFixture]
public class TestConnectionModbus
{
private ConnectionModbus DestObj;
/// <summary>
/// 初始化
/// </summary>
[TestFixtureSetUp]
public void TestInit()
{
DestObj = new ConnectionModbus(255);
}
[SetUp]
public void InitEveryMethod()
{
}
[TearDown]
public void DeInitEvertMethod()
{
}
/// <summary>
/// 反初始化,先释放所有connection清空hashmap,再释放实体
/// </summary>
[TestFixtureTearDown]
public void TestDeInit()
{
DestObj.Destroy();
}
/// <summary>
/// 测试“清空”
/// </summary>
[Test]
[NUnit.Framework.Category("Normal")]
public void TestPackageParse()
{
byte[] bufinput_new = new byte[10];//{ 0x68, 0x0F, 0x00, 0x00, 0x00, 0x02,0x01,0x01,0x14,0x00,0x01,0x00,0x01,0x40,0x00,0x01,0x00};
//设置不变项
bufinput_new[0] = 0x10; //协议标识
bufinput_new[1] = 0x00; //参考值高位,在本例中直接写死为0x00
bufinput_new[2] = 0x00; //参考值低位,在本例中直接写死为0x00
bufinput_new[3] = 0x00; //指令数高位,在本例中固定为每次发送2个指令,所以高位为0x00
bufinput_new[4] = 0x02; //指令数低位,在本例中固定为每次发送2个指令,所以低位为0x02
bufinput_new[5] = 0x04; //字节数,为指令数X2,在本例中即为0x04
bufinput_new[6] = 0x07; //寄存器值1高位
bufinput_new[7] = 0x01; //寄存器值1低位
bufinput_new[8] = 0x14; //寄存器值2高位
bufinput_new[9] = 0x00; //寄存器值2低位
byte[] bufoutput;
byte[] bufoutputComp = new byte[5];
bufoutputComp[0] = 0x10; //协议标识
bufoutputComp[1] = 0x00; //参考值高位,在本例中直接写死为0x00
bufoutputComp[2] = 0x00; //参考值低位,在本例中直接写死为0x00
bufoutputComp[3] = 0x00; //指令数高位,在本例中固定为每次发送2个指令,所以高位为0x00
bufoutputComp[4] = 0x02; //指令数低位,在本例中固定为每次发送2个指令,所以低位为0x02
DestObj.PackageParse(bufinput_new, out bufoutput);
Console.WriteLine(string.Format("bufoutput = [{0},{1},{2},{3},{4}]", bufoutput[0], bufoutput[1], bufoutput[2], bufoutput[3], bufoutput[4]));
Console.WriteLine(string.Format("bufoutputComp = [{0},{1},{2},{3},{4}]", bufoutputComp[0], bufoutputComp[1], bufoutputComp[2], bufoutputComp[3], bufoutputComp[4]));
Assert.AreEqual(bufoutput, bufoutputComp);
Assert.AreEqual(Common.Helper.MemoryCompare(bufoutput, bufoutputComp),true);
}
}
#endif
#endregion
可以有全局初始化/反初始化函数,也可以每个实例都进行单独的初始化操作,用Assert方法判断我们编写的类及其方法是否得到了所想要的结果,还可以测试在异常输入的情况下类及方法的能否按照设计要求抛出该抛出的异常。
原则上说,比较基础的类和方法的对外接口,都应该进行单元测试。