Go语言在代码规范之严格是有目共睹的,引用未使用的头文件会报“imported and not used”错误,定义未使用的变量会报“declared and not used”错误。因为golang编译器只有错误没有报警,所以代码中如果有此类问题,编译就会停止。

Golang不提供语法宽容的debug模式,也就是说,无论是什么时候,Go代码都必须保持干干净净的状态。

随手注释部分变量是编码阶段的一个常用手段,Golang的编译器每每跳出来,让你把变量的定义和相关头文件的引用也通通删掉。有时候临时注释一段代码会产生一堆未引用和未定义错误,而为了解决这些错误往往又会把代码被改得面目全非。对于debug阶段的程序员来讲,有一个管得太宽的编译器是一件痛苦的事情。

好在Golang是开源的,弄一个不太折腾的Golang编译器并不困难。

修改源码

首先下载golang代码:

$ git clone https://github.com/golang/go

拉到最新分支,现在最新的版本号是1.12.5:

$ cd go
$ git checkout -b debug-1.12.5 go1.12.5

未引用头文件和未定义变量错误相关的代码在下面4个文件中:

  • src/cmd/compile/internal/gc/main.go
  • src/cmd/compile/internal/gc/subr.go
  • src/cmd/compile/internal/gc/swt.go
  • src/cmd/compile/internal/gc/walk.go

用 if false {} 把相关代码注释掉。

具体内容可以看这个patch:

diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index 98ff2a3d27..2bb2e6fe37 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -1246,11 +1246,13 @@ func pkgnotused(lineno src.XPos, path string, name string) {
 	if i := strings.LastIndex(elem, "/"); i >= 0 {
 		elem = elem[i+1:]
 	}
+	if false {
 	if name == "" || elem == name {
 		yyerrorl(lineno, "imported and not used: %q", path)
 	} else {
 		yyerrorl(lineno, "imported and not used: %q as %s", path, name)
 	}
+	}
 }
 
 func mkpackage(pkgname string) {
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 2a976dc4f0..be662c3df8 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -291,10 +291,12 @@ func importdot(opkg *types.Pkg, pack *Node) {
 		n++
 	}
 
+	if false {
 	if n == 0 {
 		// can't possibly be used - there were no symbols
 		yyerrorl(pack.Pos, "imported and not used: %q", opkg.Path)
 	}
+	}
 }
 
 func nod(op Op, nleft, nright *Node) *Node {
diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go
index cc9a8f8b2c..6f27447bb0 100644
--- a/src/cmd/compile/internal/gc/swt.go
+++ b/src/cmd/compile/internal/gc/swt.go
@@ -70,12 +70,14 @@ func typecheckswitch(n *Node) {
 		if t != nil && !t.IsInterface() {
 			yyerrorl(n.Pos, "cannot type switch on non-interface value %L", n.Left.Right)
 		}
+		if false {
 		if v := n.Left.Left; v != nil && !v.isBlank() && n.List.Len() == 0 {
 			// We don't actually declare the type switch's guarded
 			// declaration itself. So if there are no cases, we
 			// won't notice that it went unused.
 			yyerrorl(v.Pos, "%v declared and not used", v.Sym)
 		}
+		}
 	} else {
 		// expression switch
 		top = ctxExpr
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index 509579d21f..92ad512d37 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -41,6 +41,7 @@ func walk(fn *Node) {
 		}
 	}
 
+	if false {
 	for _, ln := range fn.Func.Dcl {
 		if ln.Op != ONAME || (ln.Class() != PAUTO && ln.Class() != PAUTOHEAP) || ln.Sym.Name[0] == '&' || ln.Name.Used() {
 			continue
@@ -55,6 +56,7 @@ func walk(fn *Node) {
 			yyerrorl(ln.Pos, "%v declared and not used", ln.Sym)
 		}
 	}
+	}
 
 	lineno = lno
 	if nerrors != 0 {

生成debug版编译器

编译debug版Golang:

$ cd src
$ GOOS=linux GOARCH=amd64 ./bootstrap.bash

../../目录下会生成两个文件:

$ ls ../../
go/ go-linux-amd64-bootstrap/ go-linux-amd64-bootstrap.tbz

其中go-linux-amd64-bootstrap就是debug版的Golang。

在/usr/bin下面创建一个可执行文件debuggo,文件中的GOROOT指向go-linux-amd64-bootstrap目录:

#!/bin/bash
GOROOT=/PATH-TO-GOLANG/go-linux-amd64-bootstrap
${GOROOT}/bin/go $@

好了,大功告成,debug的时候执行:

$ debuggo build

编译器果然就老实了很多。

不过要发布的时候记得用官方的编译器做语法检查:

$ go build -a