代码生成
作为程序员,我的理想之一是使用代码去生成代码。
代码生成可以用于很多场景,其中应用最广泛的,应该是ORM
。
ORM
ORM
- Object Relational Mapping,即把程序语言中的对象,映射到关系型数据库的结构上。
对象属性、表结构都是有清晰的定义;相互之间的映射是可以有固定的规则;如果手动逐个对象、属性写这些映射规则的话,会相当繁琐。
一般可以使用反射,在程序运行时去判断对象以及属性,然后对映射做不同的处理;但也可以使用代码生成来实现。
相对于使用反射,我会更喜欢代码生成的方式,因为:
- 更加容易debug
- 编译器提供更多的检查
- 运行性能更好
IDL
生成代码的时候,一般也会需要引入一个IDL,即Interface Definition Language,对需要处理的对象结构进行严格的定义描述,然后编译器读取这样的IDL后,再进行输出。
json、yaml等格式也可以作为IDL使用,但我一般会使用其它更加严格的语言,比方说thrift,protobuf;因为:
- 不用自己再去发明一套语法
- 有更多的周边工具可以使用
使用了很多年thrift
做为DSL,我并不使用thrift本身的序列化、远程调用功能,仅仅是借用thrift作为IDL的语法,然后使用现成的thrift parser去生成我需要的代码。
thrift的开源parser有:python的ptsd,跟go的go-thrift等。
近两年则慢慢切换为protobuf,主要是因为我发现使用protobuf作为IDL的话,连parser都不需要。
protobuf的编译器protoc
解析protobuf定义文件之后,会把解析结果,以protobuf消息的形式,传递给相应的语言插件,然后由插件再去完成具体的代码生成任务。
因为protobuf本身支持的语言已经足够丰富,所有protobuf支持的语言都可以用来开发protoc插件;在这里,protoc就相当于一个跨语言支持的parser,它会把解析结果,通过标准输入、输出传递给插件。
这就带来很大的一个便利性,之前我使用thrift作为IDL的时候,很可能需要go、前端工程师再去掌握python,然后写代码生成的代码。
使用protobuf的话,go / typescript都可以用来实现插件,分别实现自己熟悉语言的代码生成即可。
代码生成
代码生成,需要工程师提高一个抽象层次去考虑自己的代码;以web开发作为比较的话,原本是直接写html就好了,可以直接从html的角度去思考;但引入jsp、PHP甚至是react这样的等“模板”后,就需要隔开一个层次去思考。
这样“隔开一层”,对于web开发者可能会比较习惯;但对于后端等工程师,则往往会需要有一个适应的过程。
在源代码内,使用字符串拼接可以做代码生成;但也可以使用模板。
如果使用go语言开发,使用go内置的text/template
库便相当顺手,go 1.6版就专门对此做了增强
Java的话,则可以考虑stringtemplate、freemaker等 .Net则可以考虑T4等。
注意,很多模板是针对html做了优化,它们可能特别适合html代码的生成,但用来生成源代码,则就非常不合适,比方说jade、razor等。
DSL
严格的说,thrift / protobuf等最多只适合对相对简单的结构、接口定义;而且,它们是对数据序列化做了考量,即需要给每个字段增加一个独立的序号。
我们若只是用它们来做IDL,不需要考虑版本兼容等问题时,额外写这么个序号其实是不必要的,虽然也不算特别麻烦。
(对于很多场景,其实使用yaml作为描述语言也是不错;几乎所有的语言也都会带有yaml的parser。)
thrift / protobuf本身都有一定的可扩展能力,但再怎么扩展,也很难将其扩展成为用来描述领域业务的语言。
groovy等脚本语言会是DSL更合适的选择。