看了一个 go 语言,感觉语法略为不习惯。

2016-01-27 09:42:17 +08:00
 fire5
还是大 python 舒服。。
22947 次点击
所在节点    Python
151 条回复
goool
2016-02-04 12:24:02 +08:00
@noli 我看了你的代码,不是很懂你的 shouldStopTrying 是在干嘛,另外代码中那么多 Exception 不明白是在表示什么。

但鉴于你 *真的* 写出了代码,那么我也要写出代码以示尊重: http://pastebin.com/czVfkiuL
goool
2016-02-04 12:27:59 +08:00
我认为你后续的讨论有诸多问题,我要滚回家了,简单说一下:

try/catch 强迫程序处理错误,而返回值没有强迫,所以返回值不好—— NO ,绝对的误解!
golang 的 defer 会改变代码的执行流程—— NO !
noli
2016-02-04 14:13:32 +08:00
@goool

那个 shouldStopTrying 其实目的很简单,就是不停地重试。最后应该加个 finally 判断一下什么时候不需要再重试了,但是我没有加,这要看需求,但是不影响 SendContentToTarget 。由于在 C# 里面 class 类型( HashSet 是一个 class )的参数传递都是 by reference 的,所以可以通过多次调用同一个方法,来不断地移除 HashSet 里面的元素。

我先说说我对你的代码的理解:

你的 send_data 目标应该是类似于我代码里面 29-53 行的 try block 里面做的事情。
然后,你的 main 函数里面做的,目标应该是我的代码里面 SendContentToTargets 做的事情。
你用一个 channel 来把“发不过去”的地址传出来。

然后,我俩的代码都回避了返回值的设计,我感觉我俩都没到点上。

如果对你的代码没有理解错的话,我认为有以下几点造成我和你的代码的实际功用是不一致的:

1.
发消息或者等回复的失败,并不应该视作“地址发不过去”,而是应该重试。
所以在我的代码里第 66 行会把这个事情扔给 SendContentToTargets 的调用者来处理,譬如说如果重试次数超过了多少,就不试了,认为它不发送不过去。 shouldStopTrying 就是为这个用的。

2.
如果目标地址的列表太长了,导致打开文件数量太多,那么运行到一定程度之后,我的代码会在第 31 行或者 32 行抛出异常,但无论是在 54 行接还是 92 或者 96 行 catch ,都不会导致判断 “该地址发不过去”。

但是在你的代码的第 12 行 看起来并不会做这个区分(连接超时还是打开文件数到达上限),并且我很好奇,如果其实根本没有连接上,那么 defer 的时候又手动 Close 了一下,是不是里面会自动判断有没有真的连接上? C# 的 try catch 机制在这个 socket Dispose 的时候会做合适的处理,然而 golang 里面虽然有 GC ,但是这资源的释放跟 GC 似乎是两回事,不知道 golang 会怎么处理?

如果是自定义的对象而不是 golang 标准库的对象呢?
当然,以上的这些你都可以认为是无关重要的细节……然而,正是这些细节会影响你的代码的可重用性和健壮性。



其实我希望看到的是,用 golang 做这个事情的时候,是不是能够把不是 send_x_target 该管的东西放在 send_x_target 之外的代码来处理。

譬如,在 ** 不更改 send_x_target 的签名前提 ** 下,我可以这么改:

https://segmentfault.com/n/1330000004414242

把控制什么应该重试,什么应该排除的条件指定放在外面,这样, send_x_target 就变得更加纯粹了。
cloudzhou
2016-02-04 14:36:11 +08:00
golang 的 error 目前是我比较困惑的地方,因为迫使你不得不认真的思考错误处理,否则容易漏掉。

@noli 我看了你的代码是在 exception 做 switch ,实际上,这和 Golang 的 err 设计类似的,
你可以理解 Golang 里面的 err 就是枚举,对应一个个的 exception
对于 network 的 error , golang 的开发者只是做的比较简单,比如 net package 的 error :

var (
// For connection setup and write operations.
errMissingAddress = errors.New("missing address")

// For both read and write operations.
errTimeout error = &timeoutError{}
errCanceled = errors.New("operation was canceled")
errClosing = errors.New("use of closed network connection")
ErrWriteToConnected = errors.New("use of WriteTo with pre-connected connection")
)
也许还有其他几种错误。

所以按照刚才 @goool 的代码,在 channel 的遍历阶段,把 error 做 switch 决定是否算错误就可以了。
但实际上,单纯从你们写的这两代码来看,我觉得, Golang 显然容易理解多了。
我阅读一些开源代码,确实是存在需要关注指定 error ,而不得不使用 string match 的方式来匹配。是比较难看的方式,但这是可以改进的。

但是从我以前 Java 的经验来看, Exception 改变了程序流程的运行是一个很大的问题。
noli
2016-02-04 14:36:59 +08:00
@goool

“ try/catch 强迫程序处理错误,而返回值没有强迫,所以返回值不好”
这不是我的观点。

我的观点是, golang 通过多值返回 err 很容易导致 “推卸责任的行为难以收拾”,所以 golang 通过多值返回处理 err 是个逗逼的设计。 而不是“返回值不好”。


“ golang 的 defer 会改变代码的执行流程”
这个也不是我的观点。

我的观点是: defer 会使得看代码的顺序和代码执行的顺序不一致,十分烧脑。
不信请自己看 https://tour.golang.org/flowcontrol/12

package main

import "fmt"

func main() {
defer fmt.Println("world")
defer fmt.Println("Awesome")
defer fmt.Println("Golang suck")

fmt.Println("hello")
}

试试猜猜输出顺序是什么?
如果再嵌套多几个别的资源,你觉得这不是一个更加反人类的设计吗?

另外多说一句 defer 会使重要资源的释放不必要的推迟(必定是到最后才回收),又是一个脑残设计
cloudzhou
2016-02-04 14:42:04 +08:00
对于大型开发中,可能需要引入自己的 error list ,比如
https://github.com/go-sql-driver/mysql/blob/master/errors.go 的 MySQLError
然后和 mysql error number 做对应,调用的时候决定错误类型已经怎么处理。
noli
2016-02-04 14:48:52 +08:00
@cloudzhou 你说的这一点就是我为什么特意用 C# 的 SocketException 来做对比的地方了。

事实上, SocketException 就应该为每一个不同的 ErrorCode 独立写成一个 SocketException 的子类。这样根本就没有什么 switch case 的需要,编译器帮你做了。 C# 这么做纯粹是历史遗留问题。

而没有 exception 就必须总是用类似与 switch case 这样的分支结构来处理这些错误,也就是我说的为什么容易导致疏漏的原因。

再况且,只要它是一个 exception ,不 catch 程序就要死掉。不会让错误扩散, golang 呢?骗过了编译器以后,运行时的错误结果还是会扩散出去的。

你非要说 exception 改变程序流程,那是因为 Java Exception 的脑残设计,滥用 Exception 把它当作类似于 yield 这样的东西来用。
难道 golang 的 defer 就不会被滥用了? 到时候情况会比 Java 好很多吗?
cloudzhou
2016-02-04 15:06:38 +08:00
@noli 按照例子,你说要对不同的错误做针对的处理,那么无论如何避免不了 switch ,只是以各种不同的 exception 表现而已。

对于错误的检查,如果忽略,确实会存在问题,错误会一层层的传递而不处理。
defer 在 Golang 共识是不能滥用,这和不处理 exception 一样的道理。

Golang 的一些设计,不是说刻意这么做,开发者自己说了,“没想好怎么做”
以后 Golang 语言层面如果有什么变化,也不奇怪。

我有很长的 Java, Python, 和半年 Golang 经验,但是在 Exception vs Error as Value 比较中,我自己也想不出什么合适,只能从语言 taste 来理解。
noli
2016-02-04 15:56:11 +08:00
@cloudzhou

switch case 和 exception 的作用肯定不一样啊。

1. 如果 SocketException 的各种 ErrorCode 都以 SocketException 的子类出现,要么我用 SocketException 一个 catch 把各种子类全部捕获了,在这种情况下,你才需要根据子类细分再手动 switch case 来处理 —— 这时候很有可能会漏掉某种子类,这个弊病跟用返回值处理是一样的

2. 然而你也可以 catch 每一个 SocketException 的子类,这样就是编译器替你做了 switch case
这种情况下你一样可以针对细分情况分别做处理:

再然后
2.1 如果我的疏漏了 SocketException 的子类没有捕捉,并且也没有 catch SocketException 的本尊,那么,会 crash

2.2 如果我最后有捕捉 SocketException 本尊,甚至直接 catch Exception (所有 Exception 的共同父类),那么尽管我依然遗漏了情况没有处理,但是因为 catch 了, exception 发生后的代码就不会被执行了,程序不会 crash 并且后续的代码不会意外地使用了无效的返回值。

也就是说,像 golang 里面这样的错误, 也是 @goool 代码里面的问题:

func send_data(n int, target string, content string, result chan Pair) {
conn, err := net.DialTimeout("tcp", target, time.Duration(1 * time.Second))
if err != nil {
result <- Pair{n, err}
return
}
defer conn.Close()

DialTimeout 的作者不必在异常发生时硬要给 conn 塞一个值,
而调用 DialTimeout 的时候也不必防御性地 return
要一遍又一遍地重复写这些 resut <-Pair{n, err} return 这种代码,我真的不能认为它 DRY

再况且,我本来出这个题目,就是要考验 golang 处理错误的能力。
他的代码根本就无视了这一部分的逻辑,你看起来当然简洁易懂咯。
实际上,就以这段为例,至少要检查一下 err 的具体情况,才能决定 Pair 是不是应该送出去。
这时候你就会发现谁的代码里面到处都是 switch case 了


再看看 抛异常的方案, send_x_target 的主要逻辑 就这几行:

var client = new TcpClient();
await client.ConnectAsync(endpoint.Address, endpoint.Port);

var stream = client.GetStream();
await stream.WriteAsync(content, 0, content.Length);

var reply = new byte[1024];
var respLength = await stream.ReadAsync(reply, 0, reply.Length);

两个版本里面都没有变过

你觉得“ Golang 显然容易理解多了”
我只能说你的口味和我的太不一样了
cloudzhou
2016-02-04 17:38:16 +08:00
@noli
他的代码例子, switch 是在外层处理的。

按照我阅读 Golang 官方仓库的理解, Golang 希望的是开发者这么处理错误:
1 统一的错误返回方式
2 开发者自己要去处理 error 结果, error as alue 指的是 error 和 return value 一样重要,不可以忽略,在返回之后就要立刻处理
3 根据 error 的严重程度定义级别,封装,比如 net.Error 里面引入 Temporary() 接口
4 如果要对 error 进行详细处理, cast 然后 switch ,或者可以引入 error number (比如 mysql driver)

作为开发者而言,你要考虑封装自己的 error ,而不是一概的抛出去。
对于我目前的体会就是,两种方式我都能接受,因为 error 不管那种方式,都是需要自己去仔细考虑的。
xxxcat
2016-02-09 21:30:09 +08:00
C#粉战斗力有两颗猴腮雷,但还是比不上 PHP 粉的三颗猴腮雷!

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

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

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

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

© 2021 V2EX