• 请不要在回答技术问题时复制粘贴 AI 生成的内容
partystart
V2EX  ›  程序员

纯吐槽帖 关于 go 的 err 和重载

  •  
  •   partystart · Dec 24, 2021 · 11465 views
    This topic created in 1642 days ago, the information mentioned may be changed or developed.

    写了几个月的业务了 写 err 真的吐了 牵扯到序列 /反序列话、有任何文件、io 操作的地方都会有 error

    报的那么多 error 有啥用? 报那么多 error 没能解决问题 第一行成功 下面的几处 error 第一行的 error 岂不是白打了? 这种与业务无强关联的地方 与业务嵌套这么深 直接全局异常捕捉不就行了?

    还有都 21 世纪了 居然不支持重载 输出一样 输入参数不一样 不能重载 就很无语。我写个方法功能一样 还得另外起个名字

    这群设计者是学术界呆太久了?

    130 replies    2021-12-29 16:36:32 +08:00
    1  2  
    hutoer
        101
    hutoer  
       Dec 26, 2021
    @cmdOptionKana 那你看看 golang make 的用法:
    make(map[string]string)
    make([]int, 2)
    make([]int, 2, 4)

    按照你说的风格,要是变成多个函数了,会不会被吐槽?
    QingXuJiaZhi
        102
    QingXuJiaZhi  
       Dec 26, 2021
    @hutoer 当然,我第一次听这种说法的时候,也是愕然了一下,但人家说的是有点道理。

    说个题外话,另外一个 Go 语言相关的更震惊我说法是:语法高亮没有必要。

    Rob Pike 平时使用一个自己做的编辑器“Acme”是不带语法高亮的,Go 团队里的另一个重要人物 Russ Cox 也使用 Acme 。事实上,可能有些人甚至没有察觉,Go 的文档服务 godoc (现在已迁移到 go.dev )里的示例代码全都没有语法高亮,但只要脑子里想的是如何解决问题,就会发现没有语法高亮一点影响都没有,甚至都忘记了自己看的代码是没有高亮的。

    我自己的感受是,看代码时确实语法高亮的影响很小,但自己写代码时高亮还是有明显的辅助作用。
    QingXuJiaZhi
        103
    QingXuJiaZhi  
       Dec 26, 2021
    @hutoer make 选择这种类似重载的风格,主要因为这种自带函数有一半“占据关键词”的副作用,因此才不得不减少函数名。而在 strings, sort 之类的标准库里,就选择了显性的风格。
    QingXuJiaZhi
        104
    QingXuJiaZhi  
       Dec 26, 2021
    @hutoer 要不是怕占了关键词(保留词),换成多个函数名反而更好(从 Go 的审美角度看),我并不觉得会被骂(唯一被骂的理由只是占了保留词)。
    hutoer
        105
    hutoer  
       Dec 26, 2021
    @cmdOptionKana 我倒是觉得重载能降低心智负担、提升代码简洁渡,例如:
    data := make([]int, 2)
    data := make([]int, 2, 4)

    比下面的简洁
    data := makeXX1([]int, 2)
    data := makeXX2([]int, 2, 4)

    还有这种( Color 是构造函数)
    color = new Color(0,0,255)

    color = new Color("#FFFFFF")

    color = new Color(Color::RED)

    比下面的简洁
    color = new Color()
    color.setByRGB(0,0,255)

    color = new Color()
    color.setByHEX("#FFFFFF")

    color = new Color()
    color.setByName(Color::RED)
    LUO12826
        106
    LUO12826  
       Dec 26, 2021   ❤️ 1
    想到以前讨论谷歌 material design 和 iOS 的设计风格时,某乎上有人大篇幅论述谷歌的右划侧边栏是如何比 iOS 的底部 tab bar 优秀的。然而后来,谷歌自己又加上了底部 tab bar
    QingXuJiaZhi
        107
    QingXuJiaZhi  
       Dec 26, 2021
    这个不需要重载啊,变长参数就可以了。
    data := make([]int, 2)
    data := make([]int, 2, 4)


    而这个
    color = new Color(0,0,255)
    color = new Color("#FFFFFF")

    Go 的习惯是
    c = color.New(0,0,255)
    c = color.ByHex("#FFFFFF")

    从简洁的角度看差别不大。
    (其中 color 是 package 名字,New 和 ByHex 是 public 函数)
    aloxaf
        108
    aloxaf  
       Dec 26, 2021
    @hutoer #99

    这个例子举得不好,换我我肯定不会这么设计——「 Color::Color("#FFFFFF")」这种,显然是可能初始化失败的。如果并作一个 API ,在强迫你显式处理错误的语言中,就不得不为明明不可能出错的逻辑写上错误处理代码。

    举个我觉得比较常见的「重载」更好的例子:
    string.replace('a', 'b')
    string.replace("abc", 'a')
    string.replace("abc", "def")
    string.replace("abc", a_function_pointer)
    ...(以下省略若干种排列组合)...
    aloxaf
        109
    aloxaf  
       Dec 26, 2021
    @aloxaf #108
    (此处「重载」用了引号,因为这种效果不一定非得重载才能实现
    maja
        110
    maja  
       Dec 26, 2021


    call/cc 用过没? 这玩意比宁那全局异常捕捉靠谱多了。
    hutoer
        111
    hutoer  
       Dec 26, 2021
    @cmdOptionKana golang make 会根据第一个参数做不同的处理,行为更像重载,变长参数不行。
    另外,变长参数我反倒觉得是不良设计,尽量少用。

    你说的 color.ByHex("#FFFFFF")这种方式,已经是牺牲可读性换取简洁性了。相对我的例子,还不是 OOP 的方式。

    其实,我只是想说重载是有用的,而不是想证明没有重载不行。
    Buges
        112
    Buges  
       Dec 26, 2021 via Android
    @hutoer go 的 make 只是缺乏泛型的 trick ,没啥好说的。
    另外不要被 OOP 荼毒太深,constructor 本身就是糟粕,可读性简洁性都不如直接用函数。
    Color::from_rgb(u8,u8,u8)和 Color::from_hex(String)返回类型都不一样,后者是可失败的(虽然在异常的语言中被隐藏了),难道该用同一个名字吗?
    另外就算需要所谓的“重载”效果,用泛型是更好的选择。Color::from<T: Into<Color>>(t:T)->Color 甚至比你写多个重载的函数更“OOP”。
    sxfscool
        113
    sxfscool  
       Dec 26, 2021
    非要和 java 一样么?为什么不去用 java
    james122333
        114
    james122333  
       Dec 26, 2021
    @Buges

    说到重点了 又不一定得写 oop
    hutoer
        115
    hutoer  
       Dec 26, 2021
    @Buges Color::from_rgb(u8,u8,u8)和 Color::from_hex(String)返回类型咋不一样了?都是 Color 实例呀。

    泛型替代不了重载

    至于“constructor 本身就是糟粕,可读性简洁性都不如直接用函数”,这个我就不讨论了,大家看问题的面不一样。
    Buges
        116
    Buges  
       Dec 26, 2021 via Android
    @hutoer 因为后者可能会失败。粗略地说,前者返回 Color ,后者返回 Color | ParseError 。异常的语言可能会隐藏这一点,但用了 checked exception 同样可以体现出来。
    泛型就是达到“重载”的效果最合适的方式,泛型代替不了重载的地方就不该用重载。

    constructor 完全是多余的复杂度,这个概念根本就没有必要。new FancyClassName(params) 比 FancyClassName.new(params) 没有任何优势。反而让代码难读又难写。如:
    FancyClassName FancyClassName::FancyClassName(params){
    blabla...
    }
    hutoer
        117
    hutoer  
       Dec 26, 2021
    @Buges “后者返回 Color | ParseError”,这个说法不恰当,返回的还是 Color ,Parse 出错只不过抛异常。
    前者函数内出错误( Color 这例子简单,大面上不会出错),也是会抛异常的。

    “泛型代替不了重载的地方就不该用重载”,这个说绝对,下面这种:
    add(float a,float b)
    add(int a,int b,int c)
    add(char a,char b,char c)

    业务毕竟多种多样的,有重载还是利于简化代码的。

    方式 1
    FancyClassName FancyClassName::FancyClassName(params){
    blabla...
    }

    instance = new FancyClassName(params)

    方式 2
    FancyClassName FancyClassName::new(params){
    blabla...
    }

    instance = new FancyClassName()
    instance.new(params)

    我觉得还是方式 1 更好、更简洁。而且方式 1 规范了 constructor
    方式 2 可以是 new 、create 、init 、build....,反而很乱

    如果你说可以这样:
    instance = new FancyClassName.new(params)

    那么 new 就是 constructor ,换了名字而已
    hutoer
        118
    hutoer  
       Dec 26, 2021
    instance = new FancyClassName.new(params) 这个写错了
    instance = FancyClassName.new(params)
    这个其实不算 constructor
    hutoer
        119
    hutoer  
       Dec 26, 2021
    我看错了,把 FancyClassName.new(params) 当实例函数了
    Buges
        120
    Buges  
       Dec 26, 2021
    @hutoer 异常只不过是一种不同的控制流,返回 Color throws ParseError 和返回 Result<Color,ParseError>本质上是一样的,都是返回数据,而这两者返回的数据“类型不同”。

    你这个例子是完全可以用泛型的:
    fn add<A: IAddable<Output=A>>(a:A,b:A,c:A)->A{

    }
    Buges
        121
    Buges  
       Dec 26, 2021
    @hutoer 上面没打完,constructor 没有提供额外的好处,限制了初始化函数的命名(这也是需要重载的一个主要原因),不能为不同的情况使用不同有意义的命名。至于命名有静态检查,并且一般都有特定的 convention,没什么混乱的。另外不要只从 OOP 的角度观察,简单的函数其实更好理解。
    hutoer
        122
    hutoer  
       Dec 26, 2021
    @Buges 抛出异常和返回错误值还是不一样的,调用的地方处理也不一样。就算是返回错误值,可以 Result<Color,Error>,Error 是基类,你这样 Result<Color,ParseError>,有其他类型的错误咋返回?所以 Color::from_rgb(u8,u8,u8)和 Color::from_hex(String)返回类型没有区别。

    我那个例子,参数个数不一样的。
    dobelee
        123
    dobelee  
       Dec 26, 2021
    @hutoer 个人觉得这种写法非常糟糕。应传递颜色类型,或使用不同的方法名。
    Buges
        124
    Buges  
       Dec 26, 2021 via Android
    @hutoer return 和 throw 是不同的控制流,需要用不同的方式处理,但本质上都是返回数据给调用方,无论通过 return 还是 throw 。实际“返回”的数据类型是不一样的。
    如果签名写了 ParseError ,自然是表明该函数只可能返回这一种错误类型,如果还可以返回其他类型那自然要写成所有可能类型的 super type ,这个和 checked exception 本质是一样的。
    参数个数不一样可以用 vararg ,或者包装到一个 collection literal 里,或者写成 macro 。如果只需要几种特定数量的参数,可以分别写成 add2 、add3 等不同函数,比起导致的问题重载并不能提供足够的好处。
    agagega
        125
    agagega  
       Dec 26, 2021 via iPhone
    C++因为长期以来没有 optional 类型(而且即使有了也有额外 overhead ),所以构造函数只能用异常表示构造失败。

    而不管是 C++风格的构造函数还是手写函数构造,都存在语义不明的问题。Swift 风格的具名参数和重载可以解决,并不一定需要带不同名字的函数。

    但即使没有构造函数,这个创建对象的函数也还是需要某种方式来「创建」对象,这种 C 风格花括号的方式有是不如构造函数一样灵活。

    另外在面向对象的继承关系里,构造函数就显得很自然,非构造函数虽然也可以做到类似效果,但就会很别扭。
    fregie
        126
    fregie  
       Dec 27, 2021
    @partystart 什么叫好,什么叫不好,这几年开发者们已经用脚投票了,你觉得什么叫好,什么叫不好?
    本身都不是面向对象的语言,你非拿它来写业务,写完了还说不好用,那能说你什么?但凡明白点面向对象的也会明白 go 本身就不适合写业务,编程语言没有银弹,不同语言有各自擅长的领域。
    LoNeFong
        127
    LoNeFong  
       Dec 27, 2021
    打起来 打起来 我就喜欢看 V 站语言战争, 一堆人瞎瘠薄操心!
    赚几个钱啊?
    房贷换完没啊?
    真 tm 无聊
    ly020044
        128
    ly020044  
       Dec 27, 2021
    我就喜欢看你们打字互喷,下次你们继续哈。
    nmap
        129
    nmap  
       Dec 28, 2021
    别用不就得了嘛,人家也没逼着你用,也没让你掏一分钱
    adoal
        130
    adoal  
       Dec 29, 2021
    你是不是对 PLT 的学术界有什么误解……

    go 的错误处理硬生生把本该用 sum type 来做的事( either ok result or error info )做成了 product type ( a tuple with both ok result and error info )……哪个学术界是这么玩的?
    1  2  
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1011 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 58ms · UTC 22:40 · PVG 06:40 · LAX 15:40 · JFK 18:40
    ♥ Do have faith in what you're doing.