GO踩过的一些坑

this

在其他面向对象语言的编程开发和定义时候,我们通常会将函数或者方法定义成this,self,方便且好记,但是在go中,这样的情况以及定义是不符合语法规则的。

首先我们先看一下官方的定义,官方推荐的标准命名Receiver Names,以下部分内容摘抄自Go Wiki: Go Code Review Comments - The Go Programming Language

Contexts

Values of the context.Context type carry security credentials, tracing information, deadlines, and cancellation signals across API and process boundaries. Go programs pass Contexts explicitly along the entire function call chain from incoming RPCs and HTTP requests to outgoing requests.

Most functions that use a Context should accept it as their first parameter:

func F(ctx context.Context, /* other arguments */) {}

A function that is never request-specific may use context.Background(), but err on the side of passing a Context even if you think you don’t need to. The default case is to pass a Context; only use context.Background() directly if you have a good reason why the alternative is a mistake.

Don’t add a Context member to a struct type; instead add a ctx parameter to each method on that type that needs to pass it along. The one exception is for methods whose signature must match an interface in the standard library or in a third party library.

Don’t create custom Context types or use interfaces other than Context in function signatures.

If you have application data to pass around, put it in a parameter, in the receiver, in globals, or, if it truly belongs there, in a Context value.

Contexts are immutable, so it’s fine to pass the same ctx to multiple calls that share the same deadline, cancellation signal, credentials, parent trace, etc.

Copying

To avoid unexpected aliasing, be careful when copying a struct from another package. For example, the bytes.Buffer type contains a []byte slice. If you copy a Buffer, the slice in the copy may alias the array in the original, causing subsequent method calls to have surprising effects.

In general, do not copy a value of type T if its methods are associated with the pointer type, *T.

Crypto Rand

Do not use package math/rand to generate keys, even throwaway ones. Unseeded, the generator is completely predictable. Seeded with time.Nanoseconds(), there are just a few bits of entropy. Instead, use crypto/rand’s Reader, and if you need text, print to hexadecimal or base64:

import (
    "crypto/rand"
    // "encoding/base64"
    // "encoding/hex"
    "fmt"
)

func Key() string {
    buf := make([]byte, 16)
    _, err := rand.Read(buf)
    if err != nil {
        panic(err)  // out of randomness, should never happen
    }
    return fmt.Sprintf("%x", buf)
    // or hex.EncodeToString(buf)
    // or base64.StdEncoding.EncodeToString(buf)
}

Receiver Names

The name of a method’s receiver should be a reflection of its identity; often a one or two letter abbreviation of its type suffices (such as “c” or “cl” for “Client”). Don’t use generic names such as “me”, “this” or “self”, identifiers typical of object-oriented languages that gives the method a special meaning. In Go, the receiver of a method is just another parameter and therefore, should be named accordingly. The name need not be as descriptive as that of a method argument, as its role is obvious and serves no documentary purpose. It can be very short as it will appear on almost every line of every method of the type; familiarity admits brevity. Be consistent, too: if you call the receiver “c” in one method, don’t call it “cl” in another. 

简单翻译总结有如下2点:

  1. 方法接受者名称应反映其身份, 并且不要使用methisself这些面向对象语言的典型标志符。
  2. 在go中方法接受者其实就是方法的另一个参数,在后面也是直接用该参数来进行数值的传递。

Receiver Type

Choosing whether to use a value or pointer receiver on methods can be difficult, especially to new Go programmers. If in doubt, use a pointer, but there are times when a value receiver makes sense, usually for reasons of efficiency, such as for small unchanging structs or values of basic type. Some useful guidelines:

  • If the receiver is a map, func or chan, don’t use a pointer to them. If the receiver is a slice and the method doesn’t reslice or reallocate the slice, don’t use a pointer to it.
  • If the method needs to mutate the receiver, the receiver must be a pointer.
  • If the receiver is a struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying.
  • If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it’s equivalent to passing all its elements as arguments to the method. If that feels too large, it’s also too large for the receiver.
  • Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy of the receiver when the method is invoked, so outside updates will not be applied to this receiver. If changes must be visible in the original receiver, the receiver must be a pointer.
  • If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention clearer to the reader.
  • If the receiver is a small array or struct that is naturally a value type (for instance, something like the time.Time type), with no mutable fields and no pointers, or is just a simple basic type such as int or string, a value receiver makes sense. A value receiver can reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. (The compiler tries to be smart about avoiding this allocation, but it can’t always succeed.) Don’t choose a value receiver type for this reason without profiling first.
  • Don’t mix receiver types. Choose either pointers or struct types for all available methods.
  • Finally, when in doubt, use a pointer receiver.

简单翻译总结如下:

选择是在方法上使用值接收器还是指针接收器是很困难的,特别是对新的Go程序员来说。如果有疑问,请使用指针,但有时值接收器是有意义的,通常是出于效率的原因,例如对于小型不变结构体或基本类型的值 。

1、如果接收对象是map、func或chan,不要使用指向它们的指针。如果接收者是一个切片,并且该方法没有重新切片或重新分配切片,则不要使用指向它的指针。
2、如果方法需要改变接收者,那么接收者必须是一个指针。
3、如果接收者是一个包含sync的结构体。互斥锁或类似的同步字段,接收方必须是指针,以避免复制。
4、如果接收对象是一个大的结构体或数组,则指针接收对象更有效。多大才算大?假设它等同于将其所有元素作为参数传递给方法。如果感觉太大,对接收者来说也太大了。
5、函数或方法,无论是并发的还是从该方法调用时,是否会改变接收者?值类型在调用方法时创建接收者的副本,因此外部更新不会应用于此接收者。如果更改必须在原始接收器中可见,则接收器必须是指针。
6、如果接收对象是结构体、数组或切片,并且其中的任何元素都是指向可能发生变化的对象的指针,则首选指针接收对象,因为它将使读者更清楚地了解意图。
如果接收对象是一个小数组或结构体,它本身就是一个值类型(例如,像time。时间类型),没有可变字段和指针,或者只是一个简单的基本类型,如int或string,值接收器是有意义的。一个值接收者可以减少可能产生的垃圾数量;如果将值传递给value方法,则可以使用堆栈上的副本而不是在堆上分配。(编译器试图聪明地避免这种分配,但它并不总是成功。)由于这个原因,不要在没有进行分析之前选择值接收器类型。
7、不要混合接收类型。为所有可用的方法选择指针或结构类型。
8、最后,如果有疑问,请使用指针接收器。

// Test ...
type Test struct {
    A int
}

// SetA ...
func (t Test) SetA(a int) {
    t.A = a
}

// SetA1 ...
func (t *Test) SetA1(a int) {
    t.A = a
}

func main() {
    t := Test{
        A: 3,
    }
    fmt.Println("demo1:")
    fmt.Println(t.A)
    t.SetA(5)
    fmt.Println(t.A)
    t1 := Test{
        A: 4,
    }
    fmt.Println("demo2:")
    fmt.Println(t1.A)
    (&t1).SetA1(6)
    fmt.Println(t1.A)
}
// output:
demo1:
3
3
demo2:
4
6

 以上是一个简短的demo,

看上面的demo我们知道, 当receiver不是指针时调用SetA其值根本没有改变。

因为Go中都是值传递,所以你如果对SetA的receiver的名称命名为thisself等,它就已经失去了本身的意义——“调用一个对象的方法就是向该对象传递一条消息”。而且对象本身的属性也并不一定会发生改变,因此用千万不要再用这些有特殊含义的名称,这个坑当时我也琢磨了很久(都是基本不扎实,直接上手敲代码导致的)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值