开始

一切都始于最近终于有闲暇时光来拜读躺在待办列表里许久的《心流》一书。在草草的浏览了两遍之后觉得没有非常的震惊,仔细想来应该是心流这个名字取得过于得好了,简单的两个字便和作者电波相通。而且书的内容对吾辈来说也并不是颠覆性的概念而更像是对过往经验和想法的一些总结性陈述。但是总的来说《心流》仍是一本非常值得一读的书,不管是你可以从中学到新的理念还是系统的梳理你的过往经验。

在看完了心流之后立刻想起了《黑客与画家》、《禅与摩托车维修艺术》这两本书,虽然都已经是readed状态的书了,无奈吾辈记忆力属实不行已经回忆不起具体内容了,就又翻出来都重读了一遍。

至于为何会想起这两本书,在读完这三本书后趁内容还在大脑中的现在可以大致给出一个解释:《黑客》和《禅》中都或多或少涉及到了和《心流》中相通的概念,无论是沉浸式的摩托车修理还是将编程和绘画艺术进行类比,都是对心流的概念的某种实践。

在这里需要叠一个甲:吾辈并没有在此要对这三本书批判一番并进行一个名的排,排名不分先后。

在此鄙人也并不敢斗胆同时对这三本书发起联合书评,下面的内容只是对三本书中的一些共通或者引起吾辈共鸣的片段的摘抄和感悟,仅此而已。

科技旁观者

但是让我惊讶的是,这些手册编写者的态度和这些修理人员的态度一样,竟然都是旁观者,所以它们可以被称为旁观者的手册。在字里行间,你隐约可以嗅到这样的意味:“这是机器,它和周围环境中的一切都没有关系,和你也没有关系,你和它也没有关系;

他不了解发生了什么事,而且也没有兴趣去研究。他对事情的表象比较感兴趣,对于内涵就不然了。这一点很重要,因为这就是他看事情的方法。我花了好长的时间才发现我们之间的这种不同,所以在这次旅程当中,很重要的一件事就是要明确这种不同。

在书的前半段和约翰夫妇同行的时候有很多类似的描述,当作者或者其他对某事能进入心流或将其当作艺术的人来说,看到这些“旁观者”的操作,是会非常抓狂的:

但是我记得有一次在明州的沙维奇,当时天气差点把我热昏了,我们在酒吧里待了大约一个钟头,出来的时候摩托车晒得几乎没法骑上去。我先发动好准备上路,但是约翰仍然在用脚踩发动器,我闻到一股汽油味,就像炼油厂传出来的一样,便告诉了他,以为这样足以提醒他是发动机湿了,所以无法发动。

“对,我也闻到了。”他边说边继续踩,不停地用力踩,有时还跳起来踩,我不知道该说什么。一直到他踩得气喘如牛,汗流浃背,再也踩不动时,我才建议他不妨把火花塞拿出来晾干,让汽缸通通风,然后我们可以回去喝杯啤酒再出来。

起初我认为,这只是他们在对待摩托车时特有的态度,但是后来才发现情形并非如此……有一天我在他们家等着一起上路,我注意到水龙头在滴水,我记得上次就已经滴了,事实上已经滴了很久。我提醒他这件事,约翰告诉我,他换过新的皮圈但还是滴水,他说了这些就不再提了,也就是说事情到此为止。如果你试过修理水龙头,但是情况依旧,那就表示你命中注定有个会滴水的水龙头。

我很惊讶,水龙头这样日复一日、年复一年地滴滴答答地响,他们难道不会神经衰弱吗?然而我发现他们一点都不担心,也不去注意这件事。所以我的结论是他们不怕被水龙头打扰。有些人的确如此。

这些文字的画面感过于强烈以至于可以使你的血压迅速升高,当然其实这些问题并不只是在科技方面(虽然编程应该算是走在科技前沿),因为传统工作也可以进入心流状态。但是在工作中也时常能看到类似的现象。可能就像上文所描述的,他们只是代码的“旁观者”吧。也只能以此来慰藉自己,降低自己的血压。

对科学的讨论

所以在 20 世纪,科学研究成果的寿命似乎比 19 世纪要短得多,就是因为科学研究的规模现在大多了。如果下一个世纪科学研究的速度是现在的十倍,那么任何科学研究成果的寿命,很可能只有现在的十分之一。是什么缩短了它的寿命?最主要的就是假设的增加,假设愈多,研究成果的寿命就愈短。近几十年来假设大量增加的原因似乎来自于科学方法的本身。你看得愈多,知道得就愈多。你不是从一大堆假设当中筛选出一项真理,你是不断地提供大量的假设。

在吾辈为数不多的几年编程实践之后得出的经验是:编程也是一项基于假设的工作,这些假设都是隐藏着的,编程工作的“道”则是要充分挖掘当前代码工作环境的假设,挖掘的越充分则代码越可靠(什么,需求又变了?)。

例如都是输出“hello world”,在单线程正确输出和在多线程竞争条件下正确输出的方法就是完全不同的(输出缓冲区是否开启,缓冲区大小,线程数量和物理CPU核心数的差距,,也都可能会影响结果)。

当然我们并不是要无限的增加假设来无限提高代码的鲁棒性,而是要使得代码的形状正好可以完美匹配当前的需求(这就要求我们可以充分挖掘当前需求假设)。

对于摘录的文字,吾辈的想法是:

虽然科学在创造越来越多的假设使得科研成果的寿命越来越短,但是在更多的假设下得到的结论也能更精确的解释某些现象(经典力学、相对论)。这一点上和软件开发是类似的(应该?),挖掘越多的假设可以开发出bug或者缺陷越少的代码,但是随着假设的增多,软件的适用范围也会越来越窄,从而影响了软件的寿命(什么,需求又变了?)。当然这可能并不完全正确,毕竟人人都想创造强大的软件。但是伟大的先人也说过:软件行业没有银弹。

在这个意义上,程序员就是一位小型科学家。科学家为某个理想化的世界创建近似模型,以便对其进行预测。只要模型的预测成真,一切都好;当预测的事件与实际情况不符时,科学家会修正模型以减少差异。同样地,当程序员被分配一个任务时,他们会创建一个初步设计,将其转化为代码,与实际用户进行评估,并迭代地完善设计,直到程序的行为与期望的产品非常接近。 from https://htdp.org/2023-3-6/Book/part_preface.html

上面这段文字摘自另一本书《How to Design Programs》,这段文字从另一个角度说明了“计算机科学”的合理性(也是一门科学)。虽然《黑客》一书的作者并不认同“计算机科学”这个学科名称:

一直不喜欢“计算机科学”(computer science)这个词。主要原因是根本不存在这种东西。计算机科学就像一个大杂烩,由于某些历史意外,很多不相干的领域被强行拼装在一起。这个学科的一端是纯粹的数学家,他们自称“计算机科学家”,只是为了得到国防部研究局(DARPA)的项目资助。中间部分是计算机博物学家,研究各种专门性的题目,比如网络数据的路由算法。另一端则是黑客,只想写出有趣的软件,对于他们来说,计算机只是一种表达的媒介,就像建筑师手里的混凝土,或者画家手里的颜料。所以,在“计算机科学”的名下,数学家、物理学家、建筑师都不得不待在同一个系里。

但是这也仅对于“黑客”而言,但是作为一个悲惨的CS打工人,提高一些“科学素养”还是很有帮助的。

对教育的批判

你成了一部机器,不断地对那些如潮水般涌来的天真学生重复同样枯燥乏味的教材。他们不了解为什么你变得这样乏味,因而对你失去了尊敬。大家也受了你的传染。

你不断上课、上课、上课的原因是,这是经营一所学校最经济的方法,让外界的人误以为学生得到了完整的教育。

真正的大学并不听命于任何民意机关,也不是由任何建筑物所构成的,只要它自己宣布这个地方已不再是圣所,真正的大学就已经消失,所遗留下来的只是一些砖墙、藏书和种种物质的结构罢了。

除了这种心灵的世界之外,不巧也有一种合法的机构有同样的名称,但是却完全是两码子事。它是非营利性的组织,隶属于州政府,同时坐落在特定的地方,它不但拥有校产,还能发薪水,收学费,还要受法律的约束。

然而这种大学,也就是合法的组织,却没有办法真正提供任何教导,它不但无法激发新知识的产生,也无法衡量学问的价值。它根本就不是真正的大学,它只像教会外表的建筑一样,坐落在某个特定的地点,提供真正的教会各种有利于生存的环境。

斐德洛认为,凡是没有办法觉察这种差异的人,就会误以为掌握了教会的建筑就等于掌握了教会。他们认为,学校的教授既然领了薪水,一旦得到上面的指示,就应该抛弃自己的见解,毫无异议地接受学校的指挥,就像受雇于一般公司行号,处处要为老板说话一样。 他们看到的是虚假的大学,而没看到真正的大学。

愈聪明愈认真的学生愈不需要分数,很可能是因为他们对学问的本身比较感兴趣。而愈懒惰愈愚笨的学生则愈需要分数,因为可以让他们知道自己是否及格了。

以上是《禅》内对于教育的批判(更多是大学),而在《黑客》中也有许多对教育的批判(更多是小学和初高中):

公立学校的老师很像监狱的狱卒。看管监狱的人主要关心的是犯人都待在自己应该待的位置。然后,让犯人有东西吃,尽可能不要发生斗殴和伤害事件,这就可以了。除此以外,他们一件事也不愿多管,没必要自找麻烦。所以,他们就听任犯人内部形成各种各样的小集团。根据我读到的材料,犯人内部的关系是扭曲、野蛮、无孔不入的。处在这种人际关系的最底层可不是好玩的事。

至于学校,不过是这个虚假环境中关住牲口的围栏。表面上,学校的使命是教育儿童。事实上,学校的真正目的是把儿童都关在同一个地方,以便大人们白天可以腾出手来把事情做完。我对这一点没有意见,在一个高度工业化的社会,对孩子不加管束,让他们四处乱跑,无疑是一场灾难。

而在《心流》中有一段对于应试教育的批判:

专注是心流的关键。于是问题来了:中国的高中生在应试的压力下不是也很专注吗,他们体会到心流了吗?我的判断是否定的。爱因斯坦就抱怨他的一次应试经历,他说过后很长时间都不能复原。为什么如此?第一,那活动不是他心向往之,而是被迫的。第二,反复无数次的复习中,没有任何新的刺激,完全是乏味的重复。故高考结束之日,就是全体考生背叛这一活动之时。上述造成心流的活动,比如攀岩、写诗、思考哥德巴赫猜想,哪能如此。一句话,能造就心流的活动,大多还需要当事者自觉自愿,乐在其中。米哈里的著作中没有对“考生的专注”多花笔墨,可能是因为在他的国家中,这种灾病不成气候。

虽然对于应试教育的批判已经是老生常谈了,但是从另外两本书看到哪怕在教育理念“遥遥领先”的美丽国,教育体制也是被疯狂批判的对象(当然级别已经不知道高到哪里去了)。而在《禅》一书中作者和学生所讨论的“良质”和心流也可以做一些类比,也可以说这段历程是老师和学生一起找寻某门课程中的心流体验的历程。而对于《黑客》来说,被同龄人孤立的书呆子,也因为在别的领域能够体会到心流而不对社交如此感兴趣:

虽然“书呆子”饱尝不受欢迎之苦,但是为了解除痛苦而让他们放弃“聪明”,我想大多数人是不会愿意的。对他们来说,平庸的智力是不可忍受的。不过,要是换了别的孩子,情况就不一样了,大多数人会接受这笔交易。对于很多人来说,这反而是更上一层楼的机会。即使是那些智力排名在前20%的学生(我在这里假设智力可以测量,那时的人们似乎都相信这一点),谁不愿意用30分的成绩换来别人的友爱和钦佩?  

我认为,这就是问题的根源。“书呆子”的目标具有两重性。他们毫无疑问想让自己受欢迎,但是他们更愿意让自己聪明。“受欢迎”并不是你在课后时间随便做一做就能实现的,尤其是在美国的中学中,在这里,所有人为了个人魅力都会进行激烈竞争。

书呆子不受欢迎的真正原因,是他们脑子里想着别的事情。他们的注意力都放在读书或者观察世界上面,而不是放在穿衣打扮、开晚会上面。他们就像头顶一杯水来踢足球,一边踢球,一边拼命保持不让水洒出来。其他人都在一门心思玩足球,遇到这样的对手,自然能够毫不费力地击败,并且心里还奇怪,对方怎么如此无能。

就算书呆子心里想着变得与其他小孩一样受欢迎,做起来却是难上加难。因为那些受欢迎的小孩从小就在琢磨如何受欢迎,打心底里追求这个。但是,书呆子从小琢磨的却是如何更聪明,心底里也是这样追求的。这都是受父母的影响,书呆子被教导追求正确答案,而受欢迎的小孩被教导讨人喜欢。

良质和画家和心流

在《禅》中作者一直在追寻良质,在《黑客》中作者将编程运动比作画家艺术,而《心流》一书则是完全的在教会读者如何在某件事上进入心流体验:

或者用更简单明了的话来说:如果你想在盖一间工厂,或是修一辆摩托车,甚至治理一个国家的时候,不会发生被 卡住的情形,那么古典的二分法,虽然必要,但是不足以满足你的需要。你必须对工作的品质有某种情感,你必须能判断什么才是好的,这一点才能促使你行动。这种感受力即使是你与生俱来的,你也仍然可以努力拓展它的范围。它不仅仅是你的直觉,也不仅仅是无法解释的技巧或是天才,它是你与良质接触之后产生的直接结果。它也是过去二分法的理性想要掩盖的一面。

当然,提升自己的精神层次并不一定要接触摩托车,单纯到像磨一把菜刀、缝一件衣服或是修补一张坏掉的椅子,它们背后的问题都是一样的。你做任何一件事都可以把它做得很漂亮,或是很丑陋。

保持内心的宁静在机械工作上并不是一件小事,它是工作的核心。能够使你平静的就是高级的手艺,反之,则是低级的。规格说明、测量仪器、品质监督与最后阶段的品质检查,这些都是达 到内心宁静的方法。而最后真正重要的,就是要达到内心的宁静,除此之外别无他物。因为只有内心宁静,我们才能觉察到良质的存在,它超越了浪漫和古典的认知,将两者融合为一。无论进行任何工作,都必须具有良质。要想具有鉴赏力,了解如何完成高级的工作,体会和工作融为一体的感觉,就要培养内心的宁静。如此一来,良质才能出现在你的心中。

在《黑客》中则是如下的阐述:

这种看法是错的。计算机和画画有许多共同之处。事实上,在我知道的所有行业中,黑客与画家最相像。黑客与画家的共同之处,在于他们都是创作者。与作曲家、建筑师、作家一样,黑客和画家都是试图创作出优秀的作品。他们本质上都不是在做研究,虽然在创作过程中,他们可能会发现一些新技术(那样当然更好)。

当然在心流中就更多了:

外科医疗的性质决定了它是最能集中注意力的。很多外科医生表示给多少钱也不干医院其他科的工作。他们认为:内科治疗常常看不清目标。神经科的目标更模糊,常常十年才能治好一个病人。除了目标清晰,外科的诊断与手术中会不断得到回馈,以评估进展。明确已获得的进展,与全神贯注地继续工作密切关联。

里柯·麦德林在工作时常有这样美好的感觉。他跟胡里欧是同事,在另一条装配线上工作。他每完成一个单元,规定的时间是43秒,每个工作日约需重复600次。大多数人很快就对这样的工作感到厌倦,但里柯做同样的工作已经5年多了,还是觉得很愉快,因为他对待工作的态度跟一名奥运选手差不多,常常思索如何打破纪录;就像一个苦练多年、只为了刷新纪录的赛跑选手,里柯也训练自己创造装配线上的新纪录。他像外科医生一般,一丝不苟地设计工具的安放顺序和每一步动作。经过5年的努力,他最好的成绩是28秒就装配完一个单元。

他力求表现得更好,一方面固然是为了争取奖金和领班的赏识,但他往往并不张扬已遥遥领先的事实;另一方面知道自己行已经够了,更何况,以最高速度工作时会产生一种快感,这时要他放慢速度简直是“强人所难”。里柯说:“这比什么都好,比看电视有意思多了。”里柯知道,他很快就会达到不能在同样工作上求进步的极限,所以他每周固定抽两个晚上去进修电子学的课程。拿到文凭后,他打算找一份更复杂的工作。我相信他会用同样的热忱,努力做好任何一份工作。

看着这些文字就会有一种爽感,这可能也是为什么“心流”这个名词吾辈最先听说也是在游戏领域吧。而游戏领域确实非常需要好的心流设计来和玩家互动。毕竟游戏的心流是外显的而且可以显著的影响游戏的销量和评价。

一些实践

所以在修理机器这方面,如果你的自我太强,往往无法把工作做好。因为你总是会被愚弄,很容易犯错,所以修理人员自大的个性对他颇为不利。如果你认识很多技术人员,我想你会同意我的观点,他们往往相当谦虚而且安静。当然,也有例外。不过即使他们起初无法保持安静和谦逊,长久工作下来,也会变成这样的个性。同时,他们还具有高度的警戒心,专注而又懂得怀疑,不会以自我为中心。

在开始修理之前,你可以把要做的事写在纸上,然后再组织成适当的结构。你会发现,在不断的重组过程当中,会出现更多的想法。这么做不但节省不少时间,而且会让你不再慌张得出问题。

谦虚当然是非常重要的品质,这对debug也非常有益。而任务分解也是另一门需要好好钻研的艺术(如何把任务拆分的足够小还能可执行),比如在敏捷开发时,如何将每次的迭代代码缩减到足够小,但是又需要保证每次的代码提交完成了一个完整的任务,这就非常考验任务分解的水平了。

有一件事情是可以借鉴的(至少可以确认),那就是应该如何学习编程。画家学习绘画的方法主要是动手去画,黑客学习编程的方法也理应如此。大多数黑客不是通过大学课程学会编程的,他们从实践中学习,13岁时就自己动手写程序了。即使上了大学,黑客学习编程依然主要通过自己写程序。

画家的作品都会保留下来,你观察这些作品,就能看出他们是怎么一步步通过实践学习绘画的。如果你把一个画家的作品按照时间顺序排列,就会发现每幅画所用的技巧,都是建立在上一幅作品学到的东西之上。某幅作品如果有特别出色之处,你往往能够在更早的作品上发现一个小规模的初期版本。

我想大多数创作者都是这样学习和工作的,作家和建筑师似乎都是如此。也许对于黑客来说,采取像画家这样的做法很有好处:应该定期地从头开始,而不要长年累月地在一个项目上不断工作,并且试图把所有的最新想法都以修订版的形式包括进去。

吾辈认为写代码和传统工业最大的区别就在于此,在计算机上运行一个完整的项目代码几乎是没有成本的,你可以在你的电脑上任意的修改、调试、观察、运行Linux的内核代码。而Linux内核这样庞大的项目类比到传统工业领域的项目的话,作为一个菜鸟是很难有资源和资格可以对其进行如此狂野的改动和调试的。因为你会创造现实的产物,成本比执行代码更高而且反馈也没有代码运行如此的即时。

这也引出了另一个话题,吾辈认为对于编程方面的技术掌握的最佳实践就是对代码的动态调试,不论是业务代码还是框架,调试永远是最快的学习途径。当然这也关乎到你写代码时的思维逻辑:“我的代码便于调试吗?”,如果现在别人要运行调试你的代码需要非常长的前期准备工作(环境、配置。。),那么这段代码必定是不合格的。这也在某种程度上契合TDD的思想:你需要写出可测试(可调式)的代码。

调试也是CS领域的另一门艺术。

杂谈里的杂谈

让我先问你一个问题:大庭广众之下,你有没有什么观点不愿说出口?如果回答是没有,那么你也许应该停下来想一想了。你的每一个观点都能毫不犹豫地说出口,你自己深深赞同这些观点,并且你也确信肯定会获得别人的赞同,这是否太过于巧合了?

一种可能是,也许事情并没有这么巧合,你的观点就是从别人那里听来的,别人告诉你什么,你就相信了什么,你把别人灌输的观点当作了自己的观点。另一种可能是,你的思想观点确实是独立思考得到的,碰巧与社会主流的思想观点一模一样。这种情况的可能性似乎不大,因为这意味着,如果别人犯错了,你也必须碰巧犯一个同样的错误。为了防止他人复制,古代制作地图的工匠会故意在地图上画错一个小地方。如果你的地图与他的地图一样,就说明不太可能是你自己独立制作的。

当然这三本书中并不只是包含了上面的内容,上面也只是摘录了目前吾辈感兴趣的一些部分。然而像《黑客》中对于从众心理的批判和下面对于编程语言的思考等等也是非常值得读者一看和思考的。

总结

现在来看,这三本书的关系就像是:《心流》给出了“良质”“编程艺术”的概况性定义:它们都是心流的一种表现形式。并且给出了完整的方法指导你如何成为“编程艺术家”。《禅》则是作者的一次对“良质”或者说“心流”的探索和得出结论的心路历程。而《黑客》就是“心流”“良质”在黑客或者说编程方面的一些实践。这也可以说是高手的某种趋同性或者说大道至简罢。

总结一下。在2023年的今天吾辈的结论是这三本书都非常非常非常值得一看,可以直接成为我的推荐图书TOP3(如果有No.4的话可能是 G.E.B. 这就是后话了)。

那么本文就到此结束,快点端上去罢。

代码的写法应当使别人理解它所需的时间最小化。

第一部分 表面层次的改进

第二章 把信息装到名字里

本章唯一的主题是:把信息塞入名字中。这句话的含意是,读者仅通过读到名字就可以获取大量信息。

下面是讨论过的几个小提示:

  • 使用专业的单词
  • 避免空泛的名字
  • 使用具体的名字来更细致地描述事物
  • 给变量名带上重要的细节
  • 为作用域大的名字采用更长的名字
  • 有目的地使用大小写、下划线等

第三章 不会误解的名字

不会误解的名字是最好的名字–阅读你代码的人应该理解你的本意,并且不会有其他的理解。遗憾的是,很多英语单词在用来编程时是多义性的,例如filter、length和limit。

在你决定使用一个名字以前,要吹毛求疵一点,来想象一下你的名字会被误解成什么。最好的名字是不会误解的。

当要定义一个值的上限或下限使,max_和min_是很好的前缀。对于包含的范围,first和last是好的选择。对于包含/排除范围,begin和end是最好的选择,因为它们最常用。

当为布尔值命名时,使用is和has这样的词来明确表示它是个布尔值,避免使用反义的词。

要小心用户对特定词的期望。例如,用户会期望get()或者size()是轻量的方法。

第四章 审美

大家都愿意读有美感的代码。通过把代码用一致的、有意义的方法“格式化”,可以把代码变得更容易读,并且可以读得更快。

下面是讨论过的一些具体技巧:

  • 如果多个代码块做相似的事情,尝试让它们有同样的剪影。
  • 把代码按“列”对齐可以让代码更容易浏览。
  • 如果在一段代码中提到A、B和C,那么不要在另一段中说B、C和A。选择一个有意义的顺序,并始终用这样的顺序。
  • 用空行来把大块代码分成逻辑上的“段落”。

第五章 该写什么样的注释

注释的目的是尽量帮助读者了解得和作者一样多。

注释的目的是帮助读者了解作者在写代码时已经知道的那些事情。本章介绍了如何发现所有的并不那么明显的信息块并且把它们写下来。

什么地方不需要注释:

  • 能从代码本身中迅速地推断的事实。
  • 用来粉饰烂代码的“拐杖式注释”–应该把代码改好。

你应该记录下来的想法包括:

  • 对于为什么代码写成这样而不是那样的内在理由(“指导性批注”)。
  • 代码中的缺陷,使用像TODO:或者XXX:这样的标记
  • 常量背后的故事,为什么是这个值。

站在读者的立场上思考:

  • 预料到代码中哪些部分会让读者说:“啊?”并且给它们加上注释。
  • 为普通读者意料之外的行为加上注释。
  • 在文件/类的级别上使用“全局观”注释来解释所有的部分是如何让一起工作的。
  • 在注释来总结代码块,使读者不致迷失在细节中。

第六章 写出言简意赅的注释

注释应当有很高的信息/空间率。

本章是关于如何把更多的信息装入更小的空间里。下面是一些具体的提示:

  • 当像 “it” 和 “this” 这样的代词可能指代多个事物时,避免使用它们。
  • 尽量精确地描述函数的行为。
  • 在注释中用精心挑选的输入/输出例子进行说明。
  • 声明代码的高层次意图,而非明显的细节。
  • 用嵌入的注释来解释难以理解的函数参数。
  • 用含义丰富的词来使注释简洁。

第二部分 简化循环和逻辑

第七章 把控制流变得易读

有几种方法可以让代码的控制流更易读。

在写一个比较时,把改变的值写在左边并且把更稳定的值写在右边更好一些。

你也可以重新排列if/else语句中的语句块。通常来讲,先处理正确的/简单的/有趣的情况。有时这些准则会冲突,但是当不冲突时,这是要遵循的经验法则。

某些编程结构,像三目运算符、do/while循环,以及goto经常会导致代码的可读性变差。最好不要使用它们,因为总是有更整洁的代替方式。

嵌套的代码块需要更加集中精力去理解。每层新的嵌套都需要读者把更多的上下文“压入栈”。应该把它们改写成更加“线性”的代码来避免深嵌套。

通常来讲提早返回可以减少嵌套并让代码整洁。“保护语句”尤其有用。

第八章 拆分超长的表达式

把你的超长表达式拆分成更容易理解的小块。

很难思考巨大的表达式。本章给出了几种拆分表达式的方法,以便读者可以一段一段地消化。

一个简单的技术是引入“解释变量”来代表较长的子表达式。这种方法有三个好处:

  • 它把巨大的表达式拆成小段。
  • 它通过用简单的名字描述子表达式来让代码文档化。
  • 它帮助读者识别代码中的主要概念。

另一个技术是用德摩根定理来操作逻辑表达式–这个技术有时可以把布尔表达式用更整洁的方式重写。

最后,尽管本章是关于拆分独立的表达式的,同样,这些技术也常应用于大的代码块。所以,你可以在任何见到复杂逻辑的地方大胆地去拆分它们。

第九章 变量与可读性

让你的变量对尽量少的代码行可见。

本章是关于程序中的变量是如何快速积累而变得难以跟踪的。你可以通过减少变量的数量和让它们尽量“轻量级”来让代码更有可读性。具体有:

  • 减少变量,即那些妨碍的变量。我们给出了几个例子来演示如何通过立刻处理结果来消除“中间结果”变量。
  • 减小每个变量的作用域,越小越好。把变量移到一个有最少代码可以看到它的地方。眼不见,心不烦。
  • 只写一次的变量更好。那些只设置一次值的变量使得代码更容易理解。

第三部分 重新组织代码

第十章 抽取不相关的子问题

对本章一个简单的总结就是“把一般代码和项目专有的代码分开”。其结果是,大部分代码都是一般代码。通过建立一大组库和辅助函数来解决一般问题,剩下的只是让你的程序与众不同的核心部分。

这个技巧有帮助的原因是它使程序员关注小而定义良好的问题,这些问题已经同项目的其他部分脱离。其结果是,对于这些子问题的解决方案倾向于更加完整和正确。你也可以在以后重用它们。

第十一章 一次只做一件事

应该把代码组织得一次只做一件事情。

本章给出了一个组织代码的简单技巧:一次只做一件事情。

如果你有很难读的代码,尝试把它所做的所有任务列出来。其中一些任务可以很容易地变成单独的函数(或类)。其他的可以简单地成为一个函数中的逻辑“段落”。具体如何拆分这些任务没有它们已经分开这个事实那样重要。难的是要准确地描述你的程序所做地所有这些小事情。

第十二章 把想法变成代码

本章讨论了一个简单的技巧,用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料地简单,但很强大。看到你在描述中所用的词和短语还可以帮助你发现哪些子问题可以拆分出来。

但是这个“用自然语言说事情”的过程不仅可以用于写代码。这个技巧叫做“橡皮鸭技术”。

另一个看待这个问题的角度是:如果你不能把问题说明白或者用词语来做设计,估计是缺少了什么东西或者什么东西缺少定义。把一个问题(或想法)变成语言真的可以让它更具体。

第十三章 少写代码

最好读的代码就是没有代码。

本章是关于写越少代码越好的。每行新的代码都需要测试、写文档和维护。另外,代码库中的代码越多,它就越重,而且在其上开发就越难。

你可以通过以下方法避免编写新代码:

  • 从项目中消除不必要的功能,不要过度设计。
  • 重新考虑需求,解决版本最简单的问题,只要能完成工作就行。
  • 经常性地通读标准库的整个API,保持对它们的熟悉程度。

第四部分 精选话题

第十四章 测试与可读性

在测试代码中,可读性仍然很重要。如果测试的可读性很好,其结果是它们也会变得很容易写,因此大家会写更多的测试。并且,如果你把事实代码设计得容易测试,代码整个设计会变得更好。

以下是如何改进测试的几个具体要点:

  • 每个测试的最高一层应该越简明越好。最好每个测试的输入/输出可以用一行代码来描述。
  • 如果测试失败了,它所发出的错误消息应该能让你容易跟踪并修正这个bug。
  • 使用最简单的并且能够完整运用代码的测试输入。
  • 给测试函数取一个有完整描述性的名字,以使每个测试所测到的东西很明确。不要用Tes1(),而用像Test_<FunctionName>_<Situation>这样的名字。

最重要的是,要使它易于改动和增加新的测试。

第一部分 敏捷开发

人与人之间的交互是复杂的,并且其效果从来都难以预期,但却是工作中最为重要的方面。

第一章 敏捷实践

敏捷联盟宣言

  • 个体和交互胜过过程和工具
  • 可以工作的软件胜过面面俱到的文档
  • 客户合作胜过合同谈判
  • 响应变化胜过遵循计划

原则

  1. 我们最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
  2. 即使到了开发的后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
  3. 经常性地交付可以工作的软件,交付的间隔可以从几周到几个月,交付的时间间隔越短越好。
  4. 在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
  5. 围绕被激励起来的个人来构建项目。给他们提供所需要的环境和支持,并且信任他们能够完成工作。
  6. 在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面的交谈。
  7. 工作的软件是首要的进度度量标准。
  8. 敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
  9. 不断地关注优秀的技能和好的设计会增强敏捷能力。
  10. 简单 - 使未完成的工作最大化的艺术 - 是根本的。
  11. 最好的架构、需求和设计出自于自组织的团队。
  12. 每隔一段时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。

第二章 极限编程概述

  • 客户作为团队成员
  • 用户素材
  • 短交付周期
    • 迭代计划
    • 发布计划
  • 验收测试
  • 结对编程
  • 驱动测试开发方法
  • 集体所有权
  • 持续集成
  • 可持续的开发速度
  • 开放的工作空间
  • 计划游戏
  • 简单的设计
    • 考虑能够工作的最简单的事情
    • 你将不需要它
    • 一次,并且只有一次
  • 重构
  • 隐喻

第三章 计划

当你能够度量你所说的,并且能够用数字去表达它时,就表示你了解了它;若你不能度量它,不能用数字去表达它,那么说明你的知识就是匮乏的、不能令人满意的。

第四章 测试

测试驱动的开发方法

还有一个更重要但是不那么明显的影响,是首先编写测试可以迫使我们使用不同的观察点。我们必须从程序调用者的有利视角去观察我们将要编写的程序。

此外,通过首先编写测试,我们就迫使自己把程序设计为可测试的。把程序设计为易于调用和可测试的,是非常重要的。为了成为易于调用和可测试的,程序
必须和它的周边环境解耦。这样,首先编写测试迫使我们解除软件中的耦合。

第五章 重构

重构就好比餐后对厨房的清理工作。第一次你没有清理它,你用餐是会快一点。但是由于没有对盘碟和用餐环境进行清洁,第二天做准备工作的时间就要更长
一点。这会再一次促使你放弃清洁工作。的确,如果跳过清洁工作,你今天总是能够很快用完餐,但是脏乱在一天天的积累。最终,你得花费大量的时间去寻找
合适的烹饪器具,凿去盘碟上已经干硬的食物残余,并把它们洗擦干净以使它们适合于烹饪。饭是天天要吃的。忽略掉清洁工作并不能真正加快做饭速度。

第六章 一次编程实践

代码仓库地址

第二部分 敏捷设计

拙劣设计的症状:

  • 僵化性: 设计难以改变
    • 很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。
  • 脆弱性: 设计易于遭到破坏
    • 对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
  • 牢固性: 设计难以重用
    • 很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。
  • 粘滞性: 难以做正确的事情
    • 做正确的事情比做错误的事情要困难。
  • 不必要的复杂性: 过分设计
    • 设计中包含有不具任何好处的基础结构。
  • 不必要的重复: 滥用鼠标
    • 设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。
  • 晦涩性: 混乱的表达
    • 很难阅读、理解。没有很好地表现出意图。

面向对象设计原则:

  • 单一职责原则 SRP
  • 开放-封闭原则 OCP
  • Liskov替换原则 LSP
  • 依赖倒置原则 DIP
  • 接口隔离原则 ISP

因此,简而言之,敏捷人员知道要做什么,是因为:

  1. 他们遵循敏捷实践去发现问题。
  2. 他们应用设计原则去诊断问题;并且
  3. 他们应用适当的设计模式去解决问题。

软件开发的这三个方面间的相互作用就是设计。

第七章 什么是敏捷设计

7.4 尽可能保持好的设计

敏捷开发人员致力于保持设计尽可能地适当、干净。这不是一个随便的或者暂时性的承诺。敏捷开发人员不是每几周才清洁他们的设计。而是每天、每小
时、 甚至每分钟都要保持软件尽可能地干净、简单并富有表现力。他们从不说,“稍后我会回来修正它。”他们决不让腐化出现。

7.5 结论

那么,什么是敏捷设计呢?敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于
保持系统设计在任何时间都尽可能得简单、干净以及富有表现力。

第八章 单一职责原则(SRP)

就一个类而言,应该仅有一个引起它变化的原因。

如果一个类承担的职责过多,就等于把这些责任耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的
设计,当变化发生时,设计会遭到意想不到的破坏。

在SRP中,我们把指责定义为“变化的原因”。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多余一个的职责。有时,我们很难注意到
这一点。我们习惯于以组的形式去考虑职责。

在此还有一个推论。变化的轴线仅当变化实际发生时才具有真正的意义。如果没有征兆,那么去应用SRP,或者任何其他的原则都是不明智的。

第九章 开放 – 封闭原则(OCP)

软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。

如果程序中的一处改动就会产生连锁反应,导致一系列相关模块的改动,那么设计就具有僵化性的臭味。

遵循开放 – 封闭原则设计出的模块具有两个主要的特征。它们是:

  1. 对于扩展是开放的
    这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块
    的功能。
  2. 对于更改是封闭的
    对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的.jar文件,都无需改动

一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有情况都贴切的模型。

既然不可能完全封闭,那么就必须有策略地对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出
最有可能发生变化的种类,然后构造抽象来隔离那些变化。

OCP背后的主要机制是抽象和多态。支持抽象和多态的关键机制之一是继承。正是使用了继承,我们才可以创建实现其基类中抽象方法的派生类。

是什么设计规则在支配着这种特殊的继承用法呢?
最佳的继承层次的特征又是什么呢?
怎样的情况会使我们创建的类层次结构掉进不符合OCP的陷阱中去呢?
这些正是Liskov替换原则要解答的问题。

第十章 Liskov替换原则(LSP)

子类型必须能够替换掉它们的基类型。

OCP是OOD中很多说法的核心。如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及健壮性。LSP是使OCP成为可能的主要原则之一。
正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。因此,如果没有
显式地强制基类类型的契约,那么代码就必须良好地并明显地表达出这一点。

第十一章 依赖倒置原则(DIP)

  • 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
  • 抽象不应该依赖于细节。细节应该依赖于抽象。

一个稍微简单但仍然非常有效的对于DIP的解释,是这样一个简单的启发式规则:“依赖于抽象。”这是一个简单的陈述,该启发规则建议不应该依赖于具体
类————也就是说,程序中所有的依赖关系都应该终止于抽象类或者接口。

根据这个启发式规则,可知:

  • 任何变量都不应该持有一个指向具体类的指针或者引用
  • 任何类都不应该从具体类派生
  • 任何方法都不应该覆写它的任何基类中的已经实现了的方法

第十二章 接口隔离原则(ISP)

不应该强迫客户依赖于它们不用的方法。

这个原则用来处理“胖”接口所具有的缺点。如果类的接口不是内聚的,就表示该类具有“胖”的接口。换句话说,类的“胖”接口可以分解成多组方法。每一组
方法都服务与一组不同的客户程序。

ISP承认存在有一些对象,它们确实不需要内聚的接口;但是ISP建议客户程序不应该看到它们作为单一的类存在。相反,客户程序看到的应该是多个具有
内聚接口的抽象类。

第三部分 薪水支付案例研究

第十三章 COMMAND模式和ACTIVE OBJECT模式

事实上,该模式横过了一条非常有趣的界线。而这个交界处正是所有有趣的复杂性之所在。大多数类都是一组方法和相应的一组变量的结合。COMMAND模式
不是这样的。它只是封装了一个没有任何变量的函数。

另一个COMMAND模式的常见用法是创建和执行事务操作。

UNDO

如果command派生类的do()方法可以记住它所执行的操作的细节,那么undo方法就可以取消这些操作,并把系统恢复到原先的状态。

第十四章 template method模式和 strategy模式:继承与委托

像大多数美丽新世界一样,它最终也被证明有些不切实际。知道1995年,人们才清楚地认识到继承非常容易被过度使用,而且过度使用的代价是非常高的。
所以我们减少了对继承的使用,常常使用组合或者委托来代替它。

template method模式和 strategy模式都可以用来分离高层的算法和低层的具体实现细节。都允许高层的算法独立于它的具体实现细节重用。此外,
strategy模式也允许具体实现细节独立于高层的算法重用,不过要以一些额外的复杂性、内存 以及运行时间开销作为代价。

第十五章 facade模式和mediator模式

本章中论述的两个模式有着共同的目的。它们都把某种策略施加到另外一组对象上。facade模式从上面施加策略,而mediator模式则从下面施加策略。
facade模式的使用是明显且受限的,而mediator模式的使用则是不明显且不受限制的。

第十六章 singleton模式和monostate模式

singleton模式的好处

  • 跨平台:可以跨多个JVM和多个计算机工作
  • 适用于任何类:只需把一个类的构造函数变成私有的,并且在其中增加相应的静态函数和变量,就可以把这个类变为singleton
  • 可以透过派生创建:给定一个类,可以创建它的一个singleton子类
  • 延迟求值:如果singleton从未使用过,那么就决不会创建它。

singleton模式的代价

  • 摧毁方法未定义:没有好的方法去摧毁一个singleton,或者解除其职责
  • 不能继承:从singleton类派生出来的类并不是singleton。
  • 效率问题:每次调用instance方法都会执行if语句。就大多数调用而言,if语句是多余的。
  • 不透明性:singleton的使用者知道它们正在使用一个singleton,因为他们必须要调用instance方法。

monostate模式的好处

  • 透明性:使用monostate对象和使用常规对象没有什么区别。使用者不需要知道对象是monostate
  • 可派生性:monostate的派生类都是monostate。
  • 多态性:由于monostate的方法不是静态的,所以可以在派生类中覆写它们。

monostate模式的代价

  • 不可转换性:不能透过派生把常规类转换成monostate类
  • 效率问题:因为monostate是真正的对象,所以会导致许多的创建和摧毁开销。
  • 内存占用:即使从未使用monostate,它的变量也要占据内存空间。
  • 平台局限性:monostate不能跨多个JVM或者多个平台工作。

如果希望透过派生去约束一个现存类,并且不介意它的所有调用者都必须要调用instance()方法来获取访问权,那么singleton是最合适的。如果希望
类的单一性本质对使用者透明,或者希望使用单一对象的多态派生对象,那么monostate是最合适的。

第十七章 null object模式

可以使用null object模式来解决这些问题。通常,该模式会消除对null进行检查的需要,并且有助于简化代码。

那些长期使用C-based语言的人已经习惯于函数对某种失败返回null或者0。我们认为对这样的函数的返回值是需要检查的。null
object模式改变了这一
点。使用该模式,我们可以确保函数总是返回有效的对象,即使在它们失败时也是如此。这些代表失败的对象“什么也不做”。

第十八章 薪水支付案例研究:第一次迭代开始

数据库实现是细节!应该尽可能地推迟考虑数据库。有太多的应用程序之所以和数据库绑定在一起而无法分离,就是因为一开始设计时就把数据库考虑在内
了。请记住抽象的定义:本质部分的放大,无关紧要部分的去除。在项目的当前阶段数据库就是无关紧要的;它只不过是一项用来存储和访问数据的技术
而已。

代码仓库提交历史

第四部分

第二十一章 factory模式

依赖倒置原则告诉我们应该优先依赖于抽象类,而避免依赖于具体类。当这些具体类不稳定时,更应该如此。

factory模式允许我们只依赖抽象接口就能创建出具体对象的实例。

可替换的工厂

使用工厂的一个主要好处就是可以把工厂的一种实现替换为另一种实现。这样,就可以在应用程序中替换一系列相关的对象。

结论

工厂是有效的工具。在遵循DIP方面工厂有着重大的作用。它们使得高层策略模块在创建类的实例时无需依赖于这些类的具体实现。它们同样也使得在一组
类的完全不同系列的实现间进行交换成为可能。然而,使用工厂会带来复杂性,这种复杂性通常是可以避免的。缺省地使用它们通常不是最好的做法。

第五部分 气象站案例研究

第二十三章 composite模式

当然,使用composite模式并不能把所有的一对多关系都转变成一对一关系。只有那些以一致的方式对待列表中的每个对象的情况才具备转换的可能性。

尽管如此,还是有相当一部分一对多的关系适合于使用composite模式。并且好处还是相当大的。

第二十四章 observer模式 – 回归为模式

如果你熟悉设计模式,那么在面临一个设计问题时,你的脑海中很可能会浮现出一个模式。随后的问题就是直接实现这个模式呢,还是通过一系列小步骤
不断地去演化代码。

observer模式的最大推动力来自开放封闭原则。使用这个模式的动机就是为了在增加新的观察对象时可以无需更改被观察的对象。这样,被观察的对象就
可以保持封闭。

第二十五章 abstract server模式、adapter模式和bridge模式

这种情况是无法避免的。不存在完美的结构。只存在那些试图去平衡当前的代价和收益的结构。随着时间的过去,这些结构肯定会随着系统需求的改变而改
变。管理这种变化的诀窍是尽可能地保持系统简单、灵活。

使用adapter模式的解决方案是简单和直接的。它让所有的依赖关系都指向正确的方向,并且实现起来非常简单。bridge模式稍稍有些复杂。我建议在开始
时不要使用bridge模式,直到你明显可以看出需要完全分离连接策略和通信策略并且需要增加新的连接策略时,才使用这种方法。

第二十六章 proxy模式和 stairway to haven模式:管理第三方API

虽然代理会带来很多讨厌的问题,但是它们具有一个非常大的好处:重要关系的分离。

在那些把业务规则和数据库实现分离显得非常重要的情况中,proxy模式是很适用的。就此而言,proxy模式可以用来分离业务规则和任何种类的实现问题。
它可以用来防止业务规则被诸如:COM、CORBA、EJB等东西污染。这是当前流行的一种保持项目的业务规则逻辑和实现机制分离的方法。

远在真正需要proxy模式或者stairway to haven模式前,就去预测对于它们的需要是非常有诱惑力的。但这几乎从来都不是一个好主意,特别是对于
proxy模式。我建议在开始时先使用facade模式,然后在必要时进行重构。

第六部分 ETS案例研究

第二十八章 visitor模式

visitor模式的一个非常常见的应用是,遍历大量的数据结构并产生报表。这使得数据结构对象中不含有任何产生报表的代码。如果想增加报表,只需增加
新的访问者,而不需要更改数据结构中的代码。这意味着报表可以被放置在不同的组件中,并且仅被那些需要它们的客户单独使用。

一般来说,如果一个应用程序中存在有需要以多种不同方式进行解释的数据结构,就可以使用visitor模式。

在每个使用访问者的情况中,所使用的数据结构都独立于它的用途。可以创建新的访问者,可以更改现有的访问者,并且可以把所有的访问者重新部署到
安装地点而不会引起现有数据结构的重新编译和重新部署。这就是visitor模式的威力。

visitor模式系列给我们提供了许多无需更改一个层次结构中的类即可修改其行为的方法。因此,它们有助于我们保持OCP。此外,它们也提供了用来分离
不同种类的功能的机制,从而使类不会和很多其他的功能混杂在一起。它们也同样有助于我们保持CCP。也应该可以清楚地看出,visitor模式系列的结构
中也使用了SRP、LSP以及DIP。

visitor模式是有诱惑力的。在它们面前很容易会失去自制力。如果它们有用就去使用它们,但是请对它们的必要性保持健康的怀疑。通常,可以使用
visitor模式解决的问题往往也可以使用更简单的方法解决。

第二十九章 state模式

有限状态自动机是软件宝库中最有用的抽象之一。它们提供了一个简单、优雅的方法去揭示和定义复杂系统的行为。它们同样也提供了一个易于理解、易于
修改的有效实现策略。我在系统的各个层面,从控制高层逻辑的GUI到最低层的通讯协议,都会使用它们。它们几乎适用于任何地方。

0%