技术债务可能是这样来的

技术债务可能是这样来的

看我技术博客的朋友可能有注意到,最近更新了一系列与CEF、PPAPI、Skia相关的文章。在研究它们的过程中,有一些有意思的经历,非常典型,可以从一个方面解释“技术债务”的由来。

接下来我会讲讲这次经历,并从此展开,看看形成技术债务的原因及应对策略。

选择容易的替代策略

因为业务需要,我得在PPAPI插件中显示另一个模块(已有模块,基于C++代码完成)传递过来的图像数据。那个模块提供的数据,图像格式是RGBA32,我在Intel Pentium主机上编译出的Skia库,默认的颜色类型是kBGRA_8888_SkColorType(kN32_SkColorType),这导致在使用Skia绘制之前,必须将收到的RGBA32格式的数据转换为BGRA,否则就什么也画不出来。转换确实可行,我试了试,不过在图像分辨率较高时,耗时会明显增加。因为需要逐像素转换,一个像素转换至少需要三次赋值和两次索引操作,百万像素的图片就需要百万次操作。

当我完成像素格式转换,看到CEF中显示出颜色正常的数据后,大大松了口气。此时我有两个想法:

  1. 就这样吧,功能实现了,PPAPI插件进程就显示图像也不干别的,慢点没太大影响

  2. 研究Skia,看为什么SkCanvas以RGBA格式的SkBitmap为后端绘图时失败

那天腊月二十八,马上要过年了,第一种选择所需要的工作仅仅是找理由说服自己,说服领导。这很容易,我们程序员老这么干啊,不是吗?

第二种选择就要困难一些,得做实验,得硬着头皮看Skia源码……这且不说,做了努力也不见得就能解决,说不定耽搁了时间和进度,最后还得回到第一种选择上去。总之我发现自己内心有点儿小拒绝,也能找到各种理由让自己放弃这个相对较难的选择。你遇到过这种情况吗?

哦卖糕的,我该怎么做呢?

后来过万年,2.14号情人节那天我就去上班了。大部分人还没到岗,我觉得研究一下第2种选择里那个问题很有必要——有时慢就是最大的缺陷啊。于是就做了下面这些事情:

  • 组合变换SkBitmap的ColorType、AlphaType

  • 变换PPAPI中Graphics 2D使用的PPB_ImageData的颜色格式

  • 变换SkCanvas的创建方式

  • 改变SkCanvas使用PPAPI创建的ImageData的方式

  • 采用Skia Images模块的方法完成RGBA到BGRA的转换以提高性能

总之折腾了一天,没找到原因!此时又想就这么算了……

选择容易的路,这种想法貌似难以避免,往往是不经意间就冒出来了。而真正去解决问题,则需要鼓足勇气努力说服自己。

第二天我觉得研究SkCanvas到底怎样使用SkBitmap来绘图的,花了大半天时间做实验、阅读Skia的源码,终于发现SkCanvas基于SkBitmap绘图时会创建一个SkBitmapDevice,而SkBitmapDevice会根据kN32_SkColorType来分支操作,而Intel Pentium主机是小端字节序,Skia的默认编译选项编译出来的库kN32_SkColorType就是BGRA,所以当我传递把SkBitmap的ColorType设置为kRGBA_8888_SkColorType,再将这个SkBitmap传递给SkCanvas时,SkCanvas构建SkBitmapDevice来作为绘图的backend,就注定了失败!

原因明白了,我又有两个选择:

  1. 认为这是Skia的局限,就此打住

  2. 研究Skia的编译系统,看怎样在小端序的主机上编译出默认使用RGBA的库

第一个选择,工作就到此完了,也可以解释给领导和小伙伴了。

第二个选择,得研究Skia的构建系统,还得了解控制颜色类型的那些宏定义,还得看SkBitmapDevice、SkBitmap到底如何使用颜色类型……就这还不知道编译出来的库是否有其它副作用(结果证明是有的,后话)……

我又为这个纠结了一阵,这天就这么下班了……

第二天上班,我鼓起勇气选择了研究Skia,嘿,别说,经过对ninja脚本、config头文件以及SkImageInfo相关类库的研究,我真了解清楚了怎样才能编译出默认使用RGBA的库,并且下班前编译出来了哦。哇,我做实验时写的小demo,使用这个新版本的库,RGBA的Bitmap用作后端正常了!

接下来的一天,我修改了PPAPI插件的代码框架,取得了大大的进步:接收到的数据可以不经转换就正常显示了!

心里挺高兴:我战胜了自己从权的想法。

然而问题来了,从本地图片文件加载图片时,Red和Blue通道反了,用到的图标看起来诡异得很,效果全部不对……折腾了三种加载本地图片的方法,都不行。可是内存里绘制是正确滴……

又来到了十字路口,面临了几个选择:

  1. 不用Skia加载图片,用其它的,解码成RGBA

  2. 用Skia,加载后获取图像数据,交换R和B(图标都不大,性能损耗可接受)

  3. 研究Skia从磁盘加载并解码图片的流程,搞明白问题出哪里

你说我选哪个?

第2个,前面干过了,就是复制点儿代码,工作量很小,很有诱惑力。

第1个,bitmap用Windows API,png、jpeg也都有解码库,之前也用过其他的图像库,解码出的数据也是RGBA的,看起来也没什么难的,就是多一些工作量。

第3个,充满未知和阻力。要知道看别人的代码总是很头疼的,尤其想Skia这种优秀的开源库,代码很棒很巧妙,模块间设计的接口挺简洁的,很多东西都抽象、封装了,要理解需要不少时间……还有,缺少文档(好吧,代码就是最好的文档,永不会遭遇文档和实现脱节的问题)……

找了会图像库,什么FreeImage、CxImage,看了会Skia源码,……思想斗争到下班也没决定,记录到本子上,第二天接着选择吧。

我回到家里就琢磨这个问题:为什么我老想选择容易的路

是啊,为什么总是拈轻怕重咧?

此时我脑子里就回想之前的工作历程,哪些能Run能出结果的代码*重构了,哪些临时策略导致了技术债务造成了恶劣影响……

后来我决定选择 3 !

现在问题已经解决了!

程序员出于(我不代表所有程序员)本能都想省事儿,选择容易的路、确定性高的路,这没什么好非议的——假如容易的路能漂亮的解决问题,它就是最好的选择。

然而为了摆脱压力而采用易行的、凑合的、从权的、临时的技术方案,多数时候会带来技术债务

技术债务是怎么来的

如你所见,我在使用Skia在PPAPI插件中绘制另一个模块传递过来的RGBA图像数据时遇到了问题,在解决问题的过程中,经历了三次选择,每一次都面临容易的妥协方案和较难的、不确定的但真正解决问题的方案。我在这三个时刻,都倾向于选择容易的、工作量小的、耗时短的方案

这不单是我个人的情况,而是大多数程序员从某个问题的多个备选解决方案中选择时的习惯性行为。这种习惯性行为,一方面出自人的一种本能——人总是不假思索地放弃挑战选择那条容易的路。因为挑战伴随着不确定性和自我控制,不如唾手可得的方法来得快速。以小孩子为例,你在他面前放两颗糖,告诉他有如果明天吃掉就可以再得到两颗糖现在吃掉就再也没有了,他很可能稍稍犹豫后就抓起那两颗糖吃掉。

我们倾向于即刻的满足,延迟满足是需要经过练习才能形成的思维和行为习惯。这是程序员选择易行解决方案的一个重要缘由。

避免麻烦和烦恼是本性,所以即便没有外界压力,我们还是倾向于选择看起来更省事儿的方案。然而这并不是程序员做出这种选择的所有原因。程序员这么做,往往还有其它的原因:

  • 交付时间的压力

  • 单位时间做得更多绩效就越好的文化导向

  • 能力不足

  • 出了问题再说的心态

稍稍展开一下下。

  • 1. 交付时间的压力

这是导致选择简单粗暴解决方案的最常见最直接的原因。

回到我前面说的问题,其实是个小问题,在这样的小问题上花将近一周的时间,在交付时间紧张的情况下是很难被允许的(我是刚好在过完春节这个缓冲期才有可能从容来解决它),自己不允许领导也不允许。当你感知到交付时间的强大压力,结果往往就是先实现再说,以后有时间再来优化。然而这是程序员说过的最大的、最堂而皇之的谎言,那些凑合事儿的实现,往往就那么着了,直到它出问题被用户和领导逼着限时修改,才开始新一轮的循环。

  • 2. 多就是好的绩效导向

很多公司鼓(qiang)励(po)大家干更多的活,如果你能更多的并行工作、更快的交付、完成更多的版本,绩效就会更好。在这种导向(制度)下,我们就会更重视数量和速度,忽略质量和可维护性,眼前不出问题就得过且过,慢慢形成只管拉屎不管擦屁股的开发习惯。

不但是程序员,程序员的领导的绩效往往也是这么个导向,所以一线管理者往往会接受中层、高管的不合理交付要求,转身就会给程序员更紧迫的交付时间把压力传递下来。

  • 3. 能力不足

这也是一个重要原因,有时程序员遇到的问题他眼下根本解决不了,只能粉饰过去或绕道而行——要从根儿上解决那就花得时间太久了,谁都无法接受(参考前面两点)。

  • 4. 出了问题再说

这也是很多程序员做事儿时的常见心态:能Run就先Run,不出问题就先这么着,想那么多干嘛,出了问题再说,反正干不完的活

这就是典型的干活心态

过年时和我原来的老板吃饭,他说了一句让我印象深刻的话:如果一个公司不能让员工觉得是给自己工作,那这个公司迟早完蛋。这其实说的是事业心态

你觉得自己是在干活还是干事业,是给自己干还是给公司干,很大程度上决定了你怎么干。

如何避免技术债务

回头来看我前面的经历,有两点是需要特别注意的:

  1. 我根本没想到会碰到这个问题(意外总是存在)

  2. 时间宽松时我才会有彻底解决问题的可能(时间允许时程序员才会向难题发起挑战)

Ok,这也是软件开发过程中避免堆积技术债务时需要参考的非常重要的两点。

关于这两点,做过两年开发的都有体会。然而深有体会的人往往并不掌握决定权,所以,我们还要让管理层和老板们意识到下面几点:

  1. 对软件开发来讲,没有一条道路是重复的。人家走过的路我们来走,仍然可能走不好,被别人验证过的方案,我们采用时仍然可能遇到各式各样的问题。即便我们开始前做了自以为最充分的评估,还是有考虑不到的问题跳出来碾压我们。

  2. 软件开发是手艺活儿,必须留够时间让程序员耐心打磨才能出好东西。

只有意识到了这几点,技术氛围、绩效导向才会改变,才有利于形成向技术债务说No的文化。


上一篇:NDGD团队(九)


下一篇:CSS呼吸灯效果