标签:
杂谈 |
第2章 缺失的科学
2.3 为什么不存在软件设计科学
注
软件管理的科学,软件设计的科学。
软件管理的科学告诉我们的是,如何为程序员分派工作,如何制订发布计划,如何估量任务所需的时间,诸如此类。
软件设计的科学在现实的编程中却没有什么人关注。
本书将要弥补这点缺憾。
第3章 软件设计的推动力
软件设计科学的目标
确保软件能提供尽可能多的帮助。
确保软件能持续提供尽可能多的帮助。
设计程序员能尽可能简单地开发和维护的软件系统,这样的系统才能为用户提供尽可能多的帮助,而且能持续提供尽可能多的帮助。
第4章 未来
软件设计师面对的主要问题是:“在设计软件时,应该做怎样的决定?”
注
4.1 软件设计的方程式
D表示这个变化的合意程度(可取程度)。我们对此项工作的需求有多么迫切?
V表示它的价值。该变化价值几何?一般来说,你可以问自己“这个变化对用户有多少用”;当然,还有很多其他方法来判断其价值。
E表示完成这个变化的成本,也就是完成它需要付出的代价。
注
d=v/e
另外这个公式没有写到维护,后面章节会做补充。
任何一点改变,其合意程度与其价值成正比,与所付出的成本成反比。
能带来较大价值、花费成本较少的变化,要比带来较少价值、花费较多成本的变化“更好”。
4.1.1 价值
这个变化能带给人多大帮助。
判断每一点可能的变化的价值,基本的依据是开发人员的开发经验,还包括对用户做恰当的研究,找到对他们帮助最大的工作。
1. 价值可能性和潜在价值
所以,在判断价值时,你应该考虑:
多少用户(占多大比例)会从此项工作中受益?
此功能对用户有价值的可能性有多大?或者换个说法:此功能发挥价值的频率有多高?
在它发挥价值的时候,它能发挥出多大的价值?
2. 平衡危害
权衡改变的价值,需要衡量它可能造成的危害,并权衡利弊。
3. 赢得用户的价值
如果某个功能找不到用户,就不存在实际的价值。
4.1.2 成本
尽管成本可以量化,落实起来却相当复杂,甚至是不可能的。有些变化包含隐形成本,难于预测,比如花在修正开发某项功能造成的bug上的时间就很难预测。不过,经验丰富的软件开发人员仍然可以在不需要知道确切数值的情况下,根据可能成本来预测。
预测某个变化的成本时,重要的是要考虑牵涉到的所有投入,而不仅仅是编程的时间。研究要花多少时间?开发人员之间的沟通要花多少时间?所做的思考要花多少时间?
4.1.3 维护
成本包含实现成本和维护成本,价值也包括当前价值和未来价值
注
4.1.5 化简方程式
软件系统都需要维护很长时间,大多数情况下,未来长期收益和维护成本才是真正需要考虑的,与之相比,当前价值和实现成本变得无足轻重。
4.1.6 你需要什么,不需要什么
注
相比降低实现成本,降低维护成本更加重要。
注
4.2 设计的质量
设计的质量好坏,正比于该系统在未来能持续帮助他人时间的长度。
4.3 不可预测的结果
如果完全不考虑未来,只根据当前已知的确切信息确定所有设计决策,那就百分百安全了
在软件设计时,可以根据已知的信息做某些决策,目的是为了创造更好的未来(提升价值,降低维护成本),而不必预测未来究竟会发生什么具体的事情。
任何工程——包括软件开发——都有“指导原则”。如果我们遵循这些原则,无论未来发生什么,一切事情都会按部就班。这就是软件设计的规律和法则,也是我们这些设计师的“指导原则”。
第5章 变化
变化定律(Law of Change):
程序存在的时间越久,它的某个部分需要变化的可能性就越高。
注
变化必然会发生。程序应该保证尽可能合理的灵活性,这样,不管未来发生什么变化,都可以应付得了。
5.2 软件设计的三大误区
注
5.2.1 编写不必要的代码
不要编写不是必需的代码,并且要删除没有用到的代码。
注
5.2.2 代码难以修改
僵化设计有两大原因:
(1)对未来做太多假设
(2)不仔细设计就编写代码
更好的办法是每次只确定一个或者少数几个需求,然后立刻让开发人员实现它。在开发过程中,用户可以扮演开发人员的角色,反复进行沟通。上次确定的功能实现并发布之后,就可以继续处理其他的功能。这样,最终得到的系统是设计良好的,完全满足用户需求的。
注
设计程序时,应当根据你现在确切知道的需求,而不是你认为未来会出现的需求。
5.2.3 过分追求通用
总会有人真的掉到过度工程的陷阱里——就如同为了烧毁蚁穴,就去制造一台轨道激光器。轨道激光器是有相当挑战的工程,它耗费不菲,制造时间相当长,维护起来也是一场噩梦。你能想象它出问题的时候应当怎么修复吗?
注
(1)因为你不能预测未来,所以无论你做得多么通用,其实都不够满足未来要面对的真实需求。
(2)如果你的代码很通用,那么它通常不能从用户的角度很好地满足规格/需求。
(3)太过通用就必须写很多不需要的代码,这样又回到了第一条规律。
仅仅根据目前确知的需求来考虑通用。
注
5.3 渐进式开发及设计
注
个人认为这里算是本书的核心,这是作者推崇的设计方法。个人认为大部分开源软件也是采取这个设计方法。
后面还会反复提到这个方法以及相关的内容。
它要求按照特定顺序,一点一点地设计和构建系统。
相比开始就建立完整的系统,一次性构建出来,这种开发方法需要时间更少,也不用考虑过多。如果你习惯其他的开发方法,头一次实践可能并不那么容易,但是经过锻炼,用起来就会变容易。
这个方法的精妙之处就在于,它是根据实现的顺序来决策的。总的来说,在其中的每个阶段,下一步都只做最容易的事情。
有些时候,你甚至需要把某个单独的功能拆分为一系列小的、简单的逻辑步骤,然后才可以很方便地实现。
第6章 缺陷与设计
“缺陷概率定律”:
在程序中新增缺陷的可能性与代码修改量成正比。
注
相比大的变化,小的变化维护成本更低。小的变化=更少的缺陷=更少的维护。
最好的设计,就是能适应外界尽可能多的变化,而软件自身的变化要尽可能少。
6.1 如果这不是问题……
注
这节很重要啊,学好了可以把手头很多活都推掉。:D
永远不要“修正”任何东西,除非它真的有问题,而且有证据表明问题确实存在。
如果你收到很多这样的请求,就说明用户很难在你的程序里找到自己想要的功能。这才是真正要改进的地方。
注
有时候用户会报告某个bug,但程序其实是完全按照预期来运行的。果真如此,就应当少数服从多数。如果相当多的用户认为某个行为是bug,它就是bug;如果只是少数用户(比如一两个)认为它是bug,那么它就不算bug。
注
在这类问题上,最有名的错误就是所谓的“提前优化”。
在你的程序中,真正需要关注速度的部分,应该局限于你可以证明的、真正让用户体会到有性能问题的那些部分。对程序的其他部分,最主要关心的还是灵活和简洁,而不是速度。
动手解决之前,真正拿到证据,证明问题确实存在。
6.2 避免重复
注
理想情况下,任何系统里的任何信息,都应当只存在一次。
这个道理对代码段也同样适用。我们不应该复制粘贴代码段;相反,应该使用各种编程技巧来处理,让各处的代码可以“使用”(use)、“调用”(call)、“包含”(include)已有的其他代码。
众多优秀设计都基于这一规律。也就是说,你能更聪明地让代码“使用”其他代码,把信息集中运用好,那么设计也就更好。在这一领域,你同样可以真正用自己的聪明才智为编程创造价值。
第7章 简洁
简洁定律(Law of Simplicity):
软件任何一部分的维护难度,反比于该部分的简洁程度。
换句话说,某一部分的代码越简洁,未来进行变化的难度就越低。
注
这条法则并不关心整个系统的简洁性,只是谈到了各个部分的简洁性。这是为什么呢?
原因在于,一般的计算机程序已经足够复杂了,没有人可以一次性全面理解它,大家都只能分部份逐步了解。虽然程序里大都有些庞大繁杂的结构,但这不要紧;要紧的是,在我们阅读代码时,应该可以理解这些庞大繁杂的结构。这些部分越简洁,就越容易被普通人理解。这一点非常重要,尤其是你把自己的代码转交给其他人,或者脱离自己的代码几个月再重新上手时,更是如此。
落实这条法则的一个好办法,就是第5章讲解的渐进式开发和设计方法。因为每次添加功能之前都有个“重新设计”的过程,所以系统能持续简化。即便不用这种方法,你也可以在增添新功能之前,花点时间去化简任何让你或你的同事觉得不够简洁的代码。
7.1 简洁与软件设计方程式
目前可行的、能够降低软件设计方程式中维护成本的最重要的事情,就是把代码变简洁。我们不必预测未来,完全可以只审视自己的代码,如果它足够复杂,就立刻动手简化它。这就是随时间推移降低维护成本的办法——持续不断地让代码变得更简洁。
7.2 简洁是相对的
注
7.3 简洁到什么程度?
简单到傻子也能懂!
降低代码学习难度的方法有很多:简洁的注释,简单的设计,循序渐进的引导,等等。
简单仍然是相对的,你的代码的目标受众不是家人,而是其他程序员
7.4 保持一致
编程也是这样——缺乏一致性,只会一团糟。有了一致性,世界就很简单。即便你做不到那么简单,至少也要做到:一旦你理解了某种复杂性,就不必再进行重复劳动。
7.5 可读性
要把各部分拆分开来,就必须留出空白。
代码到底应该留出怎样的空白,没有什么硬性的、直接的规则,唯一的规则是,代码之间留出的空白应当保持一致规范,空白应当能有助于读者理解代码的结构。
7.5.1 命名
名字应当足够长,能够完整表达其意义或描述其功能,但不能太长,以免影响阅读。
7.5.2 注释
代码的意图通常不应该用注释来说明,直接阅读代码就应当能够理解。如果发现意图不够明显,那么就说明这段代码还可以变得更简单。如果你的代码实在不能更简单,才应该写注释来说明。
注释的真实目的,是在理由不够清晰明显时加以解释。
7.6 简洁离不开设计
注
第8章 复杂性
注
除了新增功能,其他一些做法也会增加复杂性,以下列出了最常见的做法。
注
首先增加复杂性的做法是新增功能。
吐槽一下本章的分节,有点乱。
1. 扩展软件的用途
2. 新增程序员
相比众多平庸的开发人员,少量精干的开发人员更容易获得成功。
3. 做无谓的改变
4. 困于糟糕的技术
5. 理解错误
程序员不理解自己的工作,就容易设计出复杂的系统。
6. 糟糕的设计或不做设计
一般来说,它指的是“没有为变化做计划”。万物都是会变化的,项目增长时,设计仍然要维持简单。你必须一开始就做好设计,而且在系统膨胀时不断进行优秀的设计;否则,复杂性就会迅速增长,因为如果设计得不好,每项功能都会让代码加倍复杂,而不是只复杂一点点。
7. 重新发明轮子
只有在满足以下任何一个条件的前提下,重新发明轮子才有价值:
(1)你需要的东西不存在;
(2)现有的各种“轮子”都很糟糕,会把你困住;
(3)现有的“轮子”根本无法满足你的需求;
(4)现有的“轮子”缺乏良好的维护,而你也不能接过维护的任务(比如,你没有源代码)。
8.1 复杂性与软件的用途
注
最受用户喜欢的软件是专注而简洁的,并且始终执着于基本用途。
注
8.2 糟糕的技术
出现复杂性的另一个常见原因就是,系统里选择了错误的技术,尤其是最终发现并不能很好适应未来需求的技术。
使用之前,你可以通过三个因素来判断技术是否“糟糕”:生存潜力、互通性、对品质的重视。
8.2.1 生存潜力
某种技术的生存潜力,就是它持续获得维护的可能性
注
8.2.2 互通性
所谓互通性,指的是如果需要,从一种技术切换到另一种技术有多难。要了解技术的互通性,就得问问自己:“我们能不能以某种标准方式来交互,这样就更容易切换到遵循同样标准的其他系统?”
8.2.3 对品质的重视
注
8.2.4 其他原因
是否简洁,是否符合软件的基本用途
也可以考虑个人喜好
8.3 复杂性及错误的解决方案
注
解决因为设计的问题引起的软件复杂性。
真正要解决的问题是什么?
注
本节介绍的方法只是取得更好设计的方法,我觉得很容易让人产生一种遇到问题就推倒重来的错觉,实际如何实施看后面的几节比较好。
8.4 复杂问题
大多数麻烦的设计问题,都可以用在纸上画图或写出来的办法找到答案。
注
8.5 应对复杂性
注
如果系统中某个部分太过复杂,有个好办法来解决:把它分解成几个独立的小部分,逐步重新设计。每次修改都应该足够小,这样可以放心动手,不会让事情更复杂。不过这个过程中最大的危险是,新做的修改有可能会引入更多的复杂性。许多重设计或重写的工作之所以最终失败,就是因为它们引入了更多的复杂性,结果最后和原有系统同样复杂。
每个步骤都应该足够小,比如给某个变量取个更好的名字,或是给难看懂的代码增加一些注释。更常见的做法是在每个步骤中都把一个复杂的部分拆分成若干个简单的部分。
如果所有的代码都包含在一个巨大的文件里,改进的第一步就是把某个部分保存到单独的文件里。之后改进这个小部分的设计,然后把另一个部分保存到新的文件,再改进这个部分的设计。如此重复下去,最终得到的就是可靠的、可理解的、可维护的系统。
如果系统非常复杂,这么做的工作量可能相当大,所以必须有耐心
不过,还有一点也很重要,你不能专门花很长的时间来重新设计,停止开发新功能
平衡开发新功能和应对复杂性这两项任务的方法有很多。最好的一个办法就是,重新设计时只考虑让新功能更容易实现,然后实现这个功能。这样,你就可以在重新设计和开发新功能之间定期切换。它同样有助于保证新设计能够适应需求,因为设计时会考虑到实际的应用。系统的复杂性也会逐渐下降,而且你一直都跟得上用户的需求。你甚至可以这样来处理bug:如果发现修改设计之后,某些bug更容易修复,那么先重新设计代码再修复bug。
注
8.5.1 把某个部分变简单
注
学习设计模式和设计方法来处理遗留代码,学习软件工程师普遍使用的工具,都可以帮上大忙,尤其有用的是掌握多门编程语言,熟悉多种不同的类库,因为每一种语言和类库都会用自己的方式来思考问题,即便你并不使用这些语言和类库,这种思维方式也可以应用到你的具体环境中。
8.5.2 不可解决的复杂性
在程序外面妥善包装上一层,让其他程序员更容易使用和理解。
8.6 推倒重来
如果下面的条件全都满足,你才应该重写。
(1)你已经完成了准确评估,证明重写整个系统会比重新设计现有系统更有效率。只有猜测是不够的,你需要真正去做一些重新设计现有系统的试验,然后对比结果。已有的复杂系统可能很难应付,某些部分可能很难处理,但是为了知道修复它需要多少时间,你必须动手做一些尝试。
(2)你有足够的时间用来开发新的系统。
(3)你要比原有系统的设计师更高明,或者,如果原有系统是你设计的,但现在你的设计能力已经大大提升了。
(4)你完全打算好了通过一系列简单的步骤设计整个新系统,在每一步都有用户提供反馈。
(5)你有足够的资源,可兼顾维护原有系统和重新设计系统。绝对不要为了让程序员重写新系统而停止对原有系统的维护。系统只要在使用,都离不开维护。请记住,你自己的精力也是一种资源,必须慎重分配——如果两线作战,你每天有足够的时间分配给原有系统和新系统吗?
如果上面的条件都满足,那么推倒重来是可以接受的。
第9章 测试
测试法则(Law of Testing)告诉我们:
你对软件行为的了解程度,等于你真正测试它的程度。
除非亲自测试过,否则你不知道软件是否能正常运行。