自动化生成代码的秘密

我做过两个自动化生成代码的项目,scaffoldredis-orm
scaffold 主要是通过数据库表定义来生成基于表的增删改查的基础管理工作;
redis-orm 是通过yaml的结构定义文件生成关系型数据库与redis的常规操作实现。
公司里还有一套微服务的自动化生成框架,能够快速的通过protobuf的定义文件生成项目的框架代码。

自动化生成代码有个最大的优点:减少程式化的编码。所谓程式化的编码就是,通常这些编码的工作量会随着业务量的增长线性增长,同时又是最没有技术含量的工作。所以通过开发自动化生成工具非常有必要,减少无谓的工作量同时大大提升工作效率,把大家解放出来做更有意义的事。

不论是我写的自动化生成工具或是公司的微服务框架生成工具还是其它一些官方工具,都有一个共同原理,所谓自动化生成代码的秘密,即

通过结构化的元数据生成模式代码

这句话中有两个关键词:

  • 结构化的元数据
    结构化的元数据的来源可以是:

    • 数据表定义
      例子: scaffold
    • 结构化的配置文件(yaml, toml 等等)
      例子: redis-orm
    • 服务接口定义(Thrift, ProtoBuffer等等)
      例子:grpcmicro
    • 程序代码中的类型、对象、接口等等
      例子:stringermock
  • 模式代码
    模式代码,即所有生成的代码是符合一定规律的,而这种规律就是基于元数据而言的。

最简单的例子

官方的工具stringer就是一个自动化生成代码工具,主要用途是通过枚举值的变量名生成String函数接口,常用场景就是在定义程序状态码中使用。其中,结构化的元数据就是枚举类型的定义。

package codes

type Code uint32

//go:generate stringer -type=Code

const (
  OK Code = 0
  Canceled Code = 1
  Unknown Code = 2
  InvalidArgument Code = 3
)

这是一个简化版的GRPC状态码的例子,在文件所属目录下通过以下stringer命令即可生成代码文件code_string.go

$: stringer -type Code

生成的代码如下:

// Code generated by "stringer -type Code"; DO NOT EDIT

package codes

import "fmt"

const _Code_name = "OKCanceledUnknownInvalidArgument"

var _Code_index = [...]uint8{0, 2, 10, 17, 32}

func (i Code) String() string {
    if i >= Code(len(_Code_index)-1) {
        return fmt.Sprintf("Code(%d)", i)
    }
    return _Code_name[_Code_index[i]:_Code_index[i+1]]
}

原代码函数有一句注释的语句:

//go:generate stringer -type=Code

通过该语句,可以在命令行中执行如下命令,效果相同:

$: go generate

一个小技巧,在制作自动化生成代码工具的过程中有时候会很有用。

微服务框架的自动化

微服务现在很火,如何开发一个微服务框架的自动化生成工具呢?

首先,我们要清楚什么是框架

框架是对接口的抽象

这是我个人对框架的总结,通过将项目中变化的部分通过接口抽象出来,提供给开发者,将不变的或者配置可变的放入框架中。

其实,grpc 已经是一个简单的微服务框架了,只是功能比较单一,仅仅通过protobuf的定义生成客户端与服务端代码框架。它是怎么做到的?

管道的概念,做服务端的人都非常熟悉。可以用管道的概念类比一下grpc框架代码的生成过程

protoc | protoc-gen-go | plugin:grpc

protoc编译器通过读取protobuf协议与接口配置,输出结构化元数据给 protoc-gen-go,由它生成 go 代码,在protoc-gen-go中会用到 plugin:grpc 的插件实现grpc框架代码的定制生成。

当然, protoc-gen-go 调用 plugin:grpc 不是通过管道的方式。

要实现微服务框架的自动化的关键全在 plugin:grpc 中了。因为 plugin:grpc 就是一个代码生成器。你想要的所有内心戏全部可以在这里实现。包括:

  • 服务发现
  • 上下文定制
  • 错误处理
  • 日志
  • 统计

全部可以在框架里实现,仅仅暴露简单的接口供开发人员开发。

为了让生成代码更加精炼、可读性更强,共用的一些函数都会通过公用包的形式实现。

在安装GRPC的过程中,有这样一条安装命令:

$: go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

其中,包proto就是protoc生成go代码提供的公用包

** 结构化元数据 **

有时候阅读代码可以帮助我们理解protobuf协议。在公司的微服务框架里用到了custom option. 在官方文档说,这个属性对于大部分开发者都是不会用到的。因为这个属性仅有在需要开发自己的框架代码时才会使用到。编写模式代码中,可以通过custom option控制框架代码的生成。

** 模式代码 **

除了结构化的元数据,模式代码的质量直接影响了项目本身的质量。模式代码保持精炼,可读性强都是一些基本要求。不贴代码了,具体代码参见grpc.go.

如何编写自己的Plugin,除了参考GRPC本身的Plugin实现,还可以参考这个项目
micro/protobuf.

自动化生成代码常见的坑

在开发自动化生成代码工具的过程中,关键一步是编写模式代码。通常模式代码一定是通过不断的迭代才能达到所谓的完美。所以,在不断迭代的过程中,就会出现,很痛苦的,改变接口

如果只是生成的代码改变接口可能影响面还比较小,只需要相应的修改调用方代码即可。但是如果生成代码中调用的公用包接口发生改变了,可能以前生成的代码就会发生故障。这也是我真实碰到过的一个坑。为了防止类似错误,可以通过版本控制的办法解决。

通过对仓库打tag,利用gopkg.io实现版本控制,是非常快捷且高效的解决办法.

如何用好自动化代码生成工具

用好自动化代码生成工具的关键,除了对生成代码本身要很熟悉外,还需要了解生成工具编写的模式代码。了解自动化代码生成工具的原理是非常必要的。

其实框架越强大,对于业务而言越有利,但对喜欢偷懒的程序员而言是不利的。所以利用偷懒来的时间,阅读框架代码非常必要。

归根结底,自动化编程是一项泛化编程技术,以前在c++中是件高端而隐秘的事,将程序执行期的代码移至编译期生成。如今,在go语言中,可以通过模板包template光明正大的干这件事了。

以上,就是我在开发和使用自动化代码生成工具中学到的些许经验,全当抛砖引玉,欢迎指教。


本文首发扯扯皮个人简书博客, 欢迎关注。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • 我做过两个自动化生成代码的项目,scaffold和redis-orm。scaffold 主要是通过数据库表定义来生...
    一艘慢船阅读 1,510评论 0 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • 1 如果用现在的热词,沈小怡当年是我迷妹。 2012年,我在上海培训PPT,发现课堂上坐着一位美女,听得非常认真,...
    秋叶大叔阅读 739评论 2 8
  • 昨天的认真梳理了自己烦躁的原因,觉得自己内心有抗拒去完成那个一直待完成的方案。今天到办公室一做下就把方案打开,写到...
    Rachel_瑞秋阅读 258评论 1 4