代码精进之路:从码农到工匠
序二
- 近几年的银弹是微服务,但是微服务需要更强的业务建模能力和技术管理能力,否则实现和维护微服务系统只能是难上加难。
- 真正的技术Leader是能够创建并且演进架构,在架构层面上帮助大家比较容易地写出好代码的人;是能够创建良好的技术氛围,以写好代码为荣,以写坏代码为耻,促使大家不停学习的人。
- 即使我们的工作是在维护“屎山”(Shit Mountain),也请不要忘记时时仰望星空……
前言
- 我有一个梦想,我写的代码,可以像诗歌一样优美。我有一个梦想,我做的设计,能恰到好处,既不过度,也无不足。
- 不管你们有多敬业,加多少班,在面对烂系统时,你仍然会寸步难行,因为你大部分的精力不是在应对开发需求,而是在应对混乱。
- “软件的复杂性是一个基本特征,而不是偶然如此。”问题域有其复杂性,而软件在实现过程中又有很大的灵活性和抽象性,导致软件具有天然的复杂性。
- 整洁面向对象分层架构(Clean Object-oriented and LayeredArchitecture,COLA)
- 一个程序员的“美德”就在于他是否能为后人留下一段看得懂、可维护性好的代码。
- 需要我们对自己的思维习惯、学习方法和工程实践进行彻底的反省和重构。
- 任何不区分上下文和情景的教条都有可能在实施过程中遭遇惨败。
- 软件工程就是一个对现实世界的问题进行分析、抽象、建模,然后转换成计算机可以理解的语言,解释执行,实现特定业务逻辑的过程。
- 如果说思想是务虚的最高境界,那么实践就是务实的最低要求。
- 种一棵树最好的时间是十年前,其次是现在
- 管理者的一个很重要的使命就是帮助团队成长,包括制定规范和技术传承。
第一部分 技艺
- 名为万物之始,万物始于无名,道生一,一生二,二生三,三生万物。
- 一个名字虽然并不影响程序的执行,但是却对代码的表达力和可读性有着重要的影响。
1.2 命名其实很难
- 命名为什么难呢?因为命名的过程本身就是一个抽象和思考的过程,在工作中,当我们不能给一个模块、一个对象、一个函数,甚至一个变量找到合适的名称的时候,往往说明我们对问题的理解还不够透彻,需要重新去挖掘问题的本质,对问题域进行重新分析和抽象,有时还要调整设计和重构代码。因此,好的命名是我们写出好代码的基础。
- 在计算机科学中有两件难事:缓存失效和命名
1.3 有意义的命名
- 代码即文档,可读性好的代码应该有一定的自明性,也就是不借助注释和文档,代码本身就能显性化地表达开发者的意图。
- 变量名应该是名词,能够正确地描述业务,有表达力。
- 类似的还有魔术数,数字86400应该用常量SECONDS_PER_DAY来表达;每页显示10行记录的,10应该用PAGE_SIZE来表达。
- 函数命名要具体,空泛的命名没有意义。
- 函数的命名要体现做什么,而不是怎么做
- 对于一个应用系统,我们可以将类分为两大类:实体类和辅助类。
- 对于辅助类,尽量不要用Helper、Util之类的后缀,因为其含义太过笼统,容易破坏SRP(单一职责原则)。比如对于处理CSV,可以这样写:
- 包(Package)代表了一组有关系的类的集合,起到分类组合和命名空间的作用。
- 包名应该能够反映一组类在更高抽象层次上的联系。
- 包的命名要适中,不能太抽象,也不能太具体。
1.4 保持一致性
- 保持命名的一致性,可以提高代码的可读性,从而简化复杂度
- 每个概念对应一个词,并且一以贯之。
- 如果你要用类似Total、Sum、Average、Max、Min这样的限定词来修改某个命名,那么记住把限定词加到名字的最后,并在项目中贯彻执行,保持命名风格的一致性。
- 为了避免Num带来的麻烦,我建议用Count或者Total来表示总数,用Id表示序号。
- 统一语言就是要确保团队在内部的所有交流、模型、代码和文档中都要使用同一种编程语言。实际上,统一语言(Ubiquitous Language)也是领域驱动设计(Domain Driven Design,DDD)中的重要概念
1.5 自明的代码
- 我们可以通过添加中间变量让代码变得更加自明,即将计算过程打散成多个步骤,并用有意义的变量名来命名中间变量,从而把隐藏的计算过程以显性化的方式表达出来。
- 只要把计算过程打散成一系列良好命名的中间值,不透明的语义自然会变得透明。
- 如果注释是为了阐述代码背后的意图,那么这个注释是有用的;如果注释是为了复述代码功能,那么就要小心了,这样的注释往往意味着“坏味道”
- 注释要能够解释代码背后的意图,而不是对功能的简单重复
- 这里的注释和没写是一样的,因为它只是对sleep的简单复述。正确的做法应该是阐述sleep背后的原因,比如改写成如下形式就会好很多。
1.6 命名工具
- 作者一般会安装一个叫作OnlineSearch的插件,插件里自带了像SearchCode这样的代码搜索工具,也可以自己配置像Codelf这样的代码搜索工具。
1.7 本章小结
- 命名的重要性不仅体现在提升代码的可读性上,有意义的命名更能够引导我们更加深入地理解问题域,理清关键业务概念,进行合理的业务抽象,从而设计出更加符合业务语义、易于理解的系统。
- 了解如何给软件制品(Artifact,包括Module、Package、Class、Function和Variable)命名,如何写注释,如何让代码自明地表达自己,以及如何保持命名风格的一致性。
第2章 规范
- 离娄之明,公输子之巧,不以规矩,不能成方圆。
- 事物的复杂程度在很大程度上取决于其有序程度,减少无序能在一定程度上降低复杂度,这正是规范的价值所在。通过规范,把无序的混沌控制在一个能够理解的范围内,从而帮助我们减少认知成本,降低对事物认知的复杂度。
2.1 认知成本
- 所谓认知,是指人们获得知识或应用知识的过程。获得知识是要学习的,在学习过程中,我们要交的学费叫作认知成本。
- 发现共同抽象和机制可以在很大程度上帮助我们理解复杂系统。
2.2 混乱的代价
- 认知是有成本的,而混乱的代价在于让我们对事物无法形成有效的记忆和认知,导致我们每次面对的问题都是新问题,每次面临的场景都是新场景,又要重新理解一遍。
- 规范的缺失会导致工程师不知道应用中有哪些制品(Artifact)、如何给类命名、一个类应该放在哪个包(Package)或哪个模块(Module)里比较合适、错误码应该怎样去写、什么时候该打印日志、选用哪个日志级别。
- 我们有限的精力用在刀刃上,而不是用来疲于应对各种不一致和随心所欲的混乱。
2.3 代码规范
- 一个简单的原则就是将概念相关的代码放在一起:相关性越强,彼此之间的距离应该越短。
- 开发人员应在一开始就养成良好的撰写日志的习惯,并在实际的开发工作中为写日志预留足够的时间。
- 我认为比较有用的4个级别依次是ERROR、WARN、INFO和DEBUG。通常这4个级别就能够很好地满足我们的需求了。
- ERROR表示不能自己恢复的错误,需要立即被关注和解决
- 对于可预知的业务问题,最好不要用ERROR输出日志,以免污染报警系统。例如,参数校验不通过、没有访问权限等业务异常,就不应该用ERROR输出。
- INFO用于记录系统的基本运行过程和运行状态。
- 为了防止日志量过大,我们可以采用分布式配置工具来实现基于requestId判断的日志过滤,从而只打印我们所需请求的DEBUG日志。
- 针对以上问题,我建议在业务系统中设定两个异常,分别是BizException(业务异常)和SysException(系统异常),而且这两个异常都应该是Unchecked Exception。
- 最后,针对业务异常和系统异常要做统一的异常处理,类似于AOP,在应用处理请求的切面上进行异常处理收敛
- 千万不要在业务处理内部到处使用try/catch打印错误日志,这样会使功能代码和业务代码缠绕在一起,让代码显得很凌乱,并且影响代码的可读性。
- 错误码非常重要,一定要在系统搭建之初就制定好相应的规范,否则当系统上线后,系统的错误码已经对前端或者外部系统进行了透出,再重构的可能性就很小了。
- 我们可以将错误码定义成3个部分:类型+场景+自定义标识。每个部分之间用下划线连接,内容以大驼峰的方式书写。
2.4 埋点规范
- 有一句话叫“业务数据化、数据业务化”,即业务要沉淀数据、数据要反哺业务。
- 在阿里巴巴有一个超级位置模型(Super Position Model,SPM)的埋点规范,用于统计分析各种场景的用户行为数据。
2.5 架构规范
- 规范对于架构来说至关重要。从某种意义上来说,架构就是一组约束,遵从了这些约束,才能符合架构要求;反之,架构将失去意义。
2.6 防止破窗
- 环境中的不良现象如果被放任存在,就会诱使人们仿效,甚至变本加厉
- 从“破窗效应”中我们可以得到这样一个道理:任何一种已存在的不良现象都在传递着一种信息,会导致不良现象无限扩展,同时必须高度警觉那些看起来是偶然的、个别的、轻微的“过错”,如果对“过错”不闻不问、熟视无睹、反应迟钝或纠正不力,就会纵容更多的人“去打烂更多的窗户”,极有可能演变成“千里之堤,溃于蚁穴”的恶果。
- 首先,我们要有一套规范,并尽量遵守规范,不要做“打破第一扇窗”的人;其次,发现有“破窗”,要及时地修复,不要让事情进一步恶化。整洁的代码需要每个人的精心呵护,需要整个团队都具备一些工匠精神。
2.7 本章小结
- 留给公司一个方便维护、整洁优雅的代码库,是我们技术人员的最高技术使命,也是我们对公司做出的最大技术贡献。
第3章 函数
- 把简单的事情做到极致,功到自然成,最终“止于至善”。
3.2 软件中的函数
- 在英语中,Function一般代表函数式语言中的函数,而Method代表面向对象语言中的函数。
3.3 封装判断
- 如果把解释条件意图作为函数抽离出来,用函数名把判断条件的语义显性化地表达出来,就能立即提升代码的可读性和可理解性。
3.4 函数参数
- 最理想的参数数量是零(零参数函数),其次是一(一元函数),再次是二(二元函数),应尽量避免三(三元函数)
- 如果函数需要3个以上参数,就说明其中一些参数应该封装为类了。
3.5 短小的函数
- 如果是Java语言,我建议一个方法不要超过20行代码
3.6 职责单一
- 一个方法只做一件事情,也就是函数级别的单一职责原则(Single Responsibility Principle,SRP)。
3.7 精简辅助代码
- 所谓的辅助代码(Assistant Code),是程序运行中必不可少的代码,但又不是处理业务逻辑的核心代码,比如判空、打印日志、鉴权、降级和缓存检查等。
- Java 8引入了一个很有趣的特性——Optional类。Optional类主要解决的问题是“臭名昭著”的空指针异常。Optional类是一个包含可选值的包装类,意味着Optional类既可以含有对象,也可以为空。使用Java 8的这个新特性和新语法,我们可以用Optional来代替冗长的null检查:
- 对于那些对可用性要求非常高的场景,有必要制定一个服务降级的策略,以便当其中一个服务不可用时,我们仍然能够对外提供服务。
3.8 组合函数模式
- 组合函数要求所有的公有函数(入口函数)读起来像一系列执行步骤的概要,而这些步骤的真正实现细节是在私有函数里面。组合函数有助于代码保持精炼并易于复用。阅读这样的代码就像在看一本书,入口函数是目录,目录的内容指向各自的私有函数,而具体的内容是在私有函数里实现的。
- 而且并不会涉及什么高深的思想,只要我们愿意,很多时候只需要多做一点点,就可以写出更好的代码,这也是“工匠精神”的一种体现
- 我不是一个伟大的程序员,只是习惯比较好而已。
3.9 SLAP
- 抽象层次一致性(Single Level of Abstration Principle,SLAP)
- 在构筑金字塔的过程中,要求金字塔的每一层要属于同一个逻辑范畴、同一个抽象层次。
3.10 函数式编程
- 在函数式编程中,函数不仅可以调用函数,也可以作为参数被其他函数调用。
- ·减少冗余代码,让代码更简洁、可读性更好。·函数是“无副作用”的,即没有对共享的可变数据操作,可以利用多核并行处理,而不用担心线程安全问题。
3.11 本章小结
- 一个系统容易腐化的部分正是函数,不解决函数的复杂性,就很难解决系统的复杂性。
第4章 设计原则
- 每个人都有义务捍卫、遵守或完善原则。原则可以修正,但是不能肆意妄为。
- 所谓原则,就是一套前人通过经验总结出来的,可以有效解决问题的指导思想和方法论
4.1 SOLID概览
- Single Responsibility Principle(SRP):单一职责原则。 ·Open Close Principle(OCP):开闭原则。 ·Liskov Substitution Principle(LSP):里氏替换原则。 ·Interface Segregation Principle(ISP):接口隔离原则。 ·Dependency Inversion Principle(DIP):依赖倒置原则。
- 开闭原则和里氏代换原则是设计目标;单一职责原则、接口分隔原则和依赖倒置原则是设计方法。
4.2 SRP
- SRP要求每个软件模块职责要单一,衡量标准是模块是否只有一个被修改的原因。职责越单一,被修改的原因就越少,模块的内聚性(Cohesion)就越高,被复用的可能性就越大,也更容易被理解。
4.3 OCP
- 软件实体应该对扩展开放,对修改关闭。
- 装饰者模式,可以在不改变被装饰对象的情况下,通过包装(Wrap)一个新类来扩展功能;策略模式,通过制定一个策略接口,让不同的策略实现成为可能;适配器模式,在不改变原有类的基础上,让其适配(Adapt)新的功能;观察者模式,可以灵活地添加或删除观察者(Listener)来扩展系统的功能。
4.4 LSP
- 程序中的父类型都应该可以正确地被子类型替换。
- 根据LSP的定义,如果在程序中出现使用instanceof、强制类型转换或者函数覆盖,很可能意味着是对LSP的破坏。
- 可以通过提升抽象层次来解决此问题,也就是将子类中的特有函数用一种更抽象、通用的方式在父类中进行声明。
- 正方形-矩形问题(Square-rectangle Problem)
4.5 ISP
- 接口隔离原则认为不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口要好。
- 满足ISP之后,最大的好处是可以将外部依赖减到最少。你只需要依赖你需要的东西,这样可以降低模块之间的耦合(Couple)。
4.6 DIP
- DIP要求高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。
- 依赖倒置,就是要反转依赖的方向,让原来紧耦合的依赖关系得以解耦,这样依赖方和被依赖方都有更高的灵活度。
4.7 DRY
- DRY是Don’t Repeat Yourself的缩写,DRY原则特指在程序设计和计算中避免重复代码,因为这样会降低代码的灵活性和简洁性,并且可能导致代码之间的矛盾。
4.8 YAGNI
- YAGNI是针对“大设计”(Big Design)提出来的,是“极限编程”提倡的原则,是指你自以为有用的功能,实际上都是用不到的。因此,除了核心的功能之外,其他的功能一概不要提前设计,这样可以大大加快开发进程。它背后的指导思想就是尽可能快、尽可能简单地让软件运行起来。
4.9 Rule of Three
- Rule of Three也被称为“三次原则”,是指当某个功能第三次出现时,就有必要进行“抽象化”了。
4.10 KISS原则
- 他认为把事情变复杂很简单,把事情变简单很复杂。好的目标不是越复杂越好,反而是越简洁越好。
4.11 POLA原则
- POLA(Principle of least astonishment)是最小惊奇原则,写代码不是写侦探小说,要的是简单易懂,而不是时不时冒出个“Surprise”。
4.12 本章小结
- 软件是一种平衡的艺术。要清楚一点,我们不是为了满足这些原则而工作的,原则只是背后的指导思想。我们的目的是构建可用的软件系统,并尽量减少系统的复杂度。在不能满足所有原则时,要懂得适当取舍。
第5章 设计模式
- 利用模式,我们可以让一个解决方案重复使用,而不是重复造轮子。
- 设计模式描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,具有一定的普遍性,可以反复使用。其目的是提高代码的可重用性、可读性和可靠性。
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解
5.2 GoF
- 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF中提供了代理、适配器、桥接、装饰、外观、享元、组合7种结构型模式。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
5.4 插件模式
- 插件(plug-in)模式扩展方式和普通的对象扩展方式的不同之处在于,普通的扩展发生在软件内部,插件式扩展发生在软件外部
- 插件模式的难点不在于如何开发插件,而在于如何实现一套完整的插件框架。
5.5 管道模式
- 一个典型的管道模式,会涉及以下3个主要的角色。 (1)阀门:处理数据的节点。 (2)管道:组织各个阀门。 (3)客户端:构造管道并调用。
- 翻看JDK源码,你会发现,支撑Stream API背后的原理正是管道模式。在构建Stream时,会调用核心类ReferencePipeline来创建管道,其内部采用双向列表的数据结构对操作(Operation)进行存放,然后包(wrap)成Sink链表等待执行,整个处理是延迟执行的,只有在最后收集(Collect)被调用时才会被执行。
第6章 模型
- 建模的艺术就是去除实在中与问题无关的部分。
- 如果把写代码比喻成“施工”,那么架构和建模就是“设计图纸”。
6.1 什么是模型
- 模型是对现实世界的简化抽象
- 模型大致可以分为物理模型、概念模型、数学模型和思维模型等。
- 我们把用简单易懂的图形、符号或者结构化语言等表达人们思考和解决问题的形式,统称为思维模型
6.2 UML
- 推荐阅读Grady Booch等人的《面向对象分析与设计》和Larman的《UML和模式应用》这两本书。
6.3 类图
- 类(Class)封装了数据和行为,是面向对象的重要组成部分,是具有相同属性、操作、关系的对象集合的总称。
- 可见性:表示该属性对于类外的元素而言是否可见,包括公有(public)、私有(private)和受保护(protected),在类图中分别用符号+、-和#表示。
- 类和类之间的关系主要有关联关系、依赖关系和泛化关系
- 在UML类图中,用实线连接有关联关系的对象所对应的类。在代码实现上,通常将一个类的对象作为另一个类的成员变量。
- 类的关联关系也可以是单向的,单向关联用带箭头的实线表示
- 在系统中可能会存在一些类的属性对象类型为该类本身,这种特殊的关联关系称为自关联。
- 在用代码实现聚合关系时,成员对象通常作为构造方法、Setter方法或业务方法的参数注入整体对象中
- 组合(Composition)关系也表示类之间整体和部分的关联关系,但是在组合关系中,整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也将不存在,成员对象与整体对象之间具有“同生共死”的关系。
- 依赖(Dependency)关系是一种使用关系,特定事物的改变可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时,使用依赖关系。
- 泛化(Generalization)关系也称为继承关系,用于描述父类与子类之间的关系。父类称为基类或超类,子类称为派生类
- 在UML中,类与接口之间的实现关系通常是用带空心三角形的虚线来表示。
6.4 领域模型
- 从本质上来说,软件开发过程就是问题空间到解决方案空间的一个映射转化
6.6 广义模型
- C4模型由Simon Brown提出。C4模型提出使用上下文(Context)、容器(Container)、组件(Component)和代码(Code)等一系列分层的图表,来描述不同缩放级别的软件架构
7.1 什么是DDD
- 是指通过统一语言、业务抽象、领域划分和领域建模等一系列手段来控制软件复杂度的方法论。
7.2 初步体验DDD
- 评审完需求,工程师画几张UML图完成设计,就开始像上面这样写业务代码了,这样写基本不用太动脑筋,完全是过程式的代码风格。
7.3 数据驱动和领域驱动
- 数据驱动以数据库为中心,其中最重要的设计是数据模型,但随着业务的增长和项目的推进,软件开发和维护的难度会急剧增加。
- 领域模型对应的是业务实体,在程序中主要表现为类、聚合根和值对象,它更加关注业务语义的显性化表达,而不是数据的存储和数据之间的关系
- 复杂的数据库关系和对象关系之间的差异,其本质是数据模型和领域模型之间的差异,而这种差异的多样性和灵活性是很难通过规则预先定义的,这也是为什么工具的作用会很有限
7.4 DDD的优势
- 目标是创造可以被业务、技术和代码自身无歧义使用的共同术语,即统一语言
- 在软件的世界里,任何的方法论如果最终不能落在“减少代码复杂度”这个焦点上,那么都是有待商榷的。
- 代码复杂度是由业务复杂度和技术复杂度共同组成的
7.5 DDD的核心概念
- 当这样的行为从领域中被识别出来时,推荐的实践方式是将它声明成一个服务。
- 识别领域服务,主要看它是否满足以下3个特征。(1)服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象。(2)被执行的操作涉及领域中的其他对象。(3)操作是无状态的。
- 事件是表示发生在过去的事情,所以在命名上推荐使用Domain Name +动词的过去式+ Event,这样可以更准确地表达业务语义。
- 边界上下文(Bounded Context)的作用是限定模型的应用范围,在同一个上下文中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。
7.6 领域建模方法
- 任何的业务事件都会以某种数据的形式留下足迹。
- 所以企业的业务系统的主要目的之一,就是记录这些足迹,并将这些足迹形成一条有效的追溯链。
- (1)首先以满足管理和运营的需要为前提,寻找需要追溯的事件,或者称为关键业务时刻。(2)根据这些需要追溯,寻找足迹以及相应的关键业务时刻对象。(3)寻找“关键业务时刻”对象周围的“人-事-物”对象。(4)从“人-事-物”中抽象出角色。(5)把一些描述信息用对象补足。
7.7 模型演化
- 世界上唯一不变的就是变化,模型和代码一样,也需要不断地重构和演化。
7.8 为什么DDD饱受争议
- 核心业务逻辑和技术细节相分离
第二部分 思想
- 若想捉大鱼,就得潜入深渊。深渊里的鱼更有力,也更纯净。硕大而抽象,且非常美丽。
8.2 到底什么是抽象
- 抽象和具象是相对应的概念,“抽”就是抽离,“象”就是具象。
- 抽象是指为了某种目的,对一个概念或一种现象包含的信息进行过滤,移除不相关的信息,只保留与某种最终目的相关的信息。
- 抽象更接近问题的本质。
8.3 抽象是OO的基础
- 面向对象的思想主要包括3个方面:面向对象的分析(Object Oriented Analysis,OOA)、面向对象的设计(Object Oriented Design,OOD),以及我们经常提到的面向对象的编程(Object Oriented Programming,OOP)。
8.4 抽象的层次性
- 软件领域的任何问题,都可以通过增加一个间接的中间层来解决。
8.5 如何进行抽象
- 简单来说,抽象的过程就是合并同类项、归并分类和寻找共性的过程。
- 在开发工作中,很多时候就需要通过抽象层次的提升来提高代码的可读性和通用性。
- 因此,每当我们有强制类型转换,或者使用instanceof时,都值得停下来思考一下,是否需要做抽象层次的提升。
- 要自下而上地思考,总结概括;自上而下地表达,结论先行。
8.6 如何提升抽象思维
- 为什么阅读书籍比看电视更好呢?因为图像比文字更加具象,阅读的过程可以锻炼我们的抽象能力、想象能力,而看画面时你的大脑会被铺满,较少需要抽象和想象。
- 小时候,我们可能不理解语文老师为什么总是要求我们总结段落大意、中心思想。现在回想起来,这种思维训练在基础教育中是非常必要的,其实质就是帮助学生提升抽象思维的能力。
- 现实世界纷繁复杂,只有具备较强的抽象思维能力的人,才能够具备抓住事物本质的能力。
8.7 本章小结
- 归纳总结,合并同类项是进行抽象活动时最有效的方法
9.1 分治算法
- 分治法解题的一般步骤如下。(1)分解:将要解决的问题划分成若干规模较小的同类问题。(2)求解:当子问题划分得足够小时,用较简单的方法解决。(3)合并:按原问题的要求,将子问题的解逐层合并,构成原问题的解。
9.2 函数分解
- 函数过大过长是典型的代码“坏味道”,意味着这个函数可能承载着过多的职责,我们有必要“分治”一下,将大函数分解成多个短小、易读、易维护的小函数。
9.3 写代码的两次创造
- 优雅的代码很少是一次成形的,大部分情况下要经过两次创造:第一遍实现功能,第二遍重构优化。
9.5 分层设计
- 分层设计最大的好处是分离关注(Separation of concerns),这样我们就可以通过分层隔离简化一个复杂的问题,让每一层只对上一层负责,从而使每一层的职责变得相对简单。
- 分层架构的目的是通过分离关注点来降低系统的复杂度,同时满足单一职责、高内聚、低耦合、提高可复用性和降低维护成本,也是一种典型的分治思想。
9.6 横切和竖切
- 所谓竖切,就是按照领域将单体数据库拆分成多个数据库。
10.1 不教条
- 软件的第一性原理是“控制软件复杂度”
- 软件开发的生命周期风格类似一个连续光谱——有从瀑布式到敏捷,以及它们之间的多种可能性。
- 我们在实际操作中应重视指导原则,弱化方法论。
- 贫血模式提倡模型对象只包含数据,并提供简单的Getter和Setter;而充血模式提倡数据和行为放在一起,是一种更加面向对象的做法。
- 问题的核心不在于行为和数据是否在一起,而在于你能否有效地控制复杂度
10.2 批判性思维
- 批判性思维(Critical Thinking)是一种谨慎运用推理去断定一个断言是否为真的能力。它要求我们保持思考的自主性和逻辑的严密性,不被动地全盘接受,也不刻意地带着偏见去驳斥一个观点。
10.3 成长型思维
- 她在《终身成长》中提醒我们:我们获得的成功并不是能力和天赋决定的,更多受到我们在追求目标的过程中展现的思维模式的影响。
- 具有成长型思维的人相信自己可以通过学习来提升自我,相信学习和成长的力量,相信努力可以改变智力和能力
10.4 结构化思维
- 最清晰和实用的结构化表达是“提出问题,定义问题,分析问题,解决问题,最后展望未来”。如果按照这个逻
10.5 工具化思维
- (1)最差的境界是“实在懒”,拖延不喜欢的任务。(2)其次是“开明懒”,迅速做完不喜欢的任务,以摆脱之。(3)最高的境界是“智慧懒”,编写某个工具来完成不喜欢的任务,以便再也不用做这样的事情了,从而一劳永逸。
- 我经常在团队中说,每当你重复同样的工作3次以上,就应该停下来问问自己:我是不是可以通过自动化脚本、配置化,或者小工具来帮助自己提效?
10.6 好奇心
- 学习的动力不应该来自于外界的强力,而应该来自于内在,来自于我们内心对知识的渴望、对世界的好奇心
10.7 记笔记
- (1)知识内化:记笔记的过程是一个归纳整理、再理解、再吸收的过程,可以加深我们对知识的理解。(2)形成知识体系:零散的知识很容易被遗忘,而形成知识体系之后,知识之间就能有更强的连接。(3)方便回顾:笔记就像我们的硬盘,当缓存失效后,我们依然可以通过硬盘调回,保证知识不丢失。
10.8 有目标
- 所有事物都要经过两次创造的原则,第一次为心智上的创造,第二次为实际的创造
- 在学习之前,我们一定要问自己,这次学习的目标是什么?
10.9 选择的自由
- 自由并不是想做什么,就做什么。自由是一种价值观,是一种为自己过去、现在及未来的行为负责的价值观。自由是一种责任,是一种敢于做出选择,并愿意为自己的选择承担后果的责任。
- 处乱世而不惊,临虚空而不惧,喜迎阴晴圆缺,笑傲雨雪风霜
10.10 平和的心态
- 动机至善,了无私心;用无为的心,做有为的事
11.2 目标管理
- 不管是KPI的目标设定,还是OKR的KR设定,都需要满足SMART原则。如图11-3所示,S代表Specific,表示指标要具体;M代表Measurable,表示指标要可衡量;A代表Attainable,表示指标是有可能达成的;R代表Relevant,表示KR和O要有一定的相关性;最后,T代表Time bound,表示指标必须具有明确的截止期限。
11.5 Leader和Manager的区别
- Manager是管理事务,是控制和权威;而Leader是领导人心,是引领和激发
- 我们不需要这么多‘高高在上’‘指点江山’的技术Manager,而是需要更多能真正深入系统里面,深入代码细节,给团队带来改变的技术Leader。
11.6 视人为人
- 一群有情有义的人,做一件有意义的事
- ·对待上级——有胆量。 ·对待平级——有肺腑。 ·对待下级——有心肝。
第三部分 实践
- 软件的首要技术使命:管理复杂度。
- 整洁面向对象分层架构(Clean Object-oriented and Layered Architecture,COLA)
12.1 软件架构
- 软件架构划分成业务架构、应用架构、系统架构、数据架构、物理架构和运维架构
12.2 典型的应用架构
- 内部不关心外部如何使用端口,从一开始就要假定外部使用者是可替换的。
- 在我看来,洋葱架构在端口和适配器架构的基础上贯彻了将领域放在应用中心,将驱动机制(用户用例)和基础设施(ORM、搜索引擎、第三方API等)放在外围的思路。