使用 go 设计数据结构很蛋疼的一个点

65 天前
 yujianwjj

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

4767 次点击
所在节点    Go 编程语言
33 条回复
zealic
64 天前
自己把没必要 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)
}
}
}
```
su943515688
64 天前
@gitrebase 协程的 panic,其他协程无法捕获.程序就崩了.用这玩意等于埋雷
MoYi123
64 天前
多大点事.
cpp 里对空 vector popback 还是 ub 呢.
james122333
64 天前
if v, err := s.Pop() ; err != nil {
break
} else {
fmt.Println(v)
}
CLMan
64 天前
gongquanlin
64 天前
一直把 if err != nil 和 java 的 if(xxx == null) 做等值处理,就不觉着丑了哈哈哈
写 java 的时候也得写一堆 if(xxxx == null) return xxx; 的处理
hxysnail
64 天前
实现都一个版本的 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……
guanzhangzhang
64 天前
@darksword21 是的,一切都可能出错,所以 golang 的 err 返回值虽然很反人类,但是你的应用里对这个 err 怎么处理都是你可以做决定的
FYFX
64 天前
我看了一下 rust 的 vector 和 zig 的 array_list 的 pop/popOrNull 实现,它们都可以在 list 为空的时候返回 None/null 的功能,感觉是要比抛异常/报错合适
jonsmith
64 天前
该 panic 还是要 panic
sagaxu
64 天前
@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);
}
feiyan35488
64 天前
python: 相信用户
go: 相信用户都是 xx
ns09005264
64 天前
pop 没必要返回 err ,对栈而言,pop 出一个 nil 值非常合理。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1057520

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX