我的名字很难读,但我的代码很好懂

  原标题:我的名字很难读,但我的代码很好懂

  从“点子王”到“细节控”

  唐頔朏

  

  “唐同学,你的名字要怎么读呀?”

  ——这是我初次与人见面时最常被问的问题。

  这个复杂的名字得益于我爹的名字太普通,上一辈的遗憾总会想在下一辈身上“报复性”补偿。于是,他就翻字典,想给我起一个世界上独一无二的名字,最后翻到了这两个字,“頔(dí)”的字义是美好,“朏(fěi)”特指新月开始发光,有那么点书香世家的味道。

  上学的时候同学们都羡慕我这个名字,因为太难念了,不会被老师点名。结果老师们不按套路出牌:“那个名字最复杂的唐同学,给大家自我介绍一下这名字怎么来的”,结果就是从小到大,每当我进入一个新环境,都要讲一遍名字的由来,并重温一下学前班时期如何用大头铅笔把名字挤进田字格、坐飞机每次要多排一次队给名字手写盖章、学籍证明上名字打错等心酸血泪史。

  2018年,我加入了华为海思,成为一名算法工程师。在生活中,我还有多种角色,如上研乐队协会的鼓手、接话小能手、运动达人、灵感源源不断的“点子王”……

  

  乐队演奏

  实打实的“点子王”:灵感源于生活

  手机扫到某个特定物体,屏幕就会显示一个小虚拟按键,触摸即可直接打开某个预先关联好的App,比如扫到一个乐器,手机就会自动打开节拍器App;扫到跑步机,就打开设置好的运动类软件。大家都很赞同我的想法,觉得这样可以提升用户使用移动终端中的便利性。后来我用小样本学习实现了这个功能,并以此产出了一篇专利。

  热爱生活并感悟生活,在生活中寻找灵感,让灵感在工作中实现,助力生活,人人都能成为“点子王”。

  识别坏味道

  四年前,可信被提上了重要的位置。所有上库的代码都需要先经过Committer(代码提交者)检视,如果本组暂时没有Committer,就需要求助其他小组的Committer帮助检视。作为组内率先通过了专业级认证的“天选之子”,我被PL推荐参加Committer培训,那算是我和重构的初相识。

  在忐忑中,我成了小组内的Committer,但作为算法工程师的我,其实对自己的代码能力并不自信,因为软件开发和算法有一些差别。在我看来,能设计好整体架构的软件开发工程师才是大牛。一位优秀的软件开发工程师需要设计一整套架构,这就像建筑师一样,把整个建筑里的每个房间、每个模块的功能提前划分好,然后再让算法工程师去填材料。而算法工程师更加专注在前端的算法设计和仿真阶段,最后提交代码时,也只能实现自己的那部分算法逻辑。假设算法是一个厨房,那算法工程师只要把厨具摆好,确保功能实现就可以了。

  好在我看护的责任田代码大抵也和我之前的一样,面向交付的修改较少。直到 2019年“5·16”后,一个项目找上了我。

  一天,PM(项目经理)问我:“听说你是搞图像的?”

  “对呀。”我回答道。

  他神神秘秘地继续问:“那你知道冈萨雷斯的绿皮书吗?”

  “那是我上学的教材呀!”我懵懂地回答。

  “妥了!部门N项目需要对图像算法有了解的人,你愿意加入,牵头把图像算法交付搞定吗?”

  当时我手上有几个跟进的项目,有很大几率是可以落地的,说实话要抛下,我有点舍不得。但听了PM对N项目介绍,又觉得很契合我在学生时代的研究方向,而且这个项目对帮助公司渡过艰难时期有重要价值。咨询了PL(项目负责人)本哥的意见,他非常支持我。于是,我很快加入了N项目。

  N项目的服务器算法是C++交付的,发挥的空间更大,也更难了。我们这个算法交付组都是从各个小组抽调过来的,要么是算法背景的,要么是刚入职没多久,学习能力强,好培养,但就是没有人是本身代码能力强,能直接支撑产品交付的。

  于是,我把大家组织起来重新学习了C++和设计模式。一开始学习C++的时候,大家对着书看,对着书讲,很多语言特性比较复杂,听了就忘了。直到后来学到设计模式的时候,组里的聪哥提出:“不如我们每次都实现一份简单的代码,对着代码讲设计模式的好处吧。” 自己亲自动手实现过的东西,印象也更深刻。这样一来,大家的讲述更有针对性,也更有趣味了。聪哥平时还喜欢钻研算法题,于是主动开课,成为我们的小老师,并给我们布置、批改作业。大家边学边练,逐步提升了自己的知识水平。在学习过程中,我感觉自己的理论知识得到了丰富和加强,就差实践了。

  在进行算法开发的过程中,我感受到了三个痛点。一个是当前算法代码和框架耦合非常严重,功能互相耦合,数据结构共用,导致算法的UT(单元测试)代码存在大量打桩。服务器框架一修改,我们就要进行大量的适配。这是因为,这部分代码在初期框架设计阶段,没有考虑到未来演进,尤其是没有想到未来算法业务会变得这么庞大。算法代码本身是服务器软件代码的一部分,服务器软件做了改动,算法就会受到影响,单元测试代码就跑不过了。

  我们当时很崩溃,不适配吧,没有方法看护代码质量,适配吧,工作量又大,改动又频繁。大家叫苦连天,经常这样折腾肯定不行。同时,算法模块间存在耦合,存在很多重复代码,还存在大量的函数,这会导致算法运行出故障时,不会及时报错,也没有为算法提供充分的可维可测功能,问题定位困难、低效。

  我把这些痛点都总结到了一个“算法集成的痛点”的PPT里,但自己觉得能力有限无法解决,也不知道该求助谁,于是发在了项目组群里。当时的我特别像姜太公钓鱼:“这些痛点真是好痛呀,影响效率,我觉得不能再这么下去了。”但是发完后,除了收获几个大拇指点赞,就没有然后了,并没有鱼上钩。

  再见重构

  事情的转机来自项目组新加入的SE赛哥。“鱼”来了!

  他看过PPT后,给出了方案的支持,让我确信了自己感知到的“坏味道”确实是不好的,重构刻不容缓。除了我本身提出的诉求——从代码上将算法代码和服务器软件代码解耦外,赛哥还高瞻远瞩地提出:“还需要有一个形式上的解耦,要把算法仓做成独立仓。”因为除了服务器,我们还要给其他的模块交代码,我们的算法最终会服务很多个模块。当时的状态是哪个模块要用我们的算法代码了,我们就给他们拷贝一份代码。这就产生了个令我们很头疼的问题,一旦算法要改,我们就要同时改多个地方。未来我们的产品如果要演进成多种形态的话,肯定不能这样一直拷代码,这太傻了!但如果算法把自己作为一个独立的仓,打出自己的包去给所有的模块调用,就可以破解这个困局。

  讨论后,赛哥就先把初步设计思路拿到 SE例会、技术例会上串讲,通过后作为正式的需求下发。自此,我终于感觉到,痛点诉求变成产品的需求了!

  SE赛哥提了这个方案后,项目组内一位刚入职不久的新员工越姐,主动把框架的重编排给详细设计并实现了。我曾经在内心把软件设计当做一件高不可攀的事情,这件事如果是一个高级专家搞定的,我会觉得顺理成章。但这是一个刚进来,写代码都没写多久的新人主动完成的,这件事对我内心的触动很大。这让我发觉这件事没有想象中那么高不可攀,重构并不是现有的、能力强的同学才能指导设计,恰恰相反,是通过自己的设计、被拍死、讨论、再修改。这一过程,反复进化,我们的能力也得到了积累。就相当于游戏里面的打怪升级,如果你一直不去打怪,只是等着大佬“秒杀”一切怪物,那你是永远无法成长的。

  于是,我牵头发起了算法重构特性项目。算法主要包含在CPU上运行的传统算法和在NPU运行的AI算法。CPU算法由组内代码经验相对丰富的康康设计解耦方案,由于我此前主要开发NPU算法,对昇腾接口等比较熟悉,就开始设计NPU算法的解耦方案。

  我对比了多个NPU算法的实现,沉下心来绘制类图、流程图,梳理调用关系、依赖关系、输入输出。在这个过程中,我发现NPU代码中的问题:一个是存在大量冗余,所有代码虽然依葫芦画瓢,但这个葫芦有点大,需要重复拷贝的量特别多;另外就是有很多底层接口,如NPU设备的开启调用,都在顶层算法的实现中调用,没有很好地分层隔离,这也是导致重复代码多的原因。在梳理的过程中,我还发现了一些不符合昇腾调用方式的Bug。

  于是,我将NPU算法实现分为顶层算法、中层模型、底层设备三部分,让它们各司其职。底层的昇腾接口写好写对了之后,在中层模型里调用,后续新增或修改算法可以直接继承该模型,不用再操心调用实现的事,可靠性和效率都得到了保障。后面组里的仿仿对这一设计继续进行细致优化,进一步发现了模型调度的效率问题,解决了悬而未决的耗时不均匀的问题,大幅提升了性能,更是为解耦增加了一个惊喜和收获。

  

  团队合影(左四为作者)

  务实重构,人人受益

  重构的另一个障碍就是时间和人力,因为重构不仅涉及我自己的模块,还牵扯到其他相关模块,也需要他们投入人力配合。

  之后,在方案讨论中,果然少不了PK:为什么要这么修改?修改后是你负责还是我负责?这些不同的声音存在很正常,这时候我们更要将心比心,做重构不是为了炫技,没事找事,而是为了解决实实在在的问题。

  这次修改的方法有很多种,我们如果只是想节省一些自己的工作量的话,是可以只把算法设计得简单一点,但是跟框架接口的部分就会很复杂,框架每次接口适配的时候就会很痛苦,如果我们这样仅仅把工作量转接出去,这并不是重构,而是甩锅。于是我组织大家集思广益,不断探讨,一定要找出一种大家都受益的方法。

  有一次,组内的杨康同学提出了一个新视角:借鉴Sensorhub(智能传感集线器)的经验,把框架看作管道,对算法的输入、输出进行透传,这样我们做完抽象后,框架对算法的调用也会统一,对他们也是人力的节约。我想,这种大家都受益的重构,才会得到相关人员的真心支持。

  什么叫透传?就是你给我啥,我直接再给下一个人,我只是一个管道而已。借助这个想法,我们再次设计了算法接口,并终于得到了服务器框架组开发同学的认可。

  精益求精

  现在的我作为Committer,已经不是最初那个不自信的“小白”了,还常常会被“吐槽”是个细节控。

  其实最初的我也没有那么严苛,比如,我发现我们组的组员变量命名很随意,数据命名的时候一个叫“图像a”,一个叫“图像b”,起这种放之四海皆准的名字。这个时候我的第一反应就是他们没有认真思考,从这种细枝末节里感觉到他们对算法没有那么理解。如果是我来命名的话,就使用“目标图”“参考图”这样准确的命名描述,一目了然。

  组员们会常常和我抱怨:“唉呀,这些小事有什么关系吗?这一点都不影响我的交付。如果我要是特别注意的话,肯定能做好,只是我觉得没有必要。”我觉得无法反驳,我的性格也比较随和,有时候就这么放过去了。

  直到一次和老耿交流代码检视的时候,我提到了这件事。作为Committer,倒不是说代码检视很难做,而是提这种检视意见的时候给人感觉有点吹毛求疵,毕竟代码又没写错。同时作为PL,组内同学反馈代码检视的时候,组外的检视专家也不一定熟悉业务领域,提的很多也都是一些规范、风格的意见,大家觉得不太信服。另一方面,有很多代码的逻辑本身比较简单,似乎也没有太多设计可言,这样怎么能区分好代码和坏代码呢?

  当时我手头上正好有另一个组两个同学提交的代码,老耿一听,就拉着我现场检视。

  “这两位同学的代码都是逻辑比较简单的驱动开发代码。你一眼看过去,觉得有什么差别?”

  “A同学写得规规矩矩,整整齐齐。B同学代码整体一眼看过去有点参差不齐。但这有什么关系吗?”

  “OK,这就是第一眼的印象,那我们再看一下函数的名字。”

  “A命名也都挺统一的,B就有点随意。但这也不是什么关键问题。”

  “好,那我们再看一下B的实现细节。”

  “嗯,就看这个函数好了,他起的名字看起来比较奇怪,并且把变量定义放到了后面。不过这也没啥关系。”

  “那你会这么写吗?”老耿反问道。

  “那当然不会。”我笃定的回答。

  老耿说:“这就是问题所在。你有没有想过,为什么你不会这么写,他却这么写了?B同学的代码乍一看感觉也没有什么大问题,都是些小问题。但是你要想,这个代码是经过思考,一个字、一个字敲下来的,那你在敲的时候你脑子里是怎么想的,才会导致最后呈现出来这些小的变化?其实从这些小变化就能够看到,写代码的人本身有没有用心的做这件事,是否真正理解了自己写的代码。”

  我当时受到了震撼:我在检视代码的时候,只看最终结果都是能跑能交的,因此认为这些都是小问题,赞同了大家觉得代码检视是“吹毛求疵”的观点。但是却没有深入地去思考这些“小问题”的形成的原因上。

  于是,我沿着B的代码继续往下看,发现他竟然在奇怪的地方返回了。“他这个返回不会在异常的时候出Bug吗?“我继续去搜这个函数的调用点。随着进一步挖掘,发现B这个别扭的返回,在当前确实能完成功能,但是由于对某些场景做了特殊处理,所以后续修改需求的时候,很容易漏掉。分析完,我彻底心服口服,再也不认为代码检视时,专家不懂业务,只能吹毛求疵了。

  这件事给我上了生动的一课,也造就了我现在“细节控”的代码检视态度。比如,当我发现大家的变量、函数命名不贴切时,我开始不厌其烦地提出来,并根据大家的实际情况,思考是没弄懂算法原理导致,还是态度上轻视疏忽?如果没弄懂原理,就需要加强对算法的学习;如果是态度问题,那么在处理其他重要逻辑时,会不会也容易出错?需要加强对这个同学其他代码关键逻辑的检视,并提醒。

  从细节出发,通过发现一个个小问题促使大家深入思考,认真对待每一行代码,这种精益求精的态度渐渐地影响了整个团队。更高、更快、更强、更团结,每一次成长,都来源与和过去的自己比较,来源于对自己敲下的每一个字符的严格要求。

  来源 / 《华为人 》

  作者/ 唐頔朏

  转载请注明出处

  心声社区是华为的罗马广场

  长按二维码关注心声微信公众号

  分享 收藏 点赞 在看

  责任编辑: