《整洁代码的艺术》(The Art of Clean Code: Best Practices to Eliminate Complexity and Simplify Your Life)读书笔记

第1章 复杂性如何危害生产力

对于编程新手,复杂性:

  • 选择编程语言
  • 从数千个开源项目和大量现实问题中选出适合自己的
  • 决定使用哪些库
  • 决定在哪些先进技术上投入时间
  • 选择代码编辑器

“我如何开始”成了编程初学者的常见问题,最好的方式不是找本编程书(很少能读完),而是找个合适代码项目、推动它完成。原因:通过解决选择编辑器、编程语言环境等问题获取全面的技能组合,并且随着时间推移能够解决更大的难题、建立起自己的编程模式和概念洞察力

如何解决复杂性问题:简化。在编码周期的每个阶段都追求简单和专注。——在编程的每个领域都要采取彻底的极简主义

以下方法:

  • 梳理一天的工作,少做一些事,把精力集中在重要任务上
  • 对于单个软件项目,摈弃所有非必要特性,专注于最小可行产品,完成并发布它,高效、快速地验证设想
  • 尽量编写简单精炼的代码
  • 少花时间与精力在过早优化上——非必要的代码优化是多余复杂性的主要来源
  • 锁定用于编程的大块时间,避免分心、进入心流状态
  • 时间Unix哲学,代码功能只针对一个目标(“做好一件事”)
  • 在设计方案中贯彻简化,创建漂亮、整洁、专注、易于使用、符合直觉的用户界面
  • 在规划事业发展、下一个项目、每天工作或是专业领域时,使用专注技巧

1.1 何为复杂性

有不同的含义:

  • 计算机程序的计算复杂性(computational complexity),提供了一种分析对于不同输入的特定代码功能的方法。
  • 其他时候,宽松地定义为系统组件之间相互作用的数量或结构。

复杂性是由多个部分组成的,难以分析、难以理解或难以解释的一个整体。复杂性描述了一个完整的系统或实体。因为复杂性使系统难以解释,所以会引起挣扎和混乱。复杂性无处不在。

复杂性损害生产力,降低注意力。如果不及早控制复杂性,它将迅速消耗宝贵的时间

在生命的尽头,您不会根据回复了多少封电子邮件、晚了多少小时的电脑游戏,或者解了多少个数独题来判断自己是否度过了有意义的一生。

1.2 项目生命周期中的复杂性

1
规划-定义-设计-构建-测试-部署

1.3 软件和算法理论中的复杂性

  • 算法复杂度
  • 循路复杂度(cyclomatic complexity):描述了通过代码的线性无关(linearly independent)路径的数量,或至少有一条边不在其他路径上的路径的数量。例如,带有if语句的代码会导致两条独立路径通过代码,所以其循路复杂度会比没有分支的普通代码高。循路复杂度是可靠的代用指标,用于衡量认知复杂度,即理解一个特定代码库的难度。但其没有覆盖一些情况、如for循环带来的认知复杂度

1.4 学习中的复杂性

学得越多就越觉得自己的知识不足,永远不会在所有这些领域达到足够的掌握程度或感到准备好了。

1.5 过程中的复杂性

过程的复杂程度由其行动、参与者或分支的数量来计算。

当组织积累了太多的流程,复杂性就会开始堵塞系统。

在过于复杂的组织中,很难做出创新,因为复杂性无法被克服。资源被浪费,因为过程中的行动变得冗余。

为了保持过程高效,必须从根本上剔除不必要的步骤和行动。

1.6 日常生活中的复杂性,或谓七零八落

您必须解决每天都存在的分心问题,以及各种事务跟您争抢宝贵时间的问题。

卡尔.纽波特《深度工作:如何有效使用每一点脑力》——对需要深度思考的工作——如编程、研究、医学和写作——的需求越来越大,而由于通信设备和娱乐系统的普遍使用,这些工作的时间供应越来越少。

如果不粗暴地将其放到最高优先级,几乎不可能从事深度工作。外部世界在不断打扰你。

深度工作的结果是延迟满足。在大多数时候,你所渴望的是即时满足,潜意识经常想办法逃避深度工作。看信息闲聊等小奖励会轻松产生内啡肽刺激。与快乐、多彩和生动的即时满足世界相比,延迟满足变得越来越没有吸引力。

你为保持注意力和生产力所做的努力很容易被切得七零八落。答案:

卸载社交媒体应用程序,而不是试图管理花在上面的时间。减少您参与的项目和任务的数量,而不是试图通过更多的工作来做更多的时间。深入研究一种编程语言,而不是花费大量的时间在许多语言之间切换。


第2章 80/20原则

发现者:Vilfredo Pareto帕累托

80/20原则是指大部分效果(80%)出自少数起因(20%)——努力集中在几件重要的事情上,忽略许多不重要的事情。

2.1 80/20原则的基础概念

该原则认为,大部分效果出自少数原因。

它受欢迎的原因有两个方面:

  • 只要您能找出重要的事情,即导致80%结果的那20%活动,并坚持不懈地专注于这些活动,80/20原则就能让您同时保持轻松和高效。
  • 我们可以在很多情况下观察到这个原则,所以它具有相当大的可信度。

帕累托分布图:

p-1.png

2.2 应用软件优化

少部分代码函数占用了大部分运行时间,大多数代码函数对整体运行时间的占用远小于几个特定的代码函数。后者为“关键少数”,前者为琐碎多数

IBM、微软和苹果等大公司利用帕累托原则,将注意力集中在关键少数上来制造更快、更适用的计算机应用。

2.3 生产力

通过关注于关键少数而不是琐碎多数,您可以将生产力提高10倍以上。

比如一家员工,最好的群体相比其他人绩效能有16倍的差异,这是存在于全世界数百万机构中的事实。帕累托分布具备分形特征,这意味着在拥有数千名员工的大型组织中,其业绩差异甚至更加明显。

如何才能在你的组织中向帕累托分布曲线的左边移动?(成为更好的人)

p-2

2.4 成功指标

少数成功指标对你在某个领域的表现有很大影响,而其他的大部分指标则无关紧要。

为了获得采取正确行动的衡量标准,可以引入“先导指标”的概念。先导指标是在滞后指标发生之前就可以预测其变化的度量标准。如果达成更多先导指标,滞后指标可能会因此得到改善。

2.5 专注与帕累托分布

在帕累托分布中,每个等级的增长都是指数级的。所以即使是生产力的小幅增长也会导致收入的大幅增长。——“赢家通吃”现象

分散注意力没有好处,如果你不专注,就会参与许多帕累托分布。

p-3

在每个领域都存在不对等回报现象。

2.6 对程序员的意义

在编程领域,帕累托分布的结果往往比其他大多数领域更严重地偏重于顶部。比尔盖茨说:“车床操作顶尖高手的工资是普通车床操作员的几倍,但顶尖软件开发者的价值是普通软件开发者的1万倍”。

原因:顶尖程序员能解决普通程序员无能为力的一些难题;顶尖程序员洗代码的速度比普通程序员高1万倍;顶尖程序员的代码缺陷较少(缺陷害在当下,患及未来);顶尖程序员的代码易于扩展;顶尖程序员会跳出框框,找到创造性的解决方案,帮助团队专注于最重要的事情。

程序员的成功指标

要务之一是专注于写更多代码。代码写得越多,就会写得越好。这是多维问题的简化版本:优化代表性指标(写更多代码),就能在目的指标上有所进展(成为软件代码顶尖高手)。

代码写得越多,您就越理解代码,言行举止也越有专家模样。

2.7 帕累托分布具备分形特征

帕累托分布具备分形特征。

p-4

不断尝试寻找少做事多获益的方法。

2.8 80/20原则实践技巧

  • 找到成功指标。设定每日最低标准,在达到最低标准之前,都不能算已经开始了当前的工作
  • 找到生命中的大目标。如10年目标
  • 寻找用较少资源成事的方法。比如、利用他人的技能是划算之举
  • 反思自己的成功
  • 反思自己的失败
  • 阅读更多所在领域的著作
  • 花费大量时间改进和调优既有产品。如果永远在创造新产品、不去改进和优化旧产品,你的产品就永远平平无奇。
  • 微笑。保持乐观、很多事就会变得容易。微笑是一种小投入高产出的活动,其影响深远、代价轻微
  • 不做降低价值的事。不抽烟喝酒熬夜。

第3章 打造最小可行产品

Eric Ries(埃里克.莱斯)的《精益创业:新创企业的成长思维》一书普及了“最小可行性产品”(minimum viable product, MVP)的理念。

最小可行性产品只做最必要的特性,剥离其他所有特性,快速测试和验证假设,而不会浪费大量时间来实现用户最终可能不会使用的特性。

3.1 问题场景

隐身编程模式:在没有得到潜在用户反馈的情况下完成项目。它直至发布前一刻都保持神秘,希望能一鸣惊人,但多数时候这只是一种谬论。在实践中,一把就写出成功应用的情形非常之少。

  • 失去动力:在隐身编程模式中,你独立思考,随着时间推移、疑问越来越大,容易无疾而终。而如果你发布了早期版本,早期用户的赞誉或反馈都能帮助改进产品。
  • 分心:独立在隐身编程模式下工作时,很难忽视生活中各种使人分心的因素。MVP能减少从点子到市场的时间,创造让更多及时反馈涌现的环境,让你重新专注从而对抗分析问题。
  • 超时:规划失误时完成项目的另一大敌。有无数因素会拉长项目预期时长,只有少数因素能缩短项目时长。MVP摈弃所有非必要特性,所以规划失误也会减少,进度更符合预期。
  • 缺乏回应:任何软件产品都极有可能被沉默以对——既没有正面反馈,也没有负面反馈。常见原因是你的产品没能交付用户需要的特定价值。几乎不可能第一把就押中所谓的产品-市场契合点。如果在开发时没能获得来自真实世界的反馈,您就开始偏离实际,开发没人爱用的特性。MVP帮你更快地找到产品-市场契合点,基于MVP的开发直击客户的紧迫需求,提高客户的参与概率,获得他们对早期产品版本的反馈。
  • 错误假设:隐身编程模式失败的主要原因是在于你的错误假设。诸如用户是谁、以何为生、有何麻烦,以及如何使用您的产品。这些假设往往全错。没有外部测试的话,您会一直盲目开发真是受众并不想要的产品。一旦得不到反馈,或是只得到负面反馈,就失去了开发的动力。
  • 不必要的复杂性:比如你做了包含四个特性的产品,运气不错市场接受这个产品,你花了大量时间实现这四个功能,每个特性也得到了积极的反馈。软件产品未来每个版本都将至少包括这四个特性,然而特性1可能毫无意义但你仍然耗费大量时间来实现它。对于n个特性,有2^n种排列组合。发布这些特性组合时,你怎么知道哪个特性有价值哪个特性纯属浪费时间呢?

3.2 构建最小可行性产品

解决方案很简单:构建一系列MVP。每次发布只实现一个特性,您就能更透彻地了解市场接受什么特性,哪些假设符合真实情况。想办法降低复杂度。

在市场中测试过MVP,且分析出其成功的原因后,就可以构建第二个MVP,添加另一些重要特性。

通过一系列MVP来寻求正确产品形态的策略叫作快速原型。每个原型都基于从上一次发布种了解到的东西来构建,而且也在最短时间内以最小的代价带来最多的反思。你今早和经常性发布,就可以尽早找到市场-产品契合点,确定产品需求和目标市场愿望。

当你采用MVP式软件开发方法,每次只新增一个新特性,鉴别哪个特性应当保留、哪个特性应当剔除就变得很重要了。MVP软件创造过程的最后一步是对照测试(split testing):不向全体用户投放新迭代版本,只投放给一小部分用户,观察他们的显性和隐性反应。

最小可行性产品的四大要点:功能、设计、可靠、易用性

  • 反对快速原型、支持隐身模式的常见观点认为:隐身模式能保住你的点子。自己的点子足够独特、如果以MVP等不成熟的形态发布,就会被强有力的公司偷去更快做出来。——这纯属谬论,点子不值钱,执行才是王道。没什么独特点子可言,你的点子大有可能早已被其他人想到过。保密只会限制成长的可能。

设想产品,但先思考用户需求再写代码。打造精心设计、交互流畅、易于使用的MVP,提供有价值的功能。除了那些对达成目标绝对有必要的特性,摈弃其他一切特性。专注于一事。然后,尽早和频繁地发布MVP——持续验证和添加新特性,不断改进。少即是多,花时间想清楚要实现什么特性。每个特性都将在未来给其他全部特性带来直接或非直接的实现成本影响。采用对照测试方法同时验证两个产品变体带来的反馈,抓紧摈弃那些不能改进留存率、页面逗留时间、特定行为等关键用户指标的特性。软件开发不过是产品创造和价值交付过程中的一小步而已


第4章 编写整洁和简单的代码

整洁代码易于阅读、理解和修改。

整洁代码和简单代码:整洁代码往往简单,简单代码往往整洁。但也有可能存在既复杂又整洁的代码,追求简单的话就要避免复杂。整洁代码既保持整洁,也想办法对付躲不开的复杂性。

4.1 为何要写整洁代码

整洁代码对于你的同伴和未来的自己都更易于理解,人们更愿意给整洁代码添砖加瓦,写作的可能也由此提升了。因此,整洁代码能显著降低项目成本

事实上,读代码的时间和写代码的时间的比例远超10比1,使代码易于阅读,也就使其易于编写。

项目代码越多,写一行新代码耗费的时间就越多,无论代码整洁还是污糟。比如已经写了n行代码,要添加第n+1行,新增的这行代码有可能影响到既有全部代码。比如它可能拖慢性能、产生bug等。

太长的代码会导致许多其他复杂性问题,代码越多、由此增加的复杂性越会拖慢进度。

如果能用100行代码做到所有功能,根本不必花很多时间思考和重组项目代码。添加更多代码时问题才开始出现:当项目体量从100行代码膨胀到1000行代码时,细心思考,符合逻辑地将代码安排到各个模块、类或文件中,这种周到的做法会更有效率。

对于不少特别小的项目,反思、重构和结构重组会多花很多时间,而风险有时也相当大。

4.2 编写整洁代码的原则

改进代码、减少复杂度就说所谓的“重构”。重构是软件开发的关键元素。

编写整洁代码主要在于坚守两点:懂得从头构造代码的最佳途径,以及隔段时间就回头修改代码。

  • 原则1:心怀全局。要构造良好的架构,得回头思考全局。首先要决定实现什么特性。全局思维是大幅降低应用复杂度的省时方法。思考以下问题:
    • 所有文件和模块都是必须的吗?能否做强其中一些,减少代码中的相互依赖关系?
    • 可以将巨大、复杂的文件切分为两个简单的文件吗?(与上一个问题要寻找平衡)
    • 可以将代码通用化,改写成库,简化主应用吗?
    • 可以采用既有库,从而移除多行代码吗?
    • 可以利用缓存机制来避免重复计算同一个结果吗?
    • 可以采用更直接和适用的算法来完成当前算法要做的事吗?
    • 可以不做那些并不能改进综合性能的过早优化操作吗?
    • 能否用另一种更合适的编程语言解决问题?
  • 原则2:站到巨人的肩上。重新发明轮子毫无价值。只要加一行引用语句,数以百万程序员的集体智慧就能为我所用。使用代码库往往也能改进代码的运行效率,成千上万程序员用过的函数多半比自己写的更有可能被优化过。并且调用库函数的语句更易于理解,也会占用较少的项目时间。
  • 原则3:为人写代码,而不是为机器写代码。代码最终由机器来运行,但代码仍然主要由人编写。在部署到机器之前,代码多半得经过人类好几轮评判。永远假定会有别人读你的代码,它提示我们缩进、空白、注释、代码行长度等元素也可以清晰地表达代码的意图。整洁代码从根本上优化了人类的阅读体验。
  • 原则4:正确命名。有经验的程序员常常或明或暗地保有一套命名惯例共识。秉承惯例,大家都有好处:代码变得更易阅读、易懂,且较少混乱。如“最小意外原则”指出的那样,使用不符惯例的变量命名法,就会出乎其他程序员所料,这样做毫无价值。可以参考的命名规则:选用描述性较强的名称,选用没有歧义的名称,使用可拼写的名称,使用命名常量、不用魔术数等。*在不同语言中,这套惯例各有差异。
  • 原则5:一以贯之地遵循标准。官方风格指南、惯例规则。也可以让代码检查工具和集成开发环境IDE告诉你犯了什么错。
  • 原则6:使用注释。使用注释帮助读者理解代码、总结一段代码的功用。始终考虑“为人写代码”原则。
  • 原则7:避免非必要注释。有时候注释会混淆视听,我们需要保证注释有价值、避免写非必要的注释。例如已经有了有意义的命名、代码本身足以说明其功用,那就没必要写注释。
    • 不要写代码行内注释。只要使用有意义的变量名称就可以避免写代码行内注释。
    • 不要为显而易见的代码添加注释。
    • 不要注释掉旧代码,直接删它。有版本管理工具。
    • 使用文档生成功能。
  • 原则8:最小意外原则。系统中的组件应表现得就像用户预期的那样。在设计高效应用和用户体验方案时,这是条黄金原则。如打开Google、光标会自动置于搜索输入框。
  • 原则9:别重复自己。DRY是被广泛认可的原则,主张避免编写重复的代码。例如函数和循环都是消除重复代码的有用工具。对DRY原则的破坏常被称为WET(we enjoy typing, write everything twice, and waste every’s time)
  • 原则10:单一权责原则。每个函数都应当承担一件主要工作。与其用一个大函数全包全揽,不如多用一些小函数。对功能的风筝降低了整体代码的复杂度。“每个函数都该只承担一种权责”,“权责”定义为“修改的理由”,只有需要改变该全责的程序员方可要求修改其定义。如果代码本身没有错误,另有权责的其他程序员甚至不要想着要求修改。
  • 原则11:测试。测试和重构往往能降低代码复杂度,减少错误数量,但注意别用力过猛、您只需测试真实世界中确实会出现的场景。可用以改进软件应用的测试类型并无限制,有几种常见类型:单元测试、用户验收测试、冒烟测试、性能测试、承载规模扩展能力测试。
  • 原则12:小即是美。小块代码是只需相对较少的代码即可完成单个指定任务的代码。许多新手程序员会写出体积巨大的单个函数,即所谓的“上帝对象”(God object),集中处理所有事物,这些大块代码会是后期维护的噩梦。
  • 原则13:得墨忒耳律(Law of Demeter)。最大限度地减少代码元素的相互依赖,尽量减少代码对象之间的依赖关系,降低代码的复杂度、从而提升可维护性。将软件切分为最少两个部分:第一个部分定义对象;第二个部分定义操作。得墨忒耳律的目的是维护对象与操作之间的松散耦合关系,修改其中之一时,不至于严重影响其他部分。这将大幅减少维护时间。
    • 对象只应调用其自有方法或邻近对象的方法,不能借道邻近对象调用其他对象的方法。斩断方法调用链、“不与陌生人交谈”。对于有几百个类的项目,大幅减少依赖关系能降低应用程序的整体复杂度。
  • 原则14:您不会需要它。如果你只是怀疑总有一天会用到某些代码,就不该实现这些代码——因为您不会需要它!只写百分百确定必须要的代码。代码为今天而写、不为明天而写。要避免用力过猛,放过又小又费时间的修正工作。(80/20)
  • 原则15:别用太多缩进层级。滥用缩进会降低代码的可读性。
  • 原则16:使用指标。使用代码质量指标来持续跟踪代码复杂度。
  • 原则17:童子军军规和重构。童子军军规很简单:让营地比你来时更干净。养成习惯,清理你遇到的每一段代码。这不仅会改进代码还能练就编程大师的慧眼、一眼评估代码的质量。

p-7

p-6


第5章 过早优化是万恶之源

过早优化是将宝贵的资源——时间、精力、代码行投入到非必要的代码优化的行为上,是糟糕代码的主要问题之一。

5.1 6种过早优化的类型

优化代码片段时,你往往得拿复杂度来换性能。有时候通过编写整洁代码既能保持低复杂度又能获得高性能,但需要花费编程时间。懂得如何投入这些资源相当重要。

我们应当忘掉影响范围较小的效率问题(大概会占全部效率问题的97%),过早优化是万恶之源。

  • 优化函数。在确知函数被使用的频率之前,注意别花时间优化它。
  • 优化特性。别增加非必须的特性、别花时间。和拖慢产品的开发周期,妨碍您从用户处收集反馈。
  • 优化规则。试图找到还没发生的问题的解决方案,可能会妨碍有价值反馈的收集。得接受不完美,如果你不开工洗代码,就会一直困在理论的象牙塔中,永远不能完成项目。
  • 优化可扩展性。在对用户群体没有现实了解前,过早地优化应用的可扩展性,会严重干扰进度,浪费价值上万美元的开发者和服务器时间。在服务好第一位用户之前,别去尝试扩展到能服务100w用户。
  • 优化测试设计。函数纯粹只是做个实验,或者函数本身并不适合先写测试。给实验性代码添加一层测试会妨碍进度,也没有遵守快速原型哲学。真实用户才是管用的测试。
  • 优化面向对象世界建设。面向对象方法会引入非必要的复杂性和过早的“概念性”优化。从最简单的模型开始,只在需要时做扩展。别将代码对世界的建模优化到细节翔实、远超应用真实所需的程度。

5.2 性能调优的6条提示

在正确的时间进行优化很有必要。

你只该在有明确证据证实要优化的代码或功能确实是瓶颈,而且用户会喜欢甚至要求更好性能时,才执行优化。

开发应用的成本比数千名用户使用它的成本低。

  • 先度量再改进。没度量无法跟踪和评估改进,过早优化常常是未经度量就实施的优化。度量对象可以是内存占用或速度等,要拿度量结果做基准。度量性能等一般策略是,从写最直接、幼稚和易于阅读的代码开始——原型/幼稚实现手段/MVP。用表格记录度量结果——这是第一个基准。另写一套代码,用这个基准度量其性能。严格证实优化方案能够改进代码性能后,优化过的代码称为后续改进针对的心基准,如果优化不能客观改进代码则放弃优化。
  • 帕累托为王。有些特性要比其他特性占用更多内存等资源,集中改进这些瓶颈。性能优化具备分形特性,移除一个瓶颈就会发现另一个瓶颈。系统中永远有瓶颈。
  • 算法优化获胜。很多性能瓶颈都可以通过调整算法和数据结构来解决。可以问问:是否有更好的算法?能否针对具体问题调优既有算法?能否改进数据结构?
  • 缓存万岁。有效使用缓存的基础策略:
    • 提前执行运算(“离线”),将结果放到缓存中;
    • 运算出现时执行一次(“在线”),将结果放到缓存中
  • 少即是多。在多数情况下,应当使用估测策略而非最有算法。问问:当前瓶颈是什么?为什么会有瓶颈?这个问题是否值得花精力解决?可以移除特性或是提供版本吗?。在简化代码时,考虑做一下操作是否有意义:
    • 不实现某个特性,消灭瓶颈于未然;
    • 用更简单的问题来替代,简化难题;
    • 根据80/20,不做1个代价昂贵的特性,省出资源做另外10个代价低廉的特性;
    • 不实现某个重要特性,转而实现另一个更重要的特性;考虑机会成本。
  • 懂得何时停止。到达某个临界点之后、再试图改进性能就纯粹是浪费时间了。

第6章 心流

心流是人类终极表现的源代码——Steven Kotler

6.1 什么是心流

心流体验是一种完全沉浸于手头工作的状态:专注,心流的另一个名字是超专注(hyperfocus)。心流状态有六个要素:注意力、行动、自我、控制、时间、回报

6.2 如何达到心流状态

  • 清晰的目标。在心流状态中,每个行动自然而然会引出下一个行动,最终必有终极目标。在玩电子游戏时,人们常常进入心流状态、因为小举动的成功最终导向实现大目标。——实现每行代码
  • 反馈机制。反馈机制奖赏期望行为、惩罚不期望的行为。反馈是心流的前提条件。
  • 平衡机会与能力。心流是一种思维活跃状态。任务太容易的话就会厌烦、失去沉浸感;任务太难的话会太早放弃。任务必须有挑战性、但不能过于困难。进入心流状态的技巧是,持续寻求更难的挑战、但不触达导致焦虑的难度水平,还要相应地提升技能水平。这样周期式学习会让你循环提高生产力和技能。

p-5

6.3 给程序员的心流提示

心流有7个条件:知道要做什么;懂得如何做;清楚自己能做好;知晓路径;寻求挑战;提升技能应对高难度挑战;避免分心。

  • 手头始终有实用代码项目。不要花时间漫无目的地学这学那,将学习时间70%分配给实用又有意义的项目,30%分给阅读书籍、教程或观看视频。
  • 开展符合目标的有趣项目。你必须对工作兴奋起来。
  • 发挥优势。人总有不擅长的领域,在多数活动中你的技能低于平均水准。要发挥优势、磨练技能,别去管大部分劣势。找出优势,围绕优势严密安排每一天的工作。
  • 为编程准备大块时间
  • 在心流中时杜绝分心。另外保持充足睡眠、健康饮食、规律锻炼。“垃圾进、垃圾出“,有高质量输入才能有高质量输出。
  • 获取高质量信息。阅读顶级刊物上的研究论文。

我们生命中最好的时光不是那些被动接受的放松时光…..为了实现某种困难但却值得的事而自愿投入,身心都逼近极限之时,最好的时光通常才会出现。——米哈里.契克森米哈


第7章 做好一件事,以及其他Unix原则

这就是Unix哲学:编写只做一件事并且把那件事做好的程序;编写能一起工作的程序;编写处理文本流的程序,因为文本流是通用接口——Douglas Mcllroy

Unix操作系统的主流哲学很简单:做好一件事

7.1 Unix的崛起

Unix是一种设计哲学,它启发了包括Linux和macOS在内的流行操作系统。

能够支撑这等规模协作的哲学就是DOTADIW——只做一件事,做好这件事(do one thing and do it well)。

7.2 Unix哲学概览

Unix哲学的基本概念是打造易于扩展和维护的简明、精炼、模块化代码。

通过让每个函数聚焦于只实现一个目的,改进了代码的可维护性和可扩展性。

7.3 15条有用的Unix原则

  • 每个函数做好一件事
  • 简单胜于复杂
  • 小即是美。可以降低复杂度、改进可维护性、改进可测试性。
  • 尽快打造原型。早失败、多失败、败而后能进。
  • 可移植性胜于效率。典型的正面案例:虚拟化。Unix哲学主张在可移植性与效率之间选择前者,降低可移植性意味着降低应用价值。
  • 在纯文本文件中保存数据。纯文本文件是简单的文本或二进制文件,无须高级手段即可访问其内容,是人类可阅读的简单数据文件。常见的逗号分隔数值(CSV)格式就是纯文本文件格式的一种。然而这样的便利要付出效率损失的代价:为特定目的设计的特定数据格式能够有效地多地保存和读取数据,例如数据库使用专有的硬盘文件,采用详细索引和压缩方案等优化手段来呈现数据。再然而,你应当只在确定需要时才使用优化等数据呈现手段。例如小规模哦应用(如10000条数据集训练机器学习模型)、推荐使用CSV格式保存数据。采用专门格式数据库会降低可移植性、增加不必要的复杂度。
  • 使用软件杠杆获得优势。第一种杠杆:利用前辈们的集体智慧的杠杆作用:在实现复杂功能时使用库,而不是自己从头开发;查阅StackOverflow或其他群体智慧来修正代码中的缺陷,或者请其他程序员帮忙评审代码。第二种杠杆:算力,创造更好的软件与更多人分享,部署更强的算力,更频繁地使用他人的库和软件。
  • 避免使用强制性用户界面。强制性用户界面时那种要求用户在进入主执行流之前必须与程序交互的界面、如SSH/top/cat/vim等程序。强制性用户界面限制了代码易用性,因为它们被设计为必须有人参与才能工作。要让代码遵从这条原则,我们得分离用户界面与功能,这往往是改进代码的绝佳做法。
  • 把每个程序都写成过滤器。过滤器采用具体过滤机制,将输入变换为输出。这让我们可以将一个程序的输出当作另一个程序的输入,从而轻松地连接多个程序,显著提升代码的可复用性。例如,在函数里输出计算结果不是好做法。这条原则主张,程序应返回一个可被输出、保存到文件或者作为其他程序输入的字符串。过滤器的黄金标准是同质输入/输出映射,即输入的类型映射为相同的输出的类型。例如如果函数接受输入参数那么调用者会期待得到函数返回值,如果程序从文件读入数据那么调用者会期待输出为文件。设计过滤器最符合直觉的方式是保持同样的数据类别。
  • 更差即更好。编写较少功能的代码常常是较好的实践做法。资源有限时,最好先发布没那么好的产品,而不是一直挣扎改进。在有资源限制的世界中,先发布差一些的东西常常更有效率——先发优势。
  • 整洁代码胜于机灵代码。机灵代码不应以牺牲简洁性为代价。
  • 将程序设计成能与其他程序相连接。编程接口。
  • 编写健壮的代码。如果代码不易被破坏,那么它就是健壮的代码。代码健壮性体现在程序员角度和用户角度。
    • 程序员在修改代码时困难就会破坏它,如果连粗心的程序员都可以修改代码且不会轻易破坏其功能,这样的代码就强固到足以应对修改。令代码保持强固的方法之一是控制访问权限
    • 对于用户而言,如果你不能通过错误甚至有害的输入轻易破坏应用,那么应用就够强固。一种是乱敲乱输入的用户、一种是非常了解应用软件的黑客高手。前一类用户单元测试是一种强有力的工具。检查你的程序是否具备处理所有类型输入的能力,让你的代码更强固。
  • 尽量修复——但尽早暴露失败。应当尽量修复代码中的问题,但不应隐藏无法修复的错误。隐藏的错误会很快恶化,隐藏世界越长问题就越大。错误会累积。
  • 避免手工操作——尽量编写能写程序的程序。人类容易犯错,尤其是爱进行重复和枯燥的活动时容易犯错,那么可以自动生成代码时就要自动生成代码。有很多办法可以做到——例如python等现代高级编程语言就是通过这种程序编译为机器代码,编程者无需操心底层硬件编程语言。

第8章 设计中的少即是多

雅虎与谷歌搜索、黑莓与iPhone的差异:胜利者往往采用了极度简单的用户界面。在设计中,是否少即是多?

8.1 移动电话演进过程中的极简主义

即便应用复杂度大幅增加,极简设计仍然可行。移动电话技术越来越复杂,其用户界面反而越来越简单。

8.2 搜索中的极简主义

通过极简设计来表达对品牌完整性和功能聚焦的坚持,价值远远大于通过出售这幅“地产”所获的收益。

8.3 拟物设计

极简设计去除所有非必要元素,得到多半能取悦用户的漂亮产品。

对比拟物设计和非拟物设计:非拟物设计较为简洁,占用空间较少,使用的颜色和阴影之类非功能性视觉元素也较少。然而,非拟物设计缺少边界和符合人类直觉的熟悉布局,常常比较容易令读者困惑。真正的极简主义永远利用较少的高代价资源完成同一任务,有时这意味着减少网站上的视觉元素,有时这意味着添加元素减少用户思考时间。但可以确定的,用户时间是比屏幕空间更为稀缺的资源。

8.4 如何实现极简主义

  • 留白。用留白替代设计元素能改善明确度,得到更聚焦的用户体验。记住:让用户迷惑,用户就会流失。留白提升了用户界面的清晰度。留白也能让文字更显眼。
  • 去除设计元素。逐个审视设计元素,只要有可能就丢弃它。沉没成本偏差使你倾向于坚持自己的创作成果,即便它们并不必要。
  • 移除特性。随着时间推移,应用的特性会堆积起来——特性蔓延。因此越来越多资源得转移到维护既有特性上,特性蔓延会让软件变得臃肿,导致欠下技术债、削弱组织敏捷性。移除特性是为了释放精力、时间和资源,重新投资于对用户最重要的少数特性。移除特性能释放资源、时间、精力和用户界面上的空间。释放出来的资源、时间、精力和用户界面可以重新投入到对重要特性的改进中。
  • 减少字体和颜色。变化越多,就越复杂。用太多字体、字号和颜色就会引起用户的认知摩擦。
  • 一以贯之。应用往往不只有一个用户界面,而是需要用一系列界面来与用户交互。这引领我们走向极简主义的另一维度:一致性。即在应用中尽量减少设计变化。一致性确保应用看起来是个整体。为了保证品牌的一致性,软件公司会发布品牌指引(brand guidelines),要求开发者遵循。可以通过使用模版和层叠样式表来实现风格的一致性。

第9章 专注

9.1 对抗复杂性的武器

熵定义了系统中的随机性、无序性和不确定性的程度。高熵意味着高随机性和混沌。低熵意味着秩序与可预测性。系统的熵随着时间推移而增加——从而导致高熵状态。

生产意味着创造某种东西,无论是建房、写书、还是写软件应用。基本上要有高产出,就得减少熵,让资源能够以有利于更大计划的方式组织起来。

9.2 统一原则

  • 80/20原则
  • 打造最小可行性产品
  • 编写整洁和简单代码
  • 过早优化是万恶之源
  • 心流
  • 做好一件事(Unix)
  • 设计中的少即是多

书籍