关于合格工程师素养的一些思考
码农和程序员有什么区别?
我认为,码农可以做一些简单的编码工作:实现某个脚本,拼装某条 hql,“写完”某个函数。往往有一定语言知识,有一定的编码经验;但是很少对需求细致分析,缺乏“恰当”意识。他可能会用一条 sql 完成整个功能,也可能用一个 for 循环遍历所有接口,甚至为了某个不重要的 update 加锁、加事务…… “为了目的,不择手段”,而且并不确切目的是什么。
而程序员,他知道自己要做什么。可以把一个函数写的性能优良,可以把一个模块设计的很“恰当”。他熟悉 MVC, 熟悉设计模式,可以让他的模块整洁、完美。甚至,可以创造性的解决一些产品问题,让代码充满灵性。但是,往往全局观不足。
那么全局意识指的是什么呢,我们应该怎样培养我们的全局意识?
肯定不是 pm 让我做什么,我只要写代码把它实现就好了。而是要首先明确为什么要做这个,做这些的价值是什么。
我们有义务帮助产品找到最恰当的解决方案,然后用合理的方式把方案嵌入到代码中。同时,要让保证架构活力,要可以快速定位 bug、解决 bug。清楚上线流程,要保证上线后不会产生任何的消极影响。
一个合格的工程师,可以让产品需求平稳地落地。那么我们应该怎样做到呢?我认为可以从以下几点出发。
1. 明确需求细节,少做无用功、廉价功
我们应该首先应该明确产品需求是什么,但更重要的是为什么。产品为什么要这么做?预期的目标是什么?哪些数据表明这些目标达成了?如何去收集这些指标?
然后,去判断需要哪些人配合,高优先级去协调资源。如果判断人力成本过高,或者收益有限,可以说服产品延后部分需求的实现。集中精力做最重要的事情,少做无用功。同时,积累工作经验、数据经验,锻炼自己对“性价比”的敏感性,让自己的判断变得客观、有效、权威。
2.代码的执行可视化
在开始设计功能前,应该先考虑怎么去做监控。不能让功能迷迷糊糊地在线上跑着,“它应该是对的吧”……越实时的反馈,越可以早得发现问题,越可以早得为项目止损。同时也是对工作的负责,更是对自己汗水的负责:谁也不想自己辛勤写出来的代码,直到下线那天才发现一直没有运行“对过”。
-
哪些指标,证明功能是 ok 的
-
哪些报警,证明这是个 bug,那里肯定挂了
-
哪些指标,证明这个需求是有价值的 (比如:某个功能数据过低,此功能的后续迭代就应该适当降低优先级)
3.出现反馈如何应对,出现 bug 如何修复
产品的 feature 不可能让所有人都喜欢,甚至有时为了产品调性会做些对用户打压的事情。这样的功能如何让用户接受?如果不接受,产品应该如何去引导;技术应该如何辅助产品定位问题。
举个例子,首页 feed 会过滤掉用户屏蔽的条目。如果用户屏蔽了话题 A, 绑定了 A 话题的条目不会分发给用户。这是一个很合理的产品 feature, 但是用户会经常误以为对 XX 内容限流。运营就会要求我们查 bug。像这种需求,我们要做的不只是开发功能,更应该考虑后期如何应对反馈,提高响应效率。
某个关键功能出了 bug。我们应该如何跟用户解释?应该如何修复数据?修复如何快速?那么我们可能要考虑,我们要不要准备修复脚本,我们是不是需要操作幂等,数据操作该不该记录审计 log……
4.确保代码没有腐化架构
依照上面两步,我们意识到了要对业务指标可视化实时监控,要考虑问题的应急方案。然后,就可以着手设计我们的功能框架。所谓的实现需求,不是写个 function 把逻辑写完就行,更不是一个 sql 把数据丢给用户。所谓的架构,就是为了完成一套完整的功能,合理组合的多个模块。要让你的代码更好的被人看懂,要在恰当的地方完成业务监控,要用恰当的逻辑为应急提供方便。
如果发现现有的架构不能恰当的融入你的需求,千万别打个补丁硬推上去。“逻辑”谁都能写完,但是要让别人觉得,这块代码本来就应该在这里。衡量代码质量的唯一有效标准:WTF/min —— 出自《Clean Code》。
我们应该努力拽着自己的项目,让它离“屎”远一点;而不是增加一坨。写代码时,应该考虑的是怎么用,别人怎么读,而不是我怎么写!只对作者友好的代码绝对是垃圾。面向工程,面向合作。要让用的人舒服,让用的人心理成本低。
5.邀请大家进行 CodeReview
本着一个工程师的素养,我们应该努力拽着自己的项目,让它保证架构的清洁;但是,这只是一厢情愿的。在代码真正推上线前,我们需要让更多的人 code review,以求达成共识。
我的经验是,如果涉及架构调整,我会先提交一个初版,让一些有经验的工程师帮忙评审架构设计。这个阶段我们千万不要追求代码完成度、单测覆盖率。因为很可能架构设计不合理,可能会引起大量的返工,部分工作可能会直接作废。所以,我认为一个合理的 CodeReview 可以分为四步:
-
按需求调整代码架构,尽早提交框架 diff。评审者主要关注:改动是否污染了架构,调整后的架构是否易于新人学习,新增模块是否容易 debug (比如,方便构造 bug 现场)
-
填充架构细节,“优雅”地实现各模块逻辑。评审者主要关注:功能拆分是否清晰,类是否抽象合理、是否符合语法习惯 (如 python, 是否可以适当使用列表表达式、lambda)
-
确认各模块逻辑无异常、无 bad case。评审者主要关注:功能是否完全覆盖单元测试、是否已经完成了联调、是否进行了完善的功能测试
-
确保代码细节。评审者主要关注:是否合理的捕捉了第三方工具的异常,是否有合理的功能降级、模块降级,循环是否合理(严防死循环),变量命名是否表义、变量作用域是否过大……
所以,我认为 code review 不是一次性的。如果你突然收到了一个 100+ 文件变动的 merge request,那作者肯定只是想让你给他 approve:停留在形式上的 code review,至多只是因为作者想找个人“共同背锅”。合理的 code review 应该把握大框,逐步细化,渐入佳境。
其实,一步步按着上面的思路来,每一步都有特定的焦点,并不会花费大家太多时间。
6.确认好依赖,让改动平滑上线
相信经过上面严格的 review, 我们可以拿到一份大家具有共识的优良代码。但是,代码优良不代表可以部署上线。除非你在写print "hello world", 否则,你的功能与各种上下游服务势必会有千丝万缕的联系。我们必须在上线前,确认好所有的依赖不会阻塞我们。
首先我们要检查自身是否有先验逻辑未就绪。比如:知乎首页需要新增加一种卡片样式。要先确保版本兼容逻辑已上线,否则旧的客户端收到不识别的卡片可能会 crash。再确保数据库数据初始化,数据库里没东西上线没有意义。
其次要检查下游逻辑是否已经就绪。下游服务功能是否正常上线,是否能承担我们的请求压力;数据表 schema 是否变动、是否已上线,数据库容量是否足够,数据库是否能承受压力;相应缓存是否应该预热……
然后,需要通知我们的上游服务。我们的改动是否不兼容,是否会引发上游调用出错;我们的改动是否影响了性能,是否需要上游调整 timeout……
7.最小化验证尽量止损
前期做足了准备,大家足够警惕地关注着自己的指标变动。然后,我们就可以上线了。
但是,正因为各种指标可能会出现难以预期的变化,我们需要逐步扩大功能的影响范围。比如,先用办公室环境,然后放量给 10%、20% …… 的用户,同时观察指标变化。
当然我们应该“事先”明确应该观察哪些指标。比如,接口的 QPS、P95、5xx;依赖我的服务是否正常,我依赖的服务指标是否异常;存储服务、业务指标是否正常… 发现异常及时回滚。
小结就是:
-
在上线前,先想好自己应该关注哪些指标
-
上线过程,注视指标,逐步放量
-
遇到任何异常、疑似异常,及时回滚
尾声
代码编写其实没有难度。哪怕是个小白,在培训班学呆半个月,就可以实现个小功能:打印个 hello world, 转置个数组,甚至实现个快排……面试宝典、leetcode 可以帮你顺利的通过面试。
但面试通过只是说明面试官觉得我们不错,不是证明我们可以胜任之后的工作。常言道,“面试造火箭、工作拧螺丝”,面试题目与工作实战还是有相当大的差别的。不过,只要用心体味,你会发现,实际上工作要比面试的“火箭”复杂度更高。工作造的“火箭”与面试的“火箭”不在一个维度,工作的“火箭”是在帮我们从码农演变成工程师。
而工程师素养,才是衡量我们能不能在这个行业走深走远的关键。