代码大全_V2(1,2章笔记)

译序

这本书讲什么

代码大全 原名叫 code complete,它是什么,又不是什么?

  • 不是IDE中的代码自动补全功能
  • 不是软件源代码 “大全”
  • 是 “编码完成” 的意思,是一个软件项目开发过程中的重要里程碑(milestone),软件项目进行到这里,表明已经完成了所有的编码工作,即将开始系统测试

作者认为,应该首先为人编写代码,其次才是为机器;代码主要是供人阅读的。事实上,我们认为6、7、10至19章这300多页的内容是本书的精华内容。

这本书适合谁看,该怎么看

  • 初级程序员,先看第18章的 “表驱动法”:将复杂的逻辑判断转换为查表,从而简化代码的编写与维护
  • 高级程序员,先看第4章 “关键的构建决策”,本章关注的焦点是程序员和技术带头人个人必须(直接或间接)负责的项目准备工作
  • 项目经理,先看第33章 “个人性格”,程序设计是一项纯粹的脑力劳动,本章对挑选和培养优秀程序员提出了建议。事实证明,相对于聪明程度(智商),个人性格(情商)对于造就出程序员高手更具有决定性的意义
  • 低年级学生,先看第11章 “变量名的理论”,这本书用了整整一章的篇幅来讲解 “为变量命名” 这一编程种最常见的活动
  • 高年级学生,先看第8章 “防御式编程”,本章讲述如何面对严酷的充斥非法数据的真实世界,在遇到 “绝不会发生” 的事件和其他程序员犯下的错误时如何保护自己
  • 制定编码标准的人,先看第32章 “自说明代码”,本章中有一段关于注释的精彩对话,它可能会改变您在制定编码标准时对注释的要求
  • 自学编程的人,先看第7章 “高质量的子程序”,本章详细讨论了子程序的命名和参数选择等问题,其中对子程序最佳长度的讨论颇有借鉴意义
  • 喜欢参与网上争论的人,先看13.3节 “全局数据” 和 低17.3节 “goto语句”,听听学术界在这些问题上的争论也挺有意思

前言

本书的首要目的,就是希望缩小本行业中一般商业实践与大师级人员及专家们之间的知识差距。许多强大的编程技术在被编程领域的大众接触之前,都已经在学术论文和期刊里尘封了多年。

虽然今年来软件开发实践迅速发展,但普通的实践手段并没有太大变化。很多软件的开发仍然是漏洞百出、迟于交付并且超出预算,还有很多根本就无法满足用户的需求。软件业界以及学术界的研究人员已经发现了不少行之有效的实践经验,足以解决自20世纪70年代以来编程领域中日益蔓延的大多数问题。可是这些实践经验很少在高度专业化的技术期刊之外对外发表。有研究表明,一项研究成果从其诞生之日起,到进入商业实践阶段,通常要经历5-15年甚至更长的实践。这本手册就是想缩短这一漫长的过程,让那些关键性的研发成果现在就能为更多编程人员所用。

1. 欢迎进入软件创建世界

大家都知道 "construction" 这个词在一般情况下的意思是 "建筑"。建筑工人盖房子、造摩天大楼等时所进行的工作都是建筑。当你小的时候,你用积木进行 "建筑工作"。因此 "construction" 指的是建造某个东西的过程。这个过程可能包括:计划、设计、校验等方面的某些工作,但是,它主要是指在这其中的创造性工作。

1.1 什么是软件创建

  • 问题定义(problem definition)
  • 需求分析(requirements development)
  • 规划构建(construction planning)
  • 软件架构(software architecture),或高层设计(high-level design)
  • 详细设计(detailed design)
  • 编码与测试(coding and debugging)
  • 单元测试(unit testing)
  • 集成测试(integration testing)
  • 集成(integration)
  • 系统测试(system testing)
  • 保障维护(corrective maintenance)

构建有时也被认为是 "编码(coding)" 或 "编程(programming)"。"编程" 算不算是最贴切的词,因为它有一种 "把已经存在的设计机械化地翻译成计算机语言" 的意味;而构建并不都是这么机械化的,需要客观的创造力和判断力。

这里列出一些构建活动中的具体任务(task)

  • 验证有关的基础工作已经完成,因此构建活动可以顺利地进行下去
  • 确定如何测试所写的代码
  • 设计并编写类(class)和子程序(routine)
  • 创建并命名变量(variable)和具名常量(named constant)
  • 选择控制结构(control structure),组织语句块
  • 对你的代码进行单元测试和集成测试,并排除其中的错误
  • 评审开发团队其他成员的底层设计和代码,并让他们评审你的工作
  • 润湿代码,仔细进行代码的格式化和注释
  • 将单独开发的多个软件组件集成为一体
  • 调整代码(tuing code),让它更快、更省资源

既然 “构建” 中包含这么多的活动,你也许会说:“OK,伙计,还有哪些活动不是 '构建' 的一部分呢? ” 这是个好问题。一些重要的非构建活动包括管理(management)、需求分析、软件架构设计、用户界面设计、系统测试,以及维护。

1.2 软件创建的重要性

  • 构建活动是软件开发的主要组成部分。根据项目规模的不同,构建活动在整个软件开发活动总时间中所占的比例一般在30%至80%之间。在整个项目中占有这么多时间的活动必然会影响到项目的成败
  • 构建活动是软件开发中的核心活动。
  • 把主要精力集中于构建活动,可以大大提高程序员的生产率
  • 构建活动的产物-源代码-往往是对软件的唯一精确描述
  • 构建活动是唯一一项确保会完成的工作

2. 利用隐喻对编程进行更深刻的理解

计算机科学领域中有着所有学科中最为丰富多彩的语言。你走进一间安全严密、温度精确控制在20℃的房间,并在里面发现了病毒(virus)、特洛伊木马(trojan horse)、蠕虫(worm)、臭虫(bug)、逻辑炸弹(bomb)、崩溃(crash)、论坛口水战(flame)、双绞线转换头(twisted sex changer)、还有致命错误(fatal error)。。。。在其他领域中,你能遇得到这些吗?

这些形象的隐喻(比喻)描述了软件领域中各种特定的现象和事物。像这样生动活泼的隐喻还能够描述更加广泛的现象。借助这些隐喻,我们能更深刻地理解软件开发的过程。

2.1 隐喻的重要性

重要的研发成果常常产自类比(analogy)。通过把你不太理解的东西和一些你较为理解、且十分类似的东西做比较,你可以对这些不太理解的东西产生更深刻的理解。这种使用隐喻的方法叫做 “建模(modeling)”

科学史中到处都可以看到借助隐喻的力量而产生的新发现。化学家凯库勒曾梦见一条蛇咬着自己的尾巴,醒来后他意识到类似的环状分子结构正好能够解释苯的各种特性。

气体的分子运动理论则是基于一种所谓的 “撞球” 模型,它把气体分子想象成有质量且彼此之间发生弹性碰撞的小球,就像撞球一样。很多有用的理论就是基于这个模型提出来的。

而光的波动理论原则则主要是在研究光和声音之间相似性的基础上发展起来的。光和声音都有振幅(亮度、响度)、频率(颜色、音调)和其他一些共有属性。对声波理论和广播理论进行对比研究的成果丰富,科学家们甚至付诸大量的努力,想寻找一种能在真空中传播光波的介质,并将这种介质命名为 “以太(ether)”- 但他们从未找到这种介质。虽然类比催生了丰盛的成果,这一次它却把人们引入了歧途。

不过总的来说,模型的威力就在于其生动性,让你能够把握整个概念。它能隐隐地暗示各种属性(properties)、关系(relationships)以及需要补充查证的部分(additional areas of inquiry)。不过有时候,当隐喻的概念被过度引申时,模型也会误导人们。当科学家们寻求 “以太” 的时候,他们就是过度地引申了模型。

正如你所预期的那样,有些隐喻比其他一些更贴切。一个好的隐喻应该是简单的,它与另一些相关的隐喻联系密切,且能够解释大部分实验证据及其他已观测到的现象。

案例:把一块沉重的石头绑在细绳上让它来回摆动。

信奉亚里士多德学说的人:重物体自然地从高处坠落

伽利略:看到这个现象的时候想到了钟摆

这两种模型的启发能力是完全不一样的。正是因为他们所用的模型不同,这使得它们看到了不同的现象,提出了不同的问题。

隐喻的价值绝不应低估。隐喻的优点在于其可于预期的效果:能被所有的人理解。不必要的沟通和误解也因此大为减低,学习与教授更为快速。实际上,隐喻时对概念进行内在化(internalizing)和抽象(abstracting)的一种途径,它让人们在更高的层面上思考问题,从而避免低层次的错误。

人们常常轻视隐喻的力量。对前面的每一个例子而言,很自然地有人会说:“嗯,恰当的隐喻当然是更有用,但其他隐喻都是错的!” 虽然这是一种很自然的反应,实际远非如此简单。科学发展的历史并不是一系列从 “错误” 的隐喻到 “正确” 得隐喻的转变,而是一系列从 “不太合适” 的隐喻到 “更好” 的隐喻的转变,也是从不是很贴切的隐喻到更贴切的隐喻的转变,还是从在一个方面暗示人们到在另一个方面暗示人们的转变。

事实上,那些被更好的新模型所替代的旧模型也依然是很有用的。工程师们依旧在是由牛顿力学来解决大部分的工程问题-虽然从理论上说,牛顿力学已经被爱因斯坦的理论所取代。

相对于其他学科而言,软件开发还是一门很年轻的学科,它还没有成熟到拥有一套标准隐喻的程度。因此必然存在许多或相互补充、或相互抵触的隐喻。某些隐喻相对好一些,而另一些则比较糟糕。你对隐喻有多理解,也就决定了你对软件开发有多理解。

2.2 如何使用软件隐喻

与其说一个软件隐喻像是一张路线图,还不如说它是一盏探照灯。它不会告诉你到哪里去寻找答案,而仅是告诉你该如何去寻找答案。隐喻的作用更像启示(heuristic,启发、试探法),而不是算法(algorithm)

算法是一套定义明确的指令,使你能完成某个特定的任务。算法是可预测的(predictable)、确定性的(deterministic)、不易变化的(not subject to chance)。一个告诉你如何从A点到B的算法,不会让你绕路,不会让你额外地经过D、E、F等地方,更不会让你停下来闻闻玫瑰花或喝杯咖啡。

而启发式方法(试探法)是一种帮你寻求答案的技术,但它给出的答案是具有偶然性的(subject to chance),因为启发式方法仅仅告诉你该如何去找,而没有告诉你要找什么。

算法和启发式方法之间的差别很微妙,两个术语的意思也有一些重叠。就本书的目的而言,它们之间的差别就在于其距离最终解决办法的简介程度:算法直接给你解决问题的指导,而启发方法则告诉你该如何发现这些指导信息,或者至少到哪里去寻找它们。

对于编程来说,最大的挑战还是将问题概念化(conceptualizing),编程中的很多错误都是概念性的错误。正因为每一个问题在概念上都是独特的,所以要找到一套能解决所有问题的一通百通的指导规则是很难得、甚至是不太可能的。如此看来,能一般性地知道大致如何解决问题,至少也和知道如何解决特定问题一样有价值了。

那么该如何使用软件中的隐喻呢?应该用它来提高你对编程问题和编程过程的洞察力:用它来帮助你思考编程过程中的活动,想象出更好的做事情的方法。你不可能看到一行代码并说它违反了本章所描述的某个隐喻。但随着时间的流逝,人们会发现,相对于不善运用隐喻的人来说,那些使用隐喻来照亮自己的软件开发过程的人,他对于编程的理解会更好,并且能够更快地写出更好的代码。

2.3 通常的软件隐喻

软件中的书法:写作代码

对于个人规模的工作乃至小型的项目来说,这种写信的隐喻已经足够了,然而对于其他场合而言,这个隐喻还远远不够-它没有完整、充分地刻画软件开发工作。书写通常只是个人的活动,而一个软件项目多半会涉及承担许多不同职责的很多人。

对写作而言,最重要的是其原创性,但是对于软件构建来说,“努力创造真正的原创成果” 的开发效率,往往低于专注于重用(reuse)以往项目的一些设计思想、代码以及测试用例(test case)的开发效率。总之,写作这一隐喻所暗示的软件开发过程太过简单、太过呆板了。

软件的耕作法:培植系统

相对于前面呆板的用写作所做的隐喻,一些软件开发人员则认为应当将创造软件想象成类似播种和耕作的情形。你一次设计系统的一小部分、写出一段代码、做一点测试,并将成果一点点添加到整个系统中。通过这种小步前进,你可以把每次可能遇到的麻烦减到最小。

有时候人么会用很糟的隐喻去描述一种很好的技术,此时需要保全这一技术,并去寻找更好的隐喻。这个离子的增量技术是很有价值的,但把它比作播种和耕作却非常糟糕。

“每次做一点” 这个主意可能在某些方面与农作物生长类似,但把软件开发类比为耕作就很不贴切,也没有太多意义,而且我们很容易用下面即将介绍的更好的隐喻替代它。人们也很难把耕作这个隐喻引申到 “一次做一点事情” 之外。如果你认同耕作这个隐喻。你会发现自己谈论的是:

对系统计划施肥、对细节设计疏果,并通过有效的管理土地来增加代码的产量,最终取得代码大丰收。你还会说 “轮种C++和大麦”,或者让土地闲置一年以增加硬盘里面氮肥的供应量。

软件耕作这一隐喻的弱点在于它暗示了人们将无法对开发软件的过程和方式进行任何直接的控制。你在春天播下代码的种子,然后按照农历节气向土地许几个愿,你将会在秋天收获到丰盛的代码

软件的牡蛎养殖观点:系统生长

在谈论培育(growing)软件的时候,有时人们实际上是指软件的生长(accretion),这两种隐喻时紧密相关的,而软件生长是一副更发人深省的景象。看到 “生长” 这个词,就算手头没有字典,我们也都能明白它指的是通过外在的增加或吸收而逐渐地生长或变大。“生长” 这个词描述了牡蛎制造珍珠的过程,逐渐地增添微量的碳酸钙。在地质学里,“accretion” 一词的意思是 “冲积层”,指的是水流中夹带的沉积物的冲积而不断扩大的陆地。在正式术语中,"冲积层" 是指海岸沿线的陆地因受到水流冲击,水中价带的物质不断沉积而形成的增长。

跟 "生长" 密切相关的另一些词语有:增量的(incremental)、迭代的(iterative)、自适应的(adaptive)、演进的(evolutionary)。以增量方式进行设计、编译和测试,都是目前已知的最强有力的软件开发概念。

在进行增量式开发时,我们先做出软件系统的一个尽可能简单、但能运行的版本。它不必接受真实的输入,也无须对数据进行真正的处理,更不用产生真实的输出-它仅仅需要构成一个足够强壮的骨架,支撑起未来将要开发的真实系统。对于你标志出每一项基本功能,可能仅需要调用虚假的类(dummy classes)。这个最基本的起点,就像牡蛎开始孕育珍珠的那颗细小沙粒。

在骨架形成之后,你要一点点地在其上附着肌肉和皮肤:把每个虚假的类替换成真正的类;不再假装接受输入,而是把接收真实输入的代码替换进去;不再假装产生输出,而是把产生真实输出的代码替换进去。你一次增加一小部分代码,直到得到一个完全可以工作的系统。

作为一个隐喻而言,增量式开发的优势在于未做过度的承诺。比起耕作那个隐喻来,对它作不恰当地引申要更困难一些。牡蛎孕育珍珠的图景也很好地刻画了增量式开发(或说生长)的情形。

软件构建:建造软件

与 “写作” 软件或者 “培育” 软件而言, “建造” 软件的图景就更加有用了。它和软件生长的概念是相通的,且提供了更详细的指引。建造软件这一说法暗示了软件开发中存在着诸多阶段,如计划、准备及执行等,根据所建造软件的不同,这些阶段的种类和程度可能会发生变化。进一步研究这一隐喻时,你还会发现许多其他方面的相似之处。

要搭一座四足的塔,你要有一双稳健的手,要找一个平坦的表面,以及十来个完好无损的啤酒罐。而要搭一座比它大100倍的塔,光是多100倍的啤酒罐还不够,还需要同时采用完全不同的计划方法和建造方法才行。

建筑这一隐喻让人们对超大型的软件项目的认识更加深刻。超大型的结构一旦出现问题,后果将非常严重,因此有必要对这样的结构进行超出常规的规划与建设。建筑人员需要非常小心地制定并核查设计规划,在建设时留有余地以保证安全:宁可多花10%的成本买更坚固的材料,也比摩天大楼倒下来要划算得多。还需要特别关注工作的时间。在建造帝国大厦的时候,每辆运料车运输时都留有15分钟的余地。如果某辆车没能在指定时间到位,则整个工期就会厌恶。

如果需要创造在经济规模上可以匹敌帝国大厦的庞大的软件项目,那么与之相当水准的技术与管理控制也是必需的。

按房屋建筑所做的这一隐喻,可以向许多其他方向引申-这也是隐喻这一方法如此强有力的一个原因。有很多常见的软件开发术语都是从建筑这一隐喻中衍生出来的:软件架构(建筑学 architecture)、支撑性测试代码(脚手架,scaffolding)、构建(建设,construction)、基础类(foundation classes)以及分离代码(tearing code apart)。

应用软件技术:智慧工具箱

技术并不是规矩(rule),它只是分析工具(analytical tools)。好的工匠知道完成某项工作要用哪样工具,也知道该怎样正确地使用。

组成各种隐喻

因为隐喻时一种启发方法而不是算法,因此它们彼此并不排斥。你可以同时使用生长(accretion)和建筑(construction)这两个隐喻。

使用隐喻又是件说不清楚的事情(fuzzy business)。你需要适当地引申它的含义,才能从蕴含的深刻启发中受益。但若你过分地或者在错误的方向上隐身了它的含义,它也会误导你。正如人们会误用任何强大的工具一样,你也可能误用隐喻,但它的强大的功效,还会成为你智慧工具箱中的一个宝贵部分。

posted @ 2023-03-06 17:04  LHX2018  阅读(130)  评论(0编辑  收藏  举报