前言

我博客之前的Golang高效实践系列博客中已经系统的介绍了Golang的一些高效实践建议,例如:《Golang高效实践之interface、reflection、json实践》、《Golang 高效实践之defer、panic、recover实践》、《Golang 高效实践之并发实践context篇》、《Golang 高效实践之并发实践channel篇》,本文将介绍一些零散的Golang高效实践建议,虽然琐碎但是比较重要。

建议

1.代码格式go fmt工具,开发不用过多关注。

2.支持块注释和行注释,一般包开头用块注释说明,函数用行注释说明,为了提高辨识度,函数注释一般以函数名为开头。例如:

// Compile parses a regular expression and returns, if successful,

// a Regexp that can be used to match against text.

func Compile(str string) (*Regexp, error) {

3.包名尽量简洁有意义,一般是一个小写单词,不需要下划线或者驼峰命名。不要用点号引进包,除非是为了简化单元测试。

4.Go不提供getters和setters方法,用户要自己实现。例如有一个字段叫owner(小写,非导出变量),那么getter方法应该命名为Owner而不是GetOwner。如果需要setter方法应该命名为SetOwner。例如:

owner := obj.Owner()

if owner != user {

    obj.SetOwner(user)

}

5.接口命名在方法名后加er,例如:Reader,Writer,Formatter,CloseNotifier等等。

6.变量命名用驼峰例如MixedCaps或者mixedCaps,不用下划线。

7.Go和C一样是用分号作为语句的结束标记,不同的是Go是词法分析器自动加上去,不用程序员手动添加。词法分析器添加分号的标记一是行末遇到int或者float64等关键字类型,或者出现下面的特殊字符:

break continue fallthrough return ++ -- ) }

所以:

if i < f() {

    g()

}

开括号‘{’要放在‘)’后面,否则词法分析器会自动在‘)’末尾添加分到导致语法错误。所以不能像下面这样写:

if i < f()  // wrong!

{           // wrong!

    g()

}

8.非必须的else可以省略,例如:

if err := file.Chmod(); err != nil {

    log.Print(err)

    return err

}

9.声明和重新赋值:

f, err := os.Open(name)

该语句声明了f和err,紧接着:

d, err := f.Stat()

看着像声明了d和err,但实际上是声明了d,err是重新赋值。也就是说f.Stat用了上面已经存在的err,仅仅是重新给该err赋了一个新值。

所以 v:= declaration是声明还是重新赋值取决于:

1.该声明作用域已经存在一个已经声明的v,那么就是赋值(如果v已经在外面的作用域声明,那么这里会重新生成一个新的变量v)

例如:

package main

import (

"errors"

"fmt"

)

func main() {

fmt.Println(declareTest())

}

func declareTest() (err error){

//declare a new variable err in if statement

if err := hello(); err != nil {

fmt.Println(err)

}

fmt.Println(err)

return

}

func hello() error {

return errors.New("hello world")

}

程序输出:

hello world

<nil>

<nil>

2.如果是赋值,那么左边至少要有一个声明的新变量,否则会报语法错误。

10.for循环。Go的for循环和C很像,但是不支持while循环。有以下三种形式:

// Like a C for

for init; condition; post { }

// Like a C while

for condition { }

// Like a C for(;;)

for { }

也可以用for循环遍历数组、切片、字符串、map、或者读channel,例如:

for key, value := range oldMap {

    newMap[key] = value

}

for pos, char := range "iam中国人" {

fmt.Printf("character %#U start at byte position %d\n", char, pos)

}

程序输出:

character U+ 'i' start at byte position 

character U+ 'a' start at byte position 

character U+006D 'm' start at byte position 

character U+4E2D '中' start at byte position 

character U+56FD '国' start at byte position 

character U+4EBA '人' start at byte position 

// Reverse a,翻转字符切片a

for i, j := , len(a)-; i < j; i, j = i+, j- {

    a[i], a[j] = a[j], a[i]

} 

11.switch。Go的switch比C灵活,case的表达式不要求一定是常量甚至整数,例如:

func unhex(c byte) byte {

    switch {

    case '' <= c && c <= '':

        return c - ''

    case 'a' <= c && c <= 'f':

        return c - 'a' + 

    case 'A' <= c && c <= 'F':

        return c - 'A' + 

    }

    return 

}

每个case不会自动顺延到下一个case,如果需要顺延需要手动fall through。

Switch用于类型判断:

var t interface{}

t = functionOfSomeType()

switch t := t.(type) {

default:

    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has

case bool:

    fmt.Printf("boolean %t\n", t)             // t has type bool

case int:

    fmt.Printf("integer %d\n", t)             // t has type int

case *bool:

    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool

case *int:

    fmt.Printf("pointer to integer %d\n", *t) // t has type *int

}

12.命名函数返回值。Go函数的返回值可以像输入函数一样命名(当然也可以不命名),命名返回值在函数开始时就已经被初始化为类型的零值。如果函数执行return没有带返回值,那么命名函数的当前值就会被返回。例如:

func ReadFull(r Reader, buf []byte) (n int, err error) {

    for len(buf) >  && err == nil {

        var nr int

        nr, err = r.Read(buf)

        n += nr

        buf = buf[nr:]

    }

    return

}

13.用defer释放资源,比如关闭文件、释放锁。这样做有两个好处,一是保证不会忘记释放资源,另外是释放的代码贴近申请的代码,更加清楚明了。更多defer特性请参考我的《Golang 高效实践之defer、panic、recover实践》博文。

14.new(T)分配一个*T类型,指向被赋予零值的一块内存。例如:

type SyncedBuffer struct {

    lock    sync.Mutex

    buffer  bytes.Buffer

}

p := new(SyncedBuffer)  // type *SyncedBuffer,相当于p:= &SyncedBuffer{}

var v SyncedBuffer      // type  SyncedBuffer 

15.构造函数。Go并没有像C++一样为每个类型提供默认的构造函数。所以当new(T)分配的零值不能满足我们要求时,我们需要一个初始化构造函数,一般命名为NewXXX,例如:

func NewFile(fd int, name string) *File {

    if fd <  {

        return nil

    }

    f := new(File)

    f.fd = fd

    f.name = name

    f.dirinfo = nil

    f.nepipe = 

    return f

}

也可以这样顺序初始化成员:

return &File{fd, name, nil, 0}

还可以指定成员初始化:

return &File{fd: fd, name: name}

所以new(File)是等于&File{}

16.用make(T, args)创建切片、map、channel,返回已经初始化的(非零值)T类型(不是*T)。因为这三种数据结构必须在使用前完成初始化,例如切片的零值是nil,直接操作nil是会panic的。

make([]int, 10, 100)

分配一个length为10,capacity为100的切片。而new([]int)返回的值一个执行零值(nil)的切片指针。

下面的示例会清楚的区分new和make的差别:

var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful

var v  []int = make([]int, ) // the slice v now refers to a new array of 100 ints

// Unnecessarily complex:

var p *[]int = new([]int)

*p = make([]int, , )

// Idiomatic:

v := make([]int, ) 

记住只有切片、map和channel分配用到make,并且返回的不是指针。

17.数组。和切片不同,数组的大小是固定的,可以避免重新分配内存。和C语言数组不同的时,Go的数组是值,赋值时会引发数组拷贝。当数组作为参数传递给函数时,函数将会接受到数组的拷贝,而不是数组的指针。另外数组的大小也是数据类型的一部分。也就是说[10]int 和 [20]int不是同一种类型。

但是值属性本身是效率比较低的,如果不能拷贝传递可以传递数组的指针,例如:

func Sum(a *[]float64) (sum float64) {

    for _, v := range *a {

        sum += v

    }

    return

}

array := [...]float64{7.0, 8.5, 9.1}

x := Sum(&array)  // Note the explicit address-of operator

但是这样不符合Go的编程习惯。这里可以用切片避免拷贝传递。

18.切片。尽量用切片代替数组。切片本质是数组的引用,底层的数据结构还是数组。所以当把切片A赋值给切片B时,A和B指向的是同一个底层数组。当给函数传递切片时,相当于传递底层数组的指针。因此切片通常是更高效和常用。

特别需要注意的是,切片的capacity也就是cap函数的返回值是底层数组的最大长度,当切片超过了改值时将会触发重新分配,底层的数组将会扩容,并且将之前的值拷贝到新内存中。


func Append(slice, data []byte) []byte {

    l := len(slice)

    if l + len(data) > cap(slice) {  // reallocate

        // Allocate double what's needed, for future growth.

        newSlice := make([]byte, (l+len(data))*)

        // The copy function is predeclared and works for any slice type.

        copy(newSlice, slice)

        slice = newSlice

    }

    slice = slice[:l+len(data)]

    copy(slice[l:], data)

    return slice

}

Append函数最后要返回切片的值,因为切片(运行时持有指针,length和capacity的数据结构)本身是值传递的。

19.二维切片。Go的数组和切片都是一维的,如果需要创建二维的数组或者切片则需要定义数组的数组,或者切片的切片。例如:

type Transform [][]float64  // A 3x3 array, really an array of arrays.

type LinesOfText [][]byte     // A slice of byte slices.

因为切片的长度是可变的,所以每个切片元素可以有不同的长度,所以有:

text := LinesOfText{

[]byte("Now is the time"),

[]byte("for all good gophers"),

[]byte("to bring some fun to the party."),

}

需要注意的是,make只会初始化一维,二维的切片需要我们手动初始化,例如:

// Allocate the top-level slice.

picture := make([][]uint8, YSize) // One row per unit of y.

// Loop over the rows, allocating the slice for each row.

for i := range picture {

  picture[i] = make([]uint8, XSize)

}

20.map。map的key可以是任意定义了相等操作的类型,例如int,float,complex,字符串,指针,interface(只要是concrete type支持相等比较),结构体和数组。切片不能作为map的key,因为切片的相等没有定义。

map可以按k-v的方式枚举初始化,例如:

var timeZone = map[string]int{

    "UTC":  **,

    "EST": -**,

    "CST": -**,

    "MST": -**,

    "PST": -**,

} 

根据key索引value:

offset := timeZone["EST"]

当key不存在时,将会返回value对应的零值。例如:

tm := make(map[string]bool)

fmt.Println(tm["test"])

将会输出false。那怎么区分究竟是key不存在还是key存在且本身value就是零值呢?可以这样利用“comma,ok”语法:

var seconds int

var ok bool

seconds, ok = timeZone[tz]

当key存在时ok为true,seconds为对应的value。否则ok为false,seconds为对应value的零值。

可以用delete指定map的key删除元素:

delete(timeZone, "PDT")  // Now on Standard Time

21.Go的格式输出是C语言风格的,但是比C的printf更高级。所有格式输出相关的函数在fmt包中,例如:fmt.Printf,fmt.Fprintf,fmt.Sprintf等等。例如:

fmt.Printf("Hello %d\n", )

fmt.Fprint(os.Stdout, "Hello ", , "\n")

fmt.Println("Hello", )

fmt.Println(fmt.Sprint("Hello ", ))

%v输出任意值:

fmt.Printf("%v\n", timeZone)  // or just fmt.Println(timeZone)

程序结果:

map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]

又例如:

type T struct {

    a int

    b float64

    c string

}

t := &T{ , -2.35, "abc\tdef" }

fmt.Printf("%v\n", t)

fmt.Printf("%+v\n", t)

fmt.Printf("%#v\n", t)

fmt.Printf("%#v\n", timeZone)

程序输出:

&{ -2.35 abc   def}

&{a: b:-2.35 c:abc     def}

&main.T{a:, b:-2.35, c:"abc\tdef"}

map[string]int{"CST":-, "PST":-, "EST":-, "UTC":, "MST":-}

%T输出类型:

fmt.Printf("%T\n", timeZone)

运行结果:

map[string]int

%s调用类型的String()方法输出,所以不能在自定义类型的String()方法中使用%s,否则会死循环

type MyString string

func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.

}

修正版本:

type MyString string

func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.

}

22.append。append内建函数定义:

func append(slice []T, elements ...T) []T

T表示占位符,可以是任意的类型。编译时由编译器替换为实际的类型。用法:

x := []int{,,}

x = append(x, , , )

fmt.Println(x)

程序输出:[1 2 3 4 5 6].

如果想将一个切片追加到另外一个切片末尾要怎么做呢?可以使用…语法,例如:

x := []int{,,}

y := []int{,,}

x = append(x, y...)

fmt.Println(x)

如果没有…,编译将会不通过,因为y不是int类型。

总结

文章介绍了22个Golang的高效实践建议,其中包括一些编程规范和一些实践生产中容易遇到的坑,希望可以帮助到大家

引用

https://golang.org/doc/effective_go.html

Golang高效实践之泛谈篇的更多相关文章

  1. Golang 高效实践之并发实践context篇

    前言 在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel.比如说我们可以用channel来控 ...

  2. Golang 高效实践之并发实践

    前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面 ...

  3. Golang 高效实践之defer、panic、recover实践

    前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...

  4. Golang高效实践之array、slice、map

    前言 Golang的slice类型为连续同类型数据提供了一个方便并且高效的实现方式.slice的实现是基于array,slice和map一样是类似于指针语义,传递slice和map并不涉及底层数据结构 ...

  5. Golang高效实践之interface、reflection、json实践

    前言 反射是程序校验自己数据结构和类型的一种机制.文章尝试解释Golang的反射机制工作原理,每种编程语言的反射模型都是不同的,有很多语言甚至都不支持反射. Interface 在将反射之前需要先介绍 ...

  6. 高效开发之SASS篇 灵异留白事件——图片下方无故留白 你会用::before、::after吗 link 与 @import之对比 学习前端前必知的——HTTP协议详解 深入了解——CSS3新增属性 菜鸟进阶——grunt $(#form :input)与$(#form input)的区别

    高效开发之SASS篇   作为通往前端大神之路的普通的一只学鸟,最近接触了一样稍微高逼格一点的神器,特与大家分享~ 他是谁? 作为前端开发人员,你肯定对css很熟悉,但是你知道css可以自定义吗?大家 ...

  7. 打造程序员的高效生产力工具-mac篇

    打造程序员的高效生产力工具-mac篇 1   概述 古语有云:“工欲善其事,必先利其器” [1] ,作为一个程序员,他最重要的生产资源是脑力知识,最重要的生产工具是什么?电脑. 在进行重要的脑力成果输 ...

  8. 我的TDD实践---SVN架设篇

    我的TDD实践---SVN架设篇 “我的TDD实践”系列之SVN架设 写在前面: 我的TDD实践这几篇文章主要是围绕测试驱动开发所展开的,其中涵盖了一小部分测试理论,更多的则是关注工具的使用及环境的搭 ...

  9. LDA工程实践之算法篇之(一)算法实现正确性验证(转)

    研究生二年级实习(2010年5月)开始,一直跟着王益(yiwang)和靳志辉(rickjin)学习LDA,包括对算法的理解.并行化和应用等等.毕业后进入了腾讯公司,也一直在从事相关工作,后边还在yiw ...

随机推荐

  1. ElasticSearch2.3.1环境搭建哪些不为人知的坑

    首先说明一点,大家最好不要用什么尝鲜版,用比稳定版就好了,要不麻烦不断,另外出了问题,最好去官网,或者google搜索,因为这样靠谱些,要不现在好多都是低版本的,1.4的什么的,结果按照安装,多少情况 ...

  2. chrome浏览器开发者工具F12中某网站的sources下的源码如何批量保存?

    目录 chrome浏览器 开发者工具F12中某网站的sources下的源码如何批量保存 1. 常用保存Sources源码的两种方法 1.1单个文件 1.2 单个页面 2. 问题 3.解决方案 chro ...

  3. spring 5.x 系列第2篇 —— springmvc基础 (代码配置方式)

    文章目录 一.搭建hello spring工程 1.1 项目搭建 1.2 相关注解说明 二.配置自定义拦截器 三.全局异常处理 四.参数绑定 4.1 参数绑定 4.2 关于日期格式转换的三种方法 五. ...

  4. feign之间传递oauth2-token的问题和解决

    在微服务架构里,服务与服务之间的调用一般用feign就可以实现,它是一种可视化的rpc,并且集成了ribbon的负载均衡能力,所以很受欢迎. 授权服务 在授权服务里,用户通过用户名密码,或者手机和验证 ...

  5. 【工具】读取proprtties工具类

    获取properties内容: 基本的使用看网络上大多是这样的,使用时注意线程安全以及读写的实时性问题. 1.直接通过流读取(反射): InputStream inStream =  this.get ...

  6. Java中的反射(1)

    Reflection in Java 反射到底是什么呢,我被问到的时候其实也没办法很好的回答这个问题,翻一翻博客,然后逐条讲解.今天干脆就整合一下,免得以后还要去翻. 首先讲一下Java是如何在运行时 ...

  7. Java并发框架:Executor

    介绍 随着当今处理器中可用的核心数量的增加, 随着对实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行. Java 提供了自己的多线程框架,称为 Executor 框架. 1. Execut ...

  8. FireFox下Canvas使用图像合成绘制SVG的Bug

    本文适合适合对canvas绘制.图形学.前端可视化感兴趣的读者阅读. 楔子 所有的事情都会有一个起因.最近产品上需要做一个这样的功能:给一些图形进行染色处理.想想这还不是顺手拈来的事情,早就研究过图形 ...

  9. SpringMVC框架的简单理解

    首先,让我们来看下下图 SpringMVC解决了View和Controller的交互问题 其中有几个重要组成部分: (1) DispatcherServlet: 前端控制器 用于接收所有请求,并负责分 ...

  10. 【烂笔头】常用adb命令记录

    前言    Android的adb提供了很多命令,功能很强大,可以为开发和调试带来很大的便利.当然本文并不是介绍各种命令的文章,而是用于记录在平时工作中需要经常使用的命令,方便平时工作时使用,所以以后 ...