点击上方蓝字关注我们

To err is human,to forgive divine.

-Alexander Pope

初学golang我们经常会犯一些错误,虽然它们不会产生类型检查的异常,但是它们往往潜在影响软件的功能。

01

循环中易犯的错误

1.1

使用循环迭代变量的指针

先来看一段代码

in := []int{1, 2, 3}var out []*intfor _, v := range in {out = append(out, &v)}fmt.Println("Values:", *out[0], *out[1], *out[2])fmt.Println("Addresses:", out[0], out[1], out[2])

结果输出:

Values: 3 3 3
Addresses: 0xc0000a4008 0xc0000a4008 0xc0000a4008

你可能会很奇怪为什么会出现这种情况,结果不应该是  1 2 3 和三个不同的地址吗?其实真实原因for range过程中创建了每个元素的副本,而不是直接返回每个元素的引用。v在for循环引进的一个块作用域内进行声明,它是一个共享的可访问的地址。在迭代过程中,返回的变量是根据切片依次赋值的到变量v中,故而值的地址总是相同的,导致结果不如预期。那么该如何修改呢?

最简单的做法是将循环迭代变量复制到新的变量中:

in := []int{1, 2, 3}var out []*int
for  _, v := range in {v := vout = append(out, &v)
}fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])

PS:也可以直接根据range返回第一个参数作为数组索引下标 拿值

循环中goroutine使用循环迭代变量也会存在同样的问题:

list := []int{1, 2, 3}for _, v := range list {go func() {fmt.Printf("%d ", v)}()
}

输出结果:

3 3 3

1.2

循环中调用WaitGroup.Wait

按照WaitGroup的正常用法,当wg.Done()被调用len(tasks)次,wg.Wait()会被自动解除阻塞。当时下面代码中,将wg.wait()放到循环中后,导致第二次循环被阻塞,解决办法 将wg.wait()移除循环即可。

var wg sync.WaitGroup
wg.Add(len(tasks))
for _, t := range tasks {go func(t *task) { defer wg.Done()}(t)// wg.Wait()
}wg.Wait()

1.3

循环中使用defer

defer是在函数返回的时候才执行,除非我们知道自己在做什么否则你不应该在循环中使用defer

var mutex sync.Mutex
type Person struct {Age int
}
persons := make([]Person, 10)
for _, p := range persons {mutex.Lock()// defer mutex.Unlock()p.Age = 13mutex.Unlock()
}

上面的例子中,如果使用第8行代替第10行代码,则下一次循环将因为无法获取排他锁永远被阻塞。

如果你真的想在内循环中使用defer,你很可能是想委托其他函数来完成任务。

var mutex sync.Mutex
type Person struct {Age int
}
persons := make([]Person, 10)
for _, p := range persons {func() {mutex.Lock()defer mutex.Unlock()p.Age = 13}()
}

但是,有时在循环中使用defer确实比较方便,但是你真的应该知道你在做什么。Go不能容忍愚蠢的人。

02

发送到一个无保证的channel

我们可以在一个goroutine中发送数据到channels,在另一个goroutine中接收这些数据。默认情况下,发送和接收会阻塞直到对方ready。这使得goroutines可以不用显式使用锁或条件变量就可以完成同步操作。

func doReq(timeout time.Duration) obj {// ch :=make(chan obj)ch := make(chan obj, 1)go func() {obj := do()ch <- result} ()select {case result = <- ch :return resultcase<- time.After(timeout):return nil }
}

上面的代码中,doReq函数创建了一个子Goroutine来处理请求,这在go服务端程序中是常见的做法。子Goroutine执行do函数并通过channel发送结果给父节点。子Goroutine将会阻塞直到父节点从channel中收到数据。与此同时,父节点也会阻塞在select上,直到子Goroutine发送结果到channel,或者超时。当超时先发生,则会执行第12行代码并且子Goroutine将永远阻塞。

解决方案:

  • 将ch从无缓冲channel改成有缓冲channel,这样子Goroutine将永远可以发送结果数据,即使父节点已经退出

  • select中使用default语句,如果没有goroutine收到ch,则会发送默认情况。尽管这种方案不是总能生效。

...
select {
case ch <- result:
default:
}
...

03

不使用接口

接口的使用可以让我们的代码更加灵活,也是一种在代码中引入多态的方法。接口允许我们请求一组行为而不是特定类型。不使用接口不会产生任何错误,但是它会导致我们的代码不简洁、不灵活、并且不具备可拓展性。

众多接口中,io.Readerio.Writer可能是最受欢迎的。

type Reader interface {Read(p []byte) (n int, err error)
}
type Writer interface {Write(p []byte) (n int, err error)
}

这些接口功能非常强大。假设你要向一个文件中写入数据,你会定义一个save方法:

func (o *obj)Save(file os.File) error

但是第二天你又想往http.ResponseWriter中写入数据,但是你不想再定义一个新的方法,怎么办?使用io.Writer

func (o *obj)Save(w io.Writer) error

还有一个重点注意的事项,你应该知道总是请求你要使用的行为。上面的例子中,请求一个io.ReadWriteCloser也可以正常工作,但它不是一个最佳实践,因为我们只是想使用一个Write方法。接口越大抽象越弱,所以绝大多时候最好使用行为而不是具体的类型。

04

糟糕的结构体字段排序

糟糕顺序的结构体虽然也不会导致任何错误,但是它会造成更多的内存消耗。

type BadOrderedPerson struct {Veteran bool   // 1 byteName    string // 16 byteAge     int32  // 4 byte
}type OrderedPerson struct {Name    stringAge     int32Veteran bool
}

上面代码看起来两种类型都占用了相同的21bytes的内存空间,但是结果显示却完全不同。我们使用GOARCH=amd64来编译代码:

  • BadOrderedPerson 类型分配了32bytes

  • OrderedPerson类型分配了24bytes

为什么会这样呢?原因是数据结构对齐。在64位架构中,内存分配8字节的连续数据包。需要添加的填充可以通过下面的公式计算得出:

padding = (align - (offset mod align)) mod align
aligned = offset + padding= offset + ((align - (offset mod align)) mod align)
type BadOrderedPerson struct {Veteran bool     // 1 byte_       [7]byte  // 7 byte: padding for alignmentName    string   // 16 byteAge     int32    // 4 byte_       struct{} // to prevent unkeyed literals// zero sized values, like struct{} and [0]byte occurring at // the end of a structure are assumed to have a size of one byte.// so padding also will be addedd here as well.}type OrderedPerson struct {Name    stringAge     int32Veteran bool_       struct{}
}

当我们高频使用一个大的糟糕排序的结构体类型,会导致性能问题。但是不用担心,我们不用人肉检查结构体顺序定义问题,使用

maligned(https://github.com/mdempsky/maligned)

可以轻松检查此类问题。

05

测试中不使用race detector

数据竞争会引发神秘的错误,经常发生在我们代码部署线上部署很长一段时间后。正是这个原因,它也是并发系统中最常见也是最难调试的问题。为了帮助区分这类bug,Go1.1引入了一个内置的数据竞争检测器。使用过程只需要简单的添加一个-race 标志即可。

$ go test -race pkg    // to test the package
$ go run -race pkg.go  // to run the source file
$ go build -race       // to build the package
$ go install -race pkg // to install the package

启用race后,编译器会记录代码访问内存的时间和方式,而runtime监视共享变量的非同步访问。

当数据竞争被检测到,竞争检测器会打印一份报告,包括冲突访问的堆栈跟踪信息。一下是一个栗子:

WARNING: DATA RACE
Read by goroutine 185:net.(*pollServer).AddFD()src/net/fd_unix.go:89 +0x398net.(*pollServer).WaitWrite()src/net/fd_unix.go:247 +0x45net.(*netFD).Write()src/net/fd_unix.go:540 +0x4d4net.(*conn).Write()src/net/net.go:129 +0x101net.func·060()src/net/timeout_test.go:603 +0xaf
Previous write by goroutine 184:net.setWriteDeadline()src/net/sockopt_posix.go:135 +0xdfnet.setDeadline()src/net/sockopt_posix.go:144 +0x9cnet.(*conn).SetDeadline()src/net/net.go:161 +0xe3net.func·061()src/net/timeout_test.go:616 +0x3ed
Goroutine 185 (running) created at:net.func·061()src/net/timeout_test.go:609 +0x288
Goroutine 184 (running) created at:net.TestProlongTimeout()src/net/timeout_test.go:618 +0x298testing.tRunner()src/testing/testing.go:301 +0xe8

5个golang中易犯的错误相关推荐

  1. javascript中易犯的错误有哪些

    javascript中易犯的错误有哪些 一.总结 一句话总结: 比如循环中函数的使用 函数中this的指向谁(函数中this的使用) 变量的作用域 1.this.timer = setTimeout( ...

  2. C语言指针学习中易犯的错误

    1.向null地址处copy数据 char *str = NULL; strcpy(str,"aaaaabbbbb"); 错误! NULL是操作系统保护的空间,不能往里面拷贝数据: ...

  3. java犯的小错误_[Java教程]十个JavaScript中易犯的小错误,你中了几枪?

    [Java教程]十个JavaScript中易犯的小错误,你中了几枪? 0 2015-06-01 12:00:19 序言 在今天,JavaScript已经成为了网页编辑的核心.尤其是过去的几年,互联网见 ...

  4. Android4.0 Design之UI设计易犯的错误2

    想成为Android的杰出开发工程师,不懂得Android的设计规则怎么可以,Android4.0问世后谷歌公司为Android程序员规范了一系列的设计原则,不要再盲目的模仿IOS的设计了,因为And ...

  5. css html 对错号,HTML_DIV+CSS编码时易犯的错误,CSS+DIV是网站标准(或称“WEB - phpStudy...

    DIV+CSS编码时易犯的错误 CSS+DIV是网站标准(或称"WEB标准")中常用的术语之一,通常为了说明与HTML网页设计语言中的表格(table)定位方式的区别,因为XHTM ...

  6. 7个跑步易犯的错误和解决办法

    似乎所有人都认为跑步是一种非常简单的锻炼方式,然而,其实不然,跑步涉及到许多专业知识.错误的跑步,不仅影响锻炼效果,而且还容易导致受伤. 1.鞋子不合适 问题:穿着太旧的跑步鞋或者类型不合适的运动鞋容 ...

  7. Linux管理员易犯的错误

    对于初入linux的管理员们来说,迁移到Linux是一场噩梦,而且在Linux管理中稍微不小心就会出错,如果不避免这些错误的话就会给我们的网络和系统带来风险,那么我们现在就去看看Linux管理员易犯的 ...

  8. 外汇资金管理上易犯的错误

    赚钱是做外汇投资的最终目的,为了获取更多的钱,首先管理好自己手中的钱是每位投资者走向成功的必由之路,能够正确冷静科学合理的运用资金,很大程度上区分了一位投资者是否优秀.一般而言,在外汇资金管理方面投资 ...

  9. 三层开发中容易犯的错误

    http://www.cnblogs.com/yukaizhao/archive/2007/03/08/layer_develop_errors.html 三层开发中容易犯的错误 前记: 相信大家对三 ...

最新文章

  1. mysql邮箱配置文件_SQL-数据库邮箱配置
  2. 服务器python密码_python实现批量修改服务器密码的方法
  3. easyui filebox 文件上传
  4. window.onload与$(document).ready()的区别
  5. Photoshop的绘图工具
  6. win10商店下载位置_Win10删应用商店下载记录|Win10删Microsoft Store下载记录
  7. 云计算OpenStack:云在身边博客园
  8. springboot启动时报错:Failed to load property source from location 'classpath:/application.yml'
  9. java8中的接口与时间操作
  10. Activity的生命周期方面复习笔记
  11. C++连接MySQL数据库教程|如何连接数据库
  12. Mysql 省市县乡 地址分割
  13. DOS命令关机小程序
  14. shell 分割文本_shell教程(2):积木游戏之认识积木--重要的系统命令
  15. 从实习生到算法专家,他只用了2年!
  16. php画圆 锯齿,优雅的解决canvas画圆锯齿问题
  17. pandas 合并表格时出现unnamed列,和顺序被打乱的问题
  18. VScode seting.json 配置 自用
  19. sql数据库命令大全
  20. GNN模型系列(一)——Vanilla GNNs

热门文章

  1. 甘特图:项目进度管理,理想的控制工具
  2. 第七周项目30-分文件用数组求员工工资
  3. 学生HTML个人网页作业作品 ~ 超级英雄11页面网页设计成品~ 学生网页设计作业源码
  4. 为mediawiki添加GeSHi代码高亮库
  5. 2022年了你必须要学会搭建微前端项目及部署方式
  6. 编程题走迷宫_洛谷P1238 走迷宫题解
  7. 英语chrismatite黄蜡石Chrismatite单词
  8. Android学习笔记 二三 多页显示 Flipper的使用
  9. 陶晶chi液晶屏01,stc15代码
  10. 手游《火影忍者》产品分析:基本框架与资源系统