『InfoQ』Go 语言的微吐槽,这是一篇实践者对( 二 )


client,err:=NewClient()iferr!=nil{returnerr}deferclient.Close()
resp,err:=client.GetSomething()iferr!=nil{returnerr}
process(resp)
假如我想调试代码 , 并注释掉对process函数的调用:
client,err:=NewClient()iferr!=nil{returnerr}deferclient.Close()
resp,err:=client.GetSomething()iferr!=nil{returnerr}
//process(resp)
现在 , 编译器会出现:respdeclaredandnotused(resp已声明但未使用) 。 好的 , 我使用_代替resp:
client,err:=NewClient()iferr!=nil{returnerr}deferclient.Close()
_,err:=client.GetSomething()
//process(resp)
现在编译器将提示:nonewvariablesonleftsideof:=(:=左侧没有新变量) 。 !之前已声明了err , 我将使用=代替:=
client,err:=NewClient()iferr!=nil{returnerr}deferclient.Close()
_,err=client.GetSomething()
//process(resp)
终于通过编译 , 但是为了注释掉一行代码还得更改代码两次才行 。 我经常要做更多的编辑工作才能让程序通过编译 。
我希望编译器有一种开发模式 , 其中未使用的变量只会给出警告 , 而不会阻止编译 , 这样编辑-编译-调试的周期不会像现在这样麻烦 。
返回错误
在Go语言社区中有很多关于错误管理的讨论 。 我个人不介意iferr!=nil{returnerr}这种模式 。 它可以再做改进 , 并且有人已经在Go2中提出了对其改进的提案 。
最让我感到困扰的是元组样式返回 。 当一个函数可能产生错误时 , 你仍然必须在发生错误时提供有效伪值 。 比如 , 函数返回(int,error) , 那么必须return0,err , 也就是说就算一切正常 , 也还是要为返回的int提供一个值 。
我觉得这从根本上就是错的 。 首先 , 当出现错误时 , 我用不着找出一些伪值也应该能返回才是 。 这导致了指针的过度使用 , 因为returnnil,err比返回具有零值的空结构 , 和诸如returnUser{},err之类的错误要容易得多 , 也更干净 。
其次 , 提供有效伪值后 , 我们很容易假设伪值就是正确的 , 然后在调用侧略过错误而继续下去 。
//Thefactthaterrisdeclaredandusedheremakesitso//there'snowarningsaboutitbeingunusedbelow.err:=hello()iferr!=nil{returnerr}x,err:=strconv.ParseInt("notanumber",10,32)//Forgettocheckerr,nowarningdoSomething(x)
相比起简单返回nil来说 , 这种错误更难找到 。 因为如果我们返回了nil , 我们应该会在后面代码行的某处出现nil指针panic 。
我认为支持求和类型的语言(例如Rust、Haskell或OCaml)可以更优雅地解决这个问题 。 发生错误时 , 它们无需为非错误返回值提供一个值 。
enumResult{Ok(T),Err(E),}
结果要么是Ok(T) , 要么是Err(E) , 而不会两者都是 。
fnconnect(portu32)->Result{ifport>65536{//notethatIdon'thavetoprovideavalueforSocketreturnErr(Error::InvalidPort);}//...}
nil切片和JSON
在Go中创建切片的推荐方法是使用var声明 , 例如varvals[]int 。 这个语句会创建一个nil切片 , 这意味着没有数组支持此切片:它只是一个nil指针 。 append函数支持附加到一个nil切片 , 这就是为什么可以使用模式vals=append(vals,x)的原因所在 。 len函数也支持nil切片 , 当切片为nil时返回0 。 在实践中 , 大多数情况下这用起来挺不错的 , 但它也会导致奇怪的行为 。
例如 , 假设正在构建一个JSONAPI 。 我们从一个数据库查询事务并将它们转换为对象 , 以便可以将它们序列化为JSON 。 服务层如下所示:
packagemodels
import"sql"
typeCustomerstruct{Namestring`json:"name"`Emailstring`json:"email"`}