作者|logicchains译者|闫亮 Go 语言的创始人之一 Rob Pike 曾表示,他希望 Go 能够被 C++ 程序员所接受,但结果差强人意。最近,在作者就职的 HFT 公司里,一个团队成功地把一些对速度不太敏感的基础设施代码从 Python 移植到了 Go,这也促使他们决定尝试用 Go 对复杂冗余的 C++ 服务端程序进行重构,这些代码有 5W 行之多,并且对吞吐量有一定的要求。
这个服务端程序使用了跟公司核心交易软件相同的技术和库,不同地是交易软件对系统的延迟更敏感,几乎每一微秒都很重要,而 C++ 服务端并不需要这种程度的性能。
因此,使用 Go 自带的调度程序完全可以满足要求,没有必要使用交易系统实现的超优化 C++ 框架,虽然损失了一些性能但获得了更好的可维护性。需要一提的是,本文作者负责了整个代码的重写工作。前   言

从商业角度来看,这个项目是成功的:重写工作提前完成;性能在可接受的范围之内;并且整体代码量不超过 1W 行(代码量的剧减主要是因为重写团队删除了一些过时的或者不需要的特性)。但从开发者的角度来看,作者认为结果并不是最优的。Go 并不支持参数多态,作者因此使用了两到三倍的代码来实现类似功能。其中一部分是为了保障类型安全:Go 强制开发者在类型修饰和类型安全之间做出取舍,作者选择了一个比较均衡的实现。总的来说,如果需要一般的类型安全,那么相对少的代码就可以实现,而如果需要更好的类型安全,则需要更多的代码。

接下来让我们对比一下 Go 语言的优缺点。

优   点Emacs 开发平台

借助自动完成、跳转到定义、保存时的错误检查、智能重构和 GoTest 集成等插件,Emacs 成为了 Go 语言环境下最好的 IDE 工具。另外,它也可以很方便地通过 Elisp 进行定制和扩展。如果你本人恰好是 Emacs 的爱好者,这绝对是一个大大的加分项。

Goroutines(协程)

Go 实现了基于消息传递的并发,作者认为这是最简单的并发形式,使用也超级方便。通过将 GOMAXPROCS 设置为 1,Go 还允许开发者通过使用与并发代码完全相同的方式来编写并行 / 异步代码。与其它提供内置轻量级线程调度器的语言 Erlang/Elixir 和 Haskell 相比:前者缺乏静态类型,后者在实际开发中很少被管理人员采用。

没有继承

在很多情况下,基于继承的 OO(面向对象)是一种反模式,这些冗余和模糊的代码几乎没有什么好处,Go 则直接取消了这类代码。这有可能也是 Rob Pike 等人设计 Go 的初衷:谷歌内部有一大堆类似于企业版本 Fizzbuzz 的 Java/C++ 代码,他们希望能从这些代码中彻底解放出来。也就是说,尽管在旧的 C++ 服务端遗留代码中使用继承是合理的,但最好还是使用更现代的风格来重写代码,而且重写过程也并不复杂。

更好的可读性

Go 代码更易于阅读和理解。相比之下,很多 C++ 代码需要几个小时才能完全理解。Go 本身也促使开发者编写可读的代码:这种语言完全避免了下面这种自做聪明的情形(https://www.reddit.com/r/programmingcirclejerk/comments/b0wkue/go_also_forced_me_to_write_readable_code_the/):

“嘿,这篇论文(基本上没人读得懂)中的>8=3 运算符可以让我节省 10 行代码,我最好把它写进代码里,我的同事也不难理解这行代码,因为它的意思已经在类型签名中很清楚地表达出来了(反正我是没看懂):(PrimMonad W, PoshFunctor Y, ReichsLens S) => W Y S((I -> W) -> Y) -> G -> Bool "。

简单而规范的语法

当我们需要将一个封闭函数的名称添加到每个日志字符串的开头时,如果使用 Emacs,一个简单的 regexp find-replace(正则表达式) 命令就可以实现,而对于更复杂的语言则需要使用解析器。不论是通过 Emacs 宏或者是 Go 模板,简单的语法可以更容易地生成代码。

Emacs+Go== 参数多态:我们可以使用 Emacs 宏来加速生成 Go 所需要的"复制粘贴",而且,如果函数编写正确,那我们也可以用 regex 命令来更新所有的"复制粘贴"函数。这样,我们就可以很容易地更新 fooInt、fooFloat 和 fooDouble 等函数,对比支持参数多态的语言对 foo 函数的更新,整个过程没有什么太大区别。这样做的缺点是,虽然 Emacs 宏和 regex 命令可以编写和修改 Go 代码,但它仍然不如真正的多态实现那样简洁和易读;而且对于不熟悉 regex 以及可扩展编辑器(Emacs)的人来说,维护同样也不容易。

有效的内置模板

通过 Go 的文本 / 模板包,我们可以很容易地生成新代码。它还允许开发者在生成代码时使用 IO:例如,有一个同某些特定服务交互的库,它通过 XML Schema 生成。如果能够用不同的函数来生成不同的数据类型,那么就可以保证代码的类型安全。

在 C++ 中,IO 不能在编译时执行,因此不能使用上述模式来生成代码。允许编译时使用 IO 的语言有:

  • F#,通过 TypeProviders 实现。

  • Idris,也使有 TypeProviders。

  • Lisp,可以在宏中执行 IO。

  • Haskell,它有一个编译期运行的函数 IO -> Q。

  • D,编译时可以使用“import”来读取文件。

  • Nimrod,有特殊的函数实现。

  • Elixir 或 Erlang,可以通过宏执行任意的 IO。

  • Rust,可以使用函数 libsyntax 在编译时执行任意的计算和 IO。

    缺   点斯德哥尔摩综合征

前面已经提到,在允许使用 IO 的特性上,使用模板生成 Go 代码要比用 C++ 元编程好得多,而 C++ 元编程在这里显然是多余的,因为完全可以用另外一种可以支持 IO 的程序语言来生成代码。

没有实现参数多态

尽管很多人认为这在实践中并不是一个问题,但在这里,它是一个很严重的问题。如果把新的 Go 代码再移植回 C++ 的话,考虑到 C++ 的函数多态和类型多态,代码量可能会减少到目前的一半,并且具有更好的类型安全。如果用 Haskell 重写的话,代码量会更少,而使用 Clojure 的话,代码量有可能控制到 1000 行以内,当然这些代码可能很难被调试或维护。

牺牲了类型安全

针对服务器处理的各种 protobuffer messages(协议缓冲消息),我们使用了扩展属性的方式,作者最初打算为每一种消息设置一种扩展属性,这样 FooExtensionAttribute 就不能用在 Bar 函数上。Go 并没有实现参数多态和泛型,这意味着将会产生大量的重复代码,所以最终只使用了一种 ExtensionAttribute,并且类型系统也没有检查它是否用于扩展合适的消息。

二进制文件太大

如果使用代码来生成类型安全的 API,并确保每种数据类型都有明确的类型访问器和诸如此类的东西,则很容易生成超过 10W 行的 Go 代码以及 30MB 以上的二进制文件,编译时间也会更长。在这种情况下,一般会超过 10 秒。当然,这不是一个很严重的问题,因为我们可以把代码编译成静态库,这只需要一次,之后就可以通过静态链接来访问了。

内核兼容性有待提高

很多时候由于各种无奈的原因,需要把代码部署到一个旧内核上。而且,如果这个内核不支持最新的 Go 版本,就不得不换到一个旧的、很慢的 Go 版本,这多少有些令人沮丧。

结   语

Go 语言是一把双刃剑:它禁止一切复杂的抽象,不管是优秀的抽象亦或是很差的抽象。如果你和你的同事正在使用很糟糕的抽象,那切换到不能使用抽象的 Go 语言自然很好,反之亦然。当然这也要取决于判断抽象好坏的标准。

参考链接:

https://togototo.wordpress.com/2015/03/07/fulfilling-a-pikedream-the-ups-of-downs-of-porting-50k-lines-of-c-to-go/

如何提高go代码覆盖率_我是如何把5万行C++代码移植到Go的?相关推荐

  1. 如何提高go代码覆盖率_如何通过静态分析提高iOS代码质量

    随着项目的扩大,依靠人工codereview来保证项目的质量,越来越不现实,这时就有必要借助于一种自动化的代码审查工具:程序静态分析. 程序静态分析(Program Static Analysis)是 ...

  2. python可以实现什么炫酷图形吗_我是Python小玩家,一行代码能做哪些炫酷的事情?...

    python之禅 image 一行代码启动一个Web服务 python -m SimpleHTTPServer 8080 python3 -m http.server 8080 一行代码实现变量值互换 ...

  3. java 静态代码块_关于Java你不知道的那些事之代码块

    前言 普通代码块:在方法或语句中出现的{},就被称为代码块 静态代码块:静态代码块有且仅加载一次,也就是在这个类被加载至内存的时候 普通代码块和一般语句执行顺序由他们在代码中出现的次序决定,先出现先执 ...

  4. 小象学院python量化金融代码_机器学习_小象学院课件与案例代码

    [实例简介]机器学习_小象学院课件与案例代码 [实例截图] [核心代码] 1.机器学习与数学分析.pdf 2.概率论与贝叶斯先验.pdf 3.矩阵和线性代数.pdf 4.Python_代码.zip 4 ...

  5. 利用插电式电动汽车提高电网暂态稳定性 python联合PSS/E源代码,代码按照高水平文章复现 插电式电动汽车(pev)在放电模式下可以作为分布式能源和电力资源,作为车到网(V2G)设备运行;在充电模

    利用插电式电动汽车提高电网暂态稳定性 python联合PSS/E源代码,代码按照高水平文章复现,保证正确 插电式电动汽车(pev)在放电模式下可以作为分布式能源和电力资源,作为车到网(V2G)设备运行 ...

  6. java优化上传速度慢怎么办_我是如何让minio client上传速度提高几十倍的

    minio java client 使用okhttp作为底层的http实现,在产品包里面局域网上传文件的速度一直只有400~800KB/s,经过一天排查发现是-Djava.compile=none禁用 ...

  7. docker 开始容器_我是如何开始使用docker的,为什么也应该使用

    docker 开始容器 I've been lurking around in r/selfhosted for about a year now, spying on the apps that p ...

  8. 两个listmap合并去重_我是如何用单机实现亿级规模题库去重的?

    题外话:欢迎将公众号设置为星标,技术文章第一时间看到.我们将一如既往精选技术好文,提供有价值的阅读.如有读者想要投稿,可以在公众号任意文章下留言,技术博主奖励丰厚. 作者:haolujun cnblo ...

  9. 美国 cs 工作_我是如何退出美国CS计划的,回到埃及,然后立即在一家公司获得了一份开发工作。...

    美国 cs 工作 by Adham El Banhawy 由Adham El Banhawy 我如何退出美国CS计划,回到埃及,并立即在一家初创公司获得了开发工作 (How I dropped out ...

  10. 如何做相册_我是如何对2000张照片进行批量套版的

    老板:小叶,麻烦把这2000张照片去设计排版下 小叶:好嘞,老板 相册设计排版也是影楼非常重要的一环节:但是多朋友在后期工作中既想省事情,又想提高工作效率,那么这个时候,我们应该怎么做呢? 这个时候, ...

最新文章

  1. Hibernate5.2之原生SQL查询
  2. spring源码学习之路---深入AOP(终)
  3. nyoj 712 探寻宝藏
  4. OpenGL拆分视图
  5. 深入理解Kafka(4)-主题与分区
  6. 介绍一种Fiori标准应用的增强方式
  7. 关于centos 7 中service iptables save 指令使用失败的结局方案
  8. 学习使用 Go 的反射
  9. 55种数据可视化开源工具_6种用于撰写书籍的开源工具
  10. Vrep 中的运动规划1(主要是基于RRT算法)
  11. JVM学习01—下载编译openjdk源码并进行调试
  12. 文字转语音,有什么软件好用?
  13. Continous Integration.Continous Development,Continous Delivery之间的关系
  14. 学习笔记:贝塞尔曲线法
  15. 网络对抗 Exp2.1 后门原理与实践 20154311 王卓然
  16. 硬盘的S.M.A.R.T.是什么?如何看懂它
  17. 基础代谢率、BMI、体脂肪率、肌肉量、体水分率,这些都是什么?
  18. android浪漫樱花凋零动态壁纸应用源码
  19. 趣味seo-网站被搜索引擎降权恢复的六大诀窍
  20. ap计算机sql,如何用sql实现AP

热门文章

  1. 【C面试】一道简单的C语言面试题的思考——打印星阵
  2. [2018.10.11 T3] 欠钱
  3. 按顺序发起多次请求的loading使用
  4. sublime运行python_sublime交互执行python文件方法
  5. jamon java_JAMon监控web工程方法的调用性能
  6. python中常见的三种选择结构_循序渐进学Python:三种选择语句
  7. 计算单应性矩阵 python_计算视觉——相机参数标定法
  8. html提交表单原理,HTML5之Form 表单理论
  9. C++自定义sort排序
  10. Vuex之store仓库计算属性Getter