Feeds:
Posts
Comments

Posts Tagged ‘编程’

从事软件开发二十几年了,一直想总结出一些自己应遵循的准则。受“围棋十诀”和“太极拳十要”的启发,从一些书和文章中挑出对自己最有帮助的十条。其中有些条目是相互关联的,都是从不同角度强调如何降低系统复杂度、使系统设计更趋合理。

软件编程十要:

去除冗余
名副其实
单元测试
力求简练
减少关联
重视接口
层次结构
信息隐蔽
风格统一
不断改进

1. 去除冗余
去除冗余是提高软件质量的重要途径。在去除冗余的过程中,我们要把大的函数拆成小的函数,把大的类拆成小的类,引入新的接口、新的抽象类,从而使软件结构更趋合理。

冗余不仅包括完全相同的代码,也包括重复出现的类似的逻辑。如重复出现的switch语句、或if-else if-else语句。这些语句的重复出现,通常表明我们应引入一个抽象类和若干子类,通过多态实现这些逻辑。

冗余就像人身上多余的脂肪,会影响软件的“健康”。随着功能的不断增加,软件很容易变得越来越臃肿。所以,代码复查的一项重要任务就是发现及清除冗余,使软件一直处于结结实实的健康状态。

2. 名副其实
名不正,则言不顺,言不顺,则事不成。

每一个类、每一个函数、每一个变量都应有一个恰如其分的名字。类和变量的名字应是一个名词或被一个形容词修饰的名词,函数的名字应是一个动宾词组。名字应越具体(specific)越好,最好是用现实世界里的名称。如果不能给一个类或一个函数一个具体的恰如其分的名字,那就说明我们的设计有问题,很可能是这个类或函数的聚合度不够高。一个函数应且只应做其名称所规定的工作,而不应顺带做其它工作。

检查不恰当的名字是代码复查中的重要事项。

3. 单元测试
我并没有严格遵守测试驱动开发(Test-Driven Development),但我们会在设计方案确定后,决定写哪些单元测试,并从单元测试入手调试新写的类和函数。

单元测试能在一定程度上确保新增加的代码没有破坏已有的功能,增强我们在对现有程序进行改进时的信心。如果没有单元测试,我们在做较大的调整时会畏手畏脚。

在设计时,注意提高类的可单元测试性也有助于提高设计的质量。单元测试性高的类通常与其它类的耦合度比较低。

单元测试还能在某种程度上起到文档的作用。例如,从单元测试代码中,我们能看到函数如何调用,以及函数的先决条件(pre-condition),后置条件(post-condition)以及类的恒定条件(class invariant)。

4. 力求简练
简练是很多优秀的科学家艺术家追求的目标之一。软件也应如此。简练的设计和代码易于理解、易于维护,更灵活、也很可能更高效。

去除冗余能在一定程度上使程序趋于简练,但还不够,要有意识的追求简练。能用简单的设计、简单的数据结构解决问题,就不要用复杂的设计、复杂的数据结构。更不要耍聪明,把简单问题复杂化。

PASCAL的设计者Niklaus Wirth的一个研究生在开发一个编译器时用了一个复杂的数据结构处理符号表(Symbol Table),而且效果不错。Niklaus却认为在大多数情况下,使用链表就足够好了,没有必要使用复杂的数据结构,因为一个函数的局部变量通常不会很多,而且也不应鼓励在一个函数中定义太多局部变量。结果证明链表使程序既简单易懂,总体效率又高。

所以,并不是越高深的设计越好,能解决问题的简单设计往往更合理。当然,这也并不是说我们不需了解掌握复杂的数据结构。老子说“知其雄,守其雌”,我们要了解复杂的数据结构,但只有在必要时才用。

5. 减少关联
没有关联,系统就不能成为一个整体,不能协同工作。关联太多,系统就会难于理解、难于调试、难于单元测试、难于维护,变得很僵硬。所以,关联要尽量少,尽量是单向,尤其要避免循环关联。

模块化是减少关联的重要手段。每一模块都能完成某一特定功能。模块与外界的接口要小。模块如何划分、模块之间的接口如何定义都直接影响关联的多寡。模块划分是软件设计的一项重要工作,是降低系统复杂度的重要手段,须极为慎重。

6. 重视接口
《设计模式》的前言中有一句十分重要的话“Program to an interface, not to an implementation.”要面向接口编程,而不要面向实现编程。面向接口编程会使你的程序灵活,你可以随时改换你所用的实现,而不被实现的改变所影响。致人而不致于人。

接口是类之间、模块之间的合同,因而应该只包含稳定、持久、可靠的信息。理想的接口要全而粹,既满足接口使用者的全部需求,又没有任何冗余,接口的每个方法(method)都提供独立的功能。“全而粹”说起来容易,做起来却很难。依我的经验看首先要追求粹,宁缺勿滥。由简变繁易,由繁变简难。

接口是很多设计原则的基础,如开闭原则(Open-closed principle ),无循环依赖原则(Acyclic Dependencies Principle)等。故接口设计应予以高度重视。

7. 层次结构
软件设计的目的之一是降低系统的复杂度,而层次结构是降低复杂度的重要方法。每一层完成特定的功能,层之间的关联都通过接口,从而使每一层都相对独立,提高可测试性。

领域驱动设计(Domain Driven Design)推荐的层次结构包括用户界面层、应用层、领域层和基础设施层。用户界面层为最高层,领域层为最低层,用户界面层、应用层和领域层都可调用基础设施层。(见http://dddsample.sourceforge.net/architecture.html)

Jeffrey Palermo更进一步提出了洋葱结构(Onion Architecture):把水平的层状图变成圆形层状图,把基础设施层推到最外层,领域层成为系统核心。(见http://jeffreypalermo.com/blog/the-onion-architecture-part-2/)

8. 信息隐蔽
提起信息隐蔽,很多人的理解仅限于类的成员变量应是私有的。其实David L. Parnas的本意远不限于此。David L. Parnas认为“美的设计与丑的设计的区别在于美的设计封装了那些易变的随意的信息。因为(好的)接口只包含可靠的持久的信息,它们会显得简单而给人以美感。要保持这些接口的美,就要调整系统的结构,从而把所有易变的、随意的、很细节的信息隐藏起来。”

所以,在设计类、模块以及层次时,都要注意信息隐蔽。类之间、模块之间、层次之间交换的信息应越少越好,且仅应交换稳定、可靠、持久的信息。当某项设计决策需要修改时,应最好只影响某一个类或一个模块。

9. 风格统一
统一的风格能给人以美感。统一的设计规范、代码规范、文档规范能提高文档和代码的可读性,使系统更容易理解。越容易理解的系统越容易维护、调试,越容易加新功能。
在制定规范时要十分慎重,一旦制定好就要严格遵守。

10. 不断改进
软件工程师要追求完美。这并不是说软件要到完美时才能发布,而是说在开发和维护过程中要发现问题及时解决。发现有冗余、发现有不合适的名字、发现有不合理的结构,应马上修正。否则,问题积累起来,将越来越难清理。Martin Fowler等人著的《重构:改善既有代码的设计》(Refactoring: Improving the Design of Existing Code)是一本极好的指南。

Read Full Post »