正文之前
熟知软件开发的人都知道这个行业里充满了一次次悲壮的失败,每一座成功项目的丰碑下都埋葬着无数同类型的失败项目。大多数软件项目都像是一次典型的死亡行军加班是一种习惯,并会逐渐产生依赖编程远远超过程序本身的概念
程序员应该知道的97件事
谨慎行动 技术债务就像一笔贷款。在短期内,你能从中得到好处,但是,在清偿之前,你要付出利息。代码里的捷径使得新功能更难于加入,也会影响到代码重构。它们是软件缺陷和失败测试用例的滋生地,放任它们的时间越长,情况就会越糟糕。往往是情况不可收拾时,你才不得不对它们进行修正,而那时你已没有足够的时间,也承担不起由此带来的风险必须时刻追踪技术债务,尽快的或者当情况急剧恶化的时候,立即将其偿还。每当你迫不得已欠下了技术债务,就要立即记录到人物卡上或者等级到问题追踪系统里,以保证其不会被遗忘函数式编程原则的应用 函数式编程能极大地提高你的代码质量引用透明性:函数不管在何时何地被调用,都保持一致的行为,同样的输入得到同样的输出结果试问自己“用户会怎么做”(你不能算是用户) 我们都爱假设别人的心思跟我们一样,但事实上不是这回事。心理学上把这种心理状态叫做虚假同感偏差(false consensus bias)。当人们的想法和行为跟我们不同时,我们很可能会(在潜意识里)将他们归位“能力低下”的人用户卡住的时候,他们会缩小他们的注意力范围,于是更难以看到在屏幕其他区域上显示的解决方法。因为用户注意力范围缩小,使用tool tip的效果胜过点击帮助菜单用户都倾向于能用就好。他们一旦找到一种可用的方法,就会一直用下去,不管它有多么费劲。因此,提供一条显而易见的操作途径,要好过提供两三条捷径编码标准的自动化 编码标准应该是动态的,不是一成不变的。随着项目的进展,项目需求的改变,一些在刚开始时显得灵活的标准可能在几个月后会变得不够灵活美在于简单 风格之美、和谐、优雅及优美的节奏尽在于简单——柏拉图优美的代码有许多共同的属性,首要的一点就是简单。一个应用或一个系统无论有多么复杂,其中每个单独的组成部门都保持着它的间接性。无论经过多少时间,干净、简单、可测试的代码保证了系统的可维护性,也确保了系统在整个生命周期里能快速开发升级美来自于简单,亦存在于简单在你重构之前 重构代码的最佳起点就是清理已有的基础代码和基于这些代码写的测试 我们都觉得自己可以比原有系统做得更好,这是因为我们没有向原有系统中的错误学习避开重写一切的诱惑 尽可能多的重用代码是最好的选择,无论代码是多么的丑陋,毕竟它已经被测试过了,也被审查过了抛弃旧代码——尤其是存在于产品中的代码——也意味着抛弃了经年累月测试并实战过的代码,以及你还未知晓的周边代码和bug补丁。这会浪费大量的时间、精力和多年积累下来的知识逐步增加的小改动胜过一次性的大改动在每次迭代之后,要确保已有的测试用户都已通过 假如已有的测试用例还没能覆盖到你所做的修改部分,那就增加新的测试用例个人好恶和利己主义不能参杂到开发中来 如果代码的风格或结构不符合你的个人喜好,你也不能把这当作代码重构的正当理由。即使你觉得自己可以比上一个程序员做得更好,也不能将它作为重构的理由。新技术不是重构的充分理由 关于重构的最差劲的理由之一就是当前代码跟我们目前拥有的很酷的技术相比已经远远落后了,我们相信用新的语言或框架来完成这些功能会更加优雅。除非成本效益分析结果表明这种新的语言或框架能在功能性、可维护性或生产力上会有显著的提升,否则,最好还是弃之不用要记住人总是会犯错误的 重构不能一直保证新代码会超过——或相当于——原先的代码谨防共享 创建共享的代码库之前,应该仔细检查上下文环境童子军规则 若各团队能把系统当成一个整体来维护,不再“各人自扫门前雪”,我们将看到软件系统里无可挽回的退化局面会终结,而且会逐渐变得更好把代码搞得一团糟的行为也应该像社会上乱丢垃圾行为一样不受待见,因为是该做的却没有做到的行为团队要相互帮助,相互清理代码,这对每一个人都有好处,而不仅仅是他们自己在责备别人之前先检查自己的代码 假如使用的工具是一个被广泛使用的、成熟的,不同技术领域都在用的产品,那就几乎没有理由去怀疑它的品质如果其他人报告说有问题,而你却无法重现时,那就走过去看看他们到底是怎么操作的谨慎选择你的工具 现在的应用程序很少从零开始构建的从小范围开始,只采用相对必需的工具领域语言里的代码代码就是设计 大幅削减建造成本,导致的结果却是质量恶化软件设计只有通过了一系列严格的测试验证之后才能算完成我们的希望在于改良的程序设计语言及设计的最佳实践伟大的设计是由伟大的设计者作出的,他们穷尽一生精通了该门技艺关于代码布局的麻烦事 易于检索清晰的布局紧凑格式代码审查 代码审查能提供改吗质量,降低差错率。一个组织很需要这样一个硬性的、正式的过程代码审查的目的不仅仅是简单的更正代码错误,其目的应该是共享知识、建立统一的编码指导标准代码审查的时候态度要温和,确保评语是有建设性的,不是刻薄的在代码审查会议上,对代码格式应该不做评论主要着眼于在团队成员之间分享知识,抛开讽刺性的评语编写代码的理由 避免使用可更改的全局变量,因为这会让所有使用它们的代码段产生依赖对注释的一个注释代码说不清,注释来补充 无法给代码增加价值的注释就是噪音。那些只会 模仿代码的注释无法给读者提供更多的信息应该像对待代码一样对待注释。每一段注释应该为读者注入一些价值,否则纯粹就是浪费,还不如删除,或者干脆重写不断学习 你需要为你自己的教育负起责任尽量为自己找到一个导师。如果自己就是最厉害的家伙,那会阻碍你的修习之路。虽然你可以从其他人身上学到点什么,但是,在那些更聪明、经验更丰富的人身上,你能学到更多。如果找不到导师,就换一个地方每年学习一门新的语言,至少要学用一门新的技术或工具。这可以帮助你拓宽新思路,充实你当前的技术储备易用不是一种能力 好的API要遵循一致的抽象层次,并展现出它的一致性和对称性,最终组成一份表达充分的词汇表。API应该提供一种表达充分的语言,给予上层一份丰富的词汇表,让它能据此请求和回复那些有用的问题易用并且经过深思熟虑的API词汇表可以导致上层使用富有表现力的、易于理解的代码早部署,常部署 发布工程师(Release Engineer)定期在一个干净的环境中运行和测试安装过程,可以让你检查出代码中那些基于开发或测试环境所做的假定是否合理把部署放到最后意味着那些围绕着部署假定的代码会变得更加复杂所有权衡事项宜早不宜迟区分业务异常和技术异常 由于领域逻辑的原因无法完成调用而产生的业务异常,是契约的一部分,抛出异常只是按原路径返回错误的一种替代方式,客户端应该知道并且随时准备处理它有针对性的勤加练习 有针对性的勤加练习就是通过执行一项任务来提高自身的能力,关乎技巧和技术做有针对性的练习就是为了精通这项任务,并不是完成任务有偿开发的首要目标是完成一个产品,而有针对性的练习的首要目标是提高你的水平。两者是不一样的精英执行者也至少需要10000个小时的有针对性的联系才能成为专家伟大在很大程度上就是有意识选择的结果达到专家水平的主要因素就是花时间去做带有针对性的练习有针对性的练习意味着多练习你不擅长的东西学习改变你的东西,学习改变你行为的东西。祝你好运!领域特定语言不要怕搞砸 熟练的外科医生都知道为了手术必须要开几道切口,但是,她也知道这切口都是临时的,会愈合的对于改变的麻痹式恐惧在开始时就会让你的项目陷入到这种投鼠忌器的状态作为一个外科医生,为了给痊愈腾出空间,她一点都不害怕切除坏死组织不要在你的测试代码里装可爱 在你的代码里写入文本的时候,无论是注释、日志、对话框或者测试数据,都要问一下自己如果这些公开出去的话会怎么样。这样会让你少脸红几次不要忽略那个错误 不顾红灯亮起,继续前行,结果只会招致更大的损失。要在时机初现的时候就动手,把损失较少到最小不要只学习语言,还要了解它的文化内涵不要把程序钉死在老地方不要指望“魔法会在此发生” 没有开发经验的经理会认为程序员做的事情很简单,而没有管理经验的程序员也认为经理所做的事情很简单不要重复你自己 应用里的每一行代码都会被维护到,它们就是未来bug的潜在来源DRY的要求是“在一个系统内,每一块知识必须有一个单一的、明确的、权威的表示”将重复的过程调用自动化凡是有痛苦的手工过程存在的地方,都可以使用自动化,手工过程本来就应该被自动化和标准化别碰那些代码! 开发人员——甚至是高级开发人员的访问权限都应该不能超越开发服务器如同开发人员不需要访问开发服务器之外的任何服务器一样,QA团队和用户也不需要接触开发服务器上的任何东西发布经理是唯一能访问这两台服务器的人无论在哪种情况下——从根本上说,就是永远不要让开发人员有访问产品服务器的权限如果代码有问题,产品服务器不是修复它的地方封装行为,而不仅仅是状态浮点数不是真正的数 浮点数的精度是有限的,是可以穷尽的。甚至在分布范围内的间隔也不是均匀的开源助你实现雄心壮志 通过给别人的软件编写测试代码,能让你学得更快,超过软件开发里其他任何一个活动API设计的黄金法则 锁定API:可以试着把绝大多数的类和方法都表上final,以此来阻止用户的重载或代码的滥用,避免未来你在更改选择时受到约束单元测试是实践中极端重要的一部分API设计的黄金法则:只为你开发的API编写测试代码是不够的,你还必须给使用API的代码编写单元测试高手神话 如果某人看上去像是个高手,那是因为他拥有多年的学习和思维精炼过程的积累。“高手”往简单上讲就是一个有着无穷好奇心的聪明人我们不需要高手,我们需要能帮助其他人在他们领域里成为专家的专家加班加点,事倍功半如何使用bug跟踪器 一个好的bug报告需要具备三样东西: 如果重现该bug,尽可能准确的描述,间隔多久后bug会再出现一次本应该发生什么,至少按你自己的见解来说实际上发生了什么,或者至少是你记录到的尽可能多的信息bug不是工作的标准单元,不能像一行行代码那样精确地衡量着你的努力。(其实代码行也只是粗略衡量)代码的去芜存菁 通过删除基础代码中那些令人不快的功能特性,降低了全局代码熵的水平编写代码是因为他们增加价值,而不是取悦你自己如果你现在不需要,那现在就不要写额外代码的编写和维护总是会花更长的时间。一团小小的额外代码“雪球”随着时间的推移会变成一项庞大的需要维护工作程序员发明了额外的需求,既不记录在文档里,也没经过讨论确认这个额外功能特性。这需求实际上就是捏造出来的。系统需求不是程序员设定的,而是客户要做的安装我吧 记住,客户也下载了竞争对手的框架。你知道的,竞争对手总是在论坛里宣称他们的框架比你的好很多进程间通信对应用程序响应的影响 许多性能管理著作仍然执着于数据结构和算法,这些因素在某些情况下是会起一些作用,但是在现代多层企业应用中并不处于主导地位响应时间极大地依赖于对刺激作出响应的远程进程间通信的数量。用于响应刺激的远程IPC数目越多,响应时间就会越大。如何减少? 吝啬原则,优化进程间的接口,只为互动过程准备最小数量的正确的数据在可能的情况下,并行执行进程间通信,这样总的响应时间会主要取决于时延最长的IPC缓存前次IPC的结果,将来的IPC就可能不用再执行,直接用命中的本地缓存来代替保持构建的整洁 忽略事情也是脑力劳动来自于构建的警告是有用的。你要在开始注意到它们的时候就着手清除,不要等到最后“大扫除”的时候再去做让构建保持干净,不只是消灭编译错误或者测试失败:警告信息也是“代码卫生”里重要且关键的一部分知道如何使用命令行工具 设计良好的IDE不过就是一套命令行工具的图形化前端通晓两门以上编程语言 一个程序员的编程技巧跟他能得心应手处置的不同编程范例数目直接相关只懂得一种语言的程序员会被那种语言禁锢住她的思想编程范式:过程式、面向对象、函数式、逻辑型、数据流……每个程序员最好能在两种不同的范式下有熟练的编程技能,理想一点就是掌握五种编程范式程序员应该对学习新语言始终保持着兴趣,特别是对于不熟悉的范式了解你的IDE了解你的局限性 你的资源是有限的。你只有这么点时间、这点钱去做你的事情,包括还要花钱花时间让你的知识、技能和工具跟上时代。所以,你的工作要如此投入、如此快速、如此灵活、如此持久。你的工具也就这点威力,你的目标机器也就这点功率。因此,你不得不考虑一下你有限的资源对于小数组,线性查找很有竞争力知道你下次提交的内容 代码要在头脑里有着清晰的意图和有限且可达的目标知道你下次所要提交的东西。如果你无法完成,就丢弃掉更改,然后按照你已有的理解,定义出一项你确信能完成的新任务。只要有需要也要做一些投机试验,但是不要在无意之中陷入到投机模式中。千万不要把猜想得到的东西放入代码库里。大型、相关性的数据属于数据库学习外语 付出总会有回报,时机早晚会来在今天,跟简单的编程实用工艺相比,大型项目更像是社会性的事业懂另外一门语言,就是拥有了另一个灵魂要懂得什么时候倾听,而不是交谈,要知道多数语言是没有文字的对于不可言说的,必须保持沉默——Ludwig Wittgenstein要学会估算 估算就是对价值、数值、质量及其扩展项目作出近似的计算和判断。估算是基于实际数据和以往经验的实际衡量——在计算的时候不能让希望和愿望掺杂进来。估算是一个近似值,估算是不可能精确地目标是对所要达到的商业目标的陈述承诺就是答应在某个日期或者某个事件发生之时,提交否和某个质量水平的指定功能估算、目标和承诺三者是相互独立的,但是,目标和承诺要基于合理的估算。软件估算的首要用途不是为了预测一个项目的产出成果,而是为了测定一个项目的目标是否足够现实,从而使项目处于控制之下实现这些目标。估算的用途是为了让适当的项目管理和计划成为可能,允许项目的各利益相关方基于现实目标作出承诺学着说“Hello, World”让你的项目能表达它自己链接器并不神秘 要想知道可执行文件为什么是这个大小,可以看一下链接器选择生成的map文件。map文件就是一张包含了可执行文件里所有符号及它们地址的列表。它告诉你库里的哪些模块链接进来了,以及每个模块的大小。由此你就能看出可执行文件体积膨胀是从哪里发源的临时解决方案的寿命 当系统里容纳了太多临时解决方案的时候,它的熵或者或内部复杂度会随之增加,而它的可维护性却在降低征服临时解决方案的最佳途径是让它们变得多余,提供一个更优雅、更有用的解决方案使接口易于正确使用,难于错误使用 好的接口是易于正确使用,难以错误使用易用的接口看上去很自然,因为它们让你做你想做的事情要记住接口的存在是为了方便使用者,而不是实现者让不可见的更加显眼 修复bug不能算是有进展。调试就是一种浪费。只有让浪费的时间暴露在关注之下,这样你才会认识到什么导致了时间的浪费,并开始反省当初应该细心一点如果你的项目还处于可跟踪的状态之下,但仅仅一个星期之后,你却发现进度上已经延迟六个月了,这时你碰到问题了——最大的问题可能不是它长达六个月的延迟,而是有股隐形的力量已经强大到掩盖了项目延迟六个月这个事实单元测试的编写提供了该代码单元便于做单元测试的证据。它有助于揭示代码存在(或不存在)你希望它能表现出的品质,例如低耦合和高内聚单元测试的运行提供了该代码行为的证据。它有助于揭示代码拥有(或不拥有)你所希望的它在运行时能表现出的品质,例如健壮性和正确性可见性能让人充满信心,让人觉得进展是真实的,而不是虚幻的,是有备而来的,而不是凭空想象的,是可以重复的,而不是偶尔为之的在并行系统中使用消息传递可获得更好的伸缩性 人们一次次碰到的并发问题几乎都跟易变的共享内存有关系要么放弃并发,要么放弃共享内存数据流系统:在一个数据流系统里,没有明确的编程控制流,只有一系列有向图的算子,用数据路径相连接。建立起关系后,再把数据“灌”入系统要想充分驾驭计算机硬件内建的并行能力,使用消息传递是一条比共享内存编程更为成功的路径带给未来的消息 每一行代码都是带给未来某个人的消息错失采用多态的机会奇闻轶事:测试人员是你的朋友二进制文件仅此一份有代码有真相拥有(及重构)构建脚本 构建脚本也是代码。它们如此重要以至于你最好自己亲自来做结对编程,感受流程 在任务轮转到另一个结对之前,你不必非把它结束掉不可有了结对编程、合适的结对及任务的轮转方式,新来者会很快认识代码和其他团队成员特定领域类型胜过原始类型 使用静态类型语言的话,能够从编译器那里得到一些帮助,而那些拥抱动态类型语言的人会更倾向于依赖他们的单元测试预防错误 应该尊重用户的偏好来输入信息,而不是数据本身为所有动作提供不同层次的撤消操作——尤其是那些可能会破坏和修改用户数据的操作用日志记录撤销动作,并且加以分析,凸显出界面的哪部分会诱导用户犯下无意识的错误专业程序员 在专业程序员身上唯一最重要的一点就是个人责任感。专业程序员对他们的职业、估算、承诺的日程、错误和技艺都敢于负起责任,从不会把责任推卸到别人身上如果你是一名专家,你就要对自己的职业负责,对阅读和学习负责。你负责跟上这个行业和技术的发展。很多程序员都认为这应该由雇主来给他们培训。对不起,这错得离谱对于多数项目而言,问题跟踪系统的存在就是粗心大意的征兆。只有巨无霸型的系统才有bug列表,bug数量才会大到需要自动化管理专家是可以信赖的。他们对自己的职业负责,对代码的正常工作负责,对他们技艺的质量负责。即便是最后期限迫近,他们也不会放弃原则。事实上,随着压力增大,专家会更加有条不紊,他们认为这是正确的把一切都置于版本控制之下 许多项目都有一个活跃的开发分支,以及一个或多个为当前正在支持的已发布版本所创建的维护分支放下鼠标,远离键盘阅读代码读懂人性 相互理解的能力不是来自于共享的定义,而来自于共同的经验和生活方式经常重新发明轮子 如果对软件开发中更深层次的东西一无所知,你就没有足够的能力创造出永恒之作重新发明轮子对于一个开发人员的教育和技能培养来说很重要,就像健美运动员练举重一样抗拒单件模式的诱惑 单件是值得抗拒的: 单一实例的需求通常都是想象出来的。在许多情况下,它都是纯粹的投机,认为将来也不要更多的实例。在一个应用的设计里传播这种投机性,势必导致在某些时候会很痛苦。因为好的设计会拥抱需求的变化,而单件不会在概念上独立的代码单元之间,会因单件引发潜在的依赖关系。该问题的存在既因为这种关系难于察觉,又因为它们在单元之间产生了不必要的耦合单件承载了不明显的持久状态,这会妨碍到单元测试。多线程会把更深的缺陷带给单件模式。单件的清理最后会变成挑战: 对于明确的释放单件可能没有足够的支持。例如,在一个插件架构中,一个插件只有所有对象清理干净之后再卸载才是安全的没有命令用于程序退出时隐含的清理单件。必须把你对单件模式的使用限制在那些只要实例化一次的类里面。不要从任何代码那里来访问单件的全局访问点通向高性能之路布满了脏代码炸弹 在我们努力创造干净的代码过程中,软件度量会成为一个威力强大的工具简单来自于删减 挽回糟糕工作所耗费的时间会比预想的要多处置坏代码的最好方法是彻底转变编程思路,坚持无情的代码重构、移动或者删除坏代码代码应该是简单的,它只有最小数量的变量、函数、声明和其他一些语言必需的句法。额外的行、额外的变量……额外的一切,应该立即清除掉。那里所有的,那里保留下来的,应该刚好足够完成任务、完成算法或者执行计算,除此之外的任何东西都是多余的。意外引入的不必要噪音只会使得流程晦涩难懂,使得重要内容隐匿无踪单一职责原则 单一职责原则:把所有会为同一个原因而更改的东西汇集在一起,把所有会为不同理由而更改的东西独立开来——优秀设计的最根本原则之一从Yes开始 从yes开始实际上就是作为一名技术领导的核心内容通常,你会发现只要你知道提出要求时的背景,就会找到其他可以用来回应要求的方法如果你能表述出一个令人信服的理由来说明为什么这个功能要求跟现有产品格格不入,那么你就有可能就你们是否正在构建一个正确的产品进行一次破有建设性的沟通。无论这次沟通的结论是什么,每个人都会更加关注这个产品是什么,以及不是什么从yes开始意味着你要和你的同事们并肩作战,而不是相互对立请转回去做自动化、自动化、自动化充分利用代码分析工具 不要让测试成为质量保证的终点——充分利用现有的分析工具,也不要害怕推出你自己的工具为必需行为测试,而不是偶发行为 测试的一个常见缺点就是与实现细节焊死在一起,而这些细节都是偶然的,跟所要求的功能关系不大白盒测试用代码结构来决定什么是测试用例所需要的测试要严密而具体 构造软件设计的方式有两个:一个是让它足够简单,没有明显的缺陷;另一个是让它变得非常复杂,以至于看不出有什么缺陷——Tony Hoare (快速排序的发明人,1980年获得图灵奖)在睡觉的时候(或度周末的时候)进行测试 将横跨不同部门和团队的服务器组成池,确保资源能够充分利用软件开发的工程严密性来自测试 测试是通向软件可再生产性和高质量的真正途径,我们把回头争论是否要进行测试的行为视为极不专业的做法关于状态的思想一人技短,二人技长 仅仅作为一名技术专家已经不够了,你还必须高效的与其他人一起工作错上加错就是貌似正确(并且难以纠正)我写代码为人人,人人为我写代码 创造软件是技术活动和社会活动的混合我们和每个人一起分担着提高成功可能性的责任我们很少很少会写只给自己用的代码Unix工具是你的好朋友使用正确的算法和数据结构冗长的日志会让你睡不安枕 太多的日志等于没有日志WET掩盖了性能瓶颈 DRY - Don't Repeat YourselfWET - Write Every TimeDRY可以帮助我们找出并修复性能瓶颈,这在WET代码里是很难发现的当程序员和测试人员开始合作的时候编写代码时要像余生都要给它提供支持一样 你会发现多年前编写的代码依然影响着你的职业生涯,不管你是否乐意这样。你的身后流下了一条鲜明的轨迹,其中蕴含你的知识、态度、主张、专业主义、诚意以及对于你设计编写的每一个方法、类、模块的投入程度使用实例编写小函数测试为人而写你应该关心你的代码 贫乏的技术基础之上不会产生好的程序员好的程序员依赖于采用专业途径,即使有现实世界的束缚和软件工厂的压力,也一直想着写出最好的软件与其他程序员一起和睦共事。没有一个程序员是一座孤岛。几乎没有程序员是单枪匹马工作的;多数工作都是程序员抱团作战的你要希望团队有可能写出最好的软件,而不是让你自己显得很聪明心口不一的客户 不要简单的用客户的言辞来重申他们告诉你想要的是什么。在与客户谈话过程中,要换他们的言辞,然后从他们的反应里判断在交谈中使用可视化目标有助于加大注意力的广度,提高信息的保持率