Go编程模式 - 7-代码生成

简介: 良好的命名能体现出其价值。尤其是在错误码的处理上,无需再去查询错误码对应的错误内容,直接可以通过命名了解。

目录

Simple Script

为了让大家快速了解这块,我们从一个最简单的例子入手。

Template

首先创建一个模板Go文件,即容器模板:container.tmp.go

package PACKAGE_NAME
type GENERIC_NAMEContainer struct {
   
    s []GENERIC_TYPE
}
func NewGENERIC_NAMEContainer() *GENERIC_NAMEContainer {
   
    return &GENERIC_NAMEContainer{
   s: []GENERIC_TYPE{
   }}
}
func (c *GENERIC_NAMEContainer) Put(val GENERIC_TYPE) {
   
    c.s = append(c.s, val)
}
func (c *GENERIC_NAMEContainer) Get() GENERIC_TYPE {
   
    r := c.s[0]
    c.s = c.s[1:]
    return r
}

Shell

生成的shell脚本,gen.sh

#!/bin/bash

set -e

SRC_FILE=${1}
PACKAGE=${2}
TYPE=${3}
DES=${4}
#uppcase the first char
PREFIX="$(tr '[:lower:]' '[:upper:]' <<< ${TYPE:0:1})${TYPE:1}"

DES_FILE=$(echo ${TYPE}| tr '[:upper:]' '[:lower:]')_${DES}.go

sed 's/PACKAGE_NAME/'"${PACKAGE}"'/g' ${SRC_FILE} | \
    sed 's/GENERIC_TYPE/'"${TYPE}"'/g' | \
    sed 's/GENERIC_NAME/'"${PREFIX}"'/g' > ${DES_FILE}

四个参数分别为

  • 源文件名
  • 包名
  • 类型
  • 文件后缀名

Generate File

最后,增加一个创建代码的go文件。

//go:generate ./gen.sh ./template/container.tmp.go gen uint32 container
func generateUint32Example() {
   
    var u uint32 = 42
    c := NewUint32Container()
    c.Put(u)
    v := c.Get()
    fmt.Printf("generateExample: %d (%T)\n", v, v)
}

//go:generate ./gen.sh ./template/container.tmp.go gen string container
func generateStringExample() {
   
    var s string = "Hello"
    c := NewStringContainer()
    c.Put(s)
    v := c.Get()
    fmt.Printf("generateExample: %s (%T)\n", v, v)
}

Generation

我们运行一下 go generate,就能产生对应的文件。

  1. 运行go generate,工具会扫描所有的文件
  2. 如果发现注释有带 go:generate的,会自动运行后面的命令
  3. 通过命令生成的代码,会在源文件添加提示,告诉他人这是自动生成的代码,不要编辑

因此,我们不仅仅可以用shell脚本,也可以用各种二进制工具来生成代码。值得一提的是,像Kubernetes这种重量级的项目,大量地应用了这种特性。后面我也会和大家分享在开发web项目中的应用。

下面,我也来介绍几个个人认为比较有用的工具。

genny

源项目链接:https://github.com/cheekybits/genny

Go文件示例

package queue

import "github.com/cheekybits/genny/generic"

// NOTE: this is how easy it is to define a generic type
type Something generic.Type

// SomethingQueue is a queue of Somethings.
type SomethingQueue struct {
   
  items []Something
}

func NewSomethingQueue() *SomethingQueue {
   
  return &SomethingQueue{
   items: make([]Something, 0)}
}
func (q *SomethingQueue) Push(item Something) {
   
  q.items = append(q.items, item)
}
func (q *SomethingQueue) Pop() Something {
   
  item := q.items[0]
  q.items = q.items[1:]
  return item
}

脚本

cat source.go | genny gen "Something=string"

官方示例还是采用的是shell脚本,建议替换到 go:generate 中,这样的代码更统一

原理

可以简单地理解成一个类型替换的工具(PS:擅长用sed脚本的朋友也可直接通过shell脚本实现)

go-bindata

源网站链接:https://github.com/go-bindata/go-bindata

go-bindata的功能是将任意格式的源文件,转化为Go代码,使我们无需再去打开文件读取了。

这个工具多用在静态网页转化为Go代码(不符合前后端分离的实践),所以具体的使用方式我就不细讲了,大家有兴趣的可以自行阅读教程。

但它有两个优点值得我们关注:无需再进行文件读取操作、压缩。

stringer

stringer是官方提供一个字符串工具,我个人非常推荐大家使用

文档链接:https://pkg.go.dev/golang.org/x/tools/cmd/stringer

Go文件

package painkiller

type Pill int

const (
    Placebo Pill = iota
    Aspirin
    Ibuprofen
    Paracetamol
    Acetaminophen = Paracetamol
)

脚本

//go:generate stringer -type=Pill

于是,就会生成对应的方法func (Pill) String() string,也就是直接转化成了其命名。

价值

Go语言在调用 fmt 等相关包时,如果要将某个变量转化为字符串,默认会寻找它的String()方法。

这时,良好的命名 能体现出其价值。尤其是在错误码的处理上,无需再去查询错误码对应的错误内容,直接可以通过命名了解。

目录
相关文章
|
11月前
|
关系型数据库 MySQL Go
Go语言微服务框架 - 8.Gormer迭代-定制专属的ORM代码生成工具
我们对比一下GORM库提供的`gorm.Model`,它在新增、修改时,会自动修改对应的时间,这个可以帮我们减少很多重复性的代码编写。这里,我就针对现有的gormer工具做一个示例性的迭代。
86 0
|
16天前
|
JSON 编解码 中间件
go-zero代码生成器助你高效开发
go-zero代码生成器助你高效开发
|
1月前
|
关系型数据库 MySQL Go
Go - 代码生成工具
Go - 代码生成工具
25 3
|
11月前
|
设计模式 Kubernetes 监控
Go编程模式 - 8-装饰、管道和访问者模式
装饰、管道和访问者模式的使用频率不高,但在特定场景下会显得很酷
28 0
|
11月前
|
SQL 分布式计算 Go
Go编程模式 - 6-映射、归约与过滤
但是,我不建议大家在实际项目中直接使用这一块代码,毕竟其中大量的反射操作是比较耗时的,尤其是在延迟非常敏感的web服务器中。 如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。
50 0
|
11月前
|
Go
Go编程模式 - 5.函数式选项
编程的一大重点,就是要 `分离变化点和不变点`。这里,我们可以将必填项认为是不变点,而非必填则是变化点。
24 0
|
11月前
|
Go
Go编程模式 - 4.错误处理
如何Wrap Error,在多人协同开发、多模块开发过程中,很难统一。而一旦不统一,容易出现示例中的过度Unwrap的情况。
29 0
|
11月前
|
Go
Go编程模式 - 3.继承与嵌入
业务逻辑依赖控制逻辑,才能保证在复杂业务逻辑变化场景下,代码更健壮!
39 0
|
11月前
|
JSON 前端开发 Go
Go编程模式 - 2.基础编码下
尽量用`time.Time`和`time.Duration`,如果必须用string,尽量用`time.RFC3339`
25 0
|
11月前
|
编译器 Go C++
Go编程模式 - 1.基础编码上
Program to an interface, not an implementation
30 0