为了写好代码,我盯上了我的徒弟,然而……

题记:点亮每一个人的内心之火,我们就是照亮软件变革的烈阳。

积微成著,星火燎原
吴宏杰

拿到华为offer,我是“试”出来的……
大学时一次偶然的机会,我参加了华为校园招聘,宣讲人说:数据是海量的,对存储的需求是海量的……也不知道为啥一下击中了我:存储的市场前景是很好的,一定要去试试!
慎重地投递简历、参加测试,终于等来了心心念念的面试通知。当天面试官看了下我的简历,笑着问:“你不会Java,来这里做什么?”
我愣了一下,这面试官也太直接了,完全不按套路出牌。随即我就坚定地回答:“存储市场前景广阔,我很想从事这个行业。虽然我熟悉的是C#语言, 但Java和C#语言都是面向对象的语言,我想试试,请给我一个机会!”
面试官笑了笑,拿了两页Java试题推到我面前让我完成。忐忑地交卷后,他看了看答卷,又看了看我,又笑笑说:“题目完成得不错,后面还有三轮面试,祝你好运。”
就这样,带着一腔热血我进入了第二轮面试。让我惊讶的是第二轮面试居然是群面,参加面试的就我一个本科生,我有点犯怵,但是大老爷们儿不能怂啊,不试试怎么知道!于是整个面试我全神贯注,深刻理解面试官的意图,抓住机会展示自己。就这样一路过五关斩六将,我如愿以偿拿到了华为的offer,开始了存储管理软件的开发工作。
那一年,是2012年。

破解神秘数字的“身世之谜”
进公司一段时间后,偶尔会看到一些偏“冷门”的知识,“好为人师”的我通常会默默记下,然后去和我的小伙伴“尬戏”:“你知道XX是怎么实现的么?”如果小伙伴们一脸茫然,这时我就会“得意”地说:“哈哈,不知道吧,那我来告诉你……”接着我会滔滔不绝地解释一堆,期待小伙伴们抛来一个崇拜的眼神,可小伙伴们却甩来一句:“能搬砖就行了,这有啥用?平时工作又不会用到。”
整段戏垮掉。我沉默了,无法回答也无法反驳这个问题,但我内心坚信,日积月累的知识一定会在某天迸发出无法想象的能量,无论别人如何看待,我会坚持。
2015年的一天,主管突然对我说:“部门有个挂了2个月的悬赏问题单,你挑战下看看?”
我有点儿没信心:“其他组的业务我不太熟悉啊……”
主管拍了拍我的肩膀说:“没关系,试试看。”
两个月整个部门都没解决的问题,我能行吗?带着一点不自信,但又夹杂着强烈的探索欲,我接手了这个问题单。
这是一个只出现过一次的疑难问题:基于JavaScript技术的管理软件界面上,留下了一个超大的OPS(每秒操作数)值:18446744073486000000,除此以外,没有留下任何线索。
面对这串神秘的数字,如同考古学家一开始面对3000多年前美索不达米亚文明的楔形文字一样,我毫无头绪。
思来想去,猛然想起一年前我写过的一篇文章——《JS大数计算的陷阱》,如同一道光照进了我的大脑:JavaScript计算超大数字时会丢失精度。那这个数字会不会也是超大数字丢失精度后的值呢?
于是我开始在几十个模块的日志中寻找蛛丝马迹,一番排查下来,终于在“历史性能数据统计模块”的日志中找到了和这串“神秘数字”有着相同前缀的数值“18446744073485999119”。
我很快破解了这串“神秘数字”的身世之谜:JavaScript无法精确表示这一数字,数值被自动转为“18446744073486000000”。
根本原因是不同模块间性能变量类型不同。管理模块定义是无符号64位整型,性能数据统计模块定义是有符号整型。换句话说,当性能数据统计模块存入超过整型范围的数字,就会“整数溢出”,转为负值。当负值传递给管理模块后,就会变为超大数字。由于 JavaScript对大数的特殊处理机制,超大数字又会进一步演变为“神秘数字”。随后我复现了该问题,问题水落石出。
回过头来看,问题本身并不复杂,但因为工作中很少遇到大数计算的问题,可能有些“冷门”,若不知道JavaScript对大数处理的细节,我也难以破解。
时隔三年,从软件萌新到可以手撕疑难问题的攻关能手,我始终相信,有些知识积累到一定程度后总会在某一天带来意想不到的效果,多涉猎不同的领域总归有益处,所谓厚积薄发,大抵就是这个意思吧。

这两年读了大概六七十本技术书籍

技术控开始钻研心理学,我经历了什么?!
2018年,随着现网IT设备越来越多,我们部门计划开发一个云端的智能运维系统,用于管理现网设备,主打容量分析与性能分析两大竞争力特性。当时人力较为紧张,且团队在性能分析领域的经验也有所欠缺,而我之前开发过性能分析相关的小工具,积累了一点经验,团队便把目光投向了我,让我做性能分析特性开发的owner。
我心里是没底的,一方面,我对性能分析的了解,仅限于熟悉性能数据格式、性能对象与指标,具体到性能问题的定位分析,也知之甚少;另一方面,我一直做开发工作,能搞定需求分析、特性设计的工作么?
但看着团队期待的目光,我决定接受这个任务:这是挑战,也是机会!
正当我热血沸腾、信心满满埋头苦干的时候,现实给了我当头一棒——由于UCD(User-Centered Design,界面设计)工程师要对接多个特性,且对性能分析这块新业务不熟悉,以至于需求沟通传递存在偏差、损耗严重,致使我的日常工作陷入了反反复复的沟通中……
我内心无比焦灼,有什么办法避免这种“内卷”呢?我突然灵光一现,想起了几天前才看过的互联网产品经理的玩法:设计界面的人应该是最懂业务流程、对需求理解最透彻的人。这不就是我吗?!
说干就干,我利用工作之余的时间,开始阅读用户体验、视觉心理学相关的书籍,研究相关工具如Axure的使用,前后大概花了一个月终于画出了职业生涯第一份界面的原型设计稿,拿去与周边的开发、SE、用户等交流,得到了一致认可,UCD同事甚至还开玩笑:“要不要转行当UCD呀?”哈哈,我想这是对我工作最大的认可吧!
后来,需求分析人员画界面原型的实践也推广到了需求分析团队,UCD可以更专注于界面本身的优化,设计稿数从平均3稿降低到1稿,整体设计效率得到提高。

人生第一份界面设计稿

拿着设计终稿,我带着几个新员工开始开发,但因为大家都经验不足,结果质量问题频发,进度开始滞后,版本经理着急,我更着急。
我分析了一下原因,发现是我们不太理解部分设计思路、也没有遵从一些设计原则,导致质量下降、代码实现变形。
针对这种情况,我尝试让代码设计不再停留在UML(统一建模语言)图上,而是将设计做得更细,直接搭建代码框架,接口、抽象类、实体类全部定义好,团队实现接口即可,降低开发难度。换句话说,将原来的解答题改造为填空题。经过这样的实践后,情况好转,最终顺利交付了三大性能分析特性,上线后能发现客户设备存在风险并自动给出优化建议,与专家定位后给出的结论一致,体现了智能运维的价值。
“战术千万条,敢打第一条”。在面对业务难题时,要先敢于面对,再对症下药,知识不足就去学习,问题复杂就拆分降维,变疑问为答案、变解答题为填空题,一步一步啃下难题。

Committer,要做的是火星
2019年,对我来说是特别畅快的一年。为什么这么说呢?我其实一直都比较喜欢技术,这一年公司推动软件可信变革,于是我干上了自己喜欢的事情——Committer。
还记得当时Committer宣誓:坚持写好代码,不因为进度妥协。宣誓神圣而庄重,让我心潮澎湃。我不禁暗喜:码农的春天到了?
我满腔热血,撸起袖子开始大干一场。坚持读书,学习、实践优秀编码的相关知识,除打磨自己的代码外,还将这些实践知识应用到代码检视中,给团队提了一些检视意见。本以为大家会欣然接受,结果扑面而来的意见像一盆冷水差点浇灭了我所有的热情:
“杰哥,代码没有功能问题,现在需求这么多,我们先跑起来,检视意见以后再说?”
“杰哥,重构是会更好,但太耗时间了,有这个时间不如多搞几个需求……”
我终于体会到什么叫“理想很丰满,现实很骨感”。在时间紧任务重的情况下,我们往往将需求交付放在首位,好代码是交付后才会考虑的事。
但我并没有放弃。我始终认为,如果一开始就把代码写好,让代码易读、易修改,才能更好地应对易变的需求。所以我在修炼内功,努力成为代码标杆的同时,决定找“窝边草”下手——我的徒弟被我盯上了。一开始,他的一个MR(合并需求)经常被我提几十个Issue(检视意见),一起吃饭时,我也不停向他灌输优秀编码相关的概念与实践。起初他的内心是有些拒绝的:“交付要紧啊!”但是反抗“无效”,毕竟,只有我能合他的代码。慢慢的,他也就认命了。就这样,在我长达半年、日复一日的“折磨”下,徒弟的代码越来越好,开发维护效率得到提高,也获得了“王者代码”的奖励,尝到了好代码的甜头,他也变被动为主动,自己开始加强Clean Code。
通过这件事情,我也总结了一些检视代码的技巧与经验。比如,检视代码时,除了给出建议,也一并给出重构后的代码实现,让被检视人可以直观感受代码重构前后的变化。并且,无论是当面沟通或是电话会议,我都委婉地、温柔地说出我的建议,慢慢地解释、引导,甚至一起结对重构。渐渐地,大家的思想观念发生了转变,开始精心打磨代码。
“杰哥,帮我检视一下代码。”
“杰哥,没想到代码能这么简洁易读,大开眼界!”
从一开始对代码检视的不甚理解,到现在对好代码的理解越来越深刻、代码越写越漂亮、问题越来越少,我们所有人一直在成长。

测试代码也要Clean Code
转眼来到2020年,大家的代码写得越来越好,但是一直有个问题:我们的单元测试做得并不好。
单元测试是开发人员直接对代码最基本的逻辑进行测试,只有开发者本人最了解自己的代码逻辑,也最适合写单元测试代码。它可以提升开发效率和质量早已是业界共识,甚至有顶尖公司将单元测试作为版本交付不可缺少的部分。以前我们也做过单元测试,但走着走着就停摆了,花了大量时间精力写的单元测试并没有起到提升开发效率和质量的效果,反倒因为单元测试难写、难改,导致开发效率下降,团队每次提到单元测试就连连摇头。
怎么正确打开“单元测试”呢?我向知名的全球性软件及咨询公司ThoughtWorks的专家、《重构》及《软件工艺》等书的译者熊节请教,自己也翻阅了相关书籍,并在日常需求开发中进行了一些实践,终于找到问题的症结所在:我们以前做单元测试,测试的是实现细节,单元测试与实现代码关联程度较高(紧耦合),导致修改代码工作量增加,维护成本较高,我们要测试的应该是行为、接口,它们比较固定,代码会更易修改,维护成本会低一些。
有了一定的理论与实践后,我决定在部门内选一个项目做试点。恰巧NAS迁移工具正面临分布式开发改造的困扰:2个月内将5万行代码规模的单节点迁移工具改造为多节点迁移工具。由于迁移业务的特殊性,业务上容不得半点错误,质量要求很高,难度很大。
我暗想:这不正好么,用单元测试提高开发质量、提高交付效率,走起!于是我主动请缨项目设计、开发工作。由于此前有过多节点工具开发设计经验,整个设计过程较为顺畅,但到了开发阶段,听到我说要做单元测试时,兄弟们头摇得跟拨浪鼓一样:
“开发时间这么紧张,哪有时间做单元测试?”
“单元测试能有明显效果么?像之前一样开发完成后进行自验证也能保证质量吧?”
“是否可以将功能调通后,有余力再去补充单元测试?”
我给大伙大肆“鼓吹”单元测试的好处,“鼓吹”到什么程度呢,不写单元测试的程序员不是合格的程序员!同时我利用自己是Committer可以拒绝不合格代码的权力,订下一个规矩:核心代码没有单元测试代码则不予合入。在我的“威逼利诱”下,兄弟们一开始写出的单元测试不甚理想,比如还是在测试细节,测试代码难读难懂,需要重构……
经过半个月的“折磨”,大家陆续找到了感觉,在完成功能模块的同时,也把单元测试做好,过程中也拦截了不少问题,大家对于单元测试的态度也发生了转变:
“单元测试还是挺有效的,帮我拦截了无数问题了。”
“我要写完单元测试后没问题了再提交功能开发代码。”
“这个问题出现了2次,我写个单元测试防护下。”
“没想到随手简单的重构优化,居然引入了问题,还好有单元测试,保命神器啊。”
“之前预期2个月能开发完就不错了,现在加了单元测试的工作量后,居然还能提前交付?!”
“分布式迁移主流程15分钟就联调通了,这开发质量杠杠的!”
加了单元测试,也许编码时间增加了,但集成、测试的时间会减少,整体交付周期会缩短,客户发现缺陷数也大幅降低。
经过这些事情,我意识到,其实每个程序员心中都怀有追求好代码的火种,只差一个火星,就能照亮宇宙,而Committer,就是这个火星。

我和我娇小的键盘,一刻也不能分开

回望8年码途,并不平坦。面对是向SE发展还是坚持编码的选择,有过不安与迷惘,但最终,我还是选择坚持编码。不为别的,就是喜欢。我想,只要坚守对编码的这份热爱,并且坚定地走下去,我们的征途必将通往万千星光。

【本文为《华为人》版权所有。】

(0)

相关推荐