Golang高效实践之泛谈篇
前言
我博客之前的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高效实践之泛谈篇的更多相关文章
- Golang 高效实践之并发实践context篇
前言 在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel.比如说我们可以用channel来控 ...
- Golang 高效实践之并发实践
前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面 ...
- Golang 高效实践之defer、panic、recover实践
前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...
- Golang高效实践之array、slice、map
前言 Golang的slice类型为连续同类型数据提供了一个方便并且高效的实现方式.slice的实现是基于array,slice和map一样是类似于指针语义,传递slice和map并不涉及底层数据结构 ...
- Golang高效实践之interface、reflection、json实践
前言 反射是程序校验自己数据结构和类型的一种机制.文章尝试解释Golang的反射机制工作原理,每种编程语言的反射模型都是不同的,有很多语言甚至都不支持反射. Interface 在将反射之前需要先介绍 ...
- 高效开发之SASS篇 灵异留白事件——图片下方无故留白 你会用::before、::after吗 link 与 @import之对比 学习前端前必知的——HTTP协议详解 深入了解——CSS3新增属性 菜鸟进阶——grunt $(#form :input)与$(#form input)的区别
高效开发之SASS篇 作为通往前端大神之路的普通的一只学鸟,最近接触了一样稍微高逼格一点的神器,特与大家分享~ 他是谁? 作为前端开发人员,你肯定对css很熟悉,但是你知道css可以自定义吗?大家 ...
- 打造程序员的高效生产力工具-mac篇
打造程序员的高效生产力工具-mac篇 1 概述 古语有云:“工欲善其事,必先利其器” [1] ,作为一个程序员,他最重要的生产资源是脑力知识,最重要的生产工具是什么?电脑. 在进行重要的脑力成果输 ...
- 我的TDD实践---SVN架设篇
我的TDD实践---SVN架设篇 “我的TDD实践”系列之SVN架设 写在前面: 我的TDD实践这几篇文章主要是围绕测试驱动开发所展开的,其中涵盖了一小部分测试理论,更多的则是关注工具的使用及环境的搭 ...
- LDA工程实践之算法篇之(一)算法实现正确性验证(转)
研究生二年级实习(2010年5月)开始,一直跟着王益(yiwang)和靳志辉(rickjin)学习LDA,包括对算法的理解.并行化和应用等等.毕业后进入了腾讯公司,也一直在从事相关工作,后边还在yiw ...
随机推荐
- express 中间件的理解
nodejs(这指express) 中间件 铺垫: 一个请求发送到服务器,要经历一个生命周期,服务端要: 监听请求-解析请求-响应请求,服务器在处理这一过程的时候,有时候就很复杂了,将这些复杂的业务拆 ...
- Verilog写一个对数计算模块Log2(x)
网上一个能用的也没有,自己写一个把. 1.计算原理: 整数部分 网上找到了一个c语言的计算方法如下: int flog2(float x) { return ((unsigned&)x> ...
- 干货!Git 如何使用多个托管平台管理代码
考虑到github不能免费创建私有仓库原因,最近开始在使用码云托管项目,这样避免了连接数据库的用户密码等信息直接暴露在公共仓库中.今天突然想到一个点,就是能不能同时把代码推送到github和码云上呢? ...
- 计算广告之CTR预测--PNN模型
论文为:Product-based Neural Networks for User Response Prediction 1.原理 给大家举例一个直观的场景:比如现在有一个凤凰网站,网站上面有一个 ...
- hdoj2037 贪心算法——今年暑假不AC
所谓“贪心算法”是指:在对问题求解时,总是作出在当前看来是最好的选择.也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解(是否是全局最优,需要证明). 经典问题:时间序列问题 ...
- Programming In Lua 第七章
1, 2, 3, 第三点需要讲解下:for循环中,allwords函数是工厂函数,只调用一次.for循环的每次遍历,都会调用工厂函数返回的闭包函数.这样就能遍历一个文件的每一行的每一个单词. 4, 我 ...
- Python笔记【6】_函数
#!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 ''' def:函数是一段可以重复调用的代码,通 ...
- Requests方法 -- cookie绕过验证码登录操作
前言有些登录的接口会有验证码:短信验证码,图形验证码等,这种登录的话验证码参数可以从后台获取的(或者查数据库最直接).获取不到也没关系,可以通过添加 cookie 的方式绕过验证码. 1.这里以登录博 ...
- SQL Server 2016 + AlwaysOn 无域集群
目录 AlwaysOn 搭建 WSFC 配置计算机的 DNS 后缀 安装故障转移集群 验证集群 创建集群 创建文件共享见证 配置 AlwaysOn 新建可用性组 创建侦听器 可读副本的负载均衡 主角色 ...
- C++中 / 和 % 在分离各位时的妙用
在学习c++的过程中,我们一般用 / 和 % 来分解数字的各个位 取整 (/) 比如1234 / 10 等于 123.4,这相当于把前三位分解出来了 取余(%) 比如 12345 的分解方法 个位:1 ...