仓库源文

.. Kenneth Lee 版权所有 2019-2020

:Authors: Kenneth Lee :Version: 1.0

代码生成器


软件工程师都有“抽象”的爱好,看不得重复的代码,也看不得重复的Pattern。收敛重复代 码的方法是“库”,公共的逻辑走公共的路径,这种方法很成熟,是软件工程师进行抽象的 基本工具。

而重复的Pattern就不一样了,最常见的公共Pattern类似C的宏,C++的termplate,这个东 西用库是搞不定的,你要做一个加法add(a, b),a和b可能是整数,也可能是浮点数,甚至 可能是向量。他们的Pattern是一样的,但生成的机器码完全不同,这种情况库就不起作用 了,人们就会考虑使用宏或者template。宏和template为每种不同的情形生成不同的“库” ,这样重复的成分就可以减少了。

但所有调试过这类代码的人,都知道宏和template其实是僵梦。简单的还好说,稍微复杂 一点的,或者宏里套宏,template中套template的,一个简单的笔误就可以拖你几天。

但template毕竟用很久了,它自身的问题还是比较少的,所以,算法为主的代码,还是可 以接受使用template的。更大的僵梦是“自动生成器”。

十几年前,我负责一个网元管理程序的开发,这个程序可能支持几十种路由器交换机的管 理,管理的行为基本上都是一样的:从SNMPv3读到ObjectID,知道对应的是哪个具体的设 备,然后把它的界面显示出来,在特定的位置上画上端口和指示灯,然后启动不同的子窗 口管理不同的SNMP表就好了。

上一个版本的架构师强烈建议我使用他们的“优秀实践”,通过“配置文件”来描述每个设备 的行为,然后“自动”绘制界面。我一听就不对劲,但一时也找不到理由反驳他。我就去找 他们团队的一线工程师问这个方法好不好。得到的反馈基本上有两种:不用改代码的人都 说好,发现一个Bug,修改全系统的色调,只要改几行代码就好了,你有质疑还能告诉你很 多“精妙”的设计,可以解决这个那个的问题。要改代码的人则强烈反对:尼玛基本上一行 代码都不敢改,很简单的一个显示错误,简单调整一下,一款路由器好了,破坏三款交换 机……

反正最后我们没有选择这个方案,我们的版本出来后,就算功能没有那么强,很快就把旧 版本全部干掉了——其实大家都受不了。

把代码逻辑分成很多层:配置文件->一级代码->源代码->二进制代码->运行映像,对比源 代码->二进制代码->运行映像,最大的区别是“控制力”——层次越多,就好比你用一条多节 钢鞭去打鸟,和你用一根竹竿去打鸟的区别。后者你可以直接控制杆头的位置,前者,你 根本不确定你能控制什么。

什么时候,你会考虑用多节钢鞭呢?——各位读者是否通过厕所?就是这样的:

伸出来有5到10米长,什么角度都能进去的,但它是怎么进去的,你都不知道,都不在你的 控制范围内,你也只是需要它一路往里钻。就是这种逻辑简单,方法单一的,我们就用这 种方法了。你说你那这东西去干打鸟,粘知了,从隔壁阳台开自家房门这种精巧的事情, 就不要指望了。

我都不说远了,就qemu的日志生成器,仅仅是根据你的函数描述,自动生成日志输出函数 这么简单的一个实现,函数描述文件多写了个逗号,都查了我半个上午。更不要说那种逻 辑复杂得一逼,你还不知道未来会面对什么什么需求的场合了。

我想说的是,代码自动生成这种设计,在架构师和领导眼中都是很好看的,到你用死了的 时候,你都不一定知道你错在哪里,你会觉得是基层工程师不够努力,但这个很多时候都 是陷阱,千万不要吃饱了撑的没事插一只脚进去。

但是,确实有明显的例外,那就是各种DSL和脚本语言。这些确实在硬件和编程接口之间多 加了一层。但这些情形可以成功,无一例外,都是做到用户在维护,调试和优化的过程中 ,是不会考虑硬件行为的,这种机会就是基于软件构造了一个“硬件”,这种情形确实是可 以的,但它并不适合你要考虑高性能竞争力的场景——几乎没有人会对DSL和脚本语言提高性 能要求。而且,想想你的脚本语言出了一个和虚拟机和硬件配合出了问题时定位问题的难 度?——它就只适合非常成熟的场景。