我们做新执行器的时候,已经有一个很稳定的执行器在公司内广泛使用的了,
但我们认为,新旧执行器的实现是差之千里的,从一开始我们就没有重视旧执行器的实现,也没有最大限度的重用其模块,
恰恰,后来经历的几次重大重构工作,殊途同归,转了一大个圈,最后还是回到了旧执行器的基础上。
这样的教训值得深思,或许这就是成长代价。
旧执行器
基于内存对象模型实现,使用了ANTLR的词法语法功能,然后构造出一个完备语义的内存对象集合,具有分析、编译、链接、运行、调试功能。最大的优势的编译速度飞快,但占用内存量大。
新执行器
基于语言转换模型实现,包括语义分析模块,转换器模块,运行时库模块,调试器模块等,最大优势是代码编译固化能力,但编译耗时。
项目开始的时候,我们的计划是这样的:
1、利用旧执行器进行词法、语法、语义分析能力,保证用户书写代码正确。
2、通过ttcn3转换器和asn1转换,将用户代码转换成c++语言,然后调用标准的c++编译器编译成库文件。
3、通过执行器外壳加载运行时库、测试套库文件,调试器模块等进行运行。
我们犯的第一个重大错误是,实现asn1到c++的转换器模块。
asn1是一种类型描述文件,可以在ttcn3语言中使用。
因为我们是基于语言转换模型的,所以需要将asn1类型映射成c++类型,然后通过hpp文件方式导入使用。
由于asn1文件常常非常大,甚至一个文件5万多行,累积8千多个类型,
所以转换出来的c++代码非常多,直接导致编译速度非常慢。
这个问题是致命的,困扰了我们相当长的一段时间,而且花在这个asn1转换器上的开发人力也是很大的。
我们犯的第二个重大错误是,实现ttcn3到c++的转换器模块。
ttcn3转换器模块是独立的,和旧执行器语法语义分析模块是分开的,当时基于这样考虑:
1、不熟悉旧执行器,不想在旧执行器上改动过多代码。
2、考虑不充分,过度自信的认为,基于旧执行器的语法语义分析后,在外层实现一个转换器就可以搞定所有问题了。
很长一段时间内,我们的ttcn3转换器都能满足要求,但越到后来发觉越吃力,很多转换的时候单靠语法是不够的,还需要语义支持。
恰恰,我们的方案是没有语义功能的,所以需要通过旧执行器提供额外接口实现,这样的接口越来越多,但问题依旧不能完美解决。
整个项目开发周期,我们花在asn1转换器和ttcn3转换器上的开发工作是非常巨大的,约占用了60%的时间,
但这些方案都不能100%的解决问题,导致后面的多次重大重构,可以说,基本是推倒重来了一次。
重构的方案简单介绍如下:
1、否定asn1转换器,参考旧执行器使用asn1的方式,利用公司内的codec工具(基于c实现),从编译后的dll接口中获取asn1类型,
外部使用动态类型方式进行包装实现。这样的方案,c++代码量非常少,而且和asn1代码不存在正比关系,
即使10万行的asn1文件,编译速度都可以在数秒内完成。
2、否定ttcn3转换器模块,在旧执行器内部重新实现转换器工作,语法语义转换一次完成,代码转换起来变的轻松,速度也很快。
从重构的效果来看,我们大概花销了以前转换模块约20%时间,就100%的重写了一次,代码量减少很多,而且能100%解决问题。
我曾经很多次的假想,如果我们一开始就使用上正确的方案,今天我们的项目或许就很成功了。
从结果看过程,往往觉得简单,但一路走过来,又有谁能清醒如一呢?