Golang コード自動生成
Golang コード自動生成
モチベーション:単調なコード追加の繰り返しはコード自動生成に置き換えたい
Golang のコードから Golang のコードを生成する
単なるCRUDのコード実装だけなら(ビジネスロジックは関係ないため)
- domain層にentity追加
以下は(単純な追加だけなので)自動生成に置き換えられるはず
- domain層に追加したentityに対するRepositoryインターフェース
- entityのfactory
- Repositoryインターフェースの実装
- 実装したコードのテスト
AST(抽象構文木)を利用するのが正確な解析&生成ができて良さそう。
コードからの情報取得
ソースコードをparse -> AST(抽象構文木)にした後で解析して取得
- (構文木として意味を持った状態で扱えるため) 欲しいノードが探しやすい。正確に取得可
func main() { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "./example/example.go", nil, 0) // 第4引数はMode if err != nil { fmt.Println(err) return } // 最初に見つかったstruct名を入れる var name string ast.Inspect(f, func(n ast.Node) bool { x, ok := n.(*ast.TypeSpec) // type xxxx の型宣言部分取得 if !ok { return true } if _, ok := x.Type.(*ast.StructType); ok { // 取れた type が struct なら その名前取得 if name == "" { name = x.Name.String() } } return true }) fmt.Println("struct name:", name) }
構文解析参考:
コード生成
AST(抽象構文木)を組み立てる
- 構文木として意味を持った状態で扱えるため、文法ミスがない
- ※テンプレートエンジン使用での実現方法は、テンプレートの構文間違えていても気付きにくい
ASTだと、以下の少量だけでも多量のコードを書く必要がある。 jennifer (code generator) を使用すればシンプルに書ける。
package main import "fmt" func main() { fmt.Println("Hello, 世界") }
のコードを生成するのに、
- ASTの場合:
package main import ( "go/ast" "go/format" "go/token" "os" "strconv" ) func main() { f := &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{ &ast.GenDecl{ Tok: token.IMPORT, // import 部 Specs: []ast.Spec{ &ast.ImportSpec{ Path: &ast.BasicLit{ Kind: token.STRING, Value: strconv.Quote("fmt"), }, }, }, }, &ast.FuncDecl{ Name: ast.NewIdent("main"), // main 関数部 Type: &ast.FuncType{}, Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.ExprStmt{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: ast.NewIdent("fmt"), Sel: ast.NewIdent("Println"), }, Args: []ast.Expr{ &ast.BasicLit{ Kind: token.STRING, Value: strconv.Quote("Hello, 世界"), }, }, }, }, }, }, }, }, } format.Node(os.Stdout, token.NewFileSet(), f) }
ref: Goの抽象構文木(AST)を手入力してHello, Worldを作る #golang
- jennifer の場合:
package main import ( "fmt" "github.com/dave/jennifer/jen" ) func main() { f := NewFile("main") f.Func().Id("main").Params().Block( Qual("fmt", "Println").Call(Lit("Hello, world")), ) fmt.Printf("%#v", f) }