前言

  gopher原意地鼠,在golang 的世界里解释为地道的go程序员。在其他语言的世界里也有PHPer,Pythonic的说法,反而Java是个例外。虽然也有Javaer之类的说法,但似乎并不被认可。而地道或者说道地,说的是gopher写的代码无不透露出go的独特气息,比如项目结构、命名方式、代码格式、编码风格、构建方式等等。用gopher的话说,用go编写代码就像是在画一幅中国山水画,成品美不胜收,心旷神怡。

环境变量

  gopher第一条:把东西放对地方。

  go程序的运行,需要依赖于两个基础的环境变量,GOROOT与GOPATH。环境变量几乎在各类编程语言中都存在,比如java的JAVA_HOME,其实也就是编译器及相关工具或标准库所在目录。但go除了GOROOT之外,还增加了GOPATH,它指的是go程序依赖的第三方库或自有库所在目录,以指示编译器从这些地方找到依赖。GOPATH支持多个目录,通常一个目录就是一个项目,并且GOPATH目录按约定由src、pkg、bin三个目录组成。gopher们的做法是定义Global GOPATH、Project GOPATH,而更大的项目还会定义Module GOPATH。当使用go get下载依赖时,会选择GOPATH环境变量中的第一个目录存放依赖包。

变量 含义 说明
GOROOT go运行环境根目录 通常指go sdk安装目录,包含编译器、官方工具及标准库
GOPATH 工作环境目录列表 通常指第三方库、自有库及项目目录
GOPATH
  1. src: 源代码目录
  2. pkg: 编译时中间文件所在目录
  3. bin: 编译后生成可执行文件所在目录
├── src
| └── eventer.com
| └── test
| └── main.go
├── pkg
└── bin
| └── test.bin
Global GOPATH

  全局GOPATH,通常将第三方库的代码保存至此处,便于所有项目引用,而不用重复下载。因此在GOPATH中,建议将global gopath放在最前面。

Project GOPATH

  如果项目比较简单的话,可以采用global GOPATH与project GOPATH的组合,即GOPATH设置为global GOPATH与project OGPATH两个目录。比如引用了需要对源码稍作修改的开源项目时,开源项目可以跟当前项目一起放在src目录下。

Module GOPATH

  在比较复杂的项目中,如果划分的模块比较多,则可以采用global, project, module三种GOPATH组合的方式,即即GOPATH设置为global GOPATH、project OGPATH、module GOPATH三个目录。

  最后请注意,global GOPATH、project GOPATH、module GOPATH不是go语言中的概念,go要求的只有GOPATH。而因为GOPATH是一个目录列表,所以我只是在实践中将GOPATH细分为上述三者,同时便于讲述GOPATH的概念。

项目结构

  gopher第二条:按东西放在约定的地方。

  不同复杂度的项目,大致可以有两种类型的项目结构,第一种是依赖较少、较简单的项目,如下图所示,其中mydriver项目是假设要引用的开源项目,但要在当前项目的使用中稍修改,因此与当前项目的源码放在一起。

├── src
| ├── github.com
| └── eventer
| └── mydriver
| └── driver.go
| └── eventer.com
| └── test
| └── main.go
├── pkg
└── bin

  第二种是依赖较多、模块划分较细、较复杂的项目,如下图所示:

├── src
| └── eventer.com
| └── test
| └── main.go
├── pkg
├── bin
├── module1
| ├── src
| └── eventer.com
| └── lib
| └── lib1.go
| ├── pkg
| └── bin
├── module2
| ├── src
| └── eventer.com
| └── lib
| └── lib2.go
| ├── pkg
| └── bin
└── module3
| ├── src
| └── eventer.com
| └── lib
| └── lib3.go
| ├── pkg
| └── bin

  其中的module1,module2,module3是基础模块,被主项目依赖。这个时候除了将主项目路径放在GOPATH外,还应当将基础模块路径放在GOPATH中。这样的项目结构不仅让项目清晰,还可以做不同的分工。

命名规范

  gopher第三条:把名字起得go一点。

  go语言的命名与其他语言最大的不同在于首字母的大小写。大写代表公开(导出,可以在其他包内访问);小写代表私有(不导出,只能在包内访问)。除此之外,与其他语言并无二致,比如不能以数字开头。而由于关键字、保留字的减少,因而减少了一些命名上的忌讳。更为突出的是,go语言有一些建议性的命名规范,这也是gophers的圣经,理应严格遵守。

约定 范围 说明 示例
驼峰命名法 全局 统一使用驼峰命名法 var isLive = false
大小写一致 缩写短语,惯用词 如HTML,CSS, HTTP等 htmlEscape,HTMLEscape
简短命名法 局部变量 方法内、循环等使用的局部变量可以使用简短命名 比如for循环中的i,buf代表buffer等
参数命名法 函数参数、返回值、方法接收者 如果参数类型能说明含义,则参数名可以简短,否则应该采用有文档说明能力的命名 比如d Duration,t Time
通用命名法 作用域越大或者使用的地方离声明的地方太远,则应采用清晰有意义的命名 -
导出命名法 导出变量、函数、结构等 包名与导出名意义不要重复,同时包的命名要与导出的内容相关,不要使用宽泛的名字,如common,util bytes.Buffer比bytes.ByteBuffer要好
文件命名 go文件,单元测试文件 go文件名尽量以一个单词来命名,多个单词使用下线划分隔,单元测试文件以对应go文件名加_test结尾 proto_test
包命名 包的一级名称应是顶级域名,二级名称则应是项目名称,项目名称单词间以-分隔 github.com/mysql

代码格式

  gopher第四条:按统一的格式来。

  在多人协作团队中,统一的代码格式化模板是第一要义。在Java语言中,检验新人经验的一大法宝就是他有没有主动索要代码模板。而在go语言中,则没有这个必要了。因为go已经有默认的代码格式化工具了,而且代码格式化在go语言中是强制规范。所以这使得所有go程序员写出来的代码格式都是一样的。

  go默认的代码格式化工具是gofmt。另外还有一个增强工具goimport,在gofmt的基础上增加了自动删除和引入依赖包。而行长则以不超过80个字符为佳,超过请主动以多行展示。

编码风格

  gopher第五条:请学会约定

约定 说明
import 按标准库、内部包、第三方包的顺序导入包 只引一个包时使用单行模式,否则使用多行模式
变量声明 如果连续声明多个变量,应放在一起 参见例子
错误处理 不要忽略每一个error,即使只是打一行日志 go的error处理方式与C同出一辙,通过返回值来标明错误或异常,引来的争议也很多,甚至官方已经开始酝酿在go2解决这个问题
长语句打印 使用格式化方式打印 -
注释规范 变量、方法、结构等的注释直接加上声明前,并且不要加空行。废弃方法加Deprecated:即可 其中的第一行注释会被godoc识别为简短介绍,第二行开始则被认为是注释详情。注释对godoc的生成至关重要,因此关于注释会有一些技巧,我将在后面用专门的章节探讨

多变量声明

var (
name string
age int
)

注释规范

// Add 两数相加
// 两个整数相加,并返回和。
func Add(n1, n2 int)int{
return n1 + n2
}

依赖管理

  gopher第六条:使用依赖管理工具管理自有依赖与第三方依赖

  一个语言的生态是否完善甚至是否强大,除了github上面的开源项目数量之外,还有一大特征就是是否有优秀的依赖管理工具。依赖管理工具在业界已经是无处不在,yum、maven、gradle、pip、npm、cargo这些工具的大名如雷贯耳。那么go有什么呢?

  早期go的依赖是混乱的,因为没有一个工具能得到普遍认可,而官方又迟迟不出来解决问题。历数存在的工具包括godep、glide、govender等等。甚至早期还需要使用GOPATH来管理依赖,即项目的所有依赖都通过go get下载到指定的GOPATH中去。当然这种方案还可以撑大多数时间,但随着时间的流逝,随着开发人员的变动,这种管理依赖的弊端就慢慢显现出来。其实这些老路早期的java也走过,曾几何时,每个java项目里面都会有一个叫lib或libs的目录,这里放的就是当前项目依赖的包。当GO采用GOPATH来管理依赖时,开发人员只能被倒逼着用java的方式在源码库中自行管理依赖。这样相当于给依赖包做了隔离,同时又具备了版本管理(因为放在源码库)。

  后来在go1.5的时候,官方引入了vender的概念,其实这也没改变多少,只是官方让大家存放依赖包的目录名称不要乱起了,统一叫vender吧。这个方案我觉得比依赖GOPATH还糟糕,因为vendor目录脱离了版本管理,导致更换依赖包版本很困难,在当前项目对依赖包的版本更新可能会影响其他项目的使用(如果新版本的依赖包有较大变动的话),同时如何将依赖包放到vendor下呢?等等。当然官方做出的这些变动可能是想像maven那样,推动社区来完成这件事,因而直接推动了上文提到的基于vendor的依赖管理工具的诞生。直至后来官方默认的社区做出来dep,这下安静了,尽管刚开始时也不怎么好用,但有总比没有好。

  go1.11在vgo的基础上,官方推出了go module。在发布前,官方与社区的大神们还为此开吵,认为官方太不厚道且独断专行。完全忽视dep社区的存在,无视dep在go语言中的地位与贡献。喜欢八卦的朋友们,可搜索《关于Go Module的争吵》一览大神是怎么吵架的,也可从中学习他们的思想。

  作为dep与module的亲身实践者,还是乖乖地用dep吧。除非你有足够的热情去折腾,比如弄个高速的科学上网工具,或者搭建Go module proxy。开源的go module proxy比如athens,goproxy。《Hello,Go module proxy》一文详细介绍了go module的幸福与烦恼,反正我是没有感到幸福。此文很全面地介绍go module后遗症,goproxy可以解决科学上网的问题,然后也有了athens这样的工具出现,go的依赖管理是朝着越来越好的方向发展。dep与module的争议在于respect and free,至少目前看来,两者是可以共存的,特别是在国内。

  相对于java的依赖管理工具maven或gradle来说,gradle是maven的升级版,同时带来了DSL与元编程的特性,这无疑使得gradle异常地强大。但gradle.io在国内的可达情况也不尽如人意,好就好在其与maven仓库标准的兼容,使得从maven转到gradle几乎没有额外的成本及阻力。

  扯了这么多,依赖管理对于一门语言是必不可少的。c有cmake,java有maven、gradle,rust有cargo,那么go的dep或者module就用起来吧,看完大神吵架之后,喜欢哪个就选哪个。是不可能产生一个能满足所有人要求的依赖管理工具的,就连号称最牛逼的cargo也不例外。在一般的项目中,能用到的依赖管理功能也就那常用的几个而已,对大多数项目来说,适用好用就行。

构建方式

  gopher第七条:按需构建

  构建的目标是让代码成为可运行程序。构建的过程应该是低成本并且让人愉悦的,显然C在这一方面让人抓狂,而go确实做得不错。并且能在任何平台下编译出另外一个平台的可执行程序。不管你的go程序是CLI、GUI、WEB或者其他形式的网络通讯程序,在go的世界里都只需要一个命令构建成可执行程序(依赖也一并被打包),即可在目标系统上运行。在这一点上,java是望尘莫及了。

下面是用来构建go程序常用的参数,其他参数可通过go help environment命令查看。

参数 说明
CGO_ENABLED 0 or 1 是否支持cgo命令,如果go代码中有c代码,需要设置为1
GOOS darwin, freebsd, linux, windows 可执行程序运行的目标操作系统
GOARCH 386, amd64, arm 可执行程序运行的目标操作系统架构
# Linux下编译Mac 64位可执行程序
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go # Linux下编译windows 64位可执行程序
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go # 如果想减少二进制程序的大小,可以加上-ldflags "-s -w",但同时会丢掉调试信息,即不能用gdb调试了。
# 如果想更进一步减少程序大小,可以使用加壳工具,比如upx

请关注公众号

不一样的go语言-gopher的更多相关文章

  1. 不一样的go语言-构建系统与构件系统

    前言   代码的最后一步是构建成计算机可识别的二进制数据,然后才得以在计算机上运行.如果你曾经写过有点规模(至少数十个以上独立的源文件,且需要依赖第三方包)C语言项目,必定对C语言项目的构建过程印象深 ...

  2. [转]C语言SOCKET编程指南

    1.介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等 ...

  3. C语言SOCKET编程指南

    1.介绍 Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措? ...

  4. Go语言学习资料汇总

    网站: Go语言官网(访问)(中文镜像) Go语言中文网(访问) Go编译器(访问) Go语言中国社区(访问) golanghome(访问) GoLang中国(访问) Gopher Academic( ...

  5. XXE篇-本着就了解安全本质的想法,尽可能的用通俗易懂的语言去解释安全漏洞问题

    0x01 Brief Description XXE(XML External Entity) XML外部实体攻击也是常见的web漏洞之一,在学习这个漏洞之前有必要了解一下xml,可以参考w3c的基本 ...

  6. C语言 HTTP上传文件-利用libcurl库上传文件

    原文  http://justwinit.cn/post/7626/ 通常情况下,一般很少使用C语言来直接上传文件,但是遇到使用C语言编程实现文件上传时,该怎么做呢? 借助开源的libcurl库,我们 ...

  7. Go语言Web框架gwk介绍2

    Go语言Web框架gwk介绍 (二) HttpResult 凡是实现了HttpResult接口的对象,都可以作为gwk返回Web客户端的内容.HttpResult接口定义非常简单,只有一个方法: ty ...

  8. Go从入门到精通(一)go语言初始

    一.第一个go程序 package main import ( "fmt" ) func main(){ fmt.Println("hello world") ...

  9. Go语言基础知识

    Go语言的一般结构:basic_structure.go Go程序是通过package来组织的,只能同过package名称为main的包可以包含main函数(一个可执行程序只能有一个main包) 通过 ...

随机推荐

  1. mysql架构解读~mysql的多源复制

    一 场景需求 多源复制版本 5.7,目标主机5.6.21 4个DB机器的某些数据库需要数据汇总进行连表查询 二 进行搭建  1 导出相应的目的库     mysqldump -uuser -ppass ...

  2. 深入剖析ThreadLocal实现原理以及内存泄漏问题

    关于ThreadLocalMap<ThreadLocal, Object>弱引用问题: 当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMa ...

  3. JSON和JSONP的区别,以及使用方法

    (一)场景 在拉京东城市选择的基础数据时候,遇到被服务器拒绝的情况,也就是ajax跨域问题 (二)json和jsonp 说的直白一点,在我们做ajax异步的一些功能的时候,一定会或多或少的遇到两个问题 ...

  4. JS获取今天和上个月的今天

    function getLastMonth(){ var now=new Date(); var year = now.getFullYear();//getYear()+1900=getFullYe ...

  5. session和cookies

    Cookie 与session的产生过程                                 我们都知道HTTP协议本身是无状态的,客户只需要简单的向服务器来发送请求下载某些文件,客户端向 ...

  6. yum安装包另存

    yum install --downloadonly --downloaddir=/tmp <package-name> 1.yum已安装的列表 yum list installed

  7. jQuery中【width(),innerWidth(),outerWidth()】

    这个问题,已经别扭我多年了,今天终于彻底解决了,拿出来庆贺一下.jquery作为开源项目,无论从思路上,还是从严谨性上,让人崇敬. 随着时间的流逝,jquery的一些功能被逐渐挖掘出来.通过jQuer ...

  8. MySQL— 基础

    目录 一.MySQL概述 二.下载安装 三.数据库操作 四.数据表操作 五.表内容操作 一.MySQL概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracl ...

  9. python 历险记(三)— python 的常用文件操作

    目录 前言 文件 什么是文件? 如何在 python 中打开文件? python 文件对象有哪些属性? 如何读文件? read() readline() 如何写文件? 如何操作文件和目录? 强大的 o ...

  10. 【转】Python之函数与变量

    [转]Python之函数与变量 本节内容 函数介绍及其作用 函数的定义与调用 函数的参数说明 变量与作用域 值传递和引用传递 一.函数的介绍及其作用 编程语言中的函数与数学中的函数是有区别的:数学中的 ...