去年写的代码大全笔记(其实是摘记)

Reader: Roof

前言:

    虽然近年来前卫的开发实践迅速发展,但普通的实践手段并没有太大变化。很多程序的开发依然漏洞百出、迟于交付并且超出预算。
    软件业的现状是,很多程序员并未接受正规的软件开发教育。
    软件业的内行人士,几乎没有心情去分享他们的编程经验和技术,即使有,也是非常零散的。
    学习并掌握不止一门语言通常是专业程序员职业生涯的分水岭。
    软件构建这个阶段,常常被忽视。
    “代码构建”一般占据了小型项目的65%的工作量,而在中型项目上也达到了50%,同时,“构建”也要为小型项目中的75%的错误负责,在中到大型项目上这一比例也达到了50% 。而任何要为50%到75%的错误负责的活动环节,显然是应该加以改善的。
    软件中一些代价最为昂贵的错误,其罪魁祸首常常是一些小范围的代码错误,其代价甚至可以飙至上亿美元的程度。

第1章:欢迎进入软件构建的世界

    (软件构建中)在太不正规和太正规之间找一个平衡点是不容易的。
    构建活动主要关注于编码与调试,但也包含详细设计、单元测试、集成测试以及其他一些活动。
    构建有时也被认为是“编码(coding)”或“编程(programming)”。“编码”算不上是最贴切的词,因为它有一种“把已经存在的设计机械化地翻译成计算机语言”的意味;而构建不是那么机械化的,需要可观的创造力和判断力。
    构建活动的产物---源代码---往往是对软件的唯一精确描述。
    现实中不那么完美的软件项目,往往跳过需求和设计的阶段而直接跃入构建环节,之后又由于有太多的错误要修正而时间又不够,测试环节也被抛到一边了。但是,无论一个项目的计划有多匆忙、多糟糕,它都不可能扔下构建活动---这是不可或缺的环节。
    在构建活动期间,不同程序员的生产率的差异可达10到20倍。

第2章:用隐喻来更充分地理解软件开发

    重要的研发成果常常产自类比。通过把你不太理解的东西和一些你较为理解、且十分类似的东西做比较。
    有时候,当隐喻的概念被过度引申时,模型也会误导人们。
    科学发展的历史并不是一系列从“错误”的隐喻到“正确”的隐喻的转变,而是一系列从“不太合适”的隐喻到“更好”的隐喻的转变。
    对于编程来说,最大的挑战还是将问题概念化,编程中的很多错误都是概念性的错误。
    Capers Jones发表的报告称,一套100万行的代码的软件系统,平均需要69种文档(1998)。其需求规格文档一般有四五千页长,而设计文档常常是需求的两三倍长。

第3章:三思而后行:前期准备

    目前,软件开发中最常见的项目风险是糟糕的需求分析和糟糕的项目计划,因此准备工作就倾向于集中改进需求分析和项目规划。
    你可能会认为,所有的专业程序员都知道准备工作的重要性,并且在跃入构建活动之前检查确认所有先决条件都已经满足了。很不幸,这不是事实。
    在实现一个系统之前,你需要理解“这个系统应该做什么”,以及“它该如何做到这些”。
    发现错误的时间要尽可能接近引入该错误的时间。缺陷在软件食物链里面呆的时间越长,它对食物链的后继造成损坏就越严重。
    我们已经非常详细地研究了需求和设计,我想不出在编码和调试期间还会遇到什么大问题。
    忽略前期准备的迭代式开发法,最终明显会比“密切关注前期准备工作的序列式开发法”付出更高的代价。
    预先详细说明100%的需求和设计是不切实际的,不过对绝大多数项目来说,“尽早把哪些是最关键的需求要素和架构要素确定下来”是很有价值的。
    问题定义应该由客户的语言来书写,而且应该从客户的角度来描述问题。通常不应该用计算机的专业术语叙述。最好的解决方案未必是一个计算机程序。……这条规则也有例外,那就是需要解决的就是计算机本身相关的问题。
    如果没有一个良好的问题的定义,你努力解决的可能是一个错误的问题。
    “进度”和“成本”这两个字眼比咖啡和洗冷水都要提神,许多“必须要有/must haves”很快会变成“有就最好/nice to haves”。
    在提到实施这个项目的商业理由的时候,许多需求事项就会从你眼前消失。有些需求作为功能特色来看是不错的想法,但是当你评估“增加商业价值”时就会觉得它是个糟透了的主意。那些记得“考虑自己的决定所带来的商业影响”的程序员的身价与黄金相当。
    你对全部需求感到很舒服吗?你是否已经去掉了那些不可能实现的需求---那些只是为了安抚客户和老板的东西?
    有一份对设计实践的综述发现,“维护‘设计的缘由’ ”至少与“维护设计本身”一样重要。
    瞄准80/20法则:对那些构成系统80%行为的20%的类进行详细说明。
    数据通常只应该由一个子系统或一个类直接访问;例外的情况就是透过访问器类或访问器子程序---以受控且抽象的方式---来访问数据。
    架构应该使我们很容易地做到:砍掉交互式界面的类,插入一组命令行的类。
    架构应该描述一份管理稀缺资源的计划。稀缺资源包括数据库连接、线程、句柄等。
    错误处理已经被证实为现代计算机科学中最棘手的问题之一,你不能武断地处理它。
    有人估计程序中高达90%的代码是用来处理异常情况、进行错误处理、或做簿记(housekeeping)工作,意味着只有10%的代码是用来处理常规的情况。
    因为错误处理牵连到整个系统,因此最好在架构层次上对待它。
    每个类在验证其数据的有效性方面需要负何种责任?是每个类负责验证自己的数据有效性,还是有一组类负责验证整个系统的数据的有效性?某个层次上的类是否能假设它接收的数据是干净的?
    在软件中,链条的强度不是取决于最薄弱的一环,而是等于所有薄弱环节的乘积。
    通常在架构中明确地设立期望目标,就能避免出现“某些类异常健壮,而其他类勉强够健壮”的现象。
    架构应该描述所有主要决策的动机。谨防“我们向来这么做”这种自认为有理的说法。
    最后,你不应该担忧架构的任何部分。架构不应该包含任何仅仅为了取悦老板的东西。它不应该包含任何对你而言很难理解的东西。你就是那个实现架构的人;如果你自己都弄不懂,那怎么实现它?
    

第4章:关键的“构建”策略

    一套好的符号系统能把大脑从所有非必要的工作中解脱出来,集中精力去对付更高级的问题,从功效上看,能够有效的提高人类的智力。
    研究表明,编程语言的选择从多个方面影响生产率和代码质量。
    Sapir-Whorf假说是,你思考的能力取决于你是否知道能够表达该思想的词汇。
    成功编程的一个关键就在于避免随意的变化,这样你的大脑可以专注于那些真正需要的变化。
    “你如何面对自己的编程工作”,取决于你在技术浪潮中所处的位置。
    David Gries所言,编程工具不应该决定你的编程思路。
    大多数重要的编程原则并不依赖于特定的语言,而依赖于你使用语言的方式。如果你使用的语言缺乏你希望用的构建,或者倾向于出现其他种类的问题,那就应该试着去弥补它。发明你自己的编码约定、标准、类库以及其他改进措施。
    在开始编程之前,做好一些约定(convention)。“改变代码使之符合这些约定”是近乎不可能的。
    

第5章:软件构建中的设计

    根据Horst Rittel和Melvin Webber的定义,“险恶的(wicked)”问题就是那种只有通过解决或者部分解决才能被明确的问题。这个看似矛盾的定义其实是在暗示说,你必须首先把这个问题“解决”一遍以便能够明确定义它,然后再次解决该问题,从而形成可行的方案。这一过程已经如影随形地在软件开发中存在数十年了(Peters and Tripp 1976)。
    软件设计的成果应该是组织良好、干净利落的,然而形成这个设计的过程却并非如此清爽。
    从本质上说软件开发就是不断地去发掘错综复杂、相互连接的整套概念的所有细节。
    当项目确由技术因素导致失败时,其原因通常就是失控的复杂度。
    我们不应该试着在同一时间把整个程序都塞进自己的大脑,而应该试着以某种方式去组织程序,以便能够在一个时刻可以专注于一个特定的部分。
    一旦你能理解软件开发中任何其他技术目标都不如管理复杂度重要时,众多设计上的考虑就都变得直截了当了。
    (关于层次性)假设你正在编写一个新系统,其中用到很多设计不佳的旧代码,这时你就应该为新系统编写一个负责同旧代码交互的层。在设计这一层时,要让它能隐藏旧代码的低劣质量,同时为新的层次提供一组一致的服务。
    应该通过限制子系统之间的通信来让每个子系统更有存在意义。
    信息隐藏是软件的首要技术使命中格外重要的一种启发式方法,因为它强调的就是隐藏复杂度,这一点无论是从它的名称还是其实施细节上都能看得很清楚。
    设计类的接口与设计其他环节一样,都是一个迭代的过程。
    在少数情况下,信息隐藏是根本不可能的。不过大多数让信息无法隐藏的障碍都是由于惯用某种技术而导致的心理障碍。
    一种更为隐晦的信息隐藏障碍则是循环依赖。
    信息隐藏的最后一个障碍是试图在系统架构层和编码层均避免性能上的损耗。
    请养成问“我该隐藏些什么?”的习惯。
    业务规则很容易成为软件频繁变化的根源。
    状态变量用于表示程序的状态,与大多数其他的数据相比,这种东西更容易改变。
    找出容易发生变化的区域的一个好方法是:首先找出程序中可能对用户有用的最小子集。这一子集构成了系统的核心,不容易发生改变。接下来,用微小的步伐扩充这个系统。
    应用模式的一个潜在的陷阱是强迫让代码适用于某个模式。
    二分法查找算法是很优雅,可往往一个蛮力的、顺序的查找算法就足够了。
    有时候你会从开发某个程序中学到很多的知识,多得让你想带着写第一遍时所获得的体会再写一遍。
    自上而下设计通常比较容易上手,但是有时候会受底层复杂度的影响,这种影响甚至有时会使事情变得比实际的情况更复杂。自下而上设计开始起来比较复杂,但是在早期鉴别出系统的复杂度,却有助于设计出更好的高层类。当然这样做的前提是复杂度没有先把整个系统破坏掉!
    对于实施正式编码阶段前的设计工作量和设计文档的正规程度,很难有个确定的定论。有很多因素,如团队的经验、系统的预定寿命、想要得到的可靠度、项目的规模和团队的大小等等都需要考虑进去。
    有一些人鼓吹软件是一项遵守纪律的活动,他们花了相当多的精力来让我们感到愧疚。我们耗尽毕生精力也到不了“足够结构化”和“足够面向对象”的极乐世界。我们都背负着“在可塑性很强的年纪学过Basic”的原罪。但是,我敢打赌我们中的大多数人都是比那些纯化论者更优秀的设计师,尽管他们不愿意承认这一点。---P.J.Plauger
    请把设计看成是一个险恶的、杂乱的和启发式的过程。
    问题求解并不是一个确定性的活动,如果你固执在某一种方法之上,那么无异于作茧自缚。

第6章:可以工作的类


    成为高效程序员的一个关键就在于,当你开发程序任一部分的代码时,都能安全地忽视程序中尽可能多的其余部分。
    要想理解面向对象编程,首先要理解ADT。
    尽可能选择最高的抽象层次。
    阅读代码的次数要比编写代码多得多,即使在开发的初期也是如此。因此,为了让编写代码更方便而降低代码的可读性是非常不经济的。
    每当你发现自己是通过查看类的内部实现来得知该如何使用这个类的时候,你就不是在针对接口编程了,而是在透过接口针对内部实现编程了。
    研究表明,人们在做其他事情时能记住的离散项目的个数是7加减2。
    如果派生类不准备完全遵守由基类定义的同一个接口契约,继承就不是正确的实现技术了。
    如果你只是想使用一个类的实现而不是接口,那么就应该采用包含方式,而不该用继承。
    人们已经发现,过深的继承层次会显著导致错误率的增长。
    为了不确定的性能提高而增加复杂度是不妥的。
    为现实世界中的对象建模也许不是创建类的唯一理由,但它仍是个很好的理由!
    要避免创建什么都知道、什么都能干的万能类。
    只有行为而没有数据的类往往不是一个真正的类。
    类的接口应该提供一致的抽象。很多问题都是由于违背该原则而引起的。

第7章:高质量的子程序

    指针操作的可读性通常都很差,而且也很容易出错。通过把这些操作隔离在子程序内部,你就可以把精力集中于操作的意图本身,而不是指针操作机制的细节。
    为了理解程序的流程,通常并没有必要去研究那些复杂的布尔判断的细节。应该把这些判断放入函数中,以提高代码的可读性。
    我们的目标是让每一个子程序只把一件事情做好,不再做其他事情。
    研究表明,变量名的最佳长度是9到15个字符。
    对于超过200行代码的子程序来说,没有哪项研究发现它能降低成本和/或降低出错率,而且在超过200行后,你迟早会在可读性方面遇到问题。
    由Basili和Perricone所做的一项被广为引用的研究发现,程序中有39%的错误都是属于内部接口错误---也就是子程序间互相通信时所发生的错误。
    如果你发现自己一直需要传递很多参数,这就说明子程序之间的耦合太过紧密了。
    子程序的名字是它的质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计得很差劲。如果名字糟糕而且又不准确,那么它就反映不出程序是干什么的。不管怎样,糟糕的名字都意味着程序需要修改。

第8章:防御式编程

    在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机将要做什么。
    断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。
    处理错误最恰当的方式要根据出现错误的软件的类别而定。
    确定一种处理错误参数的方法,是架构层次的设计决策,需要在那里的某个层次解决。
    检查函数的返回值。即使你认定某个函数绝对不会出错,也无论如何要去检查一下。
    仅在真正例外的情况下才使用异常---换句话说,就是仅在其他编码实践方法无法解决的情况下才使用异常。
    如果某种的错误情况可以在局部处理,那就应该在局部处理掉它。不要把本来可以在局部处理掉的错误当成一个未被捕获的异常抛出去。
    仅仅因为编程语言提供了提供了异常处理机制而使用异常,是典型的“为用而用”;这也是典型的“在一种语言上编程”而非“深入一门语言去编程”的例子。
    让软件的某些部分处理“不干净的”数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再担负检查错误数据的职责。
    隔栏外部的程序应使用错误处理技术,在那里对数据做的任何假设都是不安全的。而隔栏外部的程序里就应使用断言技术,因为传进来的数据应该已在通过隔栏时被清理过了。
    你越早引入辅助调试的代码,它能够提供的帮助也越大。
    要考虑好什么地方你需要进行防御,然后因地制宜地调整你进行防御式编程的优先级。
    

第9章:伪代码编程过程

    伪代码一经写好,你就可以按照它去生成伪码了,同时还把伪代码变成编程语言中的注释。
    程序员们更喜欢使用伪代码,因为它简化了用编程语言进行构建的工作,且有助于发现细节设计的不足之处。
    如果你发现由一段伪代码发展形成的代码量超出了预期,那么就把这些代码重构为一个单独的子程序。
    已经从迷信转为理解的程序员们总会先怀疑自己的工作出了问题。
    如果你没有陷入这种“拼凑加编译”的怪圈,那就在你觉得合适的时候再去编译吧。

第10章:使用变量的一般事项

    在你引入一个新变量的时候对它做出声明,哪怕编译器不要求你一定要这样做。这样做虽然不会捕捉所有的错误,但至少能发现其中的一部分。
    在靠近变量第一次使用的位置初始化它。
    一般而言,把对一个变量的引用局部化,即把引用点尽可能集中在一起总是一种很好的做法。
    尽可能缩短变量的“存活”时间。
    变量的存活时间开始于引用它的第一条语句,结束于引用它的最后一条语句。
    通过使用一些巧妙的方法,可以给一个变量赋予多种职责。不过你最好还是远离这些奇技淫巧。
    早期绑定会降低灵活性,但有助于减小复杂度。晚期绑定可以增加灵活性,同时增加复杂度。

第11章:变量名的力量

    W.J.Hansen所做的一项研究表明,较长的名字适用于很少用到的变量或者全局变量,而较短的名字则适用于局部变量或者循环变量。
    lim不是一个合法的下标。它与last都是与first相对应的概念。不同之处是lim表示的是一个数组中并不存在的上界;而last表示的则是最终的、合法的元素。通常lim等于last+1。
    记住,名字对于代码读者的意义要比对作者更重要。
    在20世纪70年代,一条Fortran FORMAT语句中的句号错写成了逗号。结果科学家们算错了太空飞船的轨道,导致了太空探测器的丢失---损失高达16亿美元。
    

第12章:基本数据类型

    一条很好的经验法则是,程序主体中仅能出现的文字量就是0和1。
    国际市场的重要意义正在日益突现,翻译存放在字符串资源文件中的字符串要比翻译存在于代码中的字符串容易得多。
    不同于仅仅判断一个布尔表达式,你可以把这种表达式的结果赋给一个变量,从而使得这一判断的含义变得明显。
    作为一项一般性的原则,任何一种能够对可能发生改变的事物进行集中控制的技术,都是减少维护工作量的好技术。
    用一款文本编辑器来寻找代码里的2、3、4、5、6、7、8、9,以确认你没有由于不小心而使用了它们。
    在你习惯性的选用数组之前,考虑能否用其他可以顺序访问数据的容器类作为替换方案---如集合、栈、队列等。
    在C中结合ARRAY_LENGTH()宏来使用数组。
    创建自定义类型的最大优点,就在于它提供了介于你的程序和实现语言之间的一层绝缘体。
    你可能需要为标准类型定义替代类型,以便让变量在不同的硬件平台上正确地代表相同的实体。
    

第13章:不常见的数据类型

    指针错误的症状常常与引起指针错误的原因无关。因此,更正指针错误的大部分工作量便是找出它的位置。
    在C语言里,在释放内存区域之前用垃圾数据来覆盖这些内存区域,可以让与使用已释放的指针有关的错误的表现方式更以致。
    一种常见的指针错误是“悬空指针”,即使用一个已经被delete或者free的指针。
    如果你还没有养成使用auto_ptr的习惯,那么就努力吧。
    强制类型转换关闭了编译器检查类型不符的功能,因此在你的防御式编程的铠甲上挖了一个洞。一个需要很多强制类型转换的程序在架构方面可能就存在一些需要修正的问题。
    即便全局数据并不总是引发错误,也很难将其作为最佳的解决方法。
    如果你随意使用全局数据,或者认为不能随心所欲地使用它们是一种约束,那么你可能还没有充分理解信息隐藏和模块化的意义。
    你用全局数据能做的任何事情,都可以用访问器子程序做的更好。使用访问器子程序是实现抽象数据类型和信息隐藏的一种核心方法。

第14章:组织直线型代码

    如果有人在阅读你代码的时候不得不搜索整个应用程序以便找到所需的信息,那么就应该重新组织你的代码了。
    如果代码之间没有顺序依赖关系,那就设法使相关的语句尽可能的接近。
    

第15章:使用条件语句

    也许有时候你只剩下一种情况需要处理,于是就决定把这种情况编写为default子句(默认子句)。尽管这么做有时候很诱人,但却是不明智的。
    

第16章:控制循环

    一份针对程序员学员的研究把它和那些在开始或者结尾位置终止的循环做了对比。使用了带退出循环以后,学员们在一份理解力测试中的得分提高了25% 。
    for循环的关键之处在于,你在循环头处把它写好后就可以忘掉它了,无须在循环的内部做任何事情去控制它。如果存在一个必须使执行从循环中跳出的条件,那么就应该改用while循环去处理。
    避免空循环。
    不要为了终止循环而胡乱改动for循环的下标。
    避免出现依赖循环下标最终取值的代码。
    如果语言支持,请使用带标号的break结构。
    如果你开始接受编写简单代码这一原则,那就很少会写出超过15或者20行的循环。
    研究表明,当嵌套超出3层以后,程序员对循环的理解能力会极大的降低。
    

第17章:不常见的控制结构

    不要用递归去计算阶乘或者斐波那契数列。
    在某些情况下,一个完全知道goto不是好选择的程序员,也会为了增强可读性和可维护性而选用goto。
    在很大程度上,软件开发这一领域是在限制程序员对代码的使用中得到发展的。
    

第18章:表驱动法

    表驱动法的优势之一就是你可以把表里面的数据存放在文件中,在程序运行时再读取这些数据。这样一来,就可以在不改动程序本身的情况下调整保险费率等参数。
    正如Butler Lampson, Microsoft公司一位杰出的工程师所说,最好是去找一种好的方案并且同时避免引发灾难,而不要试图去寻找最佳的方案。
    

第19章:一般控制问题

    请在布尔表达式的判断里采用true和false来代表真和假。如果你的语言并不直接支持这些写法,那么就用预处理宏或者全局变量来创建它们。
    与其写一个庞大的、具有很多项的复杂判断,还不如把中间结果赋给变量,让你可以执行一个更简单的判断。
    用括号使布尔表达式更清晰。
    更好的做法是使用嵌套的判断语句来表明你的用意,而不要依赖于求值顺序和短路求值。
    按照数轴的顺序编写数值表达式。
    在C家族语言中,应该把常量放在比较的左端。
    为空语句创建一个DoNothing()预处理宏或者内联函数。
    过分深层的缩进,或者“嵌套”,已经困扰了计算机界长达25年之久,并且至今仍然是产生混乱代码的罪魁祸首之一。
    结构化编程的核心思想很简单,那就是一个应用程序应该只采用一些单入单出的控制结构。
    “程序复杂度”的一个衡量标准是,为了理解应用程序,你必须在同一时间记住智力实体的数量。这种智力游戏可以说是编程中最难的方面之一,这也使编程需要比其他任何活动都要专心。它也是程序员对“不时被打断”特别的反感---这种打断就相当于让一位杂耍艺人一边抛接三个球,一边帮你照看商店。
    Edsger Dijkstra已经就复杂度的危险提出警告:“有能力的程序员会充分地认识到自己的大脑容量是多么地有限;所以,他会非常谦卑地处理编程任务”。
    你可以通过做一些脑力练习来提高你自身的脑力游戏水平。不过,编程本身的训练就已经足够多了。

第20章:软件质量概述

    要让所有特性都能表现得尽善尽美是绝无可能的。需要根据一组互相竞争的目标寻找出一套优化的解决方案,正是这种情况使软件开发成为一个真正的工程学科。
    明确设置质量目标是开发高质量软件的一个简单而清晰的步骤,但它常常被忽视。
    有的方法如代码检查,一举可以确定问题的现象和原因;而另一些方法如测试,则只能发现问题表象,而要找到并从根本上修正缺陷还需要额外的工作。
    绝大多数项目的最大规模的一种活动就是调试以及修正那些无法正常工作的代码。
    

第21章:协同构建

    CMU软件工程研究所的调查表明,在设计过程中开发人员平均每个小时会引入1到3个缺陷,在编码阶段则会平均每小时引入5到8个,因此攻击这些盲点就成为了有效构建的关键。
    关于结对编程的早期报告指出,它的代码质量能够达到与正式检查相近的水平。全程采用结对编程的成本可能比单人开发要高大约10%~25%,但开发周期大概会缩短45% 。虽然很多情况下这样的结果相对于代码检查来说并无优势,但却大大超越了单人开发的效率。
    IBM发现,一小时的代码检查能够节省大约100小时的相关工作。
    如果两个人整天把时间浪费在争论风格的问题上,那么结对编程就不可能发挥它的威力。
    有时个人性格之间的冲突会导致组合的效能出问题,强迫无法配对的两个人进行组合是毫无意义的,因此请对个性匹配的问题保持警觉。
    用于详查的核对表有助于集中注意力。由于详查有标准的核对表和标准的角色,因此它是一个系统化过程。
    不要指望公开演示能成为提高产品技术质量的灵丹妙药。
    

第22章:开发者测试

    测试永远不可能彻底证明程序中没有错误。
    测试本身并不能改善软件的质量。测试的结果是软件质量的一个指示器,但是结果本身并不改善软件质量。想通过更多测试来改善软件的质量,就跟妄想通过天天称体重来减肥一样。
    你必须期望在你的代码里有错误。尽管这种期望似乎有悖于常理,但是你应该期望找到这个错误的人是你,而不是别人。
    作为正式测试方法的补充,优秀的程序员会使用各种不太规矩的、启发式的方法去寻找他们代码中的错误,其中一种启发式方法就是猜测错误。
    正确设计的随机数生成器可以产生你意想不到的、不寻常的测试数据组合。
    详细完整的日志记录可以为诊断错误提供帮助,还可以在产品发布之后为客户提供有效的服务。
    许多人都遇到过这样的事情:对同样的数据测试了100遍,其中99次都成功了,但就是有一次失败了。这种问题几乎总是源于忘记对某处变量进行初始化了。
    存放以往错误的数据库是另一种强大的测试工具,这样一个数据库既是管理工具,又是技术工具。
    除非在每次修改后重新对程序进行系统化的测试,否则要开发出一个高质量的软件产品几乎是痴人说梦。
    就重要性而言,测试应当与设计和编码平起平坐,这就要求项目为测试分配时间,重视测试并保障这一过程的质量。

第23章:调试

    你所面对的程序一定有一些东西需要你去了解。因为如果你确实已经透彻地理解了它,这个程序就不应该还有缺陷,你应该早就纠正了这些缺陷。
    即使某个错误初看似乎不能归咎于你,但出于你自身的利益,最好还是假设它的产生同你有关。
    程序员们在调试中陷入困境的一个原因是他们在一条死胡同里面走得太久。把要尝试的事情列出来,如果某种方法不能奏效,那就换一种方法。
    使用代码质量核对表来激发你对可能发生的缺陷的思考。
    暂时放弃思考的好处是可以减少调试带来的焦虑。不时潜入头脑中的焦虑感是一个明显的信号:到了该休息的时候了。
    人们往往会放弃让缺陷无处遁形的彻底系统分析,而去进行快速的尝试。我们每个人的投机心理都宁愿去用一种有可能在五分钟内发现缺陷的高风险方法,也不愿意为某种保证能找出缺陷的方法花上半个小时。这里的风险就是如果五分钟的方法没有奏效,你也变得麻木了。一旦把使用这种“简便”的查找方法作为信条,那么几个小时也许就在无所建树中流逝了,甚至是几天、几周、几个月……有多少次你花上了两个小时来调试原本只用三十分钟就写出来的代码?
    不要轻信编译器的第二条信息。
    “调试之魔鬼指南”说得很对:如果想让自己的生活潦倒,让自己的代码质量一塌糊涂的最好方法,就是不懂装懂地动手修补程序缺陷。
    忽略编译器所提示的程序错误太过草率,关掉编译器的警告功能则无异于掩耳盗铃。
    你是否会把调试看做是能让你更好地理解程序、错误、代码质量和解决问题方法的良机?
    

第24章:重构

    演化一开始就充满危险,但同时也是使你的软件开发接近完美的天赐良机。
    专家们认为,对未来需求有所准备的办法并不是去编写空中楼阁式的代码,而是尽可能将满足当前需求的代码清晰直白地表现出来,使未来的程序员理解这些代码到底完成了什么功能,没有完成什么功能。
    有的重构的步伐比其他重构更大,到底什么能算成是一次重构并不明确。因此请把重构的步伐放小点。
    事情很简单:应该把简单的修改当作复杂的修改。
    你是否同一时间只处理一项重构?
    你是否避免了将重构作为先写后改的代名词,或者作为拒绝重写拙劣代码的托词?
    开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想编程现实。请珍惜这些开发阶段的天赐良机!

第25章:代码调整策略

    对用户来说,程序员按时交付软件,提供一个清爽的用户界面,避免系统死机常常比程序的性能更为重要。
    Barry Boehm为我们讲述了一个故事,TRW公司开发一套系统,客户最初要求该系统响应时间不能超过1秒。这样的需求直接导致了一套极其复杂的设计方案以及1亿美元的预算。公司经过进一步研究,认为在90%的情况下即使是4秒的系统实际响应时间,也能满足客户需求。经过如此修改的系统需求为公司节约了大约7000万美元。
    在花费时间处理一个性能问题之前,请想清楚你的确是在解决一个确实需要解决的问题。
    性能问题的很多方面都是违反直觉的。
    如果没有测量性能变化,那么你想当然的优化结果不过是使代码变得更为晦涩难懂了。
    

第26章:代码调整技术

    你可以尽情的使用可读性更好的方法,而把代码运行速度放到稍后来处理。
    事实上,在任何使用线性查找的场合你都可以使用哨兵法---从链表到数组。需要注意的是你必须仔细选择哨兵值,并小心地把它们放到数据结构中去。
    编写桌面应用程序的人可以对优化毫不关心,但那些为嵌入式系统、实时系统和其他对代码有着严格速度和资源限制的系统编写代码的人仍然可以从中获益。
    

第27章:程序规模对构建的影响

    如果你习惯于开发小项目,那么你的第一个中大型项目有可能严重失控,它不会像你憧憬的那样成功,而会变成一头无法控制的野兽。
    方法论对于代码质量的影响不大,对应用程序质量影响最大的通常是编写程序的各个开发者的技能。
    

第28章:管理构建

    如果项目中有人要制定标准,那么应该由一位受人尊敬的架构师来做,而不应该由管理者来做。在软件项目中,“专家层”起的作用至少与“管理层”相同。
    版本控制对于团队项目来说是必不可少的。当把版本控制、缺陷跟踪和变更管理整合到一起的时候,其威力会更大。Microsoft公司的应用程序部门甚至认为其专有的版本控制工具是一项“主要竞争优势”。
    根据Fred Brooks定律,往一个已经落后的软件项目里加入人手只会使它更加落后。
    但如果一个项目中的任务可以分割,那么你就可以把它进一步分细,然后分配给不同的人来做,甚至可以分配给项目后期才加进来的人。
    如果进度落后了,那么就调整“可选择”和“有了更好”的优先级,并扔掉那些不重要的功能。
    程序员不仅在编程上花时间,也要花时间开会、培训、阅读邮件以及纯粹思考。
    不同程序员在天分和努力程度方面的差别十分巨大,这一点与其他所有领域都一样。
    不是所有编程项目的管理者们都会认识到,有一些编程问题与信仰有关。如果你是一名管理者,并且试图要求统一某些编码实践,那么就可能会激怒你的程序员。
    在软件开发中,非技术出身的管理者随处可见,具有技术经验但却落后于这个时代10年(以上)的管理者也比比皆是。技术出色并且其技术与时俱进的管理者实属凤毛麟角。如果你正在为一位这样的管理者工作,那么就尽可能地保住你的工作吧。这可是非常难得的待遇。
    

第29章:集成

    增量集成有助于项目增长,就像雪球从山上滚下来时那样。
    在阶段式集成中,你一次集成许多组件,很难知道错误在哪。错误即可能位于其中任何一个组件,也可能位于组件之间的连接处。在增量集成中,错误通常要么是在新的组件中,要么是位于新组件和系统之间的连接处。
    “看到系统50%的功能已经能工作”总比“听到编码‘已经完成了99%’”更有说服力。
    无论你选用哪种集成策略,daily build和冒烟测试都是软件集成的好方法。
    

第30章:编程工具

    据报道,复杂性分析工具在系统维护的生产率方面有大约20%的正面效应。
    在短时间内编写高质量代码的一种好方法是不要全部自己编写,而去找一个开源的版本(或者购买一个 ).
    建造工具是编程的基本活动的一部分。
    如果你发现自己每天多次键入某个长度超过5个字母的命令,那么它就是脚本或批处理文件不错的候选者。
    就其本质而言,编程从根本上说就是困难的---即便有好的工具支援。无论能用到哪些工具,程序员都必须与凌乱的真实世界较力;我们须得严密地思考前后次序、依赖关系、异常情况;而且我们还要与无法说清楚自己想法的最终用户交往。我们是重要应对连接到其他软件或硬件的定义不清的接口,还要解决规章制度、业务规则以及其他复杂性之源,这些复杂性来自计算机编程之外的世界。

第31章:布局与风格

    使代码看起来有条理的最大意义莫过于展示出代码的结构。
    不要将赋值语句按等号对齐。
    每行仅写一条语句。
    每行只声明一个数据。
    注释的缩进要与相应的代码一致。
    注释的风格有力地影响着注释是否具有负面作用。

第32章:自说明代码

    解释性注释通常用于解释复杂、有巧、敏感的代码块。在这些场合它们能派上用场,但通常正是因为代码含混不清,才体现出这类注释的价值。如果代码过于复杂而需要解释,最好是改进代码,而不是添加注释。
    概述性注释是这么做的:将若干代码行的意思以一两句话说出来。这种注释比重复性注释强多了,因为读者读注释能比读代码更快。
    如果右边的空间有充裕,行尾注释对于数据声明的标注正合适。
    当有人说“这些代码富于技巧”时,我会认为他们的意思是“这些代码实在糟糕”。
    至少有一家公司根据自身实践得出结论---对数据注释比对使用数据的过程做注释更重要。
    

第33章:个人性格

    假如你是软件工程师,基本的建造材料就是你的聪明才智,主要工具就是你自己。
    老板无法强迫你成为好的程序员,很多时候他甚至无法判断你是否合格。
    一旦决心成为出色的程序员,你的发展潜力是很大的。各种研究发现,不同程序员创建某个程序所需的时间差异可达10:1;;同时还发现,不同程序员调试程序所需的时间、程序实现规模、速度、错误率和检查出的错误数目也能达到10:1。
    要充分理解一个普通的程序,你得有很强的吸收细节的能力并同时消灭它们。如何专注你的聪明才智,比你有多聪明更重要。
    如果分配给你的工作净是些不能提高自身技能的短期任务,你理应表示不满。
    阅读解决问题的有关方法。
    学习成功项目的开发经验。
    现代语言产品一般都带有大量函数库,很有必要投入时间去浏览其说明。
    你愿意阅读本书就很值得称赞。你已经学到了比软件专业中多数人都更多的知识,因为大部分程序员一年下来还看不完一本书。
    只要稍稍看一些书就会使你的专业知识又迈进一步。如果每两个月能看一本计算机好书,大约每周35页,过不了多久,你就能把握本行业的脉搏,并脱颖而出。
    熟练级的程序员对语言或环境(或两者兼具)有着专业技能。这一级的程序员也许精通J2EE的盘根错节,或者对《Annotated C++ Reference Manual》如数家珍。这些程序员都是所在公司的活宝,很多程序员再也不能超越该层次。
    技术带头人具有第三级的专业才学,并明白编程工作中只有15%用来和计算机交互,其余都是与人打交道的。程序员一般只花30%的时间单独工作,与计算机交互的时间则更少。技术带头人会为人写代码,而非为机器。真正高手所写的代码,像水晶一样晶莹剔透,还配有文档。他们可不会浪费其宝贵的脑力,去重新组织用一句注释就能说清楚的某块代码逻辑。
    当初学者或中级程序员不是错,当熟练级程序员而非技术带头人也无可厚非。但如果知道自己该如何改进后,还总是在初学者或者中级程序员阶段徘徊,就是你的不对了。
    力图理解编译器的警告,而非置之不理。
    透彻理解自己的程序,而不要只是编译看看能否运行。
    如果你工作10年,你会得到10年经验还是1年经验的10次重复?必须检讨自己的行为,才能获得真正的经验。只有坚持不懈地学习,才能获取经验;如果不这样做,就无法得到经验,无论你工作多少年。
    好习惯很重要,因为程序员做的大部分事情都是无意识完成的。
    行为养成习惯,年复日久这些好坏习惯就决定了你作为程序员的优劣。
    比尔盖茨说,任何日后出色的程序员前几年就做得很好。从那以后,程序员好坏就定型了。在你搞编程颇有些年头后,很难会突然说“怎样才能使这个循环再快些呢?”或者“如何让这段代码更好看懂呢?”优秀的程序员早就养成了这些习惯。
    

第34章:软件工艺的话题

    软件往往要通过实证而不是证明,这意味着它就得反复测试和开发,直至能正确解决问题为止。
    要对编程问题找出最有效的解决方案时,盲目迷信某种方法只会缩小你的选择余地。
    由于“工具箱”形象地比喻出抽象的折中思想,所以这个比喻很有用。
    软件开发中许多顽固的方法源于对错误的畏惧心理。“试图没有错误”是最大的错误。

第35章:何处有更多信息

    你犯的错误别人早已犯过,要是不想自讨苦吃,就读读他们的书吧,这样能够避免再走弯路,并找出解决问题的新方法。

全书阅读完毕:2009.08.09

你可能感兴趣的:(去年写的代码大全笔记(其实是摘记))