加入新公司不久之后,我很快参与到了 LeanCloud 的即时通信、消息推送这两个服务从 Clojure 向 Golang 和 Java 的重构中。
但是 2022 年初大团队接手了一个坑比较多业务和一部分移交过来的人员,直属 Leader 和我沟通之后让我参与到这个刚交接过来的业务中。于是开启了一段新的挑战,需要注意的是这时候我是以一个普通工程师的角色加入的。
了解的方式途径包括代码、沟通、观察等等,回过头去看这是当时对团队现状列的一些描述:
更具体的比如,这个业务交接过来的第二天就出了线上故障,此后陆陆续续有出了几个线上故障,后端系统没有任何业务 metirc 无法知晓系统状态,甚至部分代码仓库只使用两个固定分支进行迭代,需求管理混乱等等。
作为参与者如果希望重构代码,那么首先就应该熟悉代码;如果希望做架构改动,那么就应该先熟悉架构。如果希望故障能够尽快得到解决,那么即使一开始不熟悉代码,也应该积极参与到故障排查和故障善后中去。总得来说,想改善什么先去了解和参与效率会更高。
任何新方案的实施都要以对现状的理解为基础 , 比如说现有的组织结构、对新方法的接受度、具体的业务特征、技术成熟度等。脱离具体的上下文,照搬“普适”的框架,会带来巨大的适配成本。前期花费大量精力在组织和流程上,却不直接改善交付效能,特别是业务可见的效能。这非常容易带来怀疑和抵制,让实施效果大打折扣,甚至半途而废。
从现状出发,寻求持续且渐进的改进。这是我在故障频发期间采取的策略,当然头脑里对我们需要走到哪一步会有一个大致的路径,在系统改进目标的牵引下,以小步前进,每个小步都带来改进,最终实现系统和全面的转型。渐进不是妥协。恰恰相反,它是在明确且坚定的最终目标下选择更坚实和可持续的路径。改变必须从理解和尊重现状开始。
接手团队的前两个月基本在左冲右突的救火,之后伴随着很长时间的持续且渐进的改进。比如支付系统在支付成功通知下游时的调度任务是纯内存的,机器一重启就缺失了只能手动去排查和重推。于是我把对下游的通知任务改为放到 MQ 里,这样即使机器重启通知也不会有缺失和延迟。
不知道系统的目前的状态是非常危险的,特别是在进行系统重构和优化时。因此在消除大范围故障后我做的第一件事情是引入监控系统,这样我们可以实时关注到系统的状态。
业务 Metric
团队负责的是登录、支付这类基础服务,因此业务 metric 也重点关注这两个业务。支付业务 metric 会覆盖用户发起支付、下单、到账整个生命周期,比如下单成功次数、下单失败次数、下单失败每一个失败类型的次数、支付回调处理成功失败的次数等你,可以细粒度到秒级别。
业务埋点
作为登录和支付业务,很多下游业务会在全球发行,覆盖到的客户端机型、网络情况、渠道类型都非常多,因此当某个用户反馈了问题后继的排查手段非常重要。因此我们引入了业务埋点,它可以覆盖到一个用户发起一笔支付的整个链路,从客户端、前端页面、服务端再到整个回调给下游接入方。这些埋点放在了日志搜索系统里,客户端、前端、服务端、QA 都可以自行搜索。同时这部分日志会提供给数仓团队发现进行业务分析观察整体的成功、失败走势。
告警
除了基础设施(如服务器、中间件等)云监控,还引入了:
这些报警会进行分级并集中到短信、电话以及 Slack 告警频道里
不管是代码层面还是架构层面的改造优化都采用的是小步重构,在满足新需求的同时逐步做一些代码层面的重构。
在架构层面则更为谨慎,比如支付系统的到账通知对下游业务系统的回调,原来是存储在服务器本地内存中的一旦系统重启就不会再自动重推了。之后我把到账回调进行了持久化,并引入了 RocketMQ 进行异步通知。这一改动从写设计文档、方案讨论、编码、测试所使用的时间并不长,但是灰度的过程却相对比较谨慎。
在小步重构的过程中要分清主次、短期中长期目标,不要过早的优化。比如先消除系统里的故障隐患,再推进性能优化等等。
一个很残酷的现实是到了 2022 年依然有一些服务是通过本地打包上传镜像手动部署的…
因此我将部署流程放到了 Gitlab 的 CI/CD 上,每一次代码提交都会自动进行单元测试,然后打包 MR 的代码,研发可以选择是否部署到测试环境,部署到正式环境。部署到正式环境之前会自动跑单元测试和集成测试,自动化测试通过之后才能发布到正式环境。
在我们如何做代码评审提到团队现在实施着严格的代码评审以及一定程度的自动化测试,但是这个过程并不是一蹴而就的。
交接过来的业务团队从来没有实施过 code review 制度,那么想要推行严格有效得 code review 是比较困难的。这时候可以先从做好自己开始,比如从把自己的代码发给别人 review 开始,分享一些自己案例,当遇到有意思的讨论也可以发出来让更多的人参与进来讨论,实际可见的参与感或者价值是最有说服力的。
而自动化测试,也是从:先有第一个自动化测试用例 -> 先有覆盖几个登录、支付核心接口 -> 全面覆盖登录、支付核心接口 -> 覆盖所有核心接口这样的过程,并且我们并不在是更多采用单元测试和采用集成测试上纠结,而是寻求根据实际情况逐步调整的过程。并且最重要的是能够自动化。
文档是非常重要的工具,能够全面、准确地记录项目信息,促进团队成员之间的沟通与合作。
对于架构设计、数据结构设计、前后端接口设计等都需要在 Confluence 上写好文档,并与相关的同事讨论特别是与代码评审的同事提前达成设计上的一致。设计文档应该贴在 Megre Request 的描述里,方便以后代码评审以及后继维护。
鼓励团队成员在撰写文档时进行协作。例如,一份文档可能由多个人共同编写,然后由一个主要的负责人进行审查和最终编辑。
好的文档应该具有清晰、简洁、及时更新、协作等优点。文档应该是有时效性的,如果文档涉及到某个产品或功能的版本信息,还应该包括版本号。如果文档已经过期了或者暂时还未完整则应该在文档内说明。虽然技术设计文档写在了 Confluence 上,但是我们希望所有的文档都可以通过 gitlab 的 wiki 找到,当然可以直接链接到 Confluence 上。
尽管代码评审、自动化测试、文档文化在软件工程上已经被无数历史案例证明了它们的作用, 但我向来反对不尊重现状和团队需要的工程实践,还需要考虑到业务的特性、团队成员的能力、我们所拥有的和缺失的,选择合适的时机去实践。这些工程实践需要团队管理层的支持也需要团队成员的坚持,作为 Tech Lead 在 hands-on 的过程首先应该自己做到表率的作用。
保证你想法在团队里能得到实施一个关键是,大家都信任你。一旦大家都不信任你的时候,又或者是你不信任自己的时候,那么这个项目就会出现问题。而信任感是需要我们一步步地去构建出,从一次次小的成功中去获得。通过多次小规模的改动和优化帮助其他同事可能是一个很好的切入方式。
信任是有效沟通的前提,当彼此不信任的时候,很多时候沟通没有效果的。
如果你想推动一些事情往前走,主动沟通是避免不了的。很多时候一些误会或者不理解都是因为缺乏沟通引起的。这里包括和研发、QA、产品,以及合作伙伴等等同事的沟通。我个人比较喜欢和同事保持一定频率的 one-on-one, 具体可以见我写的关于 one-on-one
研发上的混乱很多时候是团队没有一致的目标导致的,也很容易导致在不断地临时性需求中迷失。而且作为基础服务,没有计划性也让下游的需求方缺乏安全感。
因此在我成为团队管理者之后每个季度都会和团队一起制定了 OKR, 制定年度 roadmap。每个人的 OKR 都是公开且可评论的。年度 roadmap 放在 Figma 上供大家查看和评论,并及时同步给上下游。
制定 OKR 和 roadmap 也让我们可以持续投资重要但不紧急的事情,比如自动化测试、把手动类的事务自动化掉、历史遗留消除计划。这样事情从长期来看都是非常值得投资的,也是我们提高效率的方式之一。
有了 OKR 以及 roadmap 之后,也让版本管理变得更主动一些,可以看到我们的版本更新日志。我们基本保持半个月一个小版本,一个半月一个大版本的频率,当然这个频率并不总是固定,还是要看团队实际的情况。
在需求管理也需要和产品经理更多的沟通,在产品能力的建设上还有计划性和持续性。临时性的需求往往是紧急的,但也容易让我们迷失。还是应该在一个季度或者半年回头看的时候,能够看到产品上有明显的积累。
搞清楚上级的期望:过去的一年里,我一直和直属 Leader 保持每个月至少一次 one-on-one, 及时同步彼此的预期,讨论一下我关于团队管理上的困惑以及困难。也及时争取有一些应该争取的支持。
知乎上有个问题为什么管理者要多听团队的想法, 听取团队的想法这当然很重要。在我们团队也鼓励坦诚沟通,尊重一线同事决策权的文化。但是很多时候为了提高决策的效率,适当的强势是有必要的。以下是我暂时想到的一些场景:
特别是出故障时我会侵入一线工程师的职权和细节里,以保证故障能够得到及时处理和响应;需求管理混乱时我会侵入到产品经理的职权里,让产品需求更有规划性一些;有较多临时性需求时我会侵入到项目经理的职权里,让工程师能够更专注一些 。当然这会在和他们沟通好的前提下,而且会充分尊重各自的职业分工范围。
在适当的时候强势一些实际上是一种无需任何歉意的“贵族专制统治”,这也是组织赋予你作为团队管理者的权利。
现实总是残酷的,即使做了一些努力,做了一些改进。但是可能短时间内看不到直接的效果,也有可能是做的方式不对。你可能依然会遇到线上故障,也会遇到外部的质疑或者来自上级的压力。作为团队的管理者,如何及时处理故障然后做好事后总结,如何和外部伙伴沟通赢得改善的机会可能更为重要。
一句话:出了事,作为团队管理者不能怯懦!
客户端放权:虽然我有过写一些 Android Demo 以及 iOS Demo 的经历,但是在客户端方面我并不是一个合格的工程师,而且短时间内也很难在这方面有快速的补齐。因此在团队稳定之后决定把客户端的协调的工作交给了一位比较有潜力的客户端同事,TA 负责客户端接口以及架构相关的决策以及对外的客户端问题沟通。在客户端方面除了版本计划、架构设计、代码评审等为了保持对业务的熟悉我会略有参与外即使是这些事情更多的也是客户端小组的负责人来跟进,绝大多数的事情我并不会直接干预。
服务端的稳定性是团队的核心任务,因此我对服务端的关注以及实际 coding 也更多。我依然保持每周都有代码提交,跟进一线研发需求。
项管权限让渡:在各系统的稳定性得到明显提升之后,我把版本管理的职责交还给了项目经理,让项目经理在项目管理以及对外的需求沟通上发挥更大的作用。
一个团队管理者能力再强,也无法只通过一个人的能力做完所有的事情。如何适当的放权,是所有管理者应该不断学习的一项能力。
在团队相对稳定之后我决定让其中一位同事离开,可能这出乎一部分人的意料。大部人觉得当团队主管不久应该求稳以及当老好人是更好的选择。
主动裁人可能是一个非常痛苦的过程,但是对于管理者来说可能又是必须经历的事情。不合适的人在团队里可能确实起到一个人力的作用能干一些活,但是却可能拖累团队,同时也容易让优秀的人更加容易离开。因此当发现一个人真的不合适团队的时候,应该尽早考虑和安排让 TA 离开的计划。
当然发现一个人是否合适团队是一个持续观察的过程,同时也应该侧面关注和 TA 协作的同事对 TA 的评价,征求直属 Leader 的看法等等,而不是单纯的从管理者自己个人的情绪或者短期的主观判断。在技术能力、业务理解、团队融入、外部沟通这些方面综合考虑之后做的决定。
如果最终决定要做汰换 , 可以:
不合适团队的人并不是表示 TA 不够优秀,也并不意味着和被动离开的同事成“敌我”双方,更多的是双方不匹配,所以在沟通上我们还是应该尽可能地争取”和平分手“,该为离开同事争取的利益就应该主动和公司以及 HR 争取。
关于如何招聘以及如何做技术面试可以看我另写的其他博客,《如何招聘》和《如何进行技术面试》
把一个混沌的系统治理到相对不那么混沌的状态并不是一朝一夕的事情,也不可能只可通过做一两件事情就做到。
从故障频繁到系统相对平稳,之后一个季度无故障,然后连续半年无故障。这个过程需要团队所有的人共同努力。
在参与到这个团队业务的 3 个月后我成为服务端的负责人,那之后的一段时间虽然在 title 上还没有给予我负责人的头衔,但是事实上我已经成了团队的负责人。6 个月之后在一次组织架构调整中,我顺理成章地成为了团队负责人,团队成员包括:客户端研发 4 人、服务端研发 3 人、QA 1 人(另外包括 2 名外包)、产品经理 1 人、前端研发 1 人。
到了年底被直属主管和部门负责人提名年度优秀员工
然后被评选为 100 多人的大团队里唯一一个年度优秀员工
一个员工在原有职级上想要往上晋升,可能需要两到三个季度持续表现出上一个职级的能力才可能获得机会。很多时候我们会过分地纠结于是先付出还是先得到,但这样的犹豫可能会很容易让机会溜走。