go 转 Python 的心智负担增加

10 天前
 yujianwjj

之前写 go ,go 调用函数的时候,有问题就是通过 返回值 有没有 err 来判断。心智负担很小,直接撸就行。

一个简单的案例,打开文件,然后 json 解析。 go 版本是

func TestA(t *testing.T) {
	fd, err := os.OpenFile("a.txt", os.O_RDWR, 0666)
	if err != nil {
		fmt.Println("打开文件失败", err)
	}
	defer fd.Close()

	var data map[string]string
	err = json.NewDecoder(fd).Decode(&data)
	if err != nil {
		fmt.Println("解析文件失败", err)
	}
	fmt.Println(data)
}

但是到 python 这边

f = open("a.json")
data = json.load(f)
f.close()

但是吧

  1. 如果文件不存在需要处理
  2. 文件存在,open() 执行异常了要处理
  3. json.load() 会不会异常?我跳转源码看 josn.load()源码也没看到他会返回哪些异常信息。

所以我写出这种代码

try:
	with open("a.json", "r") as f:
		data = json.load(f)
except Exception as e:
	print(e)

但是这样把多种异常合到一个 Exception 了,如果出现异常,你不知道是哪个函数调用出现的异常。所以就需要这样。

try:
	with open("a.json", "r") as f:
    	try:
			data = json.load(f)
    	except Exception as e:
			print(e)        
except Exception as e:
	print(e)

然后我发现,最大的问题就是,我每次调用一个外部函数的时候,TMD 根本不知道这个函数会不会异常,总不能每个函数调用都用 try/except 处理一下?

try:
	f1()
except Exception as e:
	print(e)
    
try:
	f2()
except Exception as e:
	print(e)
    
try:
	f3()
except Exception as e:
	print(e)

写 python 给我的感受就是,想写好一个健壮的程序,有很大的心智负担,我总是要考虑我调用的函数会不会有异常。

8086 次点击
所在节点    Python
96 条回复
lasuar
10 天前
楼主你提出的是一个经典的语言设计中的 error 处理方式问题,可以去 google 搜一下各语言的 error 设计思想。具体选择真的是看自己喜好了
cloudzhou
10 天前
@wx497657341 go 压根没有 “异常处理”,除了一个 defer ,何来 “碰”

这是一个经典的 error vs exception 的错误处理两种方式
go 作者专门写了一个文章解释 https://go.dev/blog/errors-are-values

对我来说,因为有 Java 、Go 的经验,很理解两种方式为什么都存在

当需要非常严谨代码,同时需要记录具体在哪里中断的时候,Go 的每一次处理 error 就很有效果,讲究“立刻处理错误”
当只需要全局性的 try catch 然后抛出一个业务错误,不关注具体哪里出错了,异常非常有用
wuhunyu
10 天前
现在应该就三种异常处理方式吧
1. try-catch:以 java ,python 等语言为代表,应该是大部分语言的选择
2. 返回错误:以 go ,c 为代表
3. 强制处理错误:以 rust 为代表
其实写多了 java 这样的,对于一些第三方库的方法,都是直接捕获 Exception 异常的。对于必须要处理的异常,像是 go 这样显式返回错误的处理方式还挺直观的。但如果说能够明确不会发生错误,那还是 java 这种 catch 的方式好,代码会简洁很多。至于 rust 的处理,对于我这种只使用 java ,python 和 go 的开发者来说,有点过于繁琐了
crackidz
10 天前
写脚本需要看你使用的库代码质量和文档怎么样,否则很容易转角遇到惊喜,各种意义上的惊喜

我很想吐槽,很多库的作者其实工程意识不强,哪怕常见的库都有各种各样神奇的神奇 bug 。这种问题在前端库和 Python 之类的库都是很常见的问题,你甚至能在使用非常广泛的库中发现神奇的泄露等等 bug ,几年都不处理挂在那。至于那种 Catch 了之后不抛新异常或者不返回出来更是很多作者会干的事,给你打印了个日志完事...

当然,这些槽点不是这些语言生态特有的,是比较常见的开源库通用问题了...有些时候上生产,不乱用开源库是一个技术活,如何做好监控和故障重现都很需要好的开发者和 SRE 后面盯着。实话实说,这类问题一般在大家经常吐槽的 Java 和 Go 项目中处理起来稍微简单一些,一般这些的项目工程配置比较完善,即便出现定位也相对容易一些
crackidz
10 天前
@crackidz 另外补充一下 OP 提到的一些具体问题,首先大概可以确定 OP 没读 Python 的官方文档。Python 的官方文档其实这方面做的比较好的典型,会明确对应的异常,比如你提到的 https://docs.python.org/3/library/json.html#json.load 当然,Python 其实历史项目比较久,有些文档属于上古时代的,所以不代表所有的都会有完善的记录和统一的格式
nlimpid
10 天前
我对 Python 也不是很熟悉,不过我还是认为 Python 的错误处理更好些。

1. Go 的这种显式的把 Err 当成返回值的设计选择会让代码变的啰嗦一些。对于题主的例子来说,Python 生态可以根据不同的场景选择每一次处理都抛异常或者把异常合并在一起,让 Happy Path 更加清楚。



```
try:
result = foo()
do_something(result) // 这里也可以每次都 try ,和 Go 就一样了。
except FooError as e:
```

2. 我认为题主的这句话「很多时候,我跳转到函数的源码里面,也看不出会返回什么异常」是因为对语言熟悉程度导致的。我常写 Go 就知道常见的 io.EOF, os.ErrNotExist 。但从实际文档来看可能差异不大,以 JSON 为例,Python Doc 把 Exception 都放一起来相对来说还好找些。https://docs.python.org/3/library/json.html#exceptions https://pkg.go.dev/encoding/json#MarshalerError
ipwx
10 天前
楼主只要把 go 里面所有


if err := xxx(); err != nil {
return err
}

的地方全换成朴素的 xxx() 就对了。

====

然后当然也不需要

if err := xxx(); err != nil {
log.Formatf("xxx: %v", err)
return err
}

因为 python err 自动带堆栈信息。

====

总结来说就是,只要不是需要对 err 进行恢复的地方,一律就不要做 try ... catch ...
IurNusRay
10 天前
如果你想捕获特定异常,那就写多个 except, 如果你只是单纯的想在出现异常的时候是哪个函数、哪一行报的错,那就在 except 里面 print(traceback.format_exc()), 这样会把整个异常出现的链路打印出来,具体到每一行
Zy143L
10 天前
..这 处理几个常见的异常主动抛出就行了 其他没碰见的问题直接全局 try 兜底不就行了
fbichijing
10 天前
每一种语言有每一种语言的可取之处,它们射程不同,生态也不一样。
如果你所熟悉的 go 语言通杀一切,你也不必学习 python 。
如果说你刚才的目的是解析一个 json 文件,也就是原料就是文件,而目的就是将它解析成某种编程语言中的变量或者对象,方便操作。而这个过程,每种能处理这种操作的编程语言都有各自不同的表现形式。而“异常”或者“错误”,难道不是说法不同而已吗?它们都要面对:
1. 文件不存在
2. 文本编码不对
3. 文件本身的格式有问题
...
而这些需要共同面对的东西,你的 go 代码中,也只是将信息打印出来。(将有问题的信息打印出来你称之为健壮)。而每种能做这件事的编程语言,基本都有对应的捕捉这种信息的方式(行为),只是它们的代码表现有所差异而已。在我看来,捕捉这些信息是为了修复,而不是 go 语言捕捉的方式更优雅,更好看。这种事情一般有着先入为主的思想在里面,每个人的观念也有所不同。而太过于关注这个的话则忽略了主要目的,你只是想将文件正确解析出来。而输出的错误或者异常信息,它们也只是让你更好修复这些你意料之外的情况罢了。
如果你想看到整个过程中可能出现的问题,你在外层一个 try except ,然后把堆栈信息打印出来不就好了?捕获特定异常的话,一般是为了程序能继续跑。而如果是意料之外的异常,多半还是用 traceback.print_exc() 这种方式更加合适,否则都没有足够的错误信息。
------
我在学 go 中——基于某些原因——也没求一定要有什么结果。python >> go
XWZCoffee
10 天前
我就想问一下,你写 ts 难道不跟 py 一样吗,不一样都是 Error ,然后你需要定义自己的 Error ,比如 AError extends Error,然后判断抛出来的 error 是什么 error ,instanceof 判断。
fox0001
9 天前
从 Java 过来就容易适应了🐶
sardina
9 天前
你写的 go 代码也没处理打开文件时的特定异常,在 py 里咋就介意了
ZX576
9 天前
满篇的 try...except 不可取,最佳实践是

1. 如果函数出错,需要直接中止,直接在最外层捕获,统一处理,这一层你还可以加上监控之类的功能
2. 如果函数出错,可以跳过接着运行,写一个装饰器,比如叫 skip_when_met_error ,签名设计成能传入诸如 default 值这种


这样,你的业务逻辑只用考虑最正常的方式,写起来会可读很多
xiaoming1992
9 天前
说实话没看出下面两种有什么本质区别,
// golang
res, err := func()
if err != nil {
__// ...
}

// js
try {
__res = func()
} catch (e) {
__// ...
}
无非是 golang 倾向于:每个错误都是重要的,都需要显式处理(或返回给上一层);
而一些语言认为很多错误无需关注,只需要往外抛(你只需要 catch 你关注的)。
mayli
9 天前
的确,go 其实没有所谓的 python 的异常
要是真企业级,其实 java 做的比较好,调用一个函数,这个函数会抛出哪些异常都很明确,没处理直接告诉你

但是 py 这种动态语言,type hint 又没有所谓的`throws`,就没法准确描述调用代码可能出现的情况。

大概是一种平衡?专心于能跑就行 vs 完备的处理全部的情况。

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

https://ex.noerr.eu.org/t/1140619

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

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

© 2021 V2EX