购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.6 程序员的工作法则

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

1.6.1 工具化法则

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

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

图1.8

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

图1.9

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

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

图1.10

我们接着看一个系统迁移案例,系统迁移往往涉及将多个烟囱型架构迁移到平台型架构,或者将若干类似职责的系统统一到一个平台。一种常规做法是先观察和对比,再逐步切流,最后下线老系统。笔者所在的项目团队就曾开发过支持新老系统切流对比的工具。

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

图1.11

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

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

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

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

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

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

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

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

表1.4

img

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

结论:对于统一的工具平台,可以考虑不同工具的层次,使其能复用的尽量复用,并且注意平台的开放性,比如插件化能力;对于目标和实现方案差异很大的平台,则可以分别建设,比如业务优惠查询工具需要处理几天甚至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 关于YAGNI

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

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

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

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

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

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

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

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

这里得出结论:YAGNI 并非不做预设计,而是在成本、复杂度之间做权衡。我们现在需要建立一些机制(比如抽象、约束)应对它。注意,只是建立机制,在实现层面仍然按当前刚刚好的功能实现,除非能找到额外复杂度非常小的方案把预期的变化也覆盖了。 qvRosKFmKbt95tnKaansa7WRHCQb2VTC990s+VUo565LeK8aaedhUE8UUsyf7v/m

点击中间区域
呼出菜单
上一章
目录
下一章
×