美文网首页iOS开发
创建高质量的代码_1(读书笔记)

创建高质量的代码_1(读书笔记)

作者: 9d710097e616 | 来源:发表于2016-08-14 16:21 被阅读41次

软件构件中的设计

设计中的挑战

       “软件设计”一词意味着去构思、创造或者发明一套方案,把一份计算机软件的规格说明书要求转变为可实际运行的软件。设计就是把需求分析和编码调试连接在一起的活动。好的高层次设计能提供一个可以稳妥容纳多个较低层次设计的结构。好的设计对于小型项目非常有用,对于大型项目就更是不可或缺。

      设计是个了无章法的过程(即使它能得出清爽的成果)—— 说设计了无章法,是因为在此过程中你会采取很多错误的步骤,多次误入歧途。事实上,犯错误正是设计的关键所在,在设计阶段犯错误并加以改正,其代价要比在编码后才发现同样的错误并彻底修改低得多。说设计了无章法,还因为优、劣设计之间的差异往往非常微妙;另外,你很难判断设计何时算是“足够好”了。设计到什么时候细节才算够?有多少设计需要用形式化的设计符号完成,又有多少设计可以留到编码的时候再做?什么时候才算完成?因为设计永无止境,因此对上述问题最常见的回答是“到你没有时间再做了为止”。

       设计就是确定取舍和调整顺序的过程 —— 设计者工作的一个关键内容便是去衡量彼此冲突的各项设计特性,并尽量在其中寻求平衡。如果快速的反应速度比缩减开发时间更重要,那么设计者会选取一套设计方案。而如果缩减开发时间更重要,那么好的设计者又要巧妙地形成另一套不同的设计方案。

      设计受到诸多限制 —— 设计的要点,一部分是要创造可能发生的事情,而另一部分又是在限制可能发生的事情。

      设计是不确定的 —— 如果你让三个人去设计一套同样的程序,他们很可能会做出三套截然不同的方案,而每一套设计都很不错。

      设计是一个启发是过程 —— 正因为设计过程充满了不确定性,因此设计技术也就趋于具有探索性,即“经验法则”或者“试试没准能行的办法”。而不是保证能产生预期结果的可重复的过程。设计过程中总会有实验和犯错误。在一件工作或一件工作的某个方面十分凑效的设计工具或技术,不一定在下一个项目中使用。没有任何工具是用之四海而皆灵的。

       设计是自热而然形成的 —— 设计不是在谁的头脑中直接跳出来的。它是在不断的设计评估、非正式讨论、写实验代码以及修改实验代码中演化和完善的。几乎所有的系统都在其开发的起始阶段经历过某种程度的设计变更,而当它们进入后续版本后通常都会进行更大的改变。软件的性质决定了这些改变在多大程度上是有益且可被接受的。

接下来看看有哪些关键的设计概念是需要我们了解的 :

    好的设计源于对一小批关键设计概念的立即。

    软件的首要技术使命:管理复杂度 ——  两类不同的问题导致软件开发变得困难,本质的问题和偶然的问题。本质的属性是一件事物必须具备的,如果不具备就不再是该事物的属性。偶然的属性则是指一件事物碰巧具有的属性,有没有这些属性都并不影响这件事我本身。

 管理复杂度的重要性 :

        在对导致软件项目失败的原因进行调查时,人们很少把技术原因归为项目失败的首要因素。项目的失败大多数都是有不尽如人意的需求、规划和管理所导致的。但是,档项目确由技术因素导致失败时,其原因通常就是失控的复杂度。有关的软件变得极端复杂,让人无法知道它究竟是做什么的。当没有人知道对一处代码的改动会对其他代码带来什么影响时,项目就快停止进展了。

        软件的首要技术使命便是管理复杂度,它实在太重要了。

     在软件架构的层次上,可以通过把整个系统分解为多个子系统来降低问题的复杂度。保持子程序的短小精悍也能帮助你减少思考的负担。从问题的领域着手,而不是从底层实现细节入手去编写程序,在最抽象的层次上工作,也能减少人的脑力负担。

    受着人类固有限制影响的程序员的底线,就是要写出既让自己容易理解,也能让别人容易看懂,而且很少有错误的程序代码。

如何应对复杂度:

高代价、低效率的设计源于下面三种根源:

- 用复杂的方法解决简单的问题;

- 用简单但是错误的方法解决复杂的问题;

- 用不恰当的复杂方法解决复杂的问题;

现代的软件本身就很复杂,那么要如何解决,下面就用这两种方法来管理复杂度:

- 把任何人在同一时间需要处理的本质复杂度的量减到最少;

- 不要让偶然性的复杂度无谓地快速增长。

一旦你能理解软件开发中任何其他技术目标都不如管理复杂度重要时,众多设计上的考虑就都变得直截了当了。

理想的设计特征:

高质量的设计具有很多常见的特征。如果你能实现所有这些目标,你的设计就真的非常好了。这些目标之间有时会相抵触,但这也正是设计中的挑战所在--在一系列相互竞争的目标之中做出一套最好的折中方案。有些高质量设计的特征也同样是高质量程序的特征,如可靠性和性能等。而有些则只是设计范畴内的特征。

下面就列出一些设计范畴内的特征:

- 最小的复杂度,应该做出简单且易于理解的设计。如果你的设计方案不能让你在专注于程序的一部分时安心地忽视其他部分的话,这一设计就没有什么作用了。

- 易于维护

- 松散耦合

- 可扩展性

- 可重用性

- 高扇入,就是说让大量的类使用某个给定的类。

- 低扇出,就是说让一个类里少量或者适中地使用其他的类。

- 可移植性,就是说应该这样设计系统,使它能很方便地移植到其他环境中。

- 层次性

- 精简性

- 标准技术,一个系统所依赖的外来的、古怪的东西越多,别人在第一次想要理解它的时候就越是头疼。要尽量用标准化的、常用的方法,让整个系统给人一种属性的感觉。

设计的层次:

第1层:软件系统

第一层就是整个系统。有的程序员直接从系统层次就开始设计类,但是往往从子系统或者包这些类的更高组织层次来思考会更有益处。

第2层:分解为子系统或包

在这一层次上设计的主要成果是识别出所有的主要子系统。这些子系统可能会很大,比如说数据库、用户界面、业务规则、命令解释器、报表引擎等。这一层的主要设计活动就是确定如何把程序分为主要的子系统,并定义清楚允许各子系统如何使用其他子系统。对于任何至少需要几周时间才能完成的项目,在这一层次上进行划分通常都是必需的。

在这一层中,定义子系统之间的通信规则是非常重要的。为了让子系统之间的连接简单易懂且易于维护,就要尽量简化子系统之间的交互关系。最简单的交互关系是让一个子系统去调用另一个子系统中的子程序;稍微复杂一点的交互关系是在一个子系统中包含另一个子系统中的类;而最复杂的交互关系是让一个子系统中的类继承自另一个子系统中的类。

常用的子系统:

- 业务规则, 业务规则是指那些在计算机系统中编入的法律、法规、政策以及过程。

- 用户界面, 应该创建一个子系统,把用户界面组件同其他部分隔开,以便使用户界面的演化不会破坏程序的其余部分。在大多数情况下,用户界面子系统会使用多个附属的子系统或类来处理用户界面、命令行接口、菜单操作、窗体管理、帮助系统等等。

- 数据库访问, 可以把对数据库进行访问的实现细节进行隐藏起来,让程序的绝大部分可以不必关心处理底层结构的繁琐细节,并能像在业务层次一样处理数据。隐藏实现细节的子系统可以为系统提供有价值的抽象层,从而减少程序的复杂度。它把和数据库相关的操作集中起来,减少了在对数据进行操作时发生错误的几率。同时,它还能让数据库的设计结构更易于变化,做这种修改时无须修改程序的主要部分。

- 对系统的依赖性,把对操作系统的依赖因素归到一个子系统里,就如同把对硬件的依赖因素风中起来一样。

第3层: 分解为类

在这一层次上的设计包括识别出系统中所有的类。例如,数据库接口子系统可能会被进一步划分成数据访问类、持久化框架类以及数据库元数据。

当定义子系统中的类时,也就同时定义了这些类与系统的其余部分打交道的细节。尤其是要确定好类的接口。总的来说,这一层的主要设计任务是把所有子系统进行适当的分解,并确保分解出的细节都恰到好处,能够用单个的类实现。

第4层:分解成子程序

这一层的设计包括把每个类细分为子程序。当你查看类里面子程序的细节时,就会发现很多子程序都很简单,但也有些子程序是由更多层次化组织的子程序所组成的,这就需要更多的设计工作了。完整地定义出类内部的子程序,常常会有助于更好地理解类的接口,反过来,这又有助于对类的接口进行进一步的修改。

第5层:子程序内部的设计

在子程序层次上进行设计就是为每个子程序布置详细的功能。子程序内部的设计工作通常是由负责该子程序的开发人员来完成的。这里的设计工作包括编写伪代码、选择算法、组织子程序内部的代码块,以及用编程语言编写代码。这一层的设计工作总是需要做的,尽管有时做得很不在意或者很差劲,有时则是经过深思熟虑而出色完成的。

设计构造模块:启发式方法 

由于软件设计是非确定性的,因此,灵活熟练地运用一组有效的启发式方法(试探法),变成了合理的软件设计的核心工作。

关于设计启发式方法的总结: 寻找现实世界的对象;形成一致的抽象;封装实现细节;在可能的情况下继承;藏住秘密;找出容易改变的区域;保持松散耦合;探寻通用的设计模式;高内聚性;构造分层结构;严格描述类契约;分配职责;为测试而设计;避免失误;有意识地选择绑定时间;创建中央控制点;考虑使用蛮力;画一个图;保持设计模块化。

找出现实世界的对象

在确定设计方案时,首选且最流行的一种做法便是“常规的”面向对象设计方法,此方法的要点是辨识现实世界中的对象(object,物体)以及人造的对象。

使用对象进行设计的步骤是:

- 辨认对象及其属性(方法method和数据data)

- 确定可以对各个对象进行的操作

- 确定各个对象能对其他对象进行的操作

- 确定对象的哪些部分对其他对象可见——哪些部分可以是公用的,哪些部分可以是私用的

- 定义每个对象的公开接口

形成一致的抽象

优秀的程序员会在子程序接口的层次上、在类接口的层次上以及包接口的层次上--换句话说,在门把手的层次上、门的层次上以及房屋的层次上进行抽象,这样才能更快、更稳妥地进行开发。

封装实现细节

封装填补了留下的空白。抽象是说:“可以让你从高层的细节来看待一个对象。”而封装则说:“除此外,你不能看到对象的任何其他细节层次。”

隐藏秘密(信息隐藏)

信息隐藏是结构化程序设计与面向对象设计的基础之一。 在设计一个类的时候,一个关键性的决策就是确定类的哪些特性应该对外可见,而哪些特性应该隐藏起来。设计类的接口与设计其他环节一样,都是一个迭代的过程。如果你第一次没有得到合适的接口,那么就多试几次,直到设计稳定下来。如果设计仍不稳定,那你就需要换种方法再尝试。

信息隐藏中所说的秘密主要分为两大类:

1. 隐藏复杂度,这样你就不用再去应付它,除非你要特别关注的时候

2. 隐藏变化源,这样当变化发生时,其影响就能被限制在局部范围内。复杂度的根源包括复杂的数据类型、文件结构、布尔判断以及晦涩的算法等。

问题“这个类需要隐藏些什么?”,正切中了接口设计的核心,如果你能在给类的公开接口中添加函数或者数据而不牺牲该类的隐秘性,那么就做下去,否则请停住。

是设计的所有层面上,都可以通过询问该隐藏些什么来处成好的设计决策。这一问题可以在构建层面上协助你用具名常量来取代字面量,可以在类的内部生成好的子程序和参数名称,还有助于指导在系统层上做出有关类和子系统分解以及交互设计的决策。

请养成问“我该隐藏些什么?”的习惯,你会惊奇地发现,有很多刺手的设计难题都会在你面前化解。

找出容易改变的区域

好的程序设计所面临的最重要挑战之一就是适应变化。

1. 找出看起来容易变化的项目。

2. 把容易变化的项目分离出来。把第一步中找出的容易变化的组件单独划分成类,或者和其他容易同时发生变化的组件划分到同一个类中。

3. 把看起来容易变化的项目隔离开来。设法设计好类之间的接口,使其对潜在的变化不敏感。设计好类的接口,把变化限制在类的内部,且不会影响类的外部。任何使用了这个将会发生变化的类的其他类都不会察觉到变化的存在。类的接口应该肩负起保护类的隐私的职责。

下面是一些容易发生变化的区域:

. 业务规则   很容易成为软件变化的根源。

. 对硬件的依赖性  比如屏幕,键盘...

. 输入和输出  

. 非标准的语言特性

. 困难的设计区域和构建区域

. 状态变量  不要使用布尔变量作为状态变量,请换用枚举类型;使用访问器子程序取代对状态变量的直接检查。

. 数据量的限制

4. 预料不同程度的变化。 当你在考虑系统中的潜在变化时,应该设法设计好你的系统,让这些变化的影响或范围与发生该变化的可能性成反比。如果一种变化很可能发生,那么要确保系统能够很容易地对它做出响应。

找出容易发生变化的区域的一个好办法是:首先找出程序中可能对用户有用的最小子集。这一子集构成了系统的核心,不容易发生改变。接下来,用微小的步伐扩充这个系统。这里的增量可以非常微小,小到看似微不足道。当你考虑功能上的改变时,同时也要考虑质的变化:比如说让程序编程线程安全的,使程序能够本地化等。这些潜在的改进区域就构成了系统中的潜在变化。请依照信息隐藏的原则来设计这些区域。通过首先定义清楚核心,你可以认清哪些组件属于附加功能,这时就可以把他们提取出来,并把它们的可能改进隐藏起来。

保持松散的耦合

模块(类、子程序)之间的好的耦合关系会松散到恰好能使一个模块能够很容易地被其他模块使用。就像火车车厢之间一样。请尽量使你创建的模块不依赖或者很少依赖其他模块。让模块之间的关系像商业合作者一样彼此分离,而不是像连体婴儿那样紧密相连。

接下来是一些在衡量模块之间耦合度时可采用的标准:

- 规模 这里的规模指的是模块之间的连接数。只有一个参数的子程序与调用它的子程序之间的耦合关系比有六个参数的子程序与它的调用方之间的耦合关系更松散。

- 可见性 可见性指的是两个模块之间的连接的显著程度。

- 灵活性 指的是模块之间的连接是否容易改动。简而言之,一个模块越容易被其他模块所调用,那么它们之间的耦合关系就会越松散。这种设计非常不错,因为它更灵活,并且更易于维护。

下面是你会遇到的最常见的几种耦合:

- 简单数据参数耦合   当两个模块之间通过参数来传递数据,并且所有的数据都是简单数据类型的时候,这两个模块之间的耦合关系就是简单数据参数耦合的。这种耦合关系是正常的。

- 简单的对象耦合  如果一个模块实例化一个对象,那么它们之间的耦合关系就是简单对象耦合的。

- 对象参数耦合  如果对象1要求对象2传递一个对象3,那么这两个模块就是对象参数耦合的。与对象1仅仅要求对象2传递给它简单数据类型相比,这种耦合关系要更紧密一些,因为它要求对象2要求了解对象3.

查阅常用的设计模式

设计模式精炼了众多现成的解决方案,可用于解决很多软件开发中最常见的问题。有些软件问题要求全新的解决方案,但是大多数问题都和过去遇到过的问题类似,因此可以使用类似的解决方案或者模式加以解决。

与完全定制的设计方案相比,设计模式提供了下列益处:

- 设计模式通过提供现成的抽象来减少复杂度。

- 设计模式通过把常见解决方案的细节予以制度化来减少出错。

- 设计模式通过提供多种设计方案而带来启发性的价值。

- 设计模式通过把设计对话提升到一个更高的层次上来简化交流。

其他的启发式方法

- 构造分层结构。分层结构指的是一种分层的信息结构,其中最通用的或者最抽象的概念表示位于层次关系的最上面,而越来越详细的具有特定意义的表示放在更低的层次中。分层结构用作管理复杂信息的重要工具。分层结构是实现软件的首要技术使命的有用工具,因为它使你能够只关注于当前正在关注的那一层细节。其他的细节并没有完全消失,它们只是被放到了另一层次上,这样你就可以在需要的时候去考虑它们,而不是在所有的时间都要考虑所有的细节。

- 严格描述类契约。在另一更为细节的层次上,把每个类的接口看作是与程序的其余部分之间的一项契约会有助于更好地洞察程序。契约对于管理复杂度而言非常有益,因为至少从理论上来说,该对象可以很安全地忽略掉契约范围之外的任何行为。

- 分配职责。问每一个对象该对什么负责,类似于问这个对象应该隐藏些什么信息,不过我认为这问题能够带来更为广阔的答案,从而使这种方法具有特殊的价值。

- 为测试而设计。你需要把用户界面与程序的其余部分分离开来以便能够独立地检查它们吗?你需要设法组织好每一个子系统,是它与其他子系统之间的依赖关系最小吗?为测试而设计很容易产生更为规整的类接口,而这通常是非常有益处的。

- 避免失误。

- 有意识地选择绑定时间。绑定时间指的是把特定的值绑定到某一变量的时间。做早绑定的代码通常比较简单,但是也会比较缺乏灵活性。有时候,你可以通过问类似这样的问题来获得更好的理解:如果我早期绑定这些值又会这样?如果晚些绑定又会怎样?如果我在此处就初始化这张表会怎样?如果我在运行期间从用户那里读入这个变量的值又该怎样?

- 创建中央控制点。对于每一段有作用的代码,应该只有唯一的一个地方可以看到它,并且也只能在一个正确的位置去做可能。之所以这么做有助于降低复杂度,其原因在于:为了找到某样事物,你需要查找的地方越少,那么改起它来就会越容易、越安全。

- 考虑使用蛮力突破。蛮力也是一种强大的启发式工具。

- 保持设计的模块化。模块化的目标是使得每个子程序或者类看上去像个“黑盒子”:你知道进去什么,也知道出来什么,但是你不知道在里面发生了什么。黑盒子有着如此简洁的接口设计和定义明确的功能,对于给定任何特定的输入,你都能准确预期相应的输出结果。模块化这一概念和信息隐藏、封装以及其他的设计启发密切相关。但在一些时候,如果你去考虑如何把一堆黑盒子组装成系统,也许会获得单纯使用信息隐藏和封装技术所无法获得的深刻理解,因此这个概念值得放进去你的设计工具箱中。

设计实践

前面几节关注的都是与设计特性相关的启发式方法,这一节将会讲解一些设计实践的启发式方法一些你可能采用而且常常可以获得良好结果的工作步骤 。

迭代

设计是一种迭代的过程。当你在备选的设计方案之中循环并且尝试一些不同的做法时,你将同时从高层和低层的不同视角去审视问题。你从高层世界中得出的大范围图景会有助于你把相关的底层细节纳入考虑。你从低层视角中获得的细节也会为你的高层决策尊定基础。

分而治之

没有人的头脑能大到装得下一个复杂程序的全部细节。把程序分解为不同的关注区域,然后分别处理每一个区域。如果在某个区域里碰上了死胡同,那么就迭代。

增量式地改进是一种管理复杂度的强大工具。理解问题、形成计划、执行计划,而后再回顾你的做法。

建立试验性原则

有些时候,除非你更好地了解了一些实现细节,否则很难判断一种设计方法是否凑效。有一种技术能够低成本地解决这个问题,那就是建立试验性原型——即“写出用于回答特定设计问题的、量最少且能够随时扔掉的代码的活动。”

合作设计

可以采取各种和他人交流的合作方式。

要做多少设计才够

这要根据这个团队的经验情况,项目大小等来综合判断决定要做多少设计。

记录你的设计成果

- 把设计文档插入到代码里;

- 用wiki来记录设计讨论和决策;

- 写总结邮件;

- 使用数码相机;

- 保留设计挂图;

总结: 你在应用某种设计方法时越教条化,你所能解决的现实问题就会越少。请把设计看成是一个险恶的、杂乱的和启发式的过程。不要停留于你所想到的第一套解决方案,而是去寻求合作,探求间接性,在需要的时候做出原型,迭代,并进一步迭代。你将对自己的设计成果感动满意。

相关文章

网友评论

    本文标题:创建高质量的代码_1(读书笔记)

    本文链接:https://www.haomeiwen.com/subject/hqhksttx.html