go generate 生成代码
今后一段时间要研究下go generate,在官网博客上看了Rob Pike写的generating code,花了一些时间翻译了下。有几个句子翻译的是否正确有待考量,欢迎指正。
生成代码
通用计算的一个特性--图灵完备--是一个计算机程序可以编写一个计算机程序。这是一个强大的想法,尽管经常出现,但还不足够完美。例如,它是编译器定义的重要组成部分。它也是go test命令的工作原理:它扫描要测试的软件包,写出一个包含为包定制的测试工具的Go程序,然后编译并运行。现代电脑快到可以在几分之一秒完成这个看似昂贵的序列。
还有很多程序编写程序的其他例子。例如,yacc读入一个语法描述,并写出一个程序来解析该语法。Protocol buffer“编译器”读取接口描述输出结构定义,方法和其他支持代码。各种配置工具也是这样工作的,检查元数据或环境,输出自定义的本地配置。
因此,编写程序的程序是软件工程的重要组成部分,但是像yacc这些可以生成源代码的程序需要基础到构建过程中,以便可以编译它们的输出。当使用像Make这样的外部构建工具时,这通常可以很容易做到。但是在Go中,Go的工具从Go源中获取所有必要的构建信息,这有一个问题。没有机制可以单独地从go tool中运行yacc。
直到现在,就是这样。
最新的Go发布版,1.4,包含一个新命令,可以更轻松地运行这些工具。它叫做go generate,它可以通过扫描Go源码中的特殊注释来识别要运行的常规命令。了解go generate不是go build的一部分很重要。它不包含依赖关系分析,必须在运行go build之前显式运行。它旨在由Go package的作者使用,而不是其客户端。
Go generate命令很容易使用。作为一个预热,下面展示如何使用它来生成yacc语法。假设你有一个名为gopher.y的YACC输入文件,它定义了一种新语言的语法。要生成实现语法的Go源码文件,通常会调用Yacc的标准Go版本:
go tool yacc -o gopher.go -p parser gopher.y
-o选项命令输出文件,-p选项指定包名。
要使go generate驱动这个过程,在同一目录中的任何一个普通(非生成).go文件中,将该注释添加的文件中的任何位置:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
这个文本就是上面的命令,前面加上一个由go generate识别的特殊注释。注释必须从行的开始处开始,并在在//和go:generate之间没有空格。在该标记之后,该行的其余部分指定go generate运行的命令。
现在运行它。切换到源目录,运行go generate,然后go build等等。
$ cd $GOPATH/myrepo/gopher
$ go generate
$ go build
$ go test
假设没有错误,go generate命令将调用yacc来创建gopher.go文件,此时目录包含完整的go源文件,因此我们可以正常构建,测试和正常工作。每次gopher.y被修改,只需要重新运行go generate来重新生成解析器。
有关go generate如何工作的更多详细信息,包括选项,环境变量等,可以参阅设计文档。
Go generate不会影响到make或其他一些编译机制,但它依附go tool,不需要额外安装,而且很适合Go生态系统。请记住,它是为package作者,而不是客户端,只是因为它调用的程序在目标机器上可能不可用。另外,如果包含的包是通过go get导入的,一旦文件被生成(并且被测试),他就必须被检入到源码库以供客户端使用。
现在有了go generate,可以用它来做新的事情。作为一个不同寻常的如何使用go generate的例子,有一个新的程序golang.org/x/tools仓库称为stringer。它可以自动为整数常量集合编写字符串方法。它不是发行版的一部分,但它很容易安装。
$ go get golang.org/x/tools/cmd/stringer
Stringer文档中有个示例,假设我们有一些包含一组定义不同类型的整形常数:
package painkiller
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
Acetaminophen = Paracetamol
)
为了调试,我们希望变量很够很好地打印自己,这意味着我们需要一个具有如下签名的方法。
func (p Pill) String() string
手写很容易,也许是这样的:
func (p Pill) String() string {
switch p {
case Placebo:
return "Placebo"
case Aspirin:
return "Aspirin"
case Ibuprofen:
return "Ibuprofen"
case Paracetamol: // == Acetaminophen
return "Paracetamol"
}
return fmt.Sprintf("Pill(%d)", p)
}
当然还有其他的方法来写这个功能。我们可以使用一些Pill索引的字符串,或者map,或者其他一些技术。无论我们做什么,如果我们改变Pills集合,我们需要维护它来保证它是正确的。(Paracetamol的两个Name比其他的要棘手)。另外,采取哪种方法的问题取决于类型和值:有符号还是无符号,密集还是稀疏,基于零还是不基于零的等等。
Stringer程序负责处理所有这些细节。虽然它可以独立运行,但是它是由go generate驱动的要使用它,可以向源代码中添加生成注释,类型定义附近。
//go:generate stringer -type=Pill
此规则制定go generate 应运行stringer工具以生成Pill类型的String方法。输出会自动写入pill_string.go(默认情况下,我们可以使用 -output标志来覆盖)
运行之后
$ go generate
$ cat pill_string.go
// generated by stringer -type Pill pill.go; DO NOT EDIT
package pill
import "fmt"
const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"
var _Pill_index = [...]uint8{0, 7, 14, 23, 34}
func (i Pill) String() string {
if i < 0 || i+1 >= Pill(len(_Pill_index)) {
return fmt.Sprintf("Pill(%d)", i)
}
return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
}
$
每次更改Pill或常量的定义时,我们需要做的就是运行go generate来更新String方法。当然,如果我们在同一个包中有多种类型设置了这种方式,单个命可以更新所有的String方法。
毫无疑问,生成的方法是丑的。但是,那是可以接受的,因为人类不需要做与它相关的工作,机器生成的代码通常是丑的。它努力做到高效率。所有的名称都在一个单一的字符串中,这样可以节省内存(即使有数十个也只有一个字符串头)。然后,一个数组,_Pill_index,通过一个简单高效的技术从值映射到名称。也请注意,_Pill_index是uint8的一个数组(不是一个切片),这是一个足够跨越值空间的最小整数。如果有更多的值,或者更少的,那么生产的_Pill_index类型可能会改变为uint16或者int8;无论什么都效果很好。
Stringer生成的Method使用的方法会根据常量集合的属性而变化。例如,如果常量是稀疏的,它可能会使用一个map。下面是一个基于常数集的常见例子,它代表了另外一种,
const _Power_name = "p0p1p2p3p4p5..."
var _Power_map = map[Power]string{
1: _Power_name[0:2],
2: _Power_name[2:4],
4: _Power_name[4:6],
8: _Power_name[6:8],
16: _Power_name[8:10],
32: _Power_name[10:12],
...,
}
func (i Power) String() string {
if str, ok := _Power_map[i]; ok {
return str
}
return fmt.Sprintf("Power(%d)", i)
}
简而言之,自动生成的method可以做到比人类做地更好。
在go tree中已经安装了go generate的许多其他用途。包括在unicode包中生产Unicode表,为encoding/gob创建有效的编解码方法,在time包中创建时区数据等等。
请创造性的使用go generate,鼓励动手实践。
即使没有,使用新的stringer工具为您的整形常量编写String方法。让机器做这样的工作。
By Rob Pike
go generate 生成代码的更多相关文章
- mybatis-generator:generate 生成代码配置踩坑详解
mybatis-generator:generate 生成代码配置踩坑不少,在此留下笔记以便后续填坑 一.mysql返回时间问题 错误信息: [ERROR] Failed to execute goa ...
- 问题:Custom tool error: Failed to generate code for the service reference 'AppVot;结果:添加Service Reference, 无法为服务生成代码错误的解决办法
添加Service Reference, 无法为服务生成代码错误的解决办法 我的解决方案是Silverlight+WCF的应用,Done Cretiria定义了需要在做完Service端的代码后首先运 ...
- mybatis 生成代码配置 mybatis-generator:generate 的使用详解
一.环境 mysql+eclipse 二.代码配置 pom.xml <?xml version="1.0" encoding="UTF-8"?> & ...
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...
- mybatis generator maven插件自动生成代码
如果你正为无聊Dao代码的编写感到苦恼,如果你正为怕一个单词拼错导致Dao操作失败而感到苦恼,那么就可以考虑一些Mybatis generator这个差价,它会帮我们自动生成代码,类似于Hiberna ...
- 使用mybatis-generator生成代码
文档地址: http://mbg.cndocs.tk/index.html 以下是一个简单的配置内容. 一.在maven配置文件中添加mybatis-generator插件 1 2 3 4 5 ...
- mybatis自动生成代码
使用maven集成mybatis-generator插件生成Mybatis的实体类,DAO接口和Map映射文件 本例中,使用的是mysql数据库 前提:表已经建好 mybatis框架的jar包,数据 ...
- 【转】- 使用T4模板批量生成代码
前言 之前在 “使用T4模板生成代码 - 初探” 文章简单的使用了T4模板的生成功能,但对于一个模板生成多个实例文件,如何实现这个方式呢?无意发现一个解决方案 “MultipleOutputHelpe ...
- maven插件mybatis-generator生成代码配置
鸣谢:http://my.oschina.net/u/1763011/blog/324106?fromerr=nJakGh4P (也可参看此博客进行配置) http://www.cnblogs.com ...
随机推荐
- Mysql第一周
前言:好久不见,我又来写博客拉.上个月只写了几篇django-rest-framework的,而且还是根据官网的英文写的.干货不多,内心还是有点羞耻的…… 简单说下我11月去干嘛了.11月初美图给我发 ...
- 认识 var、let、const
我们通过声明.初始化.值的可变性.作用域.变量提升以及在工作中如何使用等多个方面来详细了解var.let.const等关键字功能与特点. 声明 var,let:可以先声明,后赋值(初始化),默认值是 ...
- Linux服务器病毒清理实践
背景:客户服务器被挂载木马病毒用以挖矿(比特币). 本次清理通过Linux基本命令完成.其原理也比较简单,通过ps命令查看服务器异常进程,然后通过lsof命令定位进程访问的文件,找到异常文件删除之,最 ...
- VMware中克隆虚拟机出现eth0改变为eth1情况
解决如下: 查看复制虚拟机网卡信息如下: root@jcfx-2 ~]# ifconfig eth1 Link encap:Ethernet HWaddr 00:0C:29:CC:32:63 inet ...
- 两个实用linux小工具
使用 sshpass 工具来做名密码输入 使用 alias 别名来做成命令语句. Linux命令之非交互SSH密码验证-sshpass ssh登陆不能在命令行中指定密码.sshpass的出现,解决了这 ...
- MySQL slave_exec_mode 参数说明
背景: 今天无意当中看到参数slave_exec_mode,从手册里的说明看出该参数和MySQL复制相关,是可以动态修改的变量,默认是STRICT模式(严格模式),可选值有IDEMPOTENT模式(幂 ...
- tolua++实现lua层调用c++技术分析
tolua++技术分析 cocos2dx+lua 前言 一直都使用 cocos2dx + lua 进行游戏开发,用 Lua 开发可以专注于游戏逻辑的实现,另外一方面可以实现热更新:而且 lua 是一个 ...
- Linux用户管理的复习时间
所谓三天不练手生,你还记得关于Linux用户管理的所有知识吗?现在就来跟我一起复习一下吧! 1.常用配置文件 用户信息文件: /etc/password 密码文件: /etc/shadow 用户组文件 ...
- mongoDB之集合操作
mongoDB之集合操作 mongoDB中的集合相当于mysql中的表. mongoDB中集合的创建: 第一种方式:不限制集合大小 db.createCollection("集合名称&q ...
- javaweb学习总结(七)——HttpServletResponse对象(一)(转)
转载自 http://www.cnblogs.com/xdp-gacl/p/3789624.html Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对 ...