一个 32 岁「老」码农的复盘:小尝管理

欧雷 发表于

0 条评论

2018 年国庆假期结束,在经过背调之后,我入职了京东汽车后市场(下文简称「汽后」)相关部门。因为同样是做汽车领域的业务,在买/卖好车(上一家公司)所学到的业务知识部分可以沿用。

这个部门下有多条业务线,在北京总部和杭州两地都有办公室,北京那边主要是汽配,杭州这边则以汽修为主保险等为辅。真正的前端 leader 在北京总部,而我要在负责自己所在的汽修业务线的同时兼管杭州研发中心的前端。

工作成果

我所在业务线的工作主要是围绕着一款叫做「京东云修」的 SaaS 产品展开的,它的作用是帮助汽修店管理日常工作,如:维修、财务、仓储、营销、报表等。

「京东云修」不仅有门店侧和管理侧的 PC 端 web 应用,还有 iOS、Android 客户端应用和微信小程序等。其中,前端人员基本只参与门店侧 PC 端 web 应用和微信小程序的开发,我将精力大多投放在了门店侧 PC 端 web 应用上。

「京东云修」前后端分离

我入职后的第一个任务有些大,要在正常支持业务需求开发的同时对「京东云修」门店侧 PC 端 web 应用的前端部分进行重写——是的,是「重写」而不是「重构」。由于之前从来没做过这种事情,还是比较有挑战的。

门店侧 PC 端 web 应用多年以来一直是由一个前后端一体的常规 Spring 项目支撑的,利用 FreeMarker 模板、jQuery 和 Sea.js 等完成前端功能——把系统的前端部分用当时主流的技术方案进行前后端分离改造,就是这第一个任务的内容。

因为这个事情是我所在业务线的负责人(也是杭州研发中心的实际负责人)提出要做的,所以在需求排期、资源协调等方面给予了一定的支持,与自发的相比阻力会小很多。

一上来就去弄业务主流程上的核心模块是不可能的,于是选择从最边缘的「设置」模块开刀,作为前后端分离的第一期。虽然是边缘模块,但它所包含的页面还挺多的,有的页面还有些复杂。

在正式开始前,我拟了一份包括要使用的技术、工具、规范和任务划分的方案草稿——

我所在业务线算我在内总共 4 个前端,其他 3 人无论是 React 还是 Vue 可以说没太多实战经验,从这一点来看,选哪个对团队都没什么影响。

考虑到 React 的封装不像 Vue 那样完善,能够倒逼每个人去了解学习一些概念和技术,进而提升个人甚至团队整体水平;另外,预测不久后 iOS、Android 客户端应用会采用跨端方案去开发,并且有可能会有微信小程序相关需求,当时调研的结果是各类工具对 React 语法的支持相对稍微好点。

综上所述,最终选择采用 React 技术栈,主要由 React、Dva、Ant Design 和 Umi 所组成,规范以「Umi 目录及约定」和「Airbnb JavaScript 代码风格指导」为主;样式编写则使用 Sass 和我过往工作中积累的样式库,并遵从「SUIT CSS 命名约定」。

任务的划分是我负责整体框架的搭建及核心功能的实现,然后基本是按照功能关联性和页面相似度与复杂度将「设置」模块下面的页面尽量均分给他们——一方面是我刚来,对他们的能力不甚了解;另一方面是该模块的重写主要是走量,而没有比较大的技术难点。

一切准备就绪之后,组织了一次时间比较长的会议,与所在业务线的其他前端充分讲解我的方案草稿所提到的内容,答疑并征求意见以达成思想、目标上的一致。

待在前端内部把方案敲定了,在上级的帮助下与业务线内其他岗位的人也一起开了个会,指定了协助我们的后端和测试人员,并定下了前后端分离第一期的大概排期。

到了 10 月底,重写终于要开始了!我迫不及待地让有内网搭建的 GitLab 管理权限的人帮忙建了个前端专用群组并把我设置为「拥有者」,这样我就对这个群组有完全的控制权限了——在买/卖好车时我已将 GitLab 的群组和成员管理玩得很明白了。

一般来说一家公司对 Git 仓库的命名会有一定的规则,然而这里好像没有,或者是我没看出来,于是我就按照自己的喜好定了个规则——用任天堂的游戏人物!这第一个前端项目就用任天堂第一 IP 的主角来命名——mario

马里奥
马里奥

在重写期间,我基本每周都会组织所在业务线的前端例会,每个人先汇报自己所负责任务的进度,提出所遇到暂未解决的问题,我会根据问题的性质直接给出或讨论出一个解决方案;再就是说出自己下周的任务安排,有可能会看情况进行任务调整。

当然,并不是只有在周会时才能够集中提出问题,日常开发中遇到什么问题了也是可以互相寻求帮助的。

整个重写的过程中,程序层面对我来说几乎没什么所谓的「难点」,硬要说点什么的话,就说说权限控制和新旧应用间页面跳转吧——

在基于 Spring 的旧项目里,权限控制直接被后端做掉的——页面访问被后端的控制器拦截,无权限访问就进入相应的异常页面;FreeMarker 模板中根据后端注入的权限相关变量决定某个 HTML 片段是否要渲染;需要前端动态控制的就在 FreeMarker 模板中将后端注入的权限相关变量赋值给 JS 变量。

而在新的单页面应用项目中,上面那些权限相关的控制就要完全在前端处理了,后端提供一个获取聚合了用户权限和门店权限的 HTTP 接口,在此之上做权限控制——路由配置上设置权限标识,在生成菜单和页面跳转时与已缓存的权限信息进行比对;操作按钮等 HTML 片段的条件渲染则根据权限信息中「is-」开头的属性来判断。

由于全系统完整前后端分离是长期的,不可能一步到位,在彻底分离完之前,新旧应用的页面必然会同时存在于系统当中。这样一来,相互间的跳转是个必须要解决的问题,侧重点在如何兼容路由以从新应用的页面跳转到旧应用的。

涉及到跳转的地方有两个:生成链接的 Link 组件;在代码逻辑中进行跳转的 router.push()

解决这个问题的一个重要原则就是对正常流程的代码无侵入,即在写页面时对兼容代码无感知,将来随时能够无痛移除。这就需要一个新应用具名路由与旧应用页面链接的映射,及依此进行二次封装的 Link 组件和 router.push()

在不耽误正常支持业务需求开发的前提下,我们 4 个前端加班加点齐心协力在后端和测试人员的倾力配合之下,终于在次年 1 月中旬完成了不太影响业务主流程的「设置」模块的前后端分离并上线!

为了犒劳与答谢所有参与和协助这次任务的人,俺家🐷做了烘焙食品让我带给大家——

🐷手作烘焙
🐷手作烘焙

在此期间,我同时在支持的业务需求大概有京东支付相关的调整、「双十一」大转盘抽奖活动页、EPC 与碰撞点快速选件等。

「京东云修」开放版

作为 to B 的 SaaS 产品,「京东云修」自然不是免费的,它有多个版本,不同版本的价格也不一样;并且提供的功能是针对中大型汽修店——对那些就几个人的小微型汽修店而言门槛有些高了,因此要做一个价格更低廉、功能更精简的能够满足小微型汽修店需求的「开放版」。

这个「开放版」也是要分多期去做的,它的第一期刚好紧接着前后端分离的第一期,我心里很是兴奋,因为在重写时的「伏笔」和一些想法将要被验证!

这次的需求不仅有 PC 端,还要新做个微信小程序——正如我当初所预测的,技术选型都可以省了,直接用当时调研的 Taro 开发就好。

我所在业务线共有 4 个前端,正好每个端投入 2 人:两个有微信小程序开发经验的去做微信小程序,我与另外一个人做 PC 端。

由于我们部门有点「特殊」(后面会讲原因),PC 端的视觉设计与京东的 VI 不符,虽然是肯定要进行 UI 改版的,但历史包袱有点沉重,一时半会儿还排不上日程。

因为「开放版」可以当作是一个全新的系统去开发,这时 UI 就一定要符合京东的 VI 了,前端代码也理所当然地要写进新的单页面应用项目里。

「开放版」所提供的功能有些是与其他版本重合的,只是 UI 和部分字段有所不同,尤其是「设置」模块,相关页面完全可以复用之前重写的新页面——得益于在做前后端分离时我把页面布局和样式设计成与具体页面尽可能解耦的,无论页面被设计成什么风格,具体页面的内容文件代码几乎不用修改。

之所以当初会留下这个「伏笔」,是因为当时知道日后必定会对 UI 风格进行调整,并且系统是有销售版本之分的,不同版本之间使用不一样的风格也不是没有可能的事。

为了应对上述情况并能够低成本快速切换,需要将「主题」进行集中管理,在开发时具体体现为:组件专注结构;布局专注风格;页面专注内容。

写组件时,尽可能用语义化且应变能力强的结构,相应的样式文件尽量写在页面布局对应的特定文件夹中;写页面时,尽可能用封装好的组件,尽量不写原生的 HTML 标签。

另外,销售版本之分及 UI 改版会造成一个系统里同时运行着多个「子应用」,这就需要从路由根路径上去做切割(隔离)了。

因此,控制页面布局和风格的「主题」以及路由配置都是有多份的——

mario
   ├── config
   │   └── router
   │       ├── admin                       # 非开放版的路由配置
   │       │   ├── ...
   │       │   └── index.js
   │       ├── lite                        # 开放版的路由配置
   │       │   ├── ...
   │       │   └── index.js
   │       └── index.js
   ├── src
   │   ├── components
   │   │   └── ...
   │   ├── layouts
   │   │   ├── lite                        # 开放版的主题
   │   │   │   ├── admin
   │   │   │   │   ├── theme
   │   │   │   │   │   └── ...
   │   │   │   │   ├── AdminLayout.js
   │   │   │   │   └── AdminLayout.scss
   │   │   └── tq                          # 非开放版的主题
   │   │       └── admin
   │   │           ├── theme
   │   │           │   └── ...
   │   │           ├── AdminLayout.js
   │   │           └── AdminLayout.scss
   │   └── pages
   │       └── ...
   └── ...

另一件让我感到兴奋的事是,「开放版」除了「设置」模块,还有「维修」、「财务」与「报表」等涉及业务主流程的模块,这为后续的前后端分离作了很好的铺垫。

「京东汽车」微信小程序

由于集团业务和组织架构调整,将 to C 的汽后相关业务也划分到我们部门。这样一来,从个人消费者到汽修店,再到汽配选购及汽配销售,打通了汽修服务从消费到供应的各个环节。

在 2018 年我还没入职的早些时候,为打造一个京东汽车相关业务的统一入口,建立品牌、口碑,推出了「京东汽车」微信小程序。在将这个微信小程序交给我们部门负责之后,为了让车主能够更方便地找到附近的门店并可以线上下单线下服务,上级想要在「京东汽车」微信小程序中加入「门店」模块。

之前在分配任务时,我跟所在业务线的其他前端之间有个约定——活动页和微信小程序的开发轮着来,尽可能每个人轮到的次数是相同的,以保障公平和不「偏科」。鉴于此,做「开放版」微信小程序的那两个人去接其他 PC 端的需求,而当时做 PC 端的我与另一个人来做这个需求——这是第三个也是最后一个对我有所意义的任务。

这个需求所要做的事情主要是:在「首页」加入「附近好店」用以显示离车主最近的几家汽修店,并且要在首屏内显示;新增「门店」模块,包括列表页和详情页。实际上,在「京东」客户端应用中已经有用 web 页面做的相同功能了,唯一的不同就是 UI 设计有所差别,哦对了,还有门店专属小程序码。

在这次任务中,我负责详情页开发和解决一些其他问题,另一个人则负责「附近好店」和列表页。

详情页主要包含了门店的基本信息、服务信息、核销码、优惠券、专属小程序码;功能有放大查看门店照片、地图中查看位置、拨打电话、查看并保存专属小程序码、分享门店给好友、服务购买、未消费服务核销、领优惠券等。其中,专属小程序码主要是为了分享到朋友圈和打印出来贴到门店中让车主线上下单使用。

这是第一次开发微信小程序,在做的过程中大大小小遇到了不少问题,最困扰的就是将门店专属小程序码保存到相册的功能以及偶发性闪退——

门店专属小程序码这个功能,在门店详情页中显示的时候是直接用组件和样式,而在保存到移动设备相册时则用画布去画图并生成图片,就在这个阶段遇到了绘制、渲染和配置等几类问题。

左为组件和样式,右为生成的图片
左为组件和样式,右为生成的图片

因为专属小程序码是图文结合的,图片部分很好定位,但文字部分并不能像图片那样正确绘制到计算好的位置上,需要不断地调整位置以便看上去与设计稿无异。

具体原因不清楚,一开始生成的图片背景是黑色的,而不是「京东红」,后来在 CanvasContext.draw() 的回调函数中用了 setTimeout() 就好了。

另一个渲染的问题是在部分 Android 系统的手机中生成的图片里不同区域的边界处锯齿感明显,将生成的图片比画布尺寸放大几倍的话就没那么明显的锯齿了。

在开发调试这个功能时是用的本地的一个小程序码,因此没有暴露出在程序化地将后端生成并传到 OSS 上的远程图片下载到本地时可能会出现的平台配置问题——若是没在微信公众平台上配置「downloadFile合法域名」的话,执行 wx.downloadFile() 会失败!

功能开发完了,就进入测试阶段,这时出现了一个让人头疼的问题——偶发性闪退。这个问题基本只发生在 Android 系统的手机上,并且复现率不那么高,操作路径也不怎么稳定,没法很好地排查问题,只能硬着头皮把能优化的地方都优化一遍,降低闪退发生的概率。

闪退、卡死之类的基本是由可用资源不足或资源占用超限导致的程序运行异常问题。微信小程序是运行在微信这个平台型应用上的,所以它天生有资源瓶颈,稍不留意就会造成性能问题,真可谓是「寸土寸金」啊!故此在开发时应当时时刻刻注意不要同时占用较多的计算资源。

我和共同做「门店」模块的那个人一起用能想到的方式把我们做的功能进行了优化:

  • 使用数量和层次最少的标签完成设计稿;
  • 避免过多的遍历操作;
  • 与页面渲染无关的数据不进行 this.setData()
  • 同时发出的 HTTP 请求数尽量不超过 2 个;
  • 让后端在 HTTP 接口中不要返回前端用不上的字段;
  • 列表页在首次渲染时列表部分的高度尽量不要超过一屏半,并使用虚拟列表技术。

优化之后真的降低了闪退发生的概率!为了测试,我甚至还把一直吃灰的俺家🐷的小米 2S 手机找出来刷了 MIUI 10 测测在老手机上的表现!

小米 2S 在刷机
小米 2S 在刷机

我们做的这个「门店」模块不单单是把「京东」客户端应用上的相同功能给搬运到「京东汽车」微信小程序上,还要为支持「6.18」而额外加一些营销功能。

也正因为两端功能相同,要加新东西就得两边各加一遍。由于一些原因(下文会说),「京东」客户端应用那边就交给在北京办公室的同事做了——这是我第一次也是最后一次与北京的同部门同事合作。

为了引流,汽车用品专场的活动页上也要加入「附近好店」功能。当然,这需要由做微信小程序上「附近好店」的那个人去做。这时,我才知道集团有个叫「通天塔」的活动页等运营需求的开发平台。

在做活动页上的「附近好店」功能时,那个同事遇到了通过 navigator.geolocation.getCurrentPosition() 无法获取到位置信息的问题,需要我帮忙排查一下原因。我电脑上打开那个页面是能够正常获取到位置信息的,无法复现问题,就去她电脑上排查。

首先排除了访问环境的安全性问题,即访问的网页是 HTTPS 的或者在本地环境。发现她写的代码里在调用 navigator.geolocation.getCurrentPosition() 时没有传入第二个参数,所以不知道有没有报错,传了之后立马真相大白——

navigator.geolocation.getCurrentPosition(function(pos) {
  // ...
}, function(err) {
  console.warn('在获取地理位置时出现问题:', err);
});

在 Chrome 的控制台中输出的错误信息里看到了 Network location provider at 'https://www.googleapis.com/' : No response received.,这意味着所处网络环境在访问 https://www.googleapis.com/ 时出现了问题。

如果没有断网,看到「google」这个字眼,就知道是众所周知的原因了。更为深层的原因是因为 Chrome 在返回位置信息之前需要远程调用 Google API。解决方案就是,要么弄个梯子,要么换个浏览器。

换了 Safari 或 Firefox 后如果看到 User denied Geolocation,那就要检查下电脑的定位服务是否开启并已经给浏览器授权。masOS 需要到「系统偏好设置」的「安全性与隐私」中「隐私」那里找到「定位服务」:

「安全性与隐私」设置
「安全性与隐私」设置

考虑到兼容性问题,最终选择引入腾讯位置服务的 JS SDK 来获取位置信息。

工作体验

上文中着重写了三个任务,但我实际参与的不只这些,只是它们对我来说更有意义一些。我在这里工作时间不长,从 2018 年 10 月到次年 6 月才 9 个来月时间,否则会写出更多有意义的事!

至于为什么我只在职这么短时间,以及前文在讲工作成果时未说的「原因」,这就一一道来——

部门来历

我入职的部门是京东在汽车后市场布局的重要部门,它的前身是一家成立于 2014 年左右的叫做「淘汽档口」的杭州创业公司,主营汽配、汽修相关业务,在 2017 年被京东收购。

遗留的「淘汽档口」笔记本
遗留的「淘汽档口」笔记本

说起来,在买/卖好车时就有好几个同事是来自淘汽档口,这个部门的技术负责人(老淘汽人)与买/卖好车的创始团队成员也很熟,毕竟都是阿里毕业的。

杭州研发中心的人是由老淘汽人和像我这种新招的人组成,而北京办公室的则是由从集团其他部门异动过去的和新招的人构成。

我们部门虽然顶着「京东」的名头,但实际更像是保持了「淘汽档口」特色的创业公司,运营着两个相对独立的 SaaS 产品——除了「京东云修」之外,还有北京办公室负责的「京东云配」,域名和体系都与集团相独立。因此,它显得有点「特殊」。

日常工作

杭州研发中心总共几十人,我所在的业务线就有 20 人左右,其中包括我们 4 个前端:男女对半,两个女的是老淘汽人,两个男的是新招的。那个男的因为年纪最小,被她们称为「小鲜肉」,而我作为最年长者,自然成了「老腊肉」……

在与业务线负责人(我上级)面试时提到,假如我加入的话,对我的期望主要有两个:管理业务线甚至是整个杭州研发中心的前端开发相关工作;用新的技术栈重写「京东云修」这个系统。我的日常工作就是以此为基础开展的。

这是我职业生涯中第一次受命管理,有了很多以往从未有过的体验。总结一下就是,作为一个夹在中间的管理者,应当努力做到「三赢」——达到甚至超越上级给予的期望;尽量满足下属合理的需求;尽量让自己有所成长。

既然是个管理者,日常的工作内容就不只是开发了,多了很多「杂务」,如:参加和组织各种会议,沟通、汇报与交流,协调资源等。

周期性的会议有各个负责人的会议和前端内部的,内容上比较类似,就是信息的收集与传达,这也是保障组织正常运转的基本手段。前端的周会上有时还会分享一些技术、工程等方面的知识。

我不喜欢摆架子,希望团队中的人尽量能够平等交流,一起为同一个目标努力;所以在刚入职没多久就组织了一次前端内部会议,算是第一次周会,主要是各自做下自我介绍,聊聊天,互相熟悉下。在那之后的一段时间,我又找他们单独聊天,想了解下各自的想法、需求,同时也表达了我的期望。

也正是基于这个愿景,我们之间才有了前文提到的轮着做活动页和微信小程序的约定。

虽然绝大多数时候前端团队内的氛围是和谐融洽的,但也有个别时候有人会闹情绪,这让我了解到作为一个管理者在工作中不仅要控制好自己的情绪,还要管理好下属的情绪。我会根据情况选择是放置处理,啥也不说让他们自行调节,还是叫到会议室了解他们的真实想法后想办法解决。

除了要管理好前端的人员和工作,还要管理上下游的产出,比如:后端接口返回的数据及其格式;产品与交互稿的文案、功能和交互;设计稿的元素摆放位置、配色等。

我们在日常工作中不只有同部门的协作,也会有跨部门的协作,不过不管哪种都会有扯皮的事情出现。一般来说出现这类问题时光靠当事人自己是无法解决的,需要上级的介入才行,往往上级间的对话才能解决问题。自己明白并教会下属在这种问题上要及时向上反馈很重要。

出差总部

在面试时就被告知杭州研发中心会被撤销,但具体时间未定,并且我也不确定到时会不会跟着去北京,决定还是先入职再看情况。当确定搬离时间并统计人员去留意愿时,我跟🐷商量的结论是不去北京。

自从确定了工作交接时间时起,我所在业务线的新需求都尽可能地由北京那边而不是我们杭州研发中心去接,于是乎「京东汽车」微信小程序的「门店」模块相关需求就成了我所参与的最后一个任务。

2019 年 5 月 28 日,我和后端的一个负责人在下班后到楼下的机场大巴候车厅乘车前往萧山机场,开启我们的「总部出差交接之旅」。刚到北京时已经是半夜了,我们直接去酒店洗洗睡了。

京东总部在北京亦庄的南五环与南六环之间,我们落脚的酒店离总部不是很远,来回步行即可。第二天起来得还算早,就带着旅游观光的心态跟着导航悠闲地向总部散步而去。

去总部的路上
去总部的路上

总部里已经有几栋很高层的办公楼了,但好像人还是很不够坐,附近的一些办公楼中有好多京东的员工进进出出。

在刚进总部园区大门,就听到裤兜里「叮咚」的一声,拿出手机一看才知道,原来是「京东ME」自动打卡了!在杭州的时候可从来没这体验……

忘了是从哪个门进的哪栋楼,一楼有些让我眼前一亮的东西,像「小巨蛋」、京东便利店、无人超市、「京东 X 事业部」展示厅等等。

京东便利店与「京东 X 事业部」展示厅
京东便利店与「京东 X 事业部」展示厅

楼上办公区的茶水间有零食和咖啡机,虽然不是单品,但那几天交接的日子白天就靠它了!

茶水间的咖啡机
茶水间的咖啡机

交接第一天的中午,因为要留在京东而早一步来到北京的上级与我所在业务线的新的负责人等带着我和那个一起出差的后端负责人到食堂里的「饭店」撮了一顿。总部的食堂还挺大的,种类繁多,价格不贵,味道也可以。

这回可算见到了之前一直隔着屏幕和网线联系的北京同事。尤其是在做「门店」模块支持「6.18」相关需求时有合作的那位,过后我们面对面聊过之后,觉得他对技术还挺有所追求。

与交接的前端同事合影
与交接的前端同事合影

晚上吃完饭跟交接的前端同事在楼下散步时,看到有其他部门的几个人围着一个「机器人」在调试,感觉很是有趣。

交接第二天的晚上,几个要留在京东的杭州同事叫我们到酒店楼下附近的烧烤店喝酒撸串儿,算是给我们临别饯行吧。可能是酒精的作用,平时不太外露情感的我变得有些感性,跟他们聊天还得知了一些之前意想不到的事情。

由于没有合适的机票,我们是坐高铁回杭州的。

江湖再见
江湖再见

因为是处于「6.18」活动期间,就算是出差,就算已经完成工作交接,在高铁上我依然无法松懈与放松——时刻盯着「京东ME」上的工作群,只要有人反馈「京东汽车」微信小程序的「门店」模块有什么问题,我就得协调处理,必要时还得连上手机热点提交代码。

到家已是半夜,推开门看到几天没见的做好芒果千层等我的🐷时,心里很是温暖,感觉眼睛都要湿了——

🐷的爱心芒果千层
🐷的爱心芒果千层

如果我是单身,肯定毫不犹豫地选择留在京东而离开杭州去北京;可我不是,我有如此爱我、关心我的🐷,我不忍心也不可能留她一人在杭州。我能作为一个管理者在这里与其他人相处得还不错,有她一份功劳!

总结

这段工作经历是我来杭州之后最短的一段,即便如此,也是很有意义的一段——我的身份角色转换了,从一个单纯被管理的人变成一个受命管理其他人的人,遇事时的立场和思考方式有所转变;对情绪管理的要求更加严格,不能像单纯被管理的人那样任性。

有些事是真的只有大厂才有资源、人员和能力去做。

虽然时间很短比较遗憾,但又不那么遗憾,因为我相信——一切都是被安排好的,一切都是最好的安排!