工作中用 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 一些大道至简的设计。

