V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
GopherDaily
V2EX  ›  Go 编程语言

Go 的特色不是语法的便捷,而是在工程

  •  
  •   GopherDaily · 2023-02-08 01:00:55 +08:00 · 7839 次点击
    这是一个创建于 679 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Envoy 这样的工程构建已经是非常复杂了,当然 Go 大型工程也不简单。 但是入门写 Go 基本就一条路,入门 C++ 就很依赖人的主观判断。

    学了两天 bazel ,又学了一天 cmake ,加深了这个想法。

    77 条回复    2023-02-28 10:30:11 +08:00
    lanlanye
        1
    lanlanye  
       2023-02-08 01:57:07 +08:00 via Android   ❤️ 2
    难说,工程实践的结果是越写越像 Java ,然后越来越觉得那个异常处理反人类……
    TWorldIsNButThis
        2
    TWorldIsNButThis  
       2023-02-08 01:58:32 +08:00 via iPhone
    Java 已经在应用层革过一遍 c++的命了
    securityCoding
        3
    securityCoding  
       2023-02-08 08:17:11 +08:00 via Android
    @lanlanye 已经不是反人类的问题,对代码的破坏程度简直丧心病狂
    xuyang2
        4
    xuyang2  
       2023-02-08 09:32:27 +08:00   ❤️ 34
    我觉得 go 的 if err != nil 没啥可黑的
    层层嵌套的 throw cache 才是反人类
    pursuer
        5
    pursuer  
       2023-02-08 09:50:33 +08:00
    这不是语言的问题,是生态分裂造成的,现在编译器就好几个,gcc clang msvc ,c++委员会标准库推进慢,各个平台对 c/c++的接口存在差异。
    现在的新语言很多都是唯一实现 java(不考虑 android 的话) go rust 啥的。
    当然也有一个分裂比较严重的 js ,好在 js 的灵活性在一定程度上减轻了这个问题,但是依然诞生的 webpack rollup swc esbuild 等一大堆构建工具。
    TtTtTtT
        6
    TtTtTtT  
       2023-02-08 09:54:13 +08:00
    一些编程哲学放在一边。
    Goroutine 的代码实在是太难懂了,跟 Akka 一样属于好写,但是完全看不懂。
    8355
        7
    8355  
       2023-02-08 10:05:05 +08:00   ❤️ 5
    Go 才是现代编程语言标准
    同样的需求大概率只有一种写法 而且加上代码格式化
    不会让你有在魔法世界的感觉 对新手友好度很强
    不需要考虑什么某某函数 某某方法有这样那样的问题
    dqzcwxb
        8
    dqzcwxb  
       2023-02-08 10:06:32 +08:00   ❤️ 5
    @xuyang2 #4 "throw cache" 那你说的一切都对🙏
    lysS
        9
    lysS  
       2023-02-08 10:07:36 +08:00
    @TtTtTtT 正确使用的协程很好读啊,如果协程间有太多的变量、逻辑依赖,那属于滥用
    RedisMasterNode
        10
    RedisMasterNode  
       2023-02-08 10:08:46 +08:00
    @TtTtTtT 怎么会难懂呢...或许你可以举一些认为难懂的例子大家康康具体是哪里不容易阅读
    zcreg
        11
    zcreg  
       2023-02-08 10:10:35 +08:00   ❤️ 6
    Go 让人舒服的一个点就是读别人的代码,不会有那些花里胡哨的写法
    fioncat
        12
    fioncat  
       2023-02-08 10:14:07 +08:00   ❤️ 15
    我发现一堆人真的无脑黑 Go 的错误处理。
    Go 错误处理的好处在于强迫你认真对待每个 error 。
    某些 Javaer 一有 exception 就无脑直接 throw ,他们肯定理解不了这种设计。
    libook
        13
    libook  
       2023-02-08 10:18:46 +08:00
    与其他语言很鲜明的区别是,Go 是专门为生产工程场景设计的一款[产品];为了解决生产过程中的痛点,牺牲了一些部分技术人员看重的爽点。
    xxv0
        14
    xxv0  
       2023-02-08 10:20:20 +08:00
    @xuyang2 什么是 try...catch...的层层嵌套,你是指在调用方与被调用方都 try...catch...,还是说在同一个函数的 try 块里再次 try...catch...,按理说这两种写法都是不对的。
    zbatman
        15
    zbatman  
       2023-02-08 10:21:14 +08:00   ❤️ 2
    OP 一句没提 Java ,评论句句在踩 Java ,有趣
    chendy
        16
    chendy  
       2023-02-08 10:23:20 +08:00
    所以说 go 更适合当 cpp 用,写基础设施
    写业务也就图一乐
    Nazz
        17
    Nazz  
       2023-02-08 10:28:51 +08:00
    @xuyang2 Go 是最正统的 C 语言继承者. 虽然繁琐但代码更健壮, 当然要是有关键字或语法糖来抛出错误将会更好.
    tt67wq
        18
    tt67wq  
       2023-02-08 10:29:16 +08:00
    @TtTtTtT 深有同感,到处都是 channel 的异步,完全找不到消息从哪里来到哪里去,一两个模块的异步还行,有的十来个模块相互异步调用,非常令人头秃
    Nazz
        19
    Nazz  
       2023-02-08 10:31:03 +08:00   ❤️ 1
    @TtTtTtT go 的源码实现很复杂, 一般人看不下去. map, sort, channel 皆是如此.
    Nazz
        20
    Nazz  
       2023-02-08 10:32:33 +08:00
    @tt67wq 我会尽量使用 mutex 替代 channel, channel 明显被滥用了.
    dog82
        21
    dog82  
       2023-02-08 10:38:52 +08:00
    没 go mod 之前,很痛苦的
    GeruzoniAnsasu
        22
    GeruzoniAnsasu  
       2023-02-08 10:44:48 +08:00   ❤️ 2
    @RedisMasterNode

    语法不复杂,但是要写出 对 的程序,那可复杂到天上去了。要知道 golang 只有协程,但它不提供 **异步语法**

    golang 没有 await ,这意味着你要完全自己手动处理所有 chan 和 并发开始的 goroutine 的关系和时序。2023 年了连 c++都能 await 协程了,golang 却还在用 select 和 pipe 手搓异步逻辑,我愿称其为 「 unix 原教旨主义」。


    举个例子好了。你有一个 spwaner , 它能并发地生成若干 worker ,worker 的执行时长不确定。 现在有一个要求,所有 worker 的执行结果要按启动顺序写回到同一个与 spwaner 共享的 chan 里,开始你的头脑风暴。
    Nazz
        23
    Nazz  
       2023-02-08 10:52:37 +08:00
    @GeruzoniAnsasu async 有传染性, 同步方式写异步代码对开发者更友好, 但是牺牲了性能.
    RedisMasterNode
        24
    RedisMasterNode  
       2023-02-08 10:59:41 +08:00
    @GeruzoniAnsasu 我看你的描述很多时候只需要 wait group 吧。。wait group 的使用非常简单,只有需要 goroutine 间通信的时候才会需要 channel 呀,业务应用里面 goroutine 大部分场景都是用来并行做一些事情,例如并行发起 http 调用,我 golang 用了有小几年了没有感觉到什么不适而且觉得很好理解

    当然你可能在描述一些多个 channel 之间共同协作,需要知道互相的结果,需要传递数据的情况,我不了解在其他语言怎么做的,但是我觉得常规开发里面写出这样的逻辑设计本来就已经对可读性不友好了,不能说只怪 golang 吧
    xuyang2
        25
    xuyang2  
       2023-02-08 11:10:49 +08:00
    @dqzcwxb #8 throw catch ...
    yazinnnn
        26
    yazinnnn  
       2023-02-08 11:15:20 +08:00
    monad 鄙视一下 throw 和 try catch 也就算了, 这年头 if err != nil 都能鄙视 throw 了吗
    star9029
        27
    star9029  
       2023-02-08 11:24:07 +08:00
    c++ build system 是这样的,不过 cmake/xmake 的工程能 work ,而且 cmake 有大量成熟项目(这并不影响他难用)
    gowk
        28
    gowk  
       2023-02-08 11:48:08 +08:00   ❤️ 1
    @Nazz #19
    Simplicity is Complicated
    TtTtTtT
        29
    TtTtTtT  
       2023-02-08 11:53:25 +08:00
    @RedisMasterNode
    @lysS
    最近读过的是 MinIO 的 EC pool 读写流程 hhh
    ericls
        30
    ericls  
       2023-02-08 11:56:38 +08:00 via iPhone
    我最近才开始真正写 go,

    总之很喜欢 我也说不出具体原因 tooling 也很好 想写的东西甚至乱来 根据报错也能边学边写

    这种喜欢可能有一部分来自于正在学习新东西的兴奋 但也只是一小部分
    angrylid
        31
    angrylid  
       2023-02-08 11:57:06 +08:00   ❤️ 3
    又到了爷最爱的斧子党和锯子党互相鄙视环节。

    有这时间不如多砍两棵树。
    xiangyuecn
        32
    xiangyuecn  
       2023-02-08 12:03:18 +08:00
    @xuyang2 #4 没写过 go ,如果 编写代码漏写了 if err != nil 会产生什么有趣的问题吗? 还是说不写 if err != nil 编译不过?
    lysS
        33
    lysS  
       2023-02-08 12:06:53 +08:00
    @TtTtTtT 大项目就是本就不好读,我弄数据库的,看 tidb 也是很恼火;即使注释写的很好,不是自己写的也缺少很多上下文
    sadfQED2
        34
    sadfQED2  
       2023-02-08 12:19:11 +08:00 via Android
    @xiangyuecn 跟 java 漏写 try 差不多
    blankmiss
        35
    blankmiss  
       2023-02-08 12:41:58 +08:00   ❤️ 1
    if err != nil 还不如 try catch
    hhjswf
        36
    hhjswf  
       2023-02-08 12:45:10 +08:00 via Android
    @xuyang2 反人类的理由呢
    hhjswf
        37
    hhjswf  
       2023-02-08 12:46:58 +08:00 via Android   ❤️ 1
    如果觉得 try catch 恶心,aop 可以解决。go 有什么优雅一点异常处理
    loading
        38
    loading  
       2023-02-08 13:09:57 +08:00 via Android
    @xiangyuecn 直接全局替换 err 为 _,直接就忽略了。
    lanlanye
        39
    lanlanye  
       2023-02-08 13:37:18 +08:00   ❤️ 1
    @xuyang2 很有问题啊,因为大多数需要异常处理的函数都得返回至少两个结果 (result, error) ,深层调用时每一层做的事就是执行函数,如果有 error 就往上一层抛,也会导致链式调用无法正常写出来,比如 `person.Pet().Name()` ,如果 Pet()方法是一个可能失败的 lazy load ,调用时就根本写不成这样。
    目前我只见过 Gorm 那样把 error 直接放进返回值结构里的做法可以缓解这个问题,或者希望 Go 学一学 Rust 。
    lanlanye
        40
    lanlanye  
       2023-02-08 13:39:08 +08:00
    @fioncat 我一开始也是这么认为的,但它确实造成了不便,可以参考我在楼上的回复。另外 Rust 的处理方式就很好,它同样强迫你处理每一个 error 。
    lanlanye
        41
    lanlanye  
       2023-02-08 13:42:34 +08:00
    @GeruzoniAnsasu 我想了想,开辟一个用于保存结果的数组,启动 worker 的时候传入对应顺序的数组下标,直接把结果写进对应位置……应该可行吧
    Smilecc
        42
    Smilecc  
       2023-02-08 13:45:24 +08:00
    @Nazz await 未必一定具有传染性,本质上是因为 JS 或 Rust 等语言都是无栈协程,实现方式决定具有传染性,Go 的协程是有栈的,不依赖状态机做上下文切换,我认为是可以实现的
    8355
        43
    8355  
       2023-02-08 13:47:19 +08:00
    @fioncat #12 太对了
    这才是严谨的体现
    而不是瞎糊一通结果来个兜底
    leonshaw
        44
    leonshaw  
       2023-02-08 13:51:32 +08:00   ❤️ 1
    @GeruzoniAnsasu #22 没明白你说的,对 Go 来说大部分情况只要把无栈协程模式的 await 直接改成同步调用就行了,并不需要启 goroutine 。
    举的例子只要在 goroutine 把结果写到一个 slice 对应位置就可以了,“回到同一个与 spwaner 共享的 chan 里”是伪需求,因为 chan 是 Go 特有的。
    5h4nh
        45
    5h4nh  
       2023-02-08 14:33:01 +08:00
    @fioncat 不赞同。我用 sourcegraph.com 查了一下 `if err != nil`,基本都是 `return err`,没有处理。而且还有不少人直接写 `_`。相反我认为 Java 的 checked exception 机制,强制要求 caller 写 try-catch 或者 caller 签名也加上,才是所谓「强迫你认真对待每个 error 」。另外,你说「某些 Javaer 一有 exception 就无脑直接 throw 」,我觉得他们如果换用 Go ,情况只会更糟糕吧..
    5h4nh
        46
    5h4nh  
       2023-02-08 14:35:53 +08:00   ❤️ 1
    @Nazz 我也觉得 Go 的源码很难读,其中一个原因就是变量名太追求 “Unix 风格”,我觉得有点过头了。比如这个 `sudog`.. https://stackoverflow.com/questions/68569386/whats-the-mearning-of-sudog-in-the-channel-struct-in-go
    Nazz
        47
    Nazz  
       2023-02-08 14:46:29 +08:00
    @5h4nh 源码很难读是因为复杂性, 实现得异常复杂. 当然简洁不代表性能.
    macscsbf
        48
    macscsbf  
       2023-02-08 14:50:23 +08:00
    知乎上在哪里看到的,go 的最大特色是无聊
    chenqh
        49
    chenqh  
       2023-02-08 14:53:13 +08:00
    既然 golang 代码看起来这么简单,为什么我看 crowedsec 看不懂呢?
    th00000
        50
    th00000  
       2023-02-08 14:56:24 +08:00   ❤️ 2
    @fioncat #12
    这个逻辑反过来才是对的吧
    强迫处理 error 怎么就变成优势了,这会让人增加大量的心智负担
    相反让人有选择的余地,自己根据需求去决策才是好的设计吧
    learningman
        51
    learningman  
       2023-02-08 15:00:28 +08:00
    @GeruzoniAnsasu #22 启动的时候带上个序号,返回结果的时候带上序号,waitgroup 等待所有 worker 完成。
    就算有 async await 不也是这么操作吗,难道你想 for 1 to n await result ?这样写是符合直觉,但又不是唯一的标准答案。
    Slurp
        52
    Slurp  
       2023-02-08 15:14:56 +08:00   ❤️ 6
    靠元组实现标签联合的垃圾类型系统。基于此出来的错误处理也是一坨大便,这也有人吹?
    wupher
        53
    wupher  
       2023-02-08 17:05:49 +08:00
    catch / throw 当然不完美

    if err!= nil 多层嵌套有时更变态,毕竟 runtime exception 你还可以不处理。

    学了 Rust 之后确实相信这才是更优雅的设计。
    quicksand
        54
    quicksand  
       2023-02-08 17:05:57 +08:00
    各有千秋吧,不同人肯定喜好都不一样的,这也是语言多样性的原因,没必要强行来比较。我最近也在学 go ,感觉不舒服的点就是注释文档,感觉 javadoc 这方面做的更好一些。
    JamesMackerel
        55
    JamesMackerel  
       2023-02-08 17:19:44 +08:00
    看到贵贴,想来请教一下 go 的 thread local (或类似机制)的进展……

    我知道可以用 Context 然后把每个 function 都加个 Context 参数,可是除了这种方法还有没有别的办法?
    liuxu
        56
    liuxu  
       2023-02-08 17:36:05 +08:00
    最后发现 php 依然是最好的语言,而你们都会来写 rust
    GeruzoniAnsasu
        57
    GeruzoniAnsasu  
       2023-02-08 17:48:56 +08:00
    @RedisMasterNode @Nazz @leonshaw

    不是的,wait group 只能在所有任务完成前一直阻塞住。而作为一个 spawner ,你需要时刻维护一个有长度的队列,当队列空出来时立即解除正在预约( schedule )任务的 routine 的阻塞,wait group 显然不合适。


    注意我们的目标是,让结果按照添加的顺序依次输出,而不是一次性等待所有的结果一起输出。


    有异步语法的语言,在这个场景的做法是

    - 一个有长度的阻塞队列
    - 当外界 scheduling 新任务时,spwaner 向队列获取一个空槽,如果队列已满,那么 spanwer 和 请求者都会被阻塞
    - 如果获取了空槽,将任务放入空槽,获得一个 promise
    - 创建新 promise, 在这个 promise 里 { await 任务队列的尾部任务(因为我们需要按任务的添加顺序而不是任务完成顺序来返回),await 到之后返回上一步获得的 promise }
    - 把上面这个 promise 加到 out 队列里,每次提取结果时 await out 队列的头部


    而 golang 要模拟这个做法的话,首先它没有 promise ,也没有 goroutine 的 handler ,然后要实现跟上述等价的 spawner 必须使所有调用 spanwer 的线程共享同一个 channel ,意味着 chan 要么是全局的,要么扔到 context 里。先简单考虑全局唯一 chan 的做法。(但复制 chan 用 context 传这种逆天玩意我也写过)

    提取任务槽这步没问题,但怎么模拟一个 promise ?
    - c := make(chan,1) ; go func(){c<-do();)}

    那怎么获取任务队列的尾部任务并 await 它?
    - 如果任务队列只是个简单的 channel 是做不到的,因此需要一个 slice + channel ,可是 slice 就没有锁了,你这时候要考虑一个可阻塞环境( chan )下的锁问题,头开始疼起来了

    怎么返回 await 了 c 的新 promise ?
    - ……

    对了,这个新 promise 还要放到 out 队列里
    - …………
    Nazz
        58
    Nazz  
       2023-02-08 17:54:50 +08:00
    @GeruzoniAnsasu 添加任务的时候加序列号, 线程同步后给输出结果排序
    GeruzoniAnsasu
        59
    GeruzoniAnsasu  
       2023-02-08 18:00:12 +08:00
    @Nazz @learningman @leonshaw @lanlanye @RedisMasterNode

    我提醒你们一下关于放到对应序号结果槽的实现:


    - 这个结果 array (它有大小,我这里用 array 来称呼,并不是指实现),是有「洞」的,需要有个机制能按顺序检查每个位置是否完成了,没完成要能阻塞住,意味着 array 里放的是锁或 chan 或任意什么东西总之是一个可锁对象,但有 promise 的情况下不需要这种可锁对象

    - 我们不能一次性等待一批 worker 全部完成,而是要时刻能分派已完成的 worker 占用的任务槽

    - spwaner 本身要可以等待或阻塞
    Nazz
        60
    Nazz  
       2023-02-08 18:23:43 +08:00 via Android
    @GeruzoniAnsasu 不知道我写的这个库是否满足你的需求
    https://github.com/lxzan/concurrency
    Nazz
        61
    Nazz  
       2023-02-08 18:26:18 +08:00
    @GeruzoniAnsasu 使用有锁队列保存任务; 任务完成后去队列拿下一个任务, 递归地调用;
    CRVV
        62
    CRVV  
       2023-02-08 19:28:08 +08:00   ❤️ 1
    @GeruzoniAnsasu

    package main

    import (
    "fmt"
    "math/rand"
    "time"
    )

    func worker(ch chan int, x int) {
    d := rand.Intn(10)
    time.Sleep(time.Millisecond * 10 * time.Duration(d))
    ch <- x
    }
    func main() {
    var queue []chan int

    for i := 0; i < 10; i++ {
    ch := make(chan int)
    go worker(ch, i)
    queue = append(queue, ch)
    }
    for _, ch := range queue {
    fmt.Println(<-ch)
    }

    }
    CRVV
        63
    CRVV  
       2023-02-08 19:43:07 +08:00   ❤️ 1
    @GeruzoniAnsasu

    这个东西不难写,只不过它的写法和 JavaScript 惯用的写法可能不一样。
    如果你觉得我发的这个不符合你的要求,你可以先用 Promise 写一版我来翻译成 Go
    总体上 Go 不用写 "await" 这几个字母,其它和带异步且多线程的语言完全一样。当然 Go 自带的语言功能少一些,不限于异步并发关系时序这些,所有方面的功能都少。

    > 有 promise 的情况下不需要这种可锁对象
    多线程环境下的 promise 本身也带锁或者类似的机制。单线程的 JavaScript 是另一回事。

    > 我们不能一次性等待一批 worker 全部完成,而是要时刻能分派已完成的 worker 占用的任务槽
    这个和 Promise 有关系么?我没觉得用 Promise 可以简化这件事情的实现,拿到一个结果了再开下一个任务,都一样吧。

    > spwaner 本身要可以等待或阻塞
    没看懂,什么地方可以等待?你是指可以 await spwaner() 么?
    lxdlam
        64
    lxdlam  
       2023-02-08 20:16:44 +08:00   ❤️ 1
    @GeruzoniAnsasu

    针对你的 task ,准备一个跟结果一直长度的 []chan 就可以了,扫一遍每个 channel 就可以针对每一个 chan 的阻塞策略,很直接。https://go.dev/play/p/YbtujcTry_J

    至于 Promise ,你需要的只是一个 Task 结构,注意到结果本身是否 ready 可以依靠超时 + channel ,解决,封装一个类似的结构是 naive 的,在 github 上能找到非常多的类似的库。

    可以去看一些官方 talk 理解 CPS 机制的原理,而不是尝试把某个机制 mapping 过来。
    leonshaw
        65
    leonshaw  
       2023-02-08 20:23:16 +08:00
    @GeruzoniAnsasu 本质上,是你后一个 promise await 了前一个。相应地 Go 里面为每个 goroutine make 一个 channel ,在写结果前等前一个 channel 就行了:

    in := make(chan func() any)
    out := make(chan any, 1)

    go func() {
    for result := range out {
    consume(result)
    }
    }()

    prevDone := make(chan struct{})
    close(prevDone)

    for do := range in {
    done := make(chan struct{})
    go func(do func() any, prevDone <-chan struct{}, done chan<- struct{}) {
    result := do()
    <-prevDone
    out <- result
    close(done)
    }(do, prevDone, done)
    prevDone = done
    }
    close(out)
    lxdlam
        66
    lxdlam  
       2023-02-08 20:35:31 +08:00
    @GeruzoniAnsasu

    刚注意到有个 context miss 了,我也补充几个点:

    - 使用 mpsc 跟 spsc 是非常简单的,一个基于 token 的 bucket 可以简单控制好 task 的数量,共用 token bucket 就可以控制每个 spawner 的数量。注意到这里也可以简单地基于 chan 封装一个,不需要所谓的阻塞队列。
    - spawner/worker 基于 message passing 的 channel 可以解决所有 promise 的场景,包括超时等待等。
    - 所谓的全局 channel ,js 的 microtask queue 和 marcotask queue 同样是全局的,甚至基于这两个场景你如果需要定制化 queue 的调度逻辑你需要对 runtime 有更加深入地理解,而 go 基于 token bucket 做定制可以做更多的事情。
    learningman
        67
    learningman  
       2023-02-08 21:44:00 +08:00
    @GeruzoniAnsasu #54 你别加条件,我说的实现是给你的最初版本的需求的
    wangritian
        68
    wangritian  
       2023-02-08 23:12:27 +08:00
    你们不要再打了啦
    swulling
        69
    swulling  
       2023-02-08 23:19:28 +08:00 via iPhone
    编译一个 envoy 三个小时,醉了。
    StevenRCE0
        70
    StevenRCE0  
       2023-02-09 00:12:04 +08:00   ❤️ 1
    err 判空不算大问题,但是显然是 throwable 更适合工程啊……
    人们在改进错误处理,然后到某些选手这儿直接就说不出错误不就行了,属实流汗黄豆。
    nino
        71
    nino  
       2023-02-09 00:24:29 +08:00
    @GeruzoniAnsasu 首先没有细看你的需求。但是 go 只是标准库不提供 async await Promise 这些并发原语而已,要模拟出来很简单的啊,有了之后不就和你写 JS 一样了。作为 JS 和 Go 都写过的人,可以负责任的讲,Go 并发这块灵活性比 JS 强多了,可以写出很有表现力的代码。
    goroutine + chan + sync 包里那堆东西,什么并发程序都能写出来。
    realpg
        72
    realpg  
       2023-02-09 01:01:37 +08:00
    @8355 #43
    现在 golang 也有大量瞎糊一通,各种 panic ,然后 recovery 兜底
    dbskcnc
        73
    dbskcnc  
       2023-02-09 10:09:30 +08:00
    什么舒服 /合适就用什么,大部分情况,我 go 用得挺舒服的. c/c++ 确实麻烦很多
    xsen
        74
    xsen  
       2023-02-09 11:04:06 +08:00   ❤️ 1
    @dbskcnc #72 前后用过很多语言,如 c/c++/python/java/javascript/dart 等等,到现在的 go ,相对来说用 go 的体验是最舒服的——不管是开发、调试,还是打包部署诸如此类。当然,也包括跨平台、交叉编译等
    zxCoder
        75
    zxCoder  
       2023-02-24 16:00:03 +08:00
    便捷,是大便的便吧
    echoless
        76
    echoless  
       2023-02-28 10:29:21 +08:00
    @5h4nh #46 sudog 这命名真是妙啊, 没有多少英文背景的人是无法体会的.

    然而, 能与之媲美的就是 身份证 代码里写成 sfz 了.
    echoless
        77
    echoless  
       2023-02-28 10:30:11 +08:00
    @yazinnnn #26 纯手工, 鄙视你们这些投机取巧的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3127 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 13:04 · PVG 21:04 · LAX 05:04 · JFK 08:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.