程序员技能与成长:清单和模板、程序员的工作法则

文章探讨了清单和模板在问题解决和IT领域的应用,强调了它们在决策制定、应急处理和工作流程中的重要性。同时,文章介绍了自动化法则,如持续集成和工具化,以及如何在实践中权衡YAGNI原则,以提高工作效率和代码质量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.5 清单和模板

人类的学习模式是向过去学习、向经验学习。我们“迷信”经验的力量,用已有的知识来解决新的问题,这也往往奏效。为了把这些经验传承下去,我们发明了清单和模板。我们可以将清单定义为List,比如CheckList。模板对于照猫画虎的知识传承尤其有用,在IT领域也有各种模板,比如产品需求文档、概要设计文档等。

1.5.1 清单实践

制定清单和使用清单,清单实践就这么简单吗?答案是否定的,可以先来读一个故事。

2005年8月29日,卡特里娜飓风横扫国外某市,部分地区受灾严重,洪水肆虐,通信中断,消息无法送出。当人们最终将灾情反映到某个联邦官员时却被告知耐心等待。因为需要将相关信息逐级上报,所以传统的指令和控制系统很快被大量的信息和指令淹没。政府拒绝放弃传统的指令模式,灾情正在不断恶化,各级政府却在争论决策权的归属问题,满载饮用水和食物的卡车迟迟不能进入灾区。

我们可以从这个故事总结出下几点。

◎ 面对复杂的问题,做决策往往不容易。各级政府在争论决策权时,事态已经在恶化了。

◎ 要建立应急处理机制,简单、直接、有效。

◎ 要有应对各种问题的清单。

简单总结一下,在解决问题时要有如下几个步骤。(1)出了什么问题,汇总问题和现象,试着探究原因。

(2)找到解决问题的流程,应该谁负责驱动,流程有哪些环节,有哪些可选方案。

(3)按步骤解决问题。

世界卫生组织的手术安全清单(Safe Surgery CheckList)由19个检查项目构成。其中,在实施麻醉前有如下7个检查项目。

◎ 患者本人或家属是否已经确认了患者的身份,并同意进行手术。

◎ 手术部位是否已经进行了标记。

◎ 是否给患者进行了血氧饱和度监测,仪器运转是否正常。

◎ 患者是否有既往过敏史。

◎ 是否存在气道困难和误吸的风险(这是实施全身麻醉最危险之处),以及所需设备和辅助人员是否已经就位。

◎ 是否存在失血量大于500毫升的风险,儿童为每千克7毫升。

◎ 必需的中心静脉置管、血袋和补液是否已经准备好。

由此可见,清单需要具备简洁、直接、易操作的特点。简洁是指不面面俱到,如果某个环节的检查项目过多,比如超过20项,则一不易操作,二容易出错。直接、易操作是指对清单的描述要清晰、无二义性、具体,对其效果应该能进行观测,并由此进行改进。

如表1.2所示为一份代码审查清单,这份清单仅总结了代码审查在安全维度方面的一些审查点。

成也清单,败也清单。执行者在面对太多的清单内容时很容易体力疲劳、出问题,所以最好将流程和审查本身都强制到工具中。

“在提交代码时,强制要求有代码审查记录”就是一种强制流程。前文介绍过的静态扫描工具如Checkstyle、FindBugs都可以解决一些可以规则化、模板化的问题,比如代码基础规范大小写、缩进等。

要让清单成为一种习惯,清单就应该足够简单、清晰并且深入人心。

同时,笔者提倡把清单嵌入流程中,只有在流程中,才能受到关注和持续运营,从侧面治理的模式往往会名存实亡。笔者曾经在传统软件企业工作过几年,还记得每周或者每个月,SQA和 SCM人员都会拿着长长的 Excel表让研发负责人或者 PM打钩,表示“做了某项工作”。这种审查和度量模式的最大问题是 SCM和 SQA人员会对质量和流程遵循情况做展示并反馈给领导,但是研发工程师对“清单”并不感兴趣。后来笔者来到互联网方面的研发公司,SQA的存在感几乎为零,但是我们在研发过程中形成了相关的“完成定义”,比如交付报告。研发人员必须在完成代码之后进行自测、静态代码评估和持续集成报告等,并在这些被判定为“完成”之后,才能进入下一个流程。

1.5.2 产品需求文档模板

下面给出产品需求文档模板,如表1.3所示。

1.6 程序员的工作法则

如何衡量程序员的工作成效一直是个有争议的话题,而提供一些法则来提升程序员的工作成效已成为业界的共识,也是程序员的基本素养。

1.6.1 工具化法则

持续集成是一种软件开发实践,团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,这也意味着每天都可能发生多次集成,每次集成都通过自动化构建(包括编译、发布、自动化测试)来验证,从而尽快发现集成中的错误。许多团队发现这个过程可以大大减少集成的问题,能够让团队更快地开发内聚的软件。

先来看这样一个案例。某团队在具体部署 SystemP 的持续集成时发现一个问题:SystemP 重度依赖下游系统 SystemQ,而 SystemQ 系统开发环境的不稳定导致 CI 经常大量失败。该团队做了一个工具,该工具能够自动采集对下游系统成功调用的结果,并将其转换成缓存文件,后续的 CI 都使用该缓存文件,自动完成对下游的隔离,这极大提升了CI的稳定性。解决方案如图1.8所示。

在图1.8中,当内部节点C依赖下游系统(比如SystemQ)时,为了隔离下游系统的影响,该团队开发了一个Mock拦截器。Mock拦截器的实现思路如图1.9所示,测试用例会对Mock数据是否存在进行判断,如果存在相关数据,则直接从缓存中获取。

1.9

再来看一个案例。在 SOA 架构或者微服务架构中,联调是一件比较麻烦的事情,手工联调需要上下游多个系统约定时间,一起配合,好一点的做法是下游系统自己构造本地请求来模拟RPC调用。但是笔者遇到过这样一个系统,这个系统是一个规则决策类系统,上游系统大约有20个,各自的场景用例包括参数的不同组合达到10万这个级别,构造本地测试用例的覆盖难度极高,且不易于持续维护。于是开发人员做了一点创新,思路也比较简单:做一个能够自动打印历史请求的工具,然后通过工具平台将打印的请求重发到指定的服务器上,提升研发效率。

如图1.10所示,上游系统在联调时调用了服务接口,这时工具会进行请求拦截,把上游请求序列化并保存下来。在下游要触发验证时,就可以通过重发工具平台查找对应的日志,完成输入的触发。另外,在做回归测试时,采用类似的方法就可以从线上环境的用户真实访问日志中提取用例,并将其导入线下用例环境中。

图1.10

我们接着看一个系统迁移案例,系统迁移往往涉及将多个烟囱型架构迁移到平台型架构,或者将若干类似职责的系统统一到一个平台。一种常规做法是先观察和对比,再逐步切流,最后下线老系统。

笔者所在的项目团队就曾开发过支持新老系统切流对比的工具。

为了确保新老服务结果的一致性和正确性,我们开发了一套切流对比工具,能够自动将一个接口的流量复制一份给另一个对应的接口,在执行完成后,对两个接口的结果进行详细对比,验证其正确性。在业务场景复杂的业务系统上若没有类似的工具保障,则很难保证不出问题。该工具的实现如图1.11所示。

随着工具化思路的深入人心,各个团队都会构建、处理特定的问题,或者相对宽泛领域的问题。这时为了避免重复建设,应该进行相应的治理和统筹。一些似曾相识的场景如下。

◎ 场景一:研发人员小李需要解决分布式系统的用例沉淀的问题,他做了一个线上线下日志迁移的工具。

◎ 场景二:研发人员小周为了解决联调成本高及时间要求的问题,开发了前文介绍的重发工具平台。

◎ 场景三:在SOA架构下排查问题时,需要登录不同的系统去查看对应的日志文件,这样成本很高,某工具团队开发了一个基于traceId的日志关联工具。

◎ 场景四:测试工具团队开发了一个叫作“望眼镜”的工具,用于快速定位SOA架构下系统复杂依赖关系中error的传递情况,并用时序图展示。

◎ 场景五:在优惠场景下,由于业务的复杂度、优惠规则的复杂度等各种原因,用户对于一个平台的支付工具使用存在认知偏差,对于支付的优惠券、红包和支付顺序可能存在疑惑,这时他们往往通过客服渠道反馈。客服人员一般求助产品经理或者技术人员,最终技术人员通过日志及数据库结合类似的traceId进行排查。由于类似的查询场景比较高频,所以技术同学研发了一个平台,将查询的结果翻译成客服人员可以明白的业务语义内容,供客服人员自助查询。

◎ 场景六:工具团队开发了一个故障排查模块,因为在SOA架构下定位问题基本靠口头沟通,比如交易大盘下跌了,各变更系统负责人都会去查看自身系统的错误情况,然后自己关联和思考故障的成因。那么,能不能把这些经验通过一系列规则脚本实现呢?

通过以上 6 种场景,我们可以总结出表 1.4,从该表中可以看到工具化解决问题的个性与共性。注意:以上场景其实只是冰山一角,对于上千人的团队,各种差异化的场景必然数不胜数,因此统一规划工具势在必行。

表1.4

通过以上分析可以得到一些阶段性结论:故障排查工具用于线上的即时故障定位,对日志数据不用单独存储,重点在于快和准,这个工具是单独的一套架构体系;而“望眼镜”这个工具如果也是瞬间定位,那么完全可以复用故障排查模块的核心能力;系统调用时序图只是一个增强的特性。

结论:对于统一的工具平台,可以考虑不同工具的层次,使其能复用的尽量复用,并且注意平台的开放性,比如插件化能力;对于目标和实现方案差异很大的平台,则可以分别建设,比如业务优惠查询工具需要处理几天甚至1个月前的用户投诉,从日志中提取、转换和专门存储数据。故障排查模块则在于解决故障的快速定位问题,对于超过1个小时的定位无法容忍,日志文件的存储时间默认为7天,不需要从日志中提取目标数据。这是这两个工具的最大差异,对其分别建设为宜。

1.6.2 自动化法则

自动化法则指将能自动化的都自动化。程序员为了提升效率,可以借助很多的工具来完成很多重复工作,下面就是借助工具完成自动化办公的一些案例。

1.查询和定位自动化

搜资料、查单词、定位并打开文件、打开网址、打开书签、打开计算器及其他App,这些通常是我们在办公过程中要做的事情。虽然这些事情都很简单,但是我们可以使用一个工具来更高效地做这些事情,这个工具就是Alfred。Alfred是Mac系统上的一个专注于效率提升的著名应用,通过这个工具简单配置很多流程化的操作,就可以用很简单的命令实现自动化操作。

◎ 在想搜索一个词汇时,直接通过快捷键调出Alfred输入框,输入“baidu 搜索词”即可。

◎ 若突然忘记一个单词的中文意思是什么,在 Alfred 中直接输入单词就可以知道其含义。◎ 定位文件、打开文件是 Alfred 提供的基础功能,它能让你无须打开文件浏览器便可定位或打开文件,只需调出Alfred的输入框(默认快捷键是Command+Space)输入对应的文字即可。在打开文件时直接输入要打开的文件或文件夹的名称,Alfred 便会将搜索结果显示出来,我们可以用 Command+数字进行结果选择。在定位文件或文件夹时则需要先输入“find关键词+空格+对应的名字”。

◎ 在想打开对应的网址、书签或App的时候,无须打开浏览器,直接通过Alfred搜索即可。

2.项目开发环境

如果你还在使用Eclipse,那么你一定要试试Intellij IDEA。围绕项目创建(import项目)、代码开发、Debug、单元测试,这个 IDE可以在很大程度上提升开发效率。下面是几个屡试不爽的功能。

◎ Maven项目不需要执行mvn eclipse:eclipse等命令就可以直接import。

◎ 在新增pom依赖配置后,自动下载依赖的jar文件。

◎ 对无源码的jar文件自动反编译,并且支持一键下载对应的源码(如果有的话)。

◎ 借助于一些插件可以自动生成代码,在保存后自动格式化代码、自动生成注释等。

3.Web测试

有时测试一个 We b 页面的功能,需要重复输入、单击按钮和切换页面。借助于Selenium,我们可以录制操作过程,自动生成测试脚本,然后实现相同功能的自动测试。

4.部署、运维我们在部署过程中有很多时候会借助 Shell 脚本自动完成一些任务 。 比 如 , 在 项 目 部 署 发 布 的 过 程 中 , 往 往 需 要 先 拉 取 代 码(pull.sh),然后编译(build.sh),最后部署(deploy.sh)。在改动代码后还需要进行这几步。我们可以将这三个命令放到一起来执行,即./pull.sh&./build.sh&./deploy.sh,并将上面的命令写在一个 Shell 脚本里,这样就可以在服务器的任何地方执行pbd命令,来完成刚才的几步操作。

5.测试

自动化作为有效的提效手段,是每个技术团队都在不断追求的目标。测试自动化的核心问题有两个:一个是分层,另一个是稳定。分层的思想体系在自动化测试建设比较成熟度的阶段,对一些小的日常发布完全可以做到无人值守,而对一些中大型的需求需要全面进行回归测试和质量兜底,要达到这个目的,通过一种自动化手段往往很难做到。比如单元测试或者接口测试做到了方法级别的粒度,但是在全链路层面无法涵盖;线上自动录制回放虽然通过引入线上流量进行测试,但是在新功能方面有比较大的局限。对于整个质量保障体系而言,多层次、不同粒度的测试自动化相互协同配合是个不错的选择,这也是建立单元测试、组件测试、接口测试、端到端全链路测试、录制引流回放、线上影子验证等不同维度测试的初衷。

稳定是持续运行的基础。对于一线研发人员而言,排查 CI 失败的脚本一定会有不愉快的回忆,甚至希望直接手动验证,而不要再排查繁杂的测试数据。这就是自动化的弊端,它将前人的测试经验代码化,但是也屏蔽了逻辑的细节,测试需要稳定、自动化地进行,而不是半自动化地进行。这符合引入测试成熟度的初衷:一方面评价代码覆盖,另一面评价CI的通过率。

工具化法则、自动化法则、加速法则都来自一线痛点。比如在SOA 系统中,系统的依赖数量较大,在每次进行开发环境测试时,都需要修改很多配置才能顺利进行。例如,配置RPC路由地址、配置消息中心绑定消息的接收者和发送者等。解决办法为一键配置开发沙箱环境,对每个应用都配置一个统一的项目 ID,然后自动将有相同项目 ID 的应用拉取的代码及获取的虚拟机器资源,配置成一个虚拟的沙箱体系。在同一个沙箱体系内,消息不会被发送到外部,调用也在沙箱内完成,完全隔离。

当然,解决方案不止一种,但极度的工具化,尤其是把高频的重复劳动自动化,可以大大提升效能,并让程序员获得满足感和成就感。

1.6.3 关于文档的问题

要不要写文档,文档写多少?有位经验丰富的朋友说:“应该将文档整理成思维树,标注定义、关键描述、关键词、关联词和详细的文档链接。这对新人来讲更容易找到切入点,并快速了解项目或系统的全貌,以及可能与自己未来工作相关的上下文(内涵、外延)是什么。传统的文档管理方式往往使新人在浩如烟海的文档中迷失。

关于文档的问题,笔者有如下几个观点。

◎ 我们需要保留项目研发过程中的关键文档,比如需求和系统分析文档,这些是项目中不可或缺的文档。

◎ 一个系统或平台都需要有Core文档,比如领域模型、主体架构等,由于这部分文档的更新并不频繁,所以可以定期维护。

◎ 用例即文档,使验收测试及接口测试等保持稳定,是研究细节和用户场景的入手点。

◎ 提倡活文档,具体的推荐做法是接口文档通过接口声明生成,接口声明对于每个参数都会有说明。在 Swagger Editor中,我们可以基于 YAML语法定义RESTful API,它会自动生成一篇排版优美的API文档,在API改变之后,API文档也会随之改变。

1.6.4 关于YAGNIYAGNI

(You Ain’t Gonna Need it,你不会需要它)的意思是:如无必要,别增复杂度。

Cunningham&Cunningham(http://wiki.c2.com)将其解释为:即使你非常确信自己将来需要某个特性,也不要现在就去实现它。在很多情况下,你会发现或许最终自己不需要它了,或者真正需要的特性与之前预期的有很大出入。遵循YAGNI实践有如下两个主要原因。

◎ 你节约了时间,因为你避免编写了最终证明不必要的代码。

◎ 你的代码质量更高了,因为你让代码不必为你的“推测”所污染,而这些“推测”最终可能或多或少有些错误,但此时这些错误已牢牢地依附在你的代码中了。

Martin Fowler进一步表示:当我们在考虑推定特性时,很有可能我们是错的。在这种情况下,推定特性的一个很明显的成本就是整个构建过程的成本,也就是对这个在当下没有用处的特性进行分析、编码及测试所耗费的精力。他同时表示,假设我们对这个需求的理解恰好是正确的,则即使在这种比较理想的情况下,创建这个推定特性同样会带来两种巨大的成本,第1种是软件价值的延误成本,第2种是延续这一特性所带来的成本。

这里有个思辨:如果对一个问题有两种解决方案,第2种解决方案明明有更好的“增强扩展性”,而两种解决方案的实现成本相差无几,难道我要墨守YAGNI原则吗?

下面以两个案例来说明这个问题。

◎ 第 1 个案例,某团队去年接到一个钱包积分需求,经过分析,他们的设计不仅仅实现了钱包积分,还实现了商户通用积分的生命周期管理。因为后者有扩展性,并从业务上依稀感受到未来或许有这样的必要,并且经过评估,实现成本没有增加太多,该团队就果断采取了商户通用积分的实现方案。过了 1 年,果然,类似的需求来了……

◎ 第2个案例,某团队在2014年年底规划了一个券平台,应对此前的各种红包、优惠类业务的烟囱架构问题。这个平台的提出基于若干需求背后的核心领域抽象,这是他们在几年前做红包系统时万万不能想到的。当时他们看到的需求就是红包,对应的领域对象也是红包实体、红包模板及规则等,也就是说对问题域的本质认知随着业务的发展而发展。延迟设计决策是非常重要的,对这个度的把握很难,若没有把握好,用不了两年就得返工。

这里得出结论:YAGNI 并非不做预设计,而是在成本、复杂度之间做权衡。我们现在需要建立一些机制(比如抽象、约束)应对它。

注意,只是建立机制,在实现层面仍然按当前刚刚好的功能实现,除非能找到额外复杂度非常小的方案把预期的变化也覆盖了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值