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

本文作者:绅士喵

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

相关文章
|
10天前
|
程序员 Go PHP
为什么大部分的 PHP 程序员转不了 Go 语言?
【9月更文挑战第8天】大部分 PHP 程序员难以转向 Go 语言,主要因为:一、编程习惯与思维方式差异,如语法风格和编程范式;二、学习成本高,需掌握新知识体系且面临项目压力;三、职业发展考量,现有技能价值及市场需求不确定性。学习新语言虽有挑战,但对拓宽职业道路至关重要。
41 10
|
9天前
|
Go API 开发者
深入探讨:使用Go语言构建高性能RESTful API服务
在本文中,我们将探索Go语言在构建高效、可靠的RESTful API服务中的独特优势。通过实际案例分析,我们将展示Go如何通过其并发模型、简洁的语法和内置的http包,成为现代后端服务开发的有力工具。
|
11天前
|
算法 程序员 Go
PHP 程序员学会了 Go 语言就能唬住面试官吗?
【9月更文挑战第8天】学会Go语言可提升PHP程序员的面试印象,但不足以 solely “唬住” 面试官。学习新语言能展现学习能力、拓宽技术视野,并增加就业机会。然而,实际项目经验、深入理解语言特性和综合能力更为关键。全面展示这些方面才能真正提升面试成功率。
34 10
|
10天前
|
编译器 Go
go语言学习记录(关于一些奇怪的疑问)有别于其他编程语言
本文探讨了Go语言中的常量概念,特别是特殊常量iota的使用方法及其自动递增特性。同时,文中还提到了在声明常量时,后续常量可沿用前一个值的特点,以及在遍历map时可能遇到的非顺序打印问题。
|
8天前
|
存储 监控 数据可视化
Go 语言打造公司监控电脑的思路
在现代企业管理中,监控公司电脑系统对保障信息安全和提升工作效率至关重要。Go 语言凭借其高效性和简洁性,成为构建监控系统的理想选择。本文介绍了使用 Go 语言监控系统资源(如 CPU、内存)和网络活动的方法,并探讨了整合监控数据、设置告警机制及构建可视化界面的策略,以满足企业需求。
25 1
|
15天前
|
安全 大数据 Go
深入探索Go语言并发编程:Goroutines与Channels的实战应用
在当今高性能、高并发的应用需求下,Go语言以其独特的并发模型——Goroutines和Channels,成为了众多开发者眼中的璀璨明星。本文不仅阐述了Goroutines作为轻量级线程的优势,还深入剖析了Channels作为Goroutines间通信的桥梁,如何优雅地解决并发编程中的复杂问题。通过实战案例,我们将展示如何利用这些特性构建高效、可扩展的并发系统,同时探讨并发编程中常见的陷阱与最佳实践,为读者打开Go语言并发编程的广阔视野。
|
12天前
|
存储 Shell Go
Go语言结构体和元组全面解析
Go语言结构体和元组全面解析
|
17天前
|
Go
golang语言之go常用命令
这篇文章列出了常用的Go语言命令,如`go run`、`go install`、`go build`、`go help`、`go get`、`go mod`、`go test`、`go tool`、`go vet`、`go fmt`、`go doc`、`go version`和`go env`,以及它们的基本用法和功能。
25 6
|
17天前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
19 3
|
19天前
|
缓存 安全 Java
如何利用Go语言提升微服务架构的性能
在当今的软件开发中,微服务架构逐渐成为主流选择,它通过将应用程序拆分为多个小服务来提升灵活性和可维护性。然而,如何确保这些微服务高效且稳定地运行是一个关键问题。Go语言,以其高效的并发处理能力和简洁的语法,成为解决这一问题的理想工具。本文将探讨如何通过Go语言优化微服务架构的性能,包括高效的并发编程、内存管理技巧以及如何利用Go生态系统中的工具来提升服务的响应速度和资源利用率。