前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang踩坑记录

Golang踩坑记录

作者头像
王小明_HIT
发布2023-07-08 16:01:20
1700
发布2023-07-08 16:01:20
举报
文章被收录于专栏:程序员奇点程序员奇点

Golang踩坑记录-1

平时 Golang 开发中会遇到的“坑点”,总结一下,避免重复踩坑

Interface

看代码,答问题

代码语言:javascript
复制
func main() {
   var i interface{}
   fmt.Println(i == nil)
}

结果:

代码语言:javascript
复制
true

再看如下代码:

代码语言:javascript
复制
func main() {
   var p  map[string]string
   var i interface{} = p
   fmt.Println(i == nil)
}

结果:

代码语言:javascript
复制
false
代码语言:javascript
复制
package main

import (
   "code.byted.org/live/utils/errors"
   "fmt"
)

func Test() *errors.LiveError {
   // ... 

   // if xxx {
   return nil
   // }

   //...
}

func main() {
   var err error

   err = Test()
   if err != nil {
      //
      fmt.Println("err is not nil")
   }
}

分析

在Go语言中,一个interface{}类型的变量包含两个指针,一个指向其类型,另一个指向真正的值。只有当类型和值都是nil的时候,才等于nil。当我们将一个具体类型的值赋值给一个interface类型的变量的时候,就同时把类型和值都赋值给了interface里的两个指针。如果这个具体类型的值是nil的话,interface变量依然会存储对应的类型指针和值指针。这个时候拿这个interface变量去和nil常量进行比较的话就会返回false。实战的踩坑 网上的实战例子,详细参考及详解 https://studygolang.com/articles/10635 这是我们在GoWorld分布式游戏服务器的开发中,碰到的一个实际的bug。由于GoWorld支持多种不同的数据库(包括MongoDB,Redis等)来保存服务端对象,因此GoWorld在上层提供了一个统一的对象存储接口定义,而不同的对象数据库实现只需要实现EntityStorage接口所提供的函数即可。

代码语言:javascript
复制
// EntityStorage defines the interface of entity storage backendstype 
EntityStorage interface {
    List(typeName string) ([]common.EntityID, error)
    Write(typeName string, entityID common.EntityID, data interface{}) error
    Read(typeName string, entityID common.EntityID) (interface{}, error)
    Exists(typeName string, entityID common.EntityID) (bool, error)
    Close()
    IsEOF(err error) bool
}

以一个使用Redis作为对象数据库的实现为例,函数OpenRedis连接Redis数据库并最终返回一个redisEntityStorage对象的指针。

代码语言:javascript
复制
// OpenRedis opens redis as entity storagefunc 
OpenRedis(url string, dbindex int) *redisEntityStorage {
    c, err := redis.DialURL(url)
    if err != nil {
        return nil
    }
    if dbindex >= 0 {
        if _, err := c.Do("SELECT", dbindex); err != nil {
            return nil
        }
    }
    es := &redisEntityStorage{
        c: c,
    }
    return es
}

在上层逻辑中,我们使用OpenRedis函数连接Redis数据库,并将返回的redisEntityStorage指针赋值个一个EntityStorage接口变量,因为redisEntityStorage对象实现了EntityStorage接口所定义的所有函数。

代码语言:javascript
复制
var storageEngine StorageEngine // 这是一个全局变量
storageEngine = OpenRedis(cfg.Url, dbindex)
if storageEngine != nil {
    // 连接成功
    ...} else {
    // 连接失败
    ...}

上面的代码看起来都很正常,OpenRedis在连接Redis数据库失败的时候会返回nil,然后调用者将返回值和nil进行比较,来判断是否连接成功。这个就是Go语言少有的几个深坑之一,因为不管OpenRedis函数是否连接Redis成功,都会运行连接成功的逻辑。

想要避开这个Go语言的坑,我们要做的就是避免将一个有可能为nil的具体类型的值赋值给interface变量。以上述的OpenRedis为例,一种方法是先对OpenRedis返回的结果进行非-nil检查,然后再赋值给interface变量,如下所示。

代码语言:javascript
复制
var storageEngine StorageEngine // 这是一个全局变量
redis := OpenRedis(cfg.Url, dbindex)if redis != nil {
    // 连接成功
    storageEngine = redis // 确定redis不是nil之后再赋值给interface变量} else {
    // 连接失败
    ...}

另外一种方法是让OpenRedis函数直接返回EntityStorage接口类型的值,这样就可以把OpenRedis的返回值直接正确赋值给EntityStorage接口变量。

代码语言:javascript
复制
// OpenRedis opens redis as entity storagefunc 
OpenRedis(url string, dbindex int) EntityStorage {
    c, err := redis.DialURL(url)
    if err != nil {
        return nil
    }

    if dbindex >= 0 {
        if _, err := c.Do("SELECT", dbindex); err != nil {
            return nil
        }
    }
    es := &redisEntityStorage{
        c: c,
    }
    return es
}

避雷

想要避开这个Go语言的坑,我们要做的就是避免将一个有可能为nil的具体类型的值赋值给interface变量。

参考

  • https://studygolang.com/articles/10635

Json反序列化

看代码,答问题

代码语言:javascript
复制
package main
import (
   "encoding/json"
   "fmt"
)

func main() {
   originPid := int64(1234567890123456789)
   jsonStr := fmt.Sprintf("{\"userID\":1,\"config\":{\"pid\":%v,\"target_type\":1}}", originPid)
   var result map[string]interface{}

   _ = json.Unmarshal([]byte(jsonStr), &result)

   finalPid := result["config"].(map[string]interface{})["pid"]
   fmt.Printf("PID类型:\t%T \nPID值:\t%f\n", finalPid, finalPid)
   fmt.Printf("Int64:%d", int64(finalPid.(float64)))
}

分析 To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:

JSON

Interface

booleans

bool

numbers

float64

strings

string

arrays

[]interface{}

objects

map[string]interface{}

null

nil

由于float64类型所支持的精度问题,我们会损失一丢丢精度……就如上面的转换所示,最后两位已然面目全非……

避雷

既然 json.Unmarshal 处理较大的数会产生精度问题,那么不要让它处理数字就行。json.Decoder 就能实现这样的操作。json.Decoder 支持这样一个方法:UseNumber

它是这样说明的:

UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.

UseNumber使Decoder将数字作为json.Number解析到interface{}中而不是float64。而 json.Number 提供将其转换为 Int64 类型的方法。所以,我们可以这样写:

代码语言:javascript
复制
package main

import (
   "bytes"
   "encoding/json"
   "fmt"
)

func main() {
   originPid := int64(1234567890123456789)
   jsonStr := fmt.Sprintf("{\"userID\":1,\"config\":{\"pid\":%v,\"target_type\":1}}", originPid)
   var result map[string]interface{}

   decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))
   decoder.UseNumber()
   _ = decoder.Decode(&result)

   finalPid := result["config"].(map[string]interface{})["pid"]
   fmt.Printf("PID类型:\t%T \nPID值:\t%f\n", finalPid, finalPid)

   pidValue, _ := finalPid.(json.Number).Int64()
   fmt.Printf("Int64:%d", pidValue)
}

参考

https://blog.moonlightwatch.me/2019/03/01/golang-json-unmarshal-number/

Json序列化

看代码,答问题

代码语言:javascript
复制
package main

import (
   "encoding/json"
   "fmt"
)

type MyData struct {
   One int
   two string
}

func main() {
   in := MyData{1, "two"}
   fmt.Printf("%#v\n", in)
   //prints main.MyData{One:1, two:"two"}

   encoded, _ := json.Marshal(in)
   fmt.Println(string(encoded))
   //prints 是 {"One":1,"Two":"two"} ??
}

分析

代码语言:javascript
复制
package member

type Member struct {
   id     int
   name   string
   email  string
   gender int
   age    int
}


package main

import "test/member"

func main() {
   _ = member.Member{1, "小明", "xiaoming@163.com", 1, 18} 
   //会引发panic错误???
}

我们定义结构体字段名首字母是小写的,这意味着这些字段在包外不可见,因而无法在其他包中被访问,只允许包内访问。

下面的例子中,我们将Member声明在member包中,而后在main包中创建一个变量,但由于结构体的字段包外不可见,因此无法为字段赋初始值,无法按字段还是按索引赋值,都会引发panic错误。

参考

  • https://www.liuin.cn/2018/08/23/Go-%E6%98%93%E9%87%87%E5%9D%91%E6%80%BB%E7%BB%93/
  • https://juejin.im/post/5ca2f37ce51d4502a27f0539
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-06-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Golang踩坑记录-1
    • Interface
      • 分析
      • 避雷
      • 参考
    • Json反序列化
      • 避雷
      • 参考
      • Json序列化
      • 参考
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档