这个话题确实是老调重弹,但确异常重要。
老实说,用go做正式项目之前,写过scala,但那个SBT太折磨人,偶然就上了go。两者语法的差别就不说了,但入坑之后才发现水深:没有模块部署及官方的版本管理工具,会带来很多麻烦。
反复折腾了近一年,基本上形成了一点固有的模式,做一般项目还算能够简单支持。没什么特别的技术点,更多只是一点心得。
一、目录结构
我们的代码以rpc为主,http为辅(调用rpc,也辅助测试),基本上一个项目就按照下面这个目录结构定制。
//project directory
--src //源代码目录,需纳入vcs
--package1
--package1
--main.go //package1编译生成的执行文件源代码
--othersubpackage
--package2
--package2
--main.go //这么摆放,是因为执行文件默认是[目录名]
--othersubpackage
--utils//公共的代码
--vendor //第三方包目录,glide生成,可不纳入vcs
glide.yaml //glide生成的文件,需纳入vcs
glide.lock //glide生成的文件,需纳入vcs
--cfg //配置文件存放目录,需纳入vcs
package1.yaml
--pkg
--bin
compile.sh //编译脚本
.bash_profile //工程环境
以上结构,适合于内部项目开发,按照项目进行统一采用glide进行管理。就版本管理工具有很多选择,考虑glide主要是:
- 简单,第一次用的时候半分钟搞定。只需要在src目录下,先后执行glide init、glide install;定期用一下glide up,自动升级第三方包。
- golang.org不再被墙之后,基本能一次性更新。
但要特别注意,glide在使用的时候,对tag标签的规则有一定的要求,需要遵循 Semantic Versions 的规则。之前,个别知名的库不是很规范,经常在up的时候反而是老版本,但现在这种问题很少。
安装glide也很简单,因为用的macos,所以brew install glide 就很容易搞定。
当然,github上面很多也用godep,如果有精力,可以投入时间去熟悉、使用。
编译脚本
compile.sh是一个代码编译脚本,偶尔进行一下维护就好。
#清理
go clean all
#需要维护的包,只需要列出含有执行文件的包名
MYSERVICE=("package1" "package2")
#生成本地运行文件
for SERVER in ${MYSERVICE[*]}
do
echo "正在编译服务$SERVER mac版"
go install $SERVER/...
done
#生成linux运行文件
for SERVER in ${MYSERVICE[*]}
do
echo "正在编译服务$SERVER linux版"
GOOS=linux GOARCH=amd64 go install $SERVER/...
done
环境变量设置
.bash_profile是工程的环境变量设置。最初是为了避免不同项目在go get的时候,将该项目的包放入其他项目中【后来用了glide不存在这个问题,但这个习惯继续沿用】。我们采用不同项目的根目录下增加了这个文件。每次进相关项目的时候,加载一下即可。加载很简单:
source .bash_profile //或者
. .bash_profile
暂时该文件的内容很少,最核心的就是gopath的设置,这个将直接影响到go get的包存放地址。
export GOPATH=/Users/xx/dev/code/project
采用这个方式,如果要想不同项目用不同的go版本,也就easy了。
程序配置文件
项目实施过程中,除了用环境变量,先后用了ini、json、yaml三种格式文件作为程序的启动参数配置文件。
json做配置很简单,直接读即可。可以封装一个方法:
func ReadJson(filename string, def interface{}) error {
data, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("ReadJson failed: %T %v", def, err)
}
if err = json.Unmarshal(data, def); err != nil {
return fmt.Errorf("ReadJson failed: %T %v %v", def, filename, err)
}
return nil
}
最大不足在于无法为配置写描述,这在使用的时候要配合第三方文档。
ini作为配置文件,其实很强悍,可以用第三方库config来调用,支持很全:
- [x] 可以有备注
- [x] 可以分章节
- [x] 可以在值中用变量
- [ ] idea对ini没有好的编写插件。
最终是因为用glide才开始了解yaml,发现作为配置文件,有很明显的优势:
- 写作方式类似于ini,相对于json能备注,不用写括号
- 支持数组,缩进表示内嵌struct
- ide有yaml编写插件,能够简单的颜色、缩进显示。
- 绝大部分go代码,都采用yaml,本来就有技术需要。
- 包的支持不错gopkg.in/yaml.v2,使用简单。配置文件不正确情况下,只影响对应属性的赋值。从而可以用初始值对象,健壮性提升。
这一点,可以补充一段代码来描述:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
type T struct {
F int `yaml:"a,omitempty"`
B []string
C int
D float64
}
func main() {
var t T
t.D = 9 //提供一个初始值,判定出错情况下是否改变
err := yaml.Unmarshal([]byte("a: 1\nb: 12 \nc: 3 \nd: a"), &t) //输入条件中,b和d的值是错误的
fmt.Println(err)
fmt.Println(t)
//output:
//yaml: unmarshal errors:
//line 2: cannot unmarshal !!int `12` into []string
//line 4: cannot unmarshal !!str `a` into float64
//{1 [] 3 9} <--可以看出,a、c的识别没有错,t.D依然为原始值。
}
二、常用工具
IDE
对于基本没有专属开发工具的语言而言,go的开发环境反而百花齐放。我自己先后用过LiteIDE、subtext line 3 、atom、eclipse、idea。这几个工具各有特长
- LiteIDE 功能单一,容易上手,适合新人。
- Sublime Text 、atom 资源占用少,前者的秒开太无敌。团队中,曾经搞c++、c服务端的玩过。自己用惯IDE,不是很适应同一个语言要装n个插件。
- eclipse,15年的时候很不错,每次修改代码都会自动build一次,很便于查找问题。但相对于前两者,对go语言没有特别亮点的额外支持。
- idea,这是神器,除了coding,对yaml、json的维护,写js、sql等都方便(eclpise也有)。人无我有的重构,至今还是所向披靡。
由于工程中除了go,js、sql、yaml都有涉及,实际项目中都选择的IDE。16年开始,因为插件越来越成熟,问题处理及时(晚上提交bug,早上醒来就已经搞定),现在只用idea。只是,由于go的接口实现不用显示声明,它的shift+F6有点不够强悍。
人生苦短。对于有php、python开发经历的,直接选择idea,没必要折腾其他工具。
代码分析
作为还算新的语言,加上没有类似于lightbend这种商用公司,go的配套工具比较少。
平时,内部开发可用go vet、golint这两种辅助工具进行代码分析。虽然效果远远比不上sonar,但可以将就用。
如果在github上共享,则可以用[goreport card][5]网站,通过内嵌常用工具,以网页+链接方式给出语法警告,可当做sonar简化版。