Go编码规范指南

序言

看过很多方面的编码规范,可能每一家公司都有不同的规范,这份编码规范是写给我自己的,同时希望我们公司内部同事也能遵循这个规范来写Go代码。

如果你的代码没有办法找到下面的规范,那么就遵循标准库的规范,多阅读标准库的源码,标准库的代码可以说是我们写代码参考的标杆。

格式化规范

go默认已经有了gofmt工具,但是我们强烈建议使用goimport工具,这个在gofmt的基础上增加了自动删除和引入包.

1
$ go get golang.org/x/tools/cmd/goimports

不同的编辑器有不同的配置, sublime的配置教程:http://michaelwhatcott.com/gosublime-goimports/

LiteIDE默认已经支持了goimports,如果你的不支持请点击属性配置->golangfmt->勾选goimports

保存之前自动fmt你的代码。

行长约定

一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅。

go vet

vet工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前return的逻辑,struct的tag是否符合标准等。

1
$ go get golang.org/x/tools/cmd/vet

使用如下:

1
$ go vet .

package名字

保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。

import 规范

import在多行的情况下,goimports会自动帮你格式化,但是我们这里还是规范一下import的一些规范,如果你在一个文件里面引入了一个package,还是建议采用如下格式:

1
2
3
import (
"fmt"
)

如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:

1
2
3
4
5
6
7
8
9
10
11
import (
"encoding/json"
"strings"

"myproject/models"
"myproject/controller"
"myproject/utils"

"github.com/astaxie/beego"
"github.com/go-sql-driver/mysql"
)

有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。

在项目中不要使用相对路径引入包:

1
2
3
4
5
// 这是不好的导入
import “../net”

// 这是正确的做法
import “github.com/repo/proj/src/net”

变量申明

变量名采用驼峰标准,不要使用_来命名变量名,多个变量申明放在一起

1
2
3
4
var (
Found bool
count int
)

在函数外部申明必须使用var,不要采用:=,容易踩到变量的作用域的问题。

自定义类型的string循环问题

如果自定义的类型定义了String方法,那么在打印的时候会产生隐藏的一些bug

1
2
3
4
5
6
7
8
type MyInt int
func (m MyInt) String() string {
return fmt.Sprint(m) //BUG:死循环
}

func(m MyInt) String() string {
return fmt.Sprint(int(m)) //这是安全的,因为我们内部进行了类型转换
}

避免返回命名的参数

如果你的函数很短小,少于10行代码,那么可以使用,不然请直接使用类型,因为如果使用命名变量很
容易引起隐藏的bug

1
2
3
func Foo(a int, b int) (string, ok){

}

当然如果是有多个相同类型的参数返回,那么命名参数可能更清晰:

1
func (f *Foo) Location() (float64, float64, error)

下面的代码就更清晰了:

1
2
3
// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)

错误处理

错误处理的原则就是不能丢弃任何有返回err的调用,不要采用_丢弃,必须全部处理。接收到错误,要么返回err,要么实在不行就panic,或者使用log记录下来

error 信息

error的信息不要采用大写字母,尽量保持你的错误简短,但是要足够表达你的错误的意思。

长句子打印或者调用,使用参数进行格式化分行

我们在调用fmt.Sprint或者log.Sprint之类的函数时,有时候会遇到很长的句子,我们需要在参数调用处进行多行分割:

下面是错误的方式:

1
2
3
log.Printf(“A long format string: %s %d %d %s”, myStringParameter, len(a),
expected.Size, defrobnicate(“Anotherlongstringparameter”,
expected.Growth.Nanoseconds() /1e6))

应该是如下的方式:

1
2
3
4
5
6
7
8
9
10
log.Printf( 
“A long format string: %s %d %d %s”,
myStringParameter,
len(a),
expected.Size,
defrobnicate(
“Anotherlongstringparameter”,
expected.Growth.Nanoseconds()/1e6,
),

注意闭包的调用

在循环中调用函数或者goroutine方法,一定要采用显示的变量调用,不要再闭包函数里面调用循环的参数

1
2
3
4
fori:=0;i<limit;i++{
go func(){ DoSomething(i) }() //错误的做法
go func(i int){ DoSomething(i) }(i)//正确的做法
}

http://golang.org/doc/articles/race_detector.html#Race_on_loop_counter

在逻辑处理中禁用panic

在main包中只有当实在不可运行的情况采用panic,例如文件无法打开,数据库无法连接导致程序无法
正常运行,但是对于其他的package对外的接口不能有panic,只能在包内采用。

强烈建议在main包中使用log.Fatal来记录错误,这样就可以由log来结束程序。

注释规范

注释可以帮我们很好的完成文档的工作,写得好的注释可以方便我们以后的维护。详细的如何写注释可以
参考:http://golang.org/doc/effective_go.html#commentary

bug注释

针对代码中出现的bug,可以采用如下教程使用特殊的注释,在godocs可以做到注释高亮:

1
2
// BUG(astaxie):This divides by zero. 
var i float = 1/0

http://blog.golang.org/2011/03/godoc­documenting­go­code.html

struct规范

struct申明和初始化格式采用多行:

定义如下:

1
2
3
4
type User struct{
Username string
Email string
}

初始化如下:

1
2
3
4
u := User{
Username: "astaxie",
Email: "astaxie@gmail.com",
}

recieved是值类型还是指针类型

到底是采用值类型还是指针类型主要参考如下原则:

1
2
func(w Win) Tally(playerPlayer)int    //w不会有任何改变 
func(w *Win) Tally(playerPlayer)int //w会改变数据

更多的请参考:https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Receiver_Type

带mutex的struct必须是指针receivers

如果你定义的struct中带有mutex,那么你的receivers必须是指针

参考资料

  1. https://code.google.com/p/go-wiki/wiki/CodeReviewComments
  2. http://golang.org/doc/effective_go.html
  3. http://golanghome.com/post/550
  4. https://talks.golang.org/2014/names.slide#1

Google资深工程师深度讲解Go语言

Google资深工程师深度讲解Go语言-笔记

课程结构

语法部分

综合部分

实战项目部分

课程目标

会一门编程语言

最好有项目经验

基本语法

变量

选择,循环

指针,数组,容器

面向接口

结构体

duck typing的概念

组合的思想

函数式编程

闭包的概念

多样的例题

工程化

资源管理,错误处理

测试和文档

性能调优

并发编程

goroutine和channel

理解调度器

多样的例题

Go语言的安装与开发环境

下载Go go1.9.2

IED: GoLand, IntelliJ IDEA + Go插件

变量

GOPATH

GOROOT

变量定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用var关键字
/*
var a, b, c bool
var s1, s2 string = "hello","world"
可放在函数内,或直接放在包内
使用var()集中定义变量
*/

//让编译器自动决定类型
/*
var a, b, i, s1, s2 = true, false, 3, "hello", "world"
*/

//使用:=定义变量
/*
a, b, i, s1, s2 := true, false, 3, "hello", "world"
只能在函数内使用
*/

内建变量类型

1
2
3
4
bool, string
(u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
byte, rune
float32, float64, complex64, complex128

复数

3 + 4i

3实部,4i虚部

欧拉公式(euler)

强制类型转换

类型转换是强制的

1
2
3
4
5
var a, b int = 3, 4
var c int

c = int(math.Sqrt(float64(a * a + b * b)))
//浮点数精度问题?

常量的定义

1
2
3
4
const filename = "abc.txt"
//const 数值可作为各种类型使用
const a, b = 3, 4
var c int = int(math.Sqrt(a * a + b * b))

使用常量定义枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func enums() {
//普通枚举类型
const(
cpp = iota
_
python
golang
javascript
)

//自增值枚举类型
// b, kb, mb, gb, tb, pb
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)

fmt.Println(cpp, javascript, python, golang)
fmt.Println(b, kb, mb, gb, tb, pb)
}

变量定义要点回顾

变量类型写在变量名之后

编译器可推测变量类型

没有char,只有rune

原生支持复数类型

条件语句

if

1
2
3
4
5
6
7
8
9
/*
if的条件里可以赋值
if的条件里赋值的变量作用域就在这个if语句里
*/
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(string(contents))
} else {
fmt.Println("cannot print file contents:", err)
}

switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//switch会自动break,除非使用fallthrough
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("Wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
default:
g = "A"
}
return g
}

//switch后可以没有表达式
func grade(score int) string {
switch {
case score < 60:
return "F"
case score < 80:
return "C"
case score < 90:
return "B"
default:
return "A"
}
}

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//for的条件里不需要括号
//for的条件里可以省略初始条件,结束条件,递增表达式

//省略初始条件,相当于while
func convertToBin(v int) string {
result := ""
for ; v > 0; v /= 2 {
result = strconv.Itoa(v%2) + result
}
return result
}

//省略初始条件,相当于while
for scanner.Scan() {
fmt.Println(scanner.Text())
}

//无限循环
for {
fmt.Println("abc")
}

基本语法要点回顾

for, if 后面的条件没有括号

if 条件里也可定义变量

没有while

switch不需要break,也可以直接switch多个条件

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//函数可返回多个值
func div(a,b int) (int, int) {
return a / b, a % b
}

//函数返回多个值时可以起名字
//仅用于非常简单的函数
//对于调用者而言没有区别
func div(a, b int) (q, r int) {
q = a / b
r = a % b
return
}

//函数作为参数
func apply(op func(int, int) int, a, b int) int {
fmt.Printf("Calling %s with %d, %d\n",
runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(),
a, b)
return op(a, b)
}

//可变参数列表
func sumArgs(values ...int) int {
sum := 0
for i := range values {
sum += values[i]
}
return sum
}

函数语法要点回顾

返回值类型写在最后面

可返回多个值

函数作为参数

没有默认参数,可选参数

指针

1
//指针并不能运算

IntelliJ IDEA 配置

IntelliJ IDEA 2018.1.2

IntelliJ IDEA 2020.1.2

设置

Apperance & BBehavior->Apperance**

Theme Darcula

Keymap

Mac OS X 10.5+

Editor

Font: Menlo

Size: 20

快捷键

  • 切换 Project 面板:cmd + 1
  • Project Structure:cmd + ;

File and Code Templates

Class

1
2
3
4
5
6
7
8
9
10
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
/**
* @author: ovwane
*
* @create: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE}:${SECOND}
**/

public class ${NAME} {
}

插件

Maven Helper

一旦安装了Maven Helper插件,只要打开pom文件,就可以打开该pom文件的Dependency Analyzer视图(在文件打开之后,文件下面会多出这样一个tab)。

Gradle Dependencies Helper

FindBugs-IDEA

FindBugs很多人都并不陌生,Eclipse中有插件可以帮助查找代码中隐藏的bug,IDEA中也有这款插件。

使用方法很简单,就是可以对多种级别的内容进行finbugs

.ignore

git提交时过滤掉不需要提交的文件,很方便,有些本地文件是不需要提交到Git上的。

CamelCase

将不是驼峰格式的名称,快速转成驼峰格式,安装好后,选中要修改的名称,按快捷键shift+alt+u。

Lombok plugin

开发神器,可以简化你的实体类,让你i不再写get/set方法,还能快速的实现builder模式,以及链式调用方法,总之就是为了简化实体类而生的插件。

codehelper.generator

可以让你在创建一个对象并赋值的时候,快速的生成代码,不需要一个一个属性的向里面set,根据new关键字,自动生成掉用set方法的代码,还可以一键填入默认值。

GsonFormat

一键根据json文本生成java类 非常方便

String Manipulation

字符串日常开发中经常用到的,但是不同的字符串类型在不同的地方可能有一些不同的规则,比如类名要用驼峰形式、常量需要全部大写等,有时候还需要进行编码解码等。这里推荐一款强大的字符串转换工具——String Manipulation

Key promoter X

Key Promoter X 是一个提示插件,当你在IDEA里面使用鼠标的时候,如果这个鼠标操作是能够用快捷键替代的,那么Key Promoter X会弹出一个提示框,告知你这个鼠标操作可以用什么快捷键替代。

GenerateAllSetter

一键调用一个对象的所有set方法并且赋予默认值 在对象字段多的时候非常方便,在做项目时,每层都有各自的实体对象需要相互转换,但是考虑BeanUtil.copyProperties()等这些工具的弊端,有些地方就需要手动的赋值时,有这个插件就会很方便,创建完对象后在变量名上面按Alt+Enter就会出来 generate all setter选项。

AceJump

前面介绍了一款可以通过使用快捷键来代替鼠标操作的插件,这里再介绍一款可以彻底摆脱鼠标的插件,即AceJump

AceJump允许您快速将光标导航到编辑器中可见的任何位置,只需点击“ctrl +;”,然后输入一个你想要跳转到的字符,之后键入匹配的字符就跳转到你想要挑战的地方了。

CodeGlance

在编辑区的右侧显示的代码地图。

Background image Plus

这是一款可以设置idea背景图片的插件,不但可以设置固体的图片,还可以设置一段时间后随机变化背景图片,以及设置图片的透明度等等。

active-power-mode

这是一款让你在编码的时候,整个屏幕都为之颤抖的插件。

Nyan progress bar

这是一个将你idea中的所有的进度条都变成萌新动画的小插件。

Rainbow Brackets

彩虹颜色的括号 看着很舒服 敲代码效率变高。

Alibaba Java Coding Guidelines

Alibaba Java Coding Guidelines pmd implements

Git

  • GitLab Projects (Share on GitLab)

ChineseTypography

此插件可以快速对文章内容的中文、英文、符号之间增加空格,增加文章可读性。

Usage

  • 1.Shortcut Key: Ctrl + Shift + Y(Windows/Linux)、Command + Shift + Y(Mac)
  • 2.Toolbar: Code – ChineseTypography

好的开发工具可以提高开发效率,所以的能让自己提高效率,把时间节省出来去学习,去提升自己。这些插件只是日常开发当中用到的一些,等到以后再发现了新的好玩的有意思,和提高工作效率的插件,继续分享出来。

参考

Java程序员你们想要代码写的溜,这几个IDEA插件了解下

如何在IntelliJ IDEA中使用.ignore插件忽略不必要提交的文件

idea生成类注释和方法注释的正确方法

IntelliJ IDEA 简体中文专题教程

IntelliJ IDEA 18 周岁,吐血推进珍藏已久的必装插件

老男孩Golang 学习笔记

老男孩Golang-学习笔记第一期

第1章-简介和配置

2018-02-19

一、介绍和安装

1.介绍

1.1 什么是Golang

Go也被称为Golang,它是由谷歌创建的一种开源、编译和静态类型的编程语言。

Golang的主要目标是使高可用性和可伸缩的web应用程序的开发变得简单易行。

1.2 为什么选择Golang

当有很多其他语言(如python、ruby、node.js)时,为什么选择Golang作为服务端编程语言呢?

  • 并发是语言的一个固有部分。因此编写多线程程序是小菜一碟。这是通过Goroutines和channel实现的,我们将在后面的教程中讨论。
  • Golang是一种编译语言。源代码编译成原生二进制。这在解释语言(如nodejs中使用的JavaScript)中丢失了。
  • 语言规范非常简单。整个规范适合于一个页面,您甚至可以使用它来编写自己的编译器:)
  • go编译器支持静态链接。所有的go代码都可以静态地链接到一个大的fat二进制文件中,并且可以轻松地部署到云服务器中,而不用担心依赖关系。

2.安装

2.1下载

在Mac、Windows和Linux三个平台上都支持Golang。您可以从https://golang.org/dl/下载相应平台的二进制文件。

img

Mac OS 从https://golang.org/dl/下载osx安装程序。双击启动安装。按照提示,这应该在/usr/local/go中安装了Golang,并且还会将文件夹/usr/local/go/bin添加到您的PATH环境变量中。

Windows 从https://golang.org/dl/下载MSI安装程序。双击启动安装并遵循提示。这将在位置C中安装Golang:\Go,并且还将添加目录C:\Go\bin到您的path环境变量。

Linux 从https://golang.org/dl/下载 tar文件,并将其解压到/usr/local

/usr/local/go/bin添加到PATH环境变量中。这应该安装在linux中。

2.2 windows下安装并配置环境变量

安装步骤就不在多说什么了,一路到底

A、配置环境变量

注意:如果是 msi 安装文件,Go 语言的环境变量会自动设置好。

我的电脑——右键“属性”——“高级系统设置”——“环境变量”——“系统变量”

​ 假设GO安装于C盘根目录

新建:

  • GOROOT:Go 安装路径(例:C:\Go
  • GOBIN:Go安装路径的bin路径(例:C:\Go\bin
  • GOPATH:Go工程的路径(例:E:\go)。如果有多个,就以分号分隔添加

修改:

  • Path: 添加上Go安装路径,以分号结尾

工作目录就是我们用来存放开发的源代码的地方,对应的也是Go里的GOPATH这个环境变量。这个环境变量指定之后,我们编译源代码等生成的文件都会放到这个目录下,GOPATH环境变量的配置参考上面的安装Go,配置到Windows下的系统变量里。

B、查看是否安装配置成功

使用快捷键win+R键,输入cmd,打开命令行提示符,在命令行中输入

1
2
go env  # 查看得到go的配置信息
go version # 查看go的版本号

2.3 mac系统安装并配置

安装

双击 pkg 包,顺着指引,即可安装成功。 在命令行输入 go version,获取到 go 的version,则代表安装成功。

配置环境变量

1、打开终端输入cd ~进入用户主目录; 2、输入ls -all命令查看是否存在.bash_profile; 3、存在既使用vim .bash_profile 打开文件; 4、输入 i 进入vim编辑模式; 5、输入下面代码, 其中 GOPATH: 日常开发的根目录。GOBIN:是GOPATH下的bin目录。

export GOPATH=/Users/yztc/go

export GOBIN=$GOPATH/bin

export PATH=$PATH:$GOBIN

6、点击ESC,并输入 :wq 保存并退出编辑。可输入vim .bash_profile 查看是否保存成功。

7、输入source ~/.bash_profile 完成对golang环境变量的配置,配置成功没有提示。 8、输入go env 查看配置结果

二、搭建开发工具

安装好atom工具,然后安装go-plus插件和atom-terminal-panel插件。

1.安装go-plus插件

img

2.安装atom-terminal-panel插件

img

三、第一个程序:HelloWorld

3.1 编写第一个程序

1.打开编辑器创建一个新的helloworld.go文件,并输入以下内容:

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
/* 输出 */
fmt.Println("Hello, World!")
}

2.执行go程序

执行go程序由几种方式

方式一:使用go run命令

​ step1:使用快捷键win+R,输入cmd打开命令行提示符

​ step2:进入helloworld.go所在的目录

​ step3:输入go run helloworld.go命令并观察运行结果。

方式二:使用go build命令

​ step1:使用快捷键win+R,输入cmd打开命令行提示符

​ step2:进入helloworld.go所在的目录

​ step3:输入go build helloworld.go命令进行编译,产生同名的helloworld.exe文件

​ step4:输入helloworld.exe,执行

方式三:使用 go playground

​ step1:打开一下网址https://play.golang.org/

3.2 第一个程序的解释说明

3.2.1 package

  • 在同一个包下面的文件属于同一个工程文件,不用import包,可以直接使用
  • 在同一个包下面的所有文件的package名,都是一样的
  • 在同一个包下面的文件package名都建议设为是该目录名,但也可以不是

3.2.2 import

import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包的函数,fmt 包实现了格式化 IO(输入/输出)的函数

可以是相对路径也可以是绝对路径,推荐使用绝对路径(起始于工程根目录)

  1. 点操作 我们有时候会看到如下的方式导入包

    1
    2
    3
    import(
    . "fmt"
    )

    这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调

    用的fmt.Println("hello world")可以省略的写成Println("hello world")

  2. 别名操作 别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字

    1
    2
    3
    import(
    f "fmt"
    )

    别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")

  3. _操作 这个操作经常是让很多人费解的一个操作符,请看下面这个import

    1
    2
    3
    4
    import (
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
    )

    _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数

3.3.3 main与init

  • 这两个函数在定义时不能有任何的参数和返回值
  • 虽然一个package里面可以写任意多个init函数,但推荐只用一个
  • Go程序会自动调用init()和main()
  • 每个package中的init函数都是可选的,但package main就必须包含一个main函数
  • 先调用init函数,再调用main函数
  • 运行程序,必须要运行存在main函数的go文件

初始化顺序:

程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。

四、编码规范

4.1 编码规范

4.2 注释

  • 单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释
  • 多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段

4.3 标识符

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )

4.4 语句的结尾

Go语言中是不需要类似于Java需要冒号结尾,默认一行就是一条数据

如果你打算将多个语句写在同一行,它们则必须使用 ;

参考

第1章-简介和配置

老男孩Golang第一期 第12章

第12章-结构体

2018-03-02

接口

1.1 什么是接口?

面向对象世界中的接口的一般定义是“接口定义对象的行为”。它只指定对象应该做什么。实现这种行为的方法(实现细节)是针对对象的。

在Go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口

接口定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了该接口。

1.2 接口的定义语法

定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
/* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
)

type Phone interface {
call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}

func main() {
var phone Phone

phone = new(NokiaPhone)
phone.call()

phone = new(IPhone)
phone.call()

}

结果

1
2
I am Nokia, I can call you!
I am iPhone, I can call you!
  • interface可以被任意的对象实现
  • 一个对象可以实现任意多个interface
  • 任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface

1.3 interface值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import "fmt"

type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
loan float32
}
type Employee struct {
Human //匿名字段
company string
money float32
} //Human实现Sayhi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
} //Human实现Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
} //Employee重写Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}

// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
SayHi()
Sing(lyrics string)
}

func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
//定义Men类型的变量i
var i Men
//i能存储Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//i也能存储Employee
i = Tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
//定义了slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//T这三个都是不同类型的元素,但是他们实现了interface同一个接口
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x {
value.SayHi()
}
}

结果

1
2
3
4
5
6
7
8
9
10
This is Mike, a Student:
Hi, I am Mike you can call me on 222-222-XXX
La la la la... November rain
This is Tom, an Employee:
Hi, I am Sam, I work at Things Ltd.. Call me on 444-222-XXX
La la la la... Born to be wild
Let's use a slice of Men and see what happens
Hi, I am Paul you can call me on 111-222-XXX
Hi, I am Sam, I work at Golang Inc.. Call me on 444-222-XXX
Hi, I am Mike you can call me on 222-222-XXX

那么interface里面到底能存什么值呢?如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个 interface的任意类型的对象。例如上面例子中,我们定义了一个Men interface类型的变量m,那么m里面可以存 Human、Student或者Employee值

当然,使用指针的方式,也是可以的

但是,接口对象不能调用实现对象的属性

interface函数参数

interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思 考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数

嵌入interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type Human interface {
Len()
}
type Student interface {
Human
}

type Test struct {
}

func (h *Test) Len() {
fmt.Println("成功")
}
func main() {
var s Student
s = new(Test)
s.Len()
}

结果

1
成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package test

import (
"fmt"
)

type Controller struct {
M int32
}

type Something interface {
Get()
Post()
}

func (c *Controller) Get() {
fmt.Print("GET")
}

func (c *Controller) Post() {
fmt.Print("POST")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"test"
)

type T struct {
test.Controller
}

func (t *T) Get() {
//new(test.Controller).Get()
fmt.Print("T")
}
func (t *T) Post() {
fmt.Print("T")
}
func main() {
var something test.Something
something = new(T)
var t T
t.M = 1
// t.Controller.M = 1
something.Get()
}

结果

1
T

Controller实现了所有的Something接口方法,当结构体T中调用Controller结构体的时候,T就相当于Java中的继承,T继承了Controller,因此,T可以不用重写所有的Something接口中的方法,因为父构造器已经实现了接口。

如果Controller没有实现Something接口方法,则T要调用Something中方法,就要实现其所有方法。

如果something = new(test.Controller)则调用的是Controller中的Get方法。

T可以使用Controller结构体中定义的变量

总结

接口对象不能调用接口实现对象的属性

1
2
pip install --upgrade pip
pip install shadowsocks

fast_open未启用

1
echo 3 > /proc/sys/net/ipv4/tcp_fastopen
1
2
3
4
5
6
7
8
9
10
11
12
vim /etc/shadowsocks.json

{
"server":"",
"server_port":,
"local_address": "127.0.0.1",
"local_port":1027,
"password":"",
"timeout":300,
"method":"rc4-md5",
"workers": 1
}
1
nohup sslocal -c /etc/shadowsocks.json /dev/null 2>&1 &
1
curl --socks5 127.0.0.1:1027 http://httpbin.org/ip
1
yum install -y privoxy
1
2
3
4
vim /etc/privoxy/config

listen-address 127.0.0.1:1087
forward-socks5t / 127.0.0.1:1027 .
1
2
3
4
systemctl enable privoxy.service
systemctl start privoxy.service
systemctl status privoxy.service
systemctl restart privoxy.service
1
2
3
export http_proxy=http://127.0.0.1:1087;export https_proxy=http://127.0.0.1:1087;

curl http://httpbin.org/ip

参考

CentOS 7 安装 shadowsocks 客户端

新建项目和crawlspider爬虫

1
2
3
4

scrapy startproject shopping
cd shopping
scrapy genspider -t crawl jollychic www.jollychic.com

设置settings.py 项目的路径

1
2
3
4
5
import sys
import os
project_dir = os.path.abspath(os.path.dirname(__file__))
BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'shopping'))

items.py

1
2
3
4
5
6
7
8
from scrapy import Item, Field
from scrapy.loader import ItemLoader


class JollychicItemLoader(ItemLoader):
default_output_processor = TakeFirst()
class JollychicItem(Item):
title = Field()

spiders/jollychic.py

1
2
3
4
5
6
7
from item import JollychicItemLoader, JollychicItem

def parse(self, response):
item_loader = JollychicItemLoader(item=JollychicItem, response=reponse)
item_loader.add_css()
item = item_loader.load_item()
return item

CentOS Linux release 7.5.1804 (Core)

Linux 3.10.0-862.3.3.el7.x86_64

安装依赖

1
yum install -y mercurial gcc bison

安装gvm

1
curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer | zsh

Go 实现了自举(用 Go 编译 Go),就需要用到 Go 1.4 来做编译

1
2
3
4
5
# -B 表示只安装二进制包
$ gvm install go1.4.3 -B
$ gvm use go1.4.3
$ export GOROOT_BOOTSTRAP=$GOROOT
$ gvm install go1.10.2

离线安装

1
2
3
4
5
6
# git clone 官方源go1.10.2
git clone -b go1.10.2 https://go.googlesource.com/go go1.10.2
#可以放到别的电脑上
tar cvfz go1.10.2.tar.bz2 go1.10.2
#指定本地go1.10.2 git源
gvm install go1.10.2 --source=/root/go1.10.2

[解决 golang在macos编译时fatal error: MSpanList_Insert错误]|gvm install go1.9.2 fails on macOS 10.12.6

安装好之后,指定默认使用这个版本,加上 --default 即可,省去每次敲 gvm use

1
$ gvm use go1.10.2 --default

使用

1
2
3
4
5
6
# 查看可以安装的go版本
$ gvm listall
# 查看本地安装的go版本
$ gvm list
# go环境变量
$ go env

参考

使用gvm管理多版本golang

go依赖包管理工具对比

Go 语言多版本安装及管理利器 - GVM

如何使用restgo-admin

使用如下指令克隆

1
cd $GOPATH/src
1
git clone https://github.com/winlion/restgo-admin.git

你将得到restgo-admin 目录 进入目录

1
cd restgo-admin

建立数据库

新建数据库名称为restgo-admin,编码为utf-8
将restgo-admin.sql导入到数据库中

初始化依赖包

使用前先使用如下指令安装指令安装文件

1
2
3
4
5
$ go get github.com/go-sql-driver/mysql
$ go get -v -u github.com/alecthomas/log4go
$ go get github.com/gin-gonic/gin
$ go get github.com/go-xorm/xorm
$ go get github.com/tommy351/gin-sessions

启动

使用前先使用如下指令启动应用
go run main.go
使用前先使用如下指令打包应用
build.bat

,