团队作业(三):确定分工
一、代码规范
1.1 概述
代码规范可以概括为两个部分:
- 风格规范
一些表面规定,但实际很重要。
- 设计规范
程序设计、模块之间的关系、设计模式等通用原则。
1.2 代码风格规范
风格规范原则:简明、易读、无二义性
1.2.1 缩进
1.2.2 空格
规则一:关键字之后要留空格。像 const、case 等关键字之后至少要留一个空格,否则无法辨析关键字。像 if、for、while 等关键字之后应留一个空格再跟左括号(
,以突出关键字。
规则二:函数名之后不要留空格,应紧跟左括号(
,以与关键字区别。
规则三:(
向后紧跟;)
、,
、;
这三个向前紧跟;紧跟处不留空格。
规则四:,
之后要留空格。如果;
不是一行的结束符号,其后要留空格。
规则五:赋值运算符、关系运算符、算术运算符、逻辑运算符、位运算符等双目运算符的前后应当加空格。
规则六:单目运算符 !、~、++、--、-、*、& 等前后不加空格。
规则七:像数组符号[]
、结构体成员运算符.
、指向结构体成员运算符->
,这类操作符前后不加空格。
规则八:如果大括号为空,则简洁地写成{}即可,大括号中间无须换行和加空格。
规则九:方法参数在定义和传入参数时,多个参数逗号后边都应该加空格。
规则十:注释的双斜线与注释内容之间有且仅有一个空格。
1.2.3 行宽
以前的打字机行宽为80字符,现在偏小,可以限定为100字符。1.2.4 括号
在复杂的条件表达式中,用括号清楚地表示逻辑优先级。1.2.5 断行与空白的{ }行
规则一:{
和 }
分别都要独占一行。互为一对的{
和}
要位于同一列,并且与引用它们的语句左对齐。
规则二:{}
之内的代码要向内缩进一个 Tab,且同一地位的要左对齐,地位不同的继续缩进。
1.2.6 代码行
规则一:一行代码只做一件事情,不要把多条语句放在一行上,更严格地说,不要把多个变量定义在一行上。如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于写注释。
规则二:if、else、for、while、do 等语句自占一行,执行语句不得紧跟其后。此外,非常重要的一点是,不论执行语句有多少行,就算只有一行也要加{}
,并且遵循对齐的原则,这样可以防止书写失误。
1.2.7 命名
规则一:标识符的名字最好采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名。
规则二:不要使程序中出现局部变量和全局变量同名的现象,尽管由于两者的作用域不同而不会 发生语法错误,但会使人误解。
规则三:程序中不要出现仅靠大小写来区分的相似标识符。
规则四:标识符命名是要遵循“min-length && max-information”原则,避免可要可不要的修饰词。
1.2.8 下划线
下划线用来分隔变量名字中的作用域标注和变量的语义。
1.2.9 注释
注释是为了解释程序做什么(What),为什么这样做(Why),通常用于重要的代码行或段落提示。在一般情况下,源程序有效注释量必须在 20% 以上。虽然注释有助于理解代码,但注意不可过多地使用注释。
规则一:注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多会让人眼花缭乱。
规则二:如果代码本来就是清楚的,则不必加注释。
规则三:边写代码边注释,修改代码的同时要修改相应的注释,以保证注释与代码的一致性,不再有用的注释要删除。
规则四:当代码比较长,特别是有多重嵌套的时候,应当在段落的结束处加注释,这样便于阅读。
规则五:每一条宏定义的右边必须要有注释,说明其作用。
1.2.10 成对书写
成对的符号一定要成对书写,如 ()、{}。不要写完左括号然后写内容最后再补右括号,这样很容易漏掉右括号,尤其是写嵌套程序的时候。
1.3 代码设计规范
1.3.1 单一职责原则 Single Responsibility Principle
一个类或者一个接口,最好只负责一项职责。
问题由来:类T负责两个不同的职责P1和P2。由于职责P1需要发生改变而需要修改T类,就有可能导致原来运行正常的职责P2功能发生故障。
解决方法:遵循单一职责原则。分别建立新的类来对应相应的职责;这样就能避免修改类时影响到其他的职责;
1.3.2 里氏替换原则 Liskov Substitution Principle
在使用基类的地方可以任意使用其子类,能保证子类完美替换基类;这一种精神其实是对继承机制约束规范的体现。在父类和子类的具体实现中,严格控制继承层次中的关系特征,以保证用子类替换基类时,程序行为不发生问题,且能正常进行下去。
对于继承来说,父类定义了一系列的规范和契约,虽然不强制所有的子类必须遵从,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破环。
原则包含了一下四层含义:
* 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
* 子类可以增加自己特有的方法;
* 当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更佳宽松;
* 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更加严格;
1.3.3依赖倒置原则 Dependence Inversion Principle
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象,其核心思想是依赖于抽象;
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来完成;这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原则操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I来间接与类B和类C发生联系,则会降低修改类A的几率;
在实际中,我们一般需要做到以下三点:
* 低层模块尽量都要有抽象类或者接口,或者两者都有;
* 变量的声明类型尽量是抽象类或者接口;
* 使用继承时遵循里氏替换原则;
1.3.4接口隔离原则 Interface Segregation Principle
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上,否则将会造成接口污染;类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现它们不需要的方法;
原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少;就是说,我们要为每个类建立专用的接口,而不要试图去建立一个庞大的接口供所有依赖它的类去调用;
规则:
* 一个接口只服务于一个子模块或业务逻辑,服务定制;
* 通过业务逻辑压缩接口中的public方法,让接口看起来更加精悍;
* 已经被污染了的接口,尽量修改,如果变更风险太大,则用适配器模式进行转化;
* 根据具体的业务,深入了解逻辑,用心感知去控制设计思路;
如何实施接口隔离,主要有两种方法:
1. 委托分离,通过增加一个新的接口类型来委托客户的请求,隔离客户和接口的直接依赖,注意这同时也会增加系统的开销;
2. 多重继承分离,通过接口的多重继承来实现客户的需求;
1.3.5迪米特法则
一个对象应该对其他对象保持最少的了解,其核心精神就是:不和陌生人说话,通俗之意就是一个对象对自己需要耦合关联调用的类应该知道的少;这会导致类之间的耦合度降低,每个类都尽量减少对其他类的依赖。
1.3.6合成复用原则
原则是尽量使用合成/聚合的方式,而不是使用继承;
1.3.7开闭原则
一个软件实体如类、模版和函数应该对扩展,对修改关闭;
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码来实现变化;
- 单一职责原则:实现类要职责单一;
- 里氏替换原则:不要破坏继承体系;
- 依赖倒置原则:面向接口编程;
- 接口隔离原则:设计接口的时候要精简单一;
- 迪米特法则:降低耦合;
开闭原则:总纲,对扩展开放,对修改关闭;
1.3.8三个规范代码的要求
首先,规范的代码书写清晰。绝大部分面试都要求应聘者在白纸或者白板上书写。不要因为担心没时间写代码就在纸上写潦草或者简略。通常面试代码量不会超过50行,所以关键是在写代码前形成一个清晰的思路,并能把它用某种语言清楚的写出来。
其次,规范的书写布局。由于我们平时用的是各种编程软件如VS。它里面已经加入合理的缩进和括号对齐等使代码清晰的功能。但在面试时,可能是文本编写,这时候就得格外注意布局问题。当循环、判断较多,逻辑复杂时,缩进的层次可能会较多,就更得注意,给面试官留下一个好印象。
最后,规范的代码命名合理。应尽量避免简单变量命名,如i,j,k等。建议我们写代码时,用完整的英文单词组合命名变量和函数。比如函数需要传入一个二叉树的根结点作为参数,则可命名为:BinaryTreeNode* 。pRoot,
代码的完整性
功能测试
边界测试
负面测试
总结:为提高源程序的可读性、可理解性和可维护性,降低错误的机会,提高源代码可重用性和质量,保持代码的简明清晰,避免过分的编程技巧,对程序编写人员代码实行统一风格,设定此规范。好的编码规范可以尽可能得减少一个软件的维护成本 , 甚至在其整个生命周期中,均由最初的开发人员来维护;好的编码规范可以改善软件的可读性,让开发人员尽快而彻底地理解新的代码,使得程序代码能够以名称反映含义,以形式反映结构;好的编码规范可以最大限度的提高团队开发的合作效率,减少因分工合作造成的代码结构混乱的问题;长期的规范性编码还可以让开发人员养成好的编码习惯,甚至锻炼出更加严谨的思维。
二、编码原则
2.1 函数编写原则
1.当需要某种功能的函数时,首先查看现有的库中是否提供了类似的函数。不要编写函数库中已有的函数,自制的函数在各个质量属性方面一般都不如对应的库函数。库函数是经过严格测试和实践检验的。
2.应当在函数原型中写出形参名称。这样做的目的是使数具有“自说明”和“自编档”能力。不要在函数体内定义与形参同名的局部变量,否则会遮蔽形参。
3.如果函数没有参数,那么使用void而不要空着,这是因为标准C把空的参数列表解释 为可以接受任何类型和个数的参数。
4.参数命名要恰当,输入参数和输出参数的顺序要合理。
例如:
void StringCopy ( char * strl , char * str 2 ) ;
应该改正为:
void StringCopy ( char * strDestination , char * strSource ) ;
5.如果参数是指针,且仅做输入用,则应在类型前加const ,以防止该指针指向的内存单元在函数体内被无意中修改。
6.应避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数糊弄和顺序搞错。
7.尽量不要使用类型和数目不确定的参数列表。这种风格的函数在编译时丧失了严格的静 态类型安全检查。
8.不要省略返回值的类型。如果函数没有返回值,应声明为void类型。
9.函数名字与返回值类型在语义上不可冲突。
例如:
int getchar ( void ) ; char getchar ( void ) ;
10.在函数体的“入口处”,对参数的有效性进行检查。
11.在函数体的“出口处”,对return 语句的正确性和效率进行检查。
12.函数的功能要单一,即一个函数只完成一件事情,不要设计多用途的函数,函数体的规模要小,尽量控制在50行代码之内。
13.不仅要检查输入参数的有效性,还要检查通过其他途径进入函数体内的变量的有效性, 例如全局变量、文件句柄等。
14.用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。
15.尽量避免函数带有“记忆”功能相同的输入应当产生相同的输出带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”这样 的函数既不易理解又不利于测试和维护。函数的static局部变量是函 数的“记忆”存储 器,建议尽量少用static 局部变量,除非必需。
16.如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针指向的内存单元,起到保护的作用。
2.2 语句编写原则
1.函数变量都应该初始化。
2.虽然C语言支持默认类型为int,但都不要使用默认数据类型,一定要明确指出函数、每一个形参的类型和返回值类型。
3.在使用运算符&&的表达式中,要尽量把最有可能为FALSE的子表达式放在&&的左边,在使用运算符||的表达式中,要尽量把最有可能为TRUE的子表达式放在||的左边。因为c语言对逻辑表达式的判断采取“突然死亡法”(猝死法): 如果&&左边的子表达式计算结果为FALSE ,则整个表达式就为FALSE ,后面的子表达式没有必要再计算,同样如果左边的子表达式计算结果为TRUE,则整个表达式就为TRUE,因此后面的子表达式没有必要再计算,这就可以提高程 序的执行效率。
4.不要编写太复杂的复合表达式,应该拆分为多个独立的语句。
5.在if/else结构中,要尽量把为TRUE的概率较高的条件判断置于前面,这样可以提高该段程序的性能。
6.switch没有自动跳出功能,每个case子句的结尾不要忘记加上break,不要忘记最后那个default子句。即使程序真的不需要default处理,也应该保留default:break;这样做并非多此一举,而是为了防止别人误以为你忘了default 处理,以及出于清晰性和对称性的考虑。
7.对于for循环语句,如果计数器从0开始计数,则建议for语句的循环控制变量的取值采用“前闭后开区间”写法,要防止出现“差1”错误。
8.在多重嵌套的循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,这样可以减少CPU跨切循环层的次数,从而优化程序的性能。
9.尽量使用含义直观的符号常量来表示那些将在程序中多次出现的数字或字符串。
10.需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便 于管理,可以把不同模块的常量集中存放在一个公共的头文件中。如果某一常量与其他 常量密切相关,应在定义中包含这种关系,而不应给出 一个孤立的值。
例如:
const float RADIUS = 100 ;
const float DIAMETER RADIUS*2;
2.3 指针、数组、字符串编写规范
1.不管指针变量是全局的还是局部的、静态的还是非静态的,应当在声明它的同时初始化 它,要么赋子NULL。
2.当把“&”用于指针时,就是在提取变量的地址。不能在一个指针前面连续使用多个“&”。