go常见错误总结

26 Mar 2016 go常见错误总结

最近为了学习go语言,花了点时间翻译《the way to go》这本书相关章节:

详见:https://github.com/Unknwon/the-way-to-go_ZH_CN

在翻译过程中学习了一些go常见的错误和陷阱,特此总结一下,以便自己在今后使用go时少犯错误。

1 误用短声明:=导致变量覆盖

例如,下列代码中remember变量在if语句之外永远都是false,因为if语句中误用了短声明:=。重新定义了一个remember,自动覆盖外面的remember。所以在if语句中操作的remember变量和外面定义的remember不是同一个变量,导致remember在if语句之外一直都是false。

var remember bool = false
if something {
    remember := true
}

正确写法应该是:

var remember bool = false
if something {
    remember = true 
}

2 误用字符串操作导致大量的内存开销

go语言中字符串也是不可变的,比如当连接2个字符串:a+=b,尤其在一个循环中进行类似操作时,因为源字符串不可变,会导致大量的内存拷贝,造成内存开销过大。所以一般使用一个字符数组代替字符串,将字符串内容写入一个缓存中,代码如下:

var b bytes.Buffer

for condition {
    b.WriteString(str) // 将字符串str写入缓存buffer
}
    return b.String()

3 误用defer关闭文件

如果在一个for循环内部处理一系列文件,我们希望使用defer确保文件处理完毕后能自动被关闭。代码如下:

for_, file:=range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    defer f.Close()
    f.Process(data)
}

但是,defer在循环结束后没有被执行,所以文件一直没有被关闭。因为defer仅在函数返回时才能自动执行。所以正确的写法应该是:

for_, file:=range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    f.Process(data)
    f.Close()
 }

4 误用new和make

例如错误的使用new初始化一个map,错误使用make创建一个数组等。new和make的使用场景如下:

  • 切片、映射和通道,使用make
  • 数组、结构体和所有的值类型,使用new

5 误用指向切片的指针

在go语言中,切片实际是一个指向数组的指针。所以当我们需要将切片作为一个参数传递给函数时,实际就是传递了一个指针变量,并且在函数内部可以改变该变量,而不是传递一个值拷贝,所以当切片作为参数传递是,不需要解引用切片,即:

  • 正确的做法:

func findBiggest( listOfNumbers []int ) int {}

  • 错误的做法:

func findBiggest( listOfNumbers *[]int ) int {}

6 误用指针指向一个接口类型

例如以下代码,nexter是一个接口类型,并且定义了一个next()方法读取下一字节。函数nextFew将nexter接口作为参数并读取接下来的num个字节,并返回一个切片。但是nextFew2使用一个指向nexter接口类型的指针作为参数传递给函数,编译程序时,系统会给出一个编译错误:n.next undefined (type *nexter has no field or method next) 。所以切记不要使用一个指针指向接口类型。

package main

import (
    “fmt”
)

type nexter interface {
    next() byte
}

func nextFew1(n nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next()
    }
    return b
}

func nextFew2(n *nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next() // 编译错误:n.next未定义(*nexter类型没有next成员或next方法)
    }
    return b
}

func main() {
    fmt.Println(“Hello World!”)
}

7 误用指针传递值类型参数

当为一个自定义类型定义方法时,如果不想让该方法改变接受者的数据,那么接受者是一个值类型,传递的是一个值拷贝,这里看似造成了内存开销,但其实值类型的内存是在栈上分配的,分配速度快且开销不大。但是如果传递一个指针类型,go编译器在很多情况下会认为需要创建一个对象,并将对象存入堆中,导致额外的内存分配。所以,如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

8 误用协程和通道

如果在一个循环内部使用了协程处理某些事务。当使用break、return或者panic跳出一个循环时,很有可能会导致内存溢出,因为此时协程正在处理某事务而被阻塞。因此在实际代码中,除非此处代码并发执行显得非常重要,才使用协程和通道,否则仅需写一个简单的过程式循环即可。

参考

《the way to go》

LEo at 23:11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jerry的SAP技术分享

Java异常处理:如何写出“正确”但被编译器认为有语法错误的程序

文章的标题看似自相矛盾,然而我在“正确”二字上打了引号。我们来看一个例子,关于Java异常处理(Exception Handling)的一些知识点。

1273
来自专栏IMWeb前端团队

还在纠结用不用ES6,不如来试试TypeScript

The only limits in our life are those we impose on ourselves. 弱爆的 ES6 in bro...

2300
来自专栏全栈之路

golang教程

这里有两个关键点。 - 其一是defer关键字。defer语句的含义是不管程序是否出现异常,均 在函数退出时自动执行相关代码。 - 其二是Go语言的函数允许返回...

4722
来自专栏尾尾部落

[剑指offer] 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串”+100″,”5e2″,”-123″,”3.1416″和”-1E-16″都表示数值。 ...

1102
来自专栏Java学习之路

02 Spring框架 简单配置和三种bean的创建方式

整理了一下之前学习Spring框架时候的一点笔记。如有错误欢迎指正,不喜勿喷。 上一节学习了如何搭建SpringIOC的环境,下一步我们就来讨论一下如何利...

3215
来自专栏全华班

java学习手册-JAVA程序员笔试题(一)

JAVA程序员笔试题(一) 一、选择题: 1、类的成员变量要求仅仅能够被同一package下的类访问,应该使用哪个修辞词 A. Protected、B. Pub...

3925
来自专栏py+selenium

[笨方法学python]习题51自动化测试笔记

本节自动化测试部分看不大懂,自己每步都打印出来,帮助理解。(代码标红部分为自己加入调试为打印变量值所用)

2092
来自专栏大内老A

yield在WCF中的错误使用——99%的开发人员都有可能犯的错误[上篇]

在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题。这里要说的是另一个问题:对于返回类型为...

1818
来自专栏懒人开发

AndroidStudio简单使用(二):左侧Structure

上面有说, 可以通过 Alt + 7 快捷键 调出来。 个人觉得, 这个对于查看代码结构,还是很方便的。直接可以看到代码的大体结构。 我们以 android...

2792
来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch聚合 之 Date Histogram聚合

Elasticsearch的聚合主要分成两大类:metric和bucket,2.0中新增了pipeline还没有研究。本篇还是来介绍Bucket聚合中的常用聚...

3147

扫码关注云+社区

领取腾讯云代金券