工作中用 go 设计了一个 stack 的数据结构
type Stack struct {
items []int
}
func (s *Stack) IsEmpty() bool {
return len(s.items) == 0
}
func (s *Stack) Push(item int) {
s.items = append(s.items, item)
}
func (s *Stack) Pop() (int, error) {
if s.IsEmpty() {
return 0, errors.New("pop from empty stack")
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, nil
}
func xx() {
s := Stack{}
// 往栈中 push 一些元素
s.Push(1)
for !s.IsEmpty() {
v, err := s.Pop()
if err != nil {
break
}
// do something
fmt.Println(v)
}
}
现在的问题就是这个 if err != nil {} 这一段代码在这里真的太丑了(我的函数其实是纯数据的处理,本来还是简单优雅的,加上这个 error 觉得代码变丑了),并且我的代码逻辑已经判断了 栈 不为空,里面的 err 判断其实根本没有必要,当然 go 可以强制忽略这个错误。但是,还是丑,并且强制忽略错误不严谨,看着别扭。
func xx() {
s := Stack{}
// 往栈中 push 一些元素
s.Push(1)
for !s.IsEmpty() {
v, _ := s.Pop()
// do something
fmt.Println(v)
}
}
最后我实在看不下去这种代码,直接用了 slice 。
func x2() {
var s []int
s = append(s, 1)
for len(s) != 0 {
v := s[len(s)-1]
// do something
fmt.Println(v)
s = s[:len(s)-1]
}
}
在我看来,go 的 error 如果用在业务逻辑里面,写 if err != nil {} 这种代码,我觉得没啥问题。但是在设计数据结构的时候,如果用到 error 确实很别扭,并且你还要 import errors 这个包。
我看了一下 go 的 sdk 里面一些数据结构的设计,比如 container/heap 堆的设计,它直接不判断 h.Len() 是否为 0 。这样倒是没有我说的那个 error 代码丑的问题,但是这样更不严谨了。
// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func Pop(h Interface) interface{} {
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}
如果我用 python 或者 java 这种带有异常的语言去写数据结构。
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
def push(self, item):
self.items.append(item)
def pop(self):
if self.is_empty():
raise IndexError("pop from empty stack")
return self.items.pop()
if __name__ == "__main__":
stack = Stack()
stack.push(1)
while not stack.is_empty():
v = stack.pop()
# do something
print(v)
这样我觉得好看多了。
还是不喜欢 go 一些大道至简的设计。
v = s.pop() //这里如果 panic 的话,后面的逻辑可能没法允许。
1
gitrebase 132 天前 7
python 你能 raise ,go 为啥不能 panic
|
2
BeijingBaby 132 天前 1
if err 在 go 中太常见了,习惯了就不丑了。
|
3
Nasei 132 天前
你可以加一个 不返回 err 的 pop 函数,为空时 panic 就行了,原来那个 pop 也能调用这个
|
4
povsister 132 天前 3
Pop() (int, err)
MustPop() int else panic python 能 raise ,为啥换 go 你不会 panic 了? |
5
henix 132 天前
如果是我的话可能会选择 pop 函数为空时 panic ,因为你已经提供了 IsEmpty ,为空时还要 Pop 可以认为是程序的逻辑错误(需要改程序)。
程序逻辑错误(需要程序员改程序):用 panic 外部错误(用户输入、上游第三方系统,程序员无法控制):用 error |
6
lesismal 132 天前
这种纯数据结构本身就不应该设计成返回 error 的,其他语言这种数据结构也没见过返回 error 之类的啊。。:
https://github.com/golang/go/blob/master/src/container/list/list.go |
7
doraemonki 132 天前 via Android
设计的有问题,看看别人的实现吧
|
8
Leviathann 132 天前 2
什么狗屁大道至简
go 的核心理念是又不是不能用,差不多得了 |
9
sagaxu 132 天前 2
panic => throw
recover => catch defer => finally 很多人就是这么滥用 |
10
rrfeng 132 天前 via Android
我觉得不是语言的问题。其他语言也一样。
或许用 1.22 的 range func 试试 |
11
w568w 132 天前 2
"Error is also a return value" 的设计理念就会导致这样的结果。当然这个思想本身没有错,只是 Go 执行得太尴尬了。
其他语言会加一些语法糖来缓解(例如 Rust 的 ?,Zig 的 try ),但 Go 受限于 minimum syntax sugar 的思想就只能这样弄。4 楼的 MustPop 是较优解。 Go 就是丑的,美观和写法优雅从来不是它的核心追求。如果你不能忍受,就果断换语言吧。 |
12
cmdOptionKana 132 天前
哪个语言没有丑的地方?
|
13
Keuin 132 天前
这个锅其实硬扣,可以扣到 go 头上,但是没有必要
```go var ( v int ok bool ) for v, ok = s.Pop(); ok; v, ok = s.Pop() { fmt.Println(v) } ``` 你要是喜欢用 error 的话,把`ok bool`换成`err error`也是一样的。这里体现出 Go 的问题是,没有内置 Option[T]类型和迭代器类型(虽然有库,但是没有语法糖配合,基本没有使用价值),想要语法层面有糖吃,就要封装成 channel ,有性能损失。 |
14
Trim21 132 天前
你这里可以要求调用者用 IsEmpty 来保证 pop 不为空,然后发现为空直接 panic 。
|
15
darksword21 132 天前 via iPhone 1
我用 python 现在反而很头大,因为我不是很清楚哪些操作会有异常,所以我只有两个选择,1 等出了异常改代码,2 到处 try ,其次我还不知道哪些异常是需要特殊处理的,比如 http exception 可能直接返回就行,总之我很怀念把 error 作为返回值显示处理,当然可以是我刚写不几天 python 还不太了解
|
16
hundandadi 132 天前 via Android
@BeijingBaby if err 不止一个,而是每一个的情况,咋能处理的优雅一点,比如三个函数每个都返回 err ,每个都 if err 烦死了
|
17
GeruzoniAnsasu 131 天前
你这个返回 error 明明是自找的,同 #14
菜就多练,人都简完了你硬要自己把复杂度加上去然后忍着,最后就会跟 #2 一样 说到底连你这个 Stack 类也完全没有必要 |
18
bv 131 天前
func (s *Stack) Pop() (int, bool) {
if s.IsEmpty() { return 0, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true } |
19
kalista 131 天前
@darksword21 同样遇到这个问题,所以现在我选择到处 try ,捕获异常后往上抛 error_str😂
|
20
homewORK 131 天前
为何要返回 err ? 直接返回 nil 不就好了嘛
|
21
zealic 131 天前
自己把没必要 error 的地方加了 error 怪谁
```golang package main import ( "fmt" ) type Stack struct { items []int } func (s *Stack) IsEmpty() bool { return len(s.items) == 0 } func (s *Stack) Push(item int) { s.items = append(s.items, item) } func (s *Stack) Pop() (int, bool) { if s.IsEmpty() { return 0, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true } func main() { s := Stack{} // 往栈中 push 一些元素 s.Push(1) for !s.IsEmpty() { if v, ok := s.Pop(); ok { // do something fmt.Println(v) } } } ``` |
22
su943515688 131 天前
@gitrebase 协程的 panic,其他协程无法捕获.程序就崩了.用这玩意等于埋雷
|
23
MoYi123 131 天前 1
多大点事.
cpp 里对空 vector popback 还是 ub 呢. |
24
james122333 131 天前 via Android
if v, err := s.Pop() ; err != nil {
break } else { fmt.Println(v) } |
25
CLMan 131 天前
|
26
gongquanlin 131 天前
一直把 if err != nil 和 java 的 if(xxx == null) 做等值处理,就不觉着丑了哈哈哈
写 java 的时候也得写一堆 if(xxxx == null) return xxx; 的处理 |
27
hxysnail 131 天前
实现都一个版本的 Pop 不就好了……
func (stack *Stack) MustPop() int { value, err := stack.Pop() if err != nil { panic(err) } return value } 如果有可能产生 err ,那么一定是要返回并检查 err 的; 如果不可能产生 err ,那么就实现一个不返回 err 的版本。 err 的真正槽点在于,当调用链比较深时,每一层都需要判断 err ,return err…… |
28
guanzhangzhang 131 天前
@darksword21 是的,一切都可能出错,所以 golang 的 err 返回值虽然很反人类,但是你的应用里对这个 err 怎么处理都是你可以做决定的
|
29
FYFX 131 天前
我看了一下 rust 的 vector 和 zig 的 array_list 的 pop/popOrNull 实现,它们都可以在 list 为空的时候返回 None/null 的功能,感觉是要比抛异常/报错合适
|
30
jonsmith 131 天前 via Android
该 panic 还是要 panic
|
31
sagaxu 131 天前
@gongquanlin Java 8 之后就是 Optional 了,少写很多 if (xxx == null)
public Membership getAccountMembership_classic() { Account account = accountRepository.get("johnDoe"); if(account == null || account.getMembership() == null) { throw new AccountNotEligible(); } return account.getMembership(); } 变成 public Membership getAccountMembership_optional() { return accountRepository.find("johnDoe") .flatMap(Account::getMembershipOptional) .orElseThrow(AccountNotEligible::new); } |
32
feiyan35488 131 天前
python: 相信用户
go: 相信用户都是 xx |
33
ns09005264 131 天前 via Android
pop 没必要返回 err ,对栈而言,pop 出一个 nil 值非常合理。
|