几十~几百人规模的小公司,业务、研发、产品、市场等等角色的沟通成本并不是特别高。在公司创业早期,一个人身兼数职,沟通成本就更低了。有事情,大家拉到会议室,简单学一下金字塔原理,碰到问题很快就能得到解决方案。
游戏界有一个很经典的案例,早期从北方暴雪跳槽出来的几个人,都是会写代码的美工,他们只用了 12 个人,在很短的时间内以极高的效率做出了 torchlight 这个游戏,完成度很高,令人惊叹。
公司扩张到千人时,沟通瓶颈就开始比较明显了,即使将所有人的 schedule 全部用会议塞满,还是难以快速有效地得到结论,所有人看起来都很忙,项目延期更是常事。大公司在和小公司在竞争的时候,同一起跑线的项目甚至会比小公司还慢,为什么会这样?
很简单,随着规模的扩张,大多数人的大部分时间都被浪费掉了。可以从研发的视角来举一些例子:
- 编码时间被无穷无尽的对齐会议,故障处理,日会,周会,双周会,月会挤压,只能在一天挤出两三小时来做主业
- 公司的人员配比不合理,例如 PM 有很多,但 RD 却不多,产品经理迫于 KPI 压力不断地规划短期低 ROI 项目,缺乏适当的中长期规划,导致研发人员需要不断地去做短期需求,处理短期故障,如此往复
- 部门分工不闭环,大部分需求都需要跨部门合作,需要用大量的时间来对齐
就这么随便举举例子,就感觉到现如今的大公司工程师们生存何其艰难。有条件的话,我们还是要尽量来想想办法。
这篇文章要讲的解法就是尽量将可能会出的问题都通过工具化来避免。
工具化的优点是什么呢?
- 降低事务性工作所占用的时间
- 让知识和经验在企业内传承
- 降低、消灭重复的错误
这些应该是比较好理解的,我来举几个最近碰到的案例,来说明工具化会对我们的工作效率产生怎样的影响。
依赖注入
以最近碰到的 case 为例,团队中的框架使用了 google 的 wire 作为依赖注入工具。在合作开发的项目中,有些人其实是不懂 wire 的。
我们知道这种代码生成的工具,会有一个原始文件和生成的文件,开发者负责编辑原始文件,运行 wire 生成实际需要执行的代码。比如:
wire.go => wire_gen.go
不太懂的开发者会直接去修改 wire_gen.go,在修改过之后,其实 wire.go 和 wire_gen.go 的内容就不太对得上了。但是因为编译时使用的是生成后的文件,所以上线之后验证没什么问题。
之后来了一位按正常流程开发的 RD,先修改 wire.go,然后手动执行 wire 命令,覆盖 wire_gen.go,这样前一位开发的代码就直接被覆盖消失了。
依赖注入的代码一般都用来做些初始化功能,新代码在上线后,老的业务功能可能就突然挂掉了。
从技术上来讲,这是一个比较蠢的错误,如果按照很多国内公司的管理套路,这时候可能就要祭出“敬畏线上,认真 review”的套路了。但其实我们可以用工具来避免这种问题再发生啊。
比如我们可以通过扫描 wire.go,判断 wire.go 和 wire_gen.go 的代码是不是能对得上,既然想到了这个解法,那 Google 的工程师应该也不应该想不到:
❯❯❯ wire -h
Usage: wire <flags> <subcommand> <subcommand args>
Subcommands:
check print any Wire errors found
commands list all command names
diff output a diff between existing wire_gen.go files and what gen would generate
flags describe all known top-level flags
gen generate the wire_gen.go file for each package
help describe subcommands and their syntax
show describe all top-level provider sets
Use "wire flags" for a list of top-level flags
wire 直接提供了 check 和 diff 功能,我们只要用 $? 来判断返回值就能知道命令执行是不是成功了。如果有不太懂 wire 用法的工程师胡乱做了修改,我们把 wire diff 和 wire check 配置到项目上线的 pipeline 中就可以阻止他们犯错,而不需要在上线出了故障以后再去写没什么用的悔过书。
社区里还有很多不错的 linter,也能够避免 gopher 新手犯一些错误,比如:
- sql.Rows 忘记关闭
- http.Body 忘记关闭
- err 忘记 check
当然,通过工具避免犯错是一方面,标准库里的设计问题是标准库的设计问题,我们要分开来看待。
环境和 sdk 问题
redis client 和 server 端的不匹配问题
最近还碰到了一个 redis client 和 server 端不匹配导致高延迟 问题,我认为也是可以通过工具来解决的。
社区里有 terraform 这样的 IaC 工具,在公司内做企业级框架时,环境也应该是框架要考虑的重要环节,虽然这样的问题在 Go 里有 pprof,即使是工作三四年的小小工程师也能轻松定位,但毕竟这个错误是谁都可能犯的。最终我们还是应该用 IaC 将项目所在的 prod、staging、dev 环境都管理起来。不要让这样的坑被所有工程师都踩一遍浪费时间。
基础设施 sdk 的反复修复问题
redis 和 mysql 属于使用频率较高的基础设施,一般有问题社区里也能很快地找到解决方案。存储设施以外的可就真不一定了,比如 kafka、rocketmq、配置中心,社区里并没有独霸天下的方案,所以有些公司干脆就运维一套比较简单的服务,让业务自己去找开源社区的 sdk 来接入。
当碰到问题的时候,就需要业务自己找 patch 去修复。这显然也不是有效的解法,infra 的 sdk 最佳解法应由平台方提供,公司内的基础设施部门应该负起维护 sdk 的基本责任。
在阅读 《Software Engineering at Google》的时候我们也知道,国外公司的 Deprecation 也是有专门的流程和团队负责的,为了能节省这些重复的工作,社区里也有 dependabot 这样的帮我们去检查业务使用 sdk 并自动升级的工具。
极大降低基础设施和业务部门的沟通成本,不需要每次有 bug 都广而告之了。
总结
这一篇我们主要讲的是工具,无论你的角色是一线研发,还是架构师,从问题出发我们都能够推导出差不多的解决方案。以前在某公司做业务时,我个人总结的:配置化 -> 自动化 -> 平台化 -> 中台化 -> 被优化,也是很不错的指导原则。
下一篇,或许我们会讲讲平台化是如何降低沟通成本的。