Software Construction学习——可理解性(Understandability)
代码的可理解性是针对程序员而言,防止为了使程序员能更好地理解自己所写的代码,并且让他人也能理解代码的功能。一个软件的外部性的好坏取决于其内部性,而内部代码能被更好地理解,也能使其更好优化其它内部性质从而实现一个优质代码。
一. 理解性的指标
e.g.
· 是否遵循特定的命名规范?
· 是否有足够的注释/说明
· 是只能同时执行一个事情还是多个事情可以被同时执行
· 方法是否太长或太短,是否容易理解
难以理解的代码意味着更难以维护,存在着更多的错误。
可理解性的相似性质——可读性(Readability)
可读性的重要性:相比于写新的代码,程序员的大部分时间都用来阅读并修改已有代码。
e.g.
· example a:
· example b:
两个例子所表达的东西都是相同的,并且都能被理解,但是显然例子b比例子a更加容易阅读
标识符的名字的长度:一个类、变量、方法之类的名字越长,那么对于他们的描述也就更详细,更加精确反应了他们的用途。
· 名字应当具有描述性,可以不看注释就能理解其含义
· 是否足够简洁,太长的变量名字降低了效率
· e.g. ‘a’不是一个好的变量名字;‘Age’相比之下更好;‘EmployeeAge’则更加详细
· 一般来说,一个包含一两个字母的名字不是一个好的名字;而一个名字有多长要取决于使用的语言和对应的程序。
度量方式:所有标识符的平均长度
要注意命名的独特性,因为如果多个变量的命名相同,那么就容易造成混乱。但是在多个不同的地方可以使用相同的名字,但是它们应当代表一个相同的本地变量
代码复杂度(Complexity):复杂的代码有可能更难以理解。
代码行数(LoC):越长的代码越难以理解。
注释密度(MCOMM%):注释越多,越容易理解(空白行也有助于提高可读性)
· 测量方式:每一行本地代码对应多少有意义的注释
MCOOM% = MCOMM / LOC
如何写一个可理解的代码:
· 命名规范
· 代码行的最大长度;文件最大的LoC
· 充足的注释
· 代码的布局——缩进、空行、对其、分块等
· 避免多层嵌套——增加复杂度
· 文件和包的组织
注意:代码的可读性、可理解性很多时候比效率、性能更加重要,不可读或不可理解的代码可能蕴含更多的错误。因此先写出可读易懂的代码再去逐渐调试优化。
二. 源代码的文档
代码应当“自描述(self-descriptive)”,因此就不需要外在的文档来理解代码内容。
最好的代码应当是不加注释也能很容易的被理解,否则就要增加注释来解释“为什么这么编码”
四种注释类型:
· 标题注释(Title comment):介绍了类的定义、重要的函数、包内宏的定义,其它一些重要的模块,或者是一整个源码文件。对于专有程序,标题一般包含了版权。
e.g.
· 介绍性注释(Introductory comment):介绍了类、方法或其他模块的使用意图
e.g.
· 块注释(Block comment):描述一组相关的状态的目的和策略
· 单行(Single-Line)/尾部(Trailing)/行尾(End-Of-Line) 注释:解释了一些独立的陈述或者是部分陈述
e.g.
注意:注释是声明代码是做什么(what)的,而不是声明是如何(how)做的
注释的传统:
· 开始时要说明这个文件在项目之中的作用
· 每个类都要声明这个类是干嘛用的
· 每个函数都要解释是用来干嘛的,并且解释每个参数和返回值
e.g. 用@param,@return来表明参数和返回值
· 每个变量,最重要的是类成员变量,需要有注释来解释它所扮演的角色,除非它的名字很简单易懂
· 由于很多精巧的算法需要一个很长的函数,因此内部的注释就显得十分重要,因此在每一个重要的步骤都要进行解释
三. 伪代码编程
用伪代码来表示某个模块或者算法内部处理逻辑或者流程
优点:
· 简单易懂
· 使得程序员能集中注意力在解决问题的逻辑上
· 由自然语言组织结构
伪代码的编写:
· 用简单的英语陈述
· 每一个介绍说明单独一行
· 关键字和缩进用来表示特殊的控制结构
· 每个说明从上到下进行编写,并且只有一个入口和出口
· 一组说明应当被组织成一个模块,并且这个模块要有一个名字
六个最基本的电脑操作:
· 从外界读取信息
- 读(Read):从文件中读取
- 获得(Get):从键盘中读取
· 输出信息到外界
- 打印(Print):输出到输出器上
- 写(Write):输出被写到文件上
- 展示(Put、Output、Display):输出到屏幕
- 提示(Prompt):在读取操作之前,提供一些客户端需要遵守的信息
· 计算(Compute,Calculate)
· 分配变量或者内存
- 初始化(Set)
- 过程的结果赋值(‘=’,‘<-’)
- 保存变量(Save,Store)
· 比较和选择
- IF-THEN-ELSE
· 重复操作
- WHILE, FOR,DO-UNTIL
四. 编码规范
编码规范:定义了一系列的规则,按这些规则进行编码,有助于提升代码可读性,例如——命名、代码布局/缩进、数据声明方式、文件组织方式……
选择何种编码风格是程序员个人的自由,但要做到:
(1) 在所有地方保持一致的风格;
(2) 遵从团队的统一编码风格
文件的包的原则:
复用/发布等价原则((REP) The Reuse/Release Equivalency Principle):
· 复用的粒度等价于发布的粒度
- 单独的类从来不会被复用
- 未被发行的模块不能被复用
共同封闭原则((CCP) The Common Closure Principle):
· 一个包中的所有类针对同一种变化是封闭的
· 一个包的变化会影响包里的所有类,而不会影响到其它的包
· 如果两个类紧密耦合在一起,即二者总是同时发生变化,那么二者应当在一个包内
共同复用原则((CRP) The Common Reuse Princip):
· 一个包里的所有类应被一起复用
· 如果复用了其中一个类,那么就应复用所有的类
包之间的耦合(Coupling):
包之间的依赖关系
e.g.
无圈依赖原则((ADP) The Acyclic Dependencies Principle):
· 不允许在包依赖图中出现任何圈/回路
· 无圈将容易进行测试和理解
· 若存在回路依赖则很难预测该包的变化将如何影响其他包
e.g.
消除圈的两种方式
· 创建新包
稳定依赖原则((SDP) The Stable Dependencies Principle):
· 包之间的依赖关系只能指向稳定的方向
· 被依赖者应当更稳定与依赖者
· 稳定的包较难发生改变
· 如果不稳定的包却被很多其他包依赖,会导致潜在的问题
e.g.
资料来源:MIT6.031 哈工大软件构造课程在坏的例子之中A被BCD依赖,但是A又依赖于E,而显然E比A不稳定,故这不好
在好的例子中A被BCDE所依赖,而E依赖于AFGH,故E并不稳定,且可以很容易被修改
稳定抽象原则((SAP) The Stable Abstraction Principle):
· 在稳定性与抽象度之间建立关联
- 一个包是稳定的,那么它就应该尽可能抽象
- 一个完全稳定的包中只应包含抽象类
- 不稳定的包应是具体的,以便于容易的进行修改
SAP和SDP共同构成了包之间的“依赖倒置原则DIP”
SDP: 依赖应指向稳定的方向;SAP: 稳定性隐含着抽象
因此,依赖应指向抽象的方向