代码精进之路-读后感

第一章 命名

函数名

函数命名要具体,空泛的命名没有意义。例如,processData()就不是一个好的命名,因为所有的方法都是对数据的处理,这样的命名并没 有表明要做的事情,相比之下,validateUserCredentials()或者eliminateDuplicateRequests()就要好许多。

函数的命名要体现做什么,而不是怎么做。假如我们将雇员信息存 储在一个栈中,现在要从栈中获取最近存储的一个雇员信息,那么 getLatestEmployee()就比popRecord()要好,因为栈数据结构是底层实现 细节,命名应该提升抽象层次、体现业务语义。合理的命名可以使你省 掉记住“出栈”的脑力步骤,你只需要简单地说“取最近雇员的信息”。

辅助类

对于辅助类,尽量不要用Helper、Util之类的后缀,因为其含义太 过笼统,容易破坏SRP(单一职责原则)

比如对于处理CSV,可以这 样写:

CSVHelper.parse(String)
CSVHelper.create(int[])

但是我更建议将CSVHelper拆开:

CSVParser.parse(String)
CSVBuilder.create(int[])

方法名约定

CRUD操作 方法名约定
新增 create
添加 add
删除 remove
修改 update
查询(单个结果) get
查询(多个结果) list
分页查询 page
统计 count

使用对仗词

遵守对仗词的命名规则有助于保持一致性,从而提高代码的可读 性。像first/last这样的对仗词就很容易理解;而像fileOpen()和fClose()这 样的组合则不对称,容易使人迷惑。下面列出一些常见的对仗词组:

add/remove 
increment/decrement 
open/close 
begin/end 
insert/delete 
show/hide 
create/destroy 
lock/unlock 
source/target 
first/last
min/max 
start/stop 
get/set 
next/previous 
up/down 
old/new

后置限定词

很多程序中会有表示计算结果的变量,例如总额、平均值、最大值 等。如果你要用类似Total、Sum、Average、Max、Min这样的限定词来 修改某个命名,那么记住把限定词加到名字的最后,并在项目中贯彻执 行,保持命名风格的一致性。

这种方法有很多优点。首先,变量名中最重要的部分,即为这一变 量赋予主要含义的部分应位于最前面,这样可以突出显示,并会被首先 阅读到。其次,可以避免同时在程序中使用totalRevenue和revenueTotal 而产生的歧义。如果贯彻限定词后置的原则,我们就能收获一组非常优 雅、具有对称性的变量命名,例如revenueTotal(总收入)、 expenseTotal(总支出)、revenueAverage(平均收入)和 expenseAverage(平均支出)。

需要注意的一点是Num这个限定词,Num放在变量名的结束位置表 示一个下标,customerNum表示的是当前客户的序号。为了避免Num带 来的麻烦,我建议用Count或者Total来表示总数,用Id表示序号。这 样,customerCount表示客户的总数,customerId表示客户的编号。

注释

  1. 不要复述功能

为了复述代码功能而存在的注释,主要作用是弥补我们表达意图时 遭遇的失败,这时要考虑这样的注释是否是必需的。如果编程语言足够 有表达力,或者我们擅长用代码显性化地表达意图,那么也许根本就不 需要注释。因此,在写注释时,你应该自省自己是否在表达能力上存在 不足,真正的高手是尽量不写注释。

  1. 要解释背后的意图

注释要能够解释代码背后的意图,而不是对功能的简单重复.

第二章 规范

日志规范

1.ERROR级别

ERROR表示不能自己恢复的错误,需要立即被关注和解决。例 如,数据库操作错误、I/O错误(网络调用超时、文件读取错误等)、 未知的系统错误(NullPointerException、OutOfMemoryError等)。

对于ERROR,我们不仅要打印线程堆栈,最好打印出一定的上下 文(链路TraceId、用户Id、订单Id、外部传来的关键数据),以便于排 查问题

ERROR要接入监控和报警系统。ERROR需要人工介入处理,及时 止损,否则会影响系统的可用性。当然也不能滥用ERROR,否则就会 出现“狼来了”的情况。我在实际工作中曾碰到过系统每天会发出上千条 错误报警的情况,导致根本没有人看报警内容,在真正出现问题时,也 没有人关注,从而引发线上故障。因此,一定要做好ERROR输出的场 景定义和规范,再配合监控治理,双管齐下,确保线上系统的稳定。

2.WARN级别

对于可预知的业务问题,最好不要用ERROR输出日志,以免污染 报警系统。例如,参数校验不通过、没有访问权限等业务异常,就不应 该用ERROR输出。

需要注意的是,在短时间内产生过多的WARN日志,也是一种系统 不健康的表现。因此,我们有必要为WARN配置一个适当阈值的报警, 比如访问受限WARN超过100次/分,则发出报警。这样在WARN日志过 于频繁时,我们能及时收到系统报警,去跟进用户问题。例如,如果是 产品设计上有缺陷导致用户频繁出现操作卡点,可以考虑做一下流程或 者产品上的优化。

3.INFO级别

INFO用于记录系统的基本运行过程和运行状态。

通常来说,优先根据INFO日志可初步定位,主要包括系统状态变化日志、业务流程的核心处理、关键动作和业务流程的状态变化。适当 的INFO可以协助我们排查问题,但是切忌把INFO当成DEBUG使用,这 样会导致记录的数据过多,一方面影响系统性能,日志文件增长过快, 消耗不必要的存储资源;另一方面也不利于阅读日志文件。

4.DEBUG级别

DEBUG是输出调试信息,如request/response的对象内容。在输出对 象内容时,要覆盖Object的toString方法,否则输出的是对象的内存地 址,就起不到调试的作用了。

通常在开发和预发环境下,DEBUG日志会打开,以方便开发和调 试。而在线上环境,DEBUG开关需要关闭,因为在生产环境下开启 DEBUG会导致日志量非常大,其损耗是难以接受的。只有当线上出现 bug或者棘手的问题时,才可以动态地开启DEBUG。为了防止日志量过 大,我们可以采用分布式配置工具来实现基于requestId判断的日志过 滤,从而只打印我们所需请求的DEBUG日志。

异常

我建议在业务系统中设定两个异常,分别是 BizException(业务异常)和SysException(系统异常),而且这两个异常都应该是Unchecked Exception。

千万不要在业务处理内部到处使用try/catch打印错误日志,这样会 使功能代码和业务代码缠绕在一起,让代码显得很凌乱,并且影响代码 的可读性。

第三章 函数

参数数量

最理想的参数数量是零(零参数函数),其次是一(一元函数), 再次是二(二元函数),应尽量避免三(三元函数)。

如果函数需要3 个以上参数,就说明其中一些参数应该封装为类了。

短小的函数

如果是Java语言,我建议一个方法不要超过20 行代码,当我把这个规定作为团队代码审查的硬性指标后,发现代码质 量得到了显著的改善。

职责单一

即一个方法只做一件事情,也就是函数级别的单一职责原则.

不仅可以提升代码的可读性,还能提升代码的可复用性。 因为职责越单一,功能越内聚,就越有可能被复用,这和代码的行数没 有直接的关联性,但是有间接的关联性。

精简辅助代码

所谓的辅助代码(Assistant Code),是程序运行中必不可少的代 码,但又不是处理业务逻辑的核心代码,比如判空、打印日志、鉴权、 降级和缓存检查等。这些代码往往会在多个函数中重复冗余,减少辅助 代码可以让代码显得更加干净整洁,易于维护。

如果辅助代码太多,会极大地干扰代码的可读性,读这种代码会让 人抓狂,摸不着头脑。因此,我们应该尽量减少辅助代码对业务代码的 干扰。让函数中的代码能直观地体现业务逻辑,而不是让业务代码淹没 在辅助代码中。

组合函数模式

组合函数要求所有的公有函数(入口函数)读起来像一系列执行步 骤的概要,而这些步骤的真正实现细节是在私有函数里面。组合函数有 助于代码保持精炼并易于复用。

SLAP

抽象层次一致性(Single Level of Abstration Principle,SLAP),是 和组合函数密切相关的一个原则。组合函数要求将一个大函数拆成多个 子函数的组合,而SLAP要求函数体中的内容必须在同一个抽象层次 上。如果高层次抽象和底层细节杂糅在一起,就会显得凌乱,难以理 解。

按照组合函数和SLAP原则,我们要在入口函数中只显示业务处理 的主要步骤。具体的实现细节通过私有方法进行封装,并通过抽象层次 一致性来保证,一个函数中的抽象在同一个水平上,而不是高层抽象和 实现细节混杂在一起。

在构筑金字塔的过程中,要求金字塔的每一层要属于同一个逻辑范 畴、同一个抽象层次。在这一点上,金字塔原理和SLAP是相通的,世 界就是如此奇妙,很多道理在不同的领域同样适用。

第八章 抽象

抽象是什么

抽象就是简化事物,抓住事物本质的过程,抽象能帮助我们接近问题的本质.

分层抽象

分层抽象在软件的世界里随处可见,是软件架构的核心.

每一层的抽象只关注本层相关的信息,对上层屏蔽复杂性,从而简化整个系统的设计

如何进行抽象

  1. 寻找共性

抽象的过程就是合并同类项、归并分类和寻找共性的过程,将有内在逻辑关系的事物放在一起,然后给这个分类进行命名,这个名字就代表了这组分类的抽象.

  1. 提升抽象层次

当我们发现有些东西无法归到一个类别中时,我们可以通过上升一个抽象层次的方式,让它们在更高的抽象层次上产生逻辑关系。

  1. 构筑金字塔

自上而下地表达,结论先行。其中,自下而上总结概括的过程就是抽象的过程,构建金字塔 的过程就是寻找逻辑关系、抽象概括的过程。经常锻炼用结构化的方式去处理问题,搭建自己的金字塔,可以帮助我们理清问题的脉络,提升抽象能力。

金字塔结构让我们通过抽象概括将混乱无序的信息形成不同的抽象 层次,从而便于理解和记忆,这是一个非常实用的方法论。

如何提升抽象思维

  1. 多阅读,阅读 的过程可以锻炼我们的抽象能力、想象能力,而看画面时你的大脑会被 铺满,较少需要抽象和想象。
  2. 多总结,做总结最好的方式就是写文章,记录也是很好的总结习惯。以读书 笔记来说,最好不要原文摘录书中的内容,而是要用自己的话总结归 纳,这样不仅可以加深理解,还可以提升自己的抽象思维能力。
  3. 多写作

抽象之美

“抽象”作为名词,代表着一种思维方式,它的伟大之处在于可以让 我们撇开细枝末节,去把握事物更本质、更一般的特性,从而更有效地 对问题域进行分析设计。“抽象”作为动词,代表着一种能力,它是我们 理解概念、理清概念之间逻辑关系的基础,也是我们面向对象分析设计 所要求的底层能力。

归纳总结,合并同类项是进行抽象活动时最有效的方法。同时,我 们也要注意到抽象是有层次性的。当一个概念无法涵盖其外延的时候, 我们有必要提升一个抽象层次来减少它的内涵,让其有更大的外延。

建议读者一定要多多培养自己的抽象思维。阅读、写文章,以及逻 辑思维训练都是提升抽象思维能力非常好的方式。只要坚持学习和锻 炼,你慢慢就能体会到一种不一样的美——抽象之美。

第九章 分治

分治的价值

分治的价值在于,我们不应该试着在同一时间把整个问题域都塞进 自己的大脑,而应该试着以某种方式去组织问题,以便在一个时刻专注 于一个特定的部分。这样做的目的是尽量降低在任意时间所要思考问题 的复杂度。

分治的步骤

分治法解题的一般步骤如下。

(1)分解:将要解决的问题划分成若干规模较小的同类问题。

(2)求解:当子问题划分得足够小时,用较简单的方法解决。

(3)合并:按原问题的要求,将子问题的解逐层合并,构成原问题的解。

写代码的两次创造

  1. 第一遍实现功能

不要试图一次就写出“完美的”代码,这样只会拖慢我们的节奏。就 像写文章,第一遍可以写得粗糙一点,把大概意思写出来,然后再仔细 打磨,斟酌推敲,直到达到理想的样子。

写代码也是如此,第一遍以实现功能为主,可以允许一定的冗长和 复杂,比如有过多的缩进和嵌套循环,有过长的参数列表,名称可以随 意取,还会有部分的重复代码。第一遍主要是为了理清逻辑,为第二遍 的重构优化做好准备。

  1. 第二遍重构优化

如果只是止步于功能实现,那么代码最多只是一个半成品。而实际 情况是我们的代码库中有太多这样的半成品,导致系统的复杂度不断攀 升,越来越难维护。因此,我们需要有第二次创造——重构优化,即在 第一遍实现功能的基础上,看一看是否可以做得更好:命名合理吗?职 责单一吗?满足OCP吗?函数是否过长?抽象是否合理?

因此,最好的优化肯定不是等系统上线后再去做,因为这样往往就 等于“再也不会去做”。优化工作本应该是我们编码工作的一部分,拆成两步,主要对编码效率上的考量。

第十章 技术人的素养

不教条

软件的第一原则是控制软件的复杂度,不要拘泥于某种特定的开发过程或者编程范式,但凡能提高代码可读性、可扩展性和可维护性的方法,都是值得考虑的.

批判性思维

批判性思维是一种谨慎运用推理去断定一个断言是否为真的能力。它要求我们保持思考的自主性和逻辑的严密性,不被动地全盘接受,也不刻意地带着偏见去驳斥一个观点

成长性思维

具有成长型思维的人相信自己可以通过学习来提升自我,相信学习和成长的力量,相信努力可以改变智力和能力.

成长性思维的人会用更加理性的态度看待一时的成败得失,内心坚定地相信成长和学习的力量.

结构化思维

什么是结构化思维呢?我给结构化思维的定义就是“逻辑+套路”。

所谓逻辑,是指结构之间必须是有逻辑关系的。例如,你说话时 用“第一、第二、第三”这个逻辑顺序是合理的,而如果用“第一、第 二、第四”就会显得很奇怪。实际上,组织思想的逻辑只有4种。

1.逻辑

(1)演绎顺序:比如“大前提、小前提、结论”的演绎推理方式就 是演绎顺序的。

(2)时间(步骤)顺序:比如“第一、第二、第三”和“首先、再 者、然后”等,大多数的时间顺序同时也是因果顺序。

(3)空间(结构)顺序:比如“前端、后端、数据”和“波士顿、纽 约、华盛顿”等,化整为零(将整体分解为部分)等都是空间顺序。在 做空间分解时,要注意满足“相互独立,完全穷尽”(Mutually Exclusive Collectively Exhaustive,MECE)原则。

(4)程度(重要性)顺序:比如“最重要、次重要、不重要”等。 只要我们的思想和表达在这4种逻辑顺序之内,就是有逻辑的,否则就是没有逻辑的。

2.套路

套路是指我们解决问题的方法论、路径和经验。比如,5W2H分析法就是非常好的,是可以帮助我们分析问题的一个“套路”。试想一下, 面对任何一个问题,你如果都能从“Why、Who、When、Where、 What、How和How much”(如图10-6所示)这7个方面去思考,是不是 比不知道这个方法论的人用点状模式思考要全面得多呢?

逻辑是一种能力,而套路是方法论、经验;逻辑属于道,而方法论 属于术。二者都很重要,只有熟练地掌握二者,我们才能有更好的结构 化思维。接下来,通过两个案例来介绍结构化思维在实际工作场景中的 应用。

结构化表达:

  1. 最清晰和实用的结构化表达是“提出问题,定义问题,分析问题,解决问题,最后展望未来”。
  2. 总分总的结构:我们说事情时,应该像电影镜头一样,先从远拉近,再由近拉远。

工具化思维

当出现重复工作时,学会智慧懒,学会创造工具,让自己一劳永逸,提升工作效率

忙到没有时间思考的时候,建议停下来,思考一下,有没有更偷懒的方式可以帮助我提升效率

好奇心

学习的动力应该来自内在,来自我们内心对知识的渴望,对世界的好奇,而不是外在.

好奇心能使我们在工作中不断学习、积累经验,从而提高工作效率。

记笔记

1.使用云笔记,支持多端使用,要有目录的层次结构、标签和搜索功能
2.归类分组,定期回顾笔记内容,尽量按照合理的方式对笔记进行重组,形成一个有逻辑关系的树形结构,这样更方便检索,逐渐形成自己的知识体系
3.不要复制粘贴:笔记内容最好是自己消化后的总结.建议把链接也放在笔记下面,方便溯源
4.结构化表达:简短的内容要突出重点,篇幅长的内容最好有目录

有目标

目标的重要性,以前是被我低估的。实际上,我之前的很多焦虑和 迷茫都是目标不清晰导致的.

先想清楚目标,然后努力实现。不管是人生大 问题,还是阶段性要完成的事情,都需要目标清晰、有的放矢。

很多人表示看过很多技术文章,但是总感觉自己依然一无所 知。一个很重要的原因就是,没有带着目标去学习。在这个信息爆炸的 时代,如果只是碎片化地接收各个公众号推送的文章,学习效果几乎可 以忽略不计。在学习之前,我们一定要问自己,这次学习的目标是什 么?是想把Redis的持久化原理搞清楚?还是把Redis的主从同步机制弄明白?亦或是想学习整个Redis Cluster的架构体系?如果我们能够带着问题与目标去搜集相关的资料并学习,就会事半功倍。这种学习模式的 效果会比碎片化阅读好得多。

平和的心态

我的座右铭是**“动机至善,了无私心;用无为的心,做有为的 事”**。首先,我们做事情的出发点必须是善的。其次,“有为的事”是指 要认真做事,认真生活;“无为的心”代表一种平和的心态,一种活在当 下的智慧。也就是做事要积极,但是心态要放平。

精进

精进就是你每天必须进步一点点!记住,慢就是快。千万不要忽视每天进步一点点的力量,也不要试图一口吃成胖子, 真正的进步是滴水穿石的累积,这就是精进。

但凡能持续学习和精进的人,其结果都不会差。

思维导图

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/kaihuishang666/article/details/106483857