Go语言IDE GoLand的BUG

简介:

前言:

GoLand 是 Jetbrains 推出的 Golang IDE,在内侧阶段我就开始使用了,刚出的时候我还在博客中发表过文章(看了下日期是 16 年年底)。

那时候它还不是很完善,BUG 很多。准确的说也不算 BUG,主要是语法提示上的各种不足,重构功能也很弱。后来我有一段时间没有写 Go 代码,直到它更新为正式版我才差不多又抽出机会继续写 Go 代码了。虽然它已经很完善了,但还是发现它的一个很小但又很明显的 BUG,不过这个 BUG 却恰好给我造成了麻烦,所以我才想发文描述一下。

有关零值:

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

官方文档中明确指出,Slice(切片)的零值是 nil,在没有明确初始化的情况下这是显而易见的,任何 Go 开发者应该都知道才对。

但是对于 GoLand 而言,声明并直接初始化的空 Slice 和声明不初始化 Slice 是一回事。为什么这么说呢?如果你在 GoLand 中写入以下两句代码:

var sliceNoInit []string

sliceInit := []string{}

第二行代码的等号右边会有波浪线,提示这种写法可以被 Cleanup。如果你根据 IDE 提示按 Alt +Enter 那么第二行代码会被重构成:

var sliceInit []string

你没有看错,GoLand 将一个初始化过的 Slice 重构为了未初始化的的 Slice…… GoLand 认为前者是冗余的写法。的确,大部分函数对于 nil Slice 和 empty Slice 而言,都是一样的,例如它们的长度都是 0。不过:

nil Slice 的长度为 0,所以长度为 0 的都可以转变 nil Slice

这样考虑就有问题了。这有什么问题吗?它们两个长度不都是零么?当然有问题,在 len 函数的注释清晰的写了这么一句话:

if v is nil, len(v) is zero

这是什么意思呢?就是说 len 函数允许参数为 nil,但只要参数为 nil 不管任何类型都会返回 0。所以 nil Slice 的长度为零是一种不严禁的说法,应该说调用 len 函数的结果为零。

所以,初始化后空的 Slice 是绝对不等同于没有初始化的 nil Slice 的。最直接的,它们对 slice == nil 的结果就相反。

带来的麻烦:

在使用某些 JSON 框架的时候,nil Slice 会被解析为 null 而不是 [],如果你有 web 开发经验就知道这是个多么严重的小问题了。而我恰好就是遇到这个问题了,最后追查原因原来是我手动初始化的空 Slice 被重构成了未初始化 nil Slice,直接导致了这个转换为 null 的结果。

我们用伪代码举例(不涉及具体框架):

var list []string


if err,result := QueryAll();err== nil{


 list = result.Rows

}

ToJson(map[string]interface{}{

 "list": list,

})

有没有看出什么问题?如果 QueryAll 返回错误的话,那么 list 永远不会被初始化,则很有可能在转换时被解析为 null 而不是 []。

如下代码:

package main





import (


 "encoding/json"


 "github.com/pkg/errors"


 "fmt"


)





type result struct {


 Rows []string


 Columes []string


}





func main() {


 //1、var list []string






 //2、list := make([]string, 0)


 list := make([]string, 0)






 //3、list := []string{}






 if err, result := QueryAll(); err == nil {


 list = result.Rows


 }

 ss, err := json.Marshal(map[string]interface{}{


 "list": list,


 })





 fmt.Printf("ss:%s, error:%v", string(ss), err)


}





func QueryAll() (error, result) {


 return errors.New("error"), result{}


}

如果用第一种方式初始化:

var list []string



结果为:


ss:{"list":null}, error:<nil>

用第二种和第三种方式初始化:

list := make([]string, 0)


list := []string{}



结果为:


ss:{"list":[]}, error:<nil>

如果你想这么写解决这个弊端:

list := []string{}

那么你会被 GoLand 活活纠正成上面那样…… 然后带来错误的结果。

真实细节:

在 GoLand 的博客中有一篇文章提到了关于 Slice 的零值,他们是这么认为的:

a nil slice is functionally equivalent to a zero-length slice, even though it points to nothing

而造成 gin 的 JSON 方法将 nil Slice 转换 null 的原因是 json.Marshal() 就是这么处理的,也就是说几乎任何涉及到 JSON 处理的地方都有可能因为 GoLand 团队认为二者在功能上完全等价而造成问题。

解决办法:

进入 Settings -> Editor - Inspections

展开 Go - Declaration redundancy

将 Empty slice declared via literal 右边的勾去掉

aa713ef0cfc9a384c38400895810d8cd98509fae

这么做可以关掉 GoLand 对直接初始化的空 Slice 的冗余检查。

不过有意思的是还有一种以代码的方式杜绝这种提示,那就是使用 make 函数:

list := make([]string, 0)

实际上使用 make 创建一个长度为 0 的 empty Slice 和不插入一个值的直接初始化这两者才是等价的…… 但是 GoLand 却不会将 make 函数这种初始化方式算做“冗余”写法。


原文发布时间为:2018-09-5

本文作者:绅士喵

本文来自云栖社区合作伙伴“我的小碗汤”,了解相关信息可以关注“我的小碗汤”。

相关文章
|
8月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
8月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
244 0
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
114 10
|
6月前
|
人工智能 IDE Go
JetBrains GoLand 2025.1 发布 - 为 Go 开发者打造的完整 IDE
JetBrains GoLand 2025.1 (macOS, Linux, Windows) - 为 Go 开发者打造的完整 IDE
227 0
JetBrains GoLand 2025.1 发布 - 为 Go 开发者打造的完整 IDE
|
8月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
187 3
Go 语言入门指南:切片