博客
Our Experience 与 Golang
抽象形状抽象形状
加入27,000多个网络安全通讯订阅者

这篇博客文章将反映我们最近将相当大的(〜30KLOC)JRuby应用程序移植到Google Go的经验,并讨论了我们喜欢语言和生态系统的许多方面,以及我发现的一些相关方面。

TL; DR

It was a good experience, Go is generally very 不错, and we are all comfortable that this was the correct decision for many reasons. I'd建议任何对Golang感到好奇的人尝试一下;特别是要坚持到头两个星期,在那里您很可能会找到一些Golang'首先是意识形态"为什么每一行都是if语句?")

为什么去?

在UpGuard,我们正在努力实现无代理部署。以前,每台计算机上都安装了一个代理,该代理将仅扫描其所运行的计算机。以前从未考虑过代理性能,因为从来没有资源瓶颈。我们未来的部署将涉及少量'connection managers,'是通过SSH进入目标节点以进行远程扫描的代理。在这种情况下,将发生许多并发SSH连接。对我们来说,关键的问题是,每个SSH通道在多个SSH会话上运行时,Ruby SSH库不是线程安全的。扫描远程节点时,我们非常依赖此性能。因此,我们无法使用Ruby中的线程来实现我们的目标。一种可能是进程分离,而不是线程分离,这仍然具有许多其他优点,但是JRuby的成本之一是JVM中需要的数十亿内存。如果使用进程隔离,则必须实例化多个JVM,并且餐巾纸数学会很快得出令人沮丧的结论。十亿内存的8个进程中,每一个就是80亿内存的RAM,这比我的笔记本电脑当前安装的内存还多,并且很难在需求文档中写下来。

因此,决定重新编写。那'这绝非易事,这意味着在我们努力实现功能均等的过程中,有时并不会产生新的业务价值。但是现在'已经完成了项目,我们'对我们已做出正确决定感到满意。在Golang中沉浸了几个月之后,我现在对它的评价为90%/ 10%。在进行此项目之前,我从未使用过Go。

伟大的方面

对我们来说有什么好处?按重要性的顺序排列。

  • It is approachable for the whole 球队 . Competent young programmers can pick up Go 与out having seen it before and be productive 与in hours. Go guides you towards writing good code 与 appropriate 呃or handling. There aren'许多不良的默认设置会导致废话。只需很少的纪律/培训/经验即可开始'right'与到目前为止我遇到的大多数其他语言相比。
  • 通过代码大小和开发人员人数,Go可以很好地处理不断增长的痛苦。代码仍然存在'nice'通过曲柄功能开发。具有许多开发人员的大型代码库往往会增长。人们很少想把地毯拉出来'fix all the shit.'去处理都很好。类型系统和良好的内置测试使您可以修复主要的代码块,并让程序在之后保持滴答作响。
  • 它具有JSON和XML的良好库(XML仍然缺乏'不幸的是,它死亡,不幸的是,SSH,数据库连接以及其他各种情况。库设计合理;他们组成很好。例如,我们经常使用的是SSH会话(基本上是ssh -L)提供的Dial功能,它非常优雅地满足了我们应用程序的特定需求。然后,很容易附加net / http,通过转发的端口连接到远程数据库,等等。
  • 良好的语言级别并发支持&并行性。语言中内置的键入通道是一个很棒的功能。编写并行化解决方案变得很有趣 设计  挑战,实施进展顺利。该语言悄悄地帮助您实现自己的实现,通常只需尝试20次即可实现。在许多其他语言中执行线程是一种令人恐惧且繁重的体验,'我通常会担心我'我已经忘记了,确切地说它将在以后爆炸。到目前为止,Go是我在多线程代码中遇到的一种更令人愉悦的语言(尽管有两个明显的缺陷……稍后会详细介绍),并且无疑是我使用过的最令人愉悦的主流语言:c / c ++ / c# / java / python / ruby​​ / perl / scala。
  • 垃圾收集。没有任何常规的反GC参数适用于我们的用例(或适用于此情况的95-99%的软件):我们的应用程序不是实时的,不是游戏引擎,也不是在嵌入式系统上运行,不是内核,主要的性能瓶颈将是网络I / O等待,因此无论如何使用C / C ++都不会带来实质性的性能提升。
  • Go已经足够成熟和稳定,并且有一个活跃的社区继续推动图书馆向前发展。语言没有'似乎不再动了。 Google似乎致力于持续改进golang。
  • 简单的可分配项。 Go发出一个自包含的二进制文件,仅需要libc,libpthread和其他一些标准的东西。 (好吧...。libxml对我们以及其他一些数据库库也是如此)。我们可以制作一个完整的docker映像,其中包含代理程序和所有必备组件,且少于20MB。
  • Go工具都很棒。 gofmt,去测试,去yacc,goxc持续很有趣,但是'使用额外的库(libxml(破坏所有XML的方式))时无法工作。
  • 奖励积分至少是一些很酷的因素,有望吸引有才华的工程师。
  • It'真正令人兴奋的是创造出一种新的酷炫的东西。这种兴奋会带来长期的商业价值回报,因为它有助于在任何时间范围内保持开发人员的积极性和热情。不断为乐队提供帮助的卑劣系统是一种奇怪的折磨,有时管理层会对团队造成伤害,我'从来没有完全理解。

My personal, bike-sheddy, highly opinionated (correct) list of 不错 things about go includes:

  1. 强大的静态类型安全性是所有程序的正确选择> ~5KLOCs.
  2. 本机> interpreted
  3. 对多线程和多进程IPC的良好语言级别支持。
  4. 并非JavaScript的加分点
  5. 不是Java的加分点
  6. 不属于node.js的奖励积分(请参见第1、2、3、4和6点)
  7. 避免语义空白(和全局解释器锁定(以及良好的FFI弥补语言缺陷的态度)的加分点(嗨,蒂姆!))

Go满足了所有这些要点。特别是第6点。

差的方面

我们发现有什么不好的地方? (无礼的咆哮警告...)

  • IDE support is not quite what other languages have. MSVC is still hard to beat. RubyMine was very 不错 for the older codebase. Java has a lot of excellent tooling (something needs to auto-generate all the repetitious code &接口存根...)。去不是'与这些语言在同一个联盟中,但这也是一门年轻得多的语言。一世'我相信很棒的事情很快就会出现。
  • Go's ideologies are grating at first. 2/3 the code is if blocks for any 呃or that might occur, although I'我不确定哪种应用程序比我们有更多潜在的失败途径'重新建设。最终这不是'不再烦人,但是当下一个问题出现时...
  • 不-影子这给我们造成了许多麻烦。引入新变量以掩盖旧变量可能会出现问题,尤其是对于返回多个值的函数。重构某些内容并破坏某些内部循环中的预先存在的变量太容易了。 -Wshadow是更安全的默认值。也许可以对变量进行一些受控的范围界定,或者引入一个允许在if语句外保留变量的运算符,例如:if $ resultToKeep,err:= someOperation(foo); 呃!= nil {
        // 呃or
    }
    resultToKeep仍在作用域中,但err没有't。另一种方法是使用线性或级联的if / else语句,这很丑陋,并且在需要添加循环或其他内容时会立即崩溃。我想尝试的一种很好的启发式方法是允许小名称(tmp,err,ok)持久存在并相互破坏,但是>3个char变量可能不会破坏。一世'll bet that 'i', 'err' and 'ok'是大多数代码库中最常见的变量名称。
  • 参数多态性。泛型是好的。接口不是通用的。通过interface {}投射所有内容都是愚蠢的。很多时候,我想使用泛型围绕其他任意事物创建小型容器类型。通常,可以通过某种方式(通过出色的重构并且没有净收益)来实现我想要的接口,但是生成的代码远没有泛型那么清晰。从模板生成Go代码是一种可能,但它需要像下面这样:

    布拉 <T> {
        数据T
    }

    构建系统:
    make_me_a_Blah_for(int)
    make_me_a_Blah_for(字符串)
    make_me_a_Blah_for(实际存在的ArbitraryComplexType) 
  • 可变的非范围有限切片。这可能真的很危险,尤其是在多线程应用程序中。考虑一下代码片段:

    数组:= []字符串{"path", "with", "a", "glob", "**", "in", "it"}
    路径BeforeGlob := array[:4]
    路径Remaining := array[4:]

    recurseThroughGlobs(pathBeforeGlob,pathRemaining)

    // recurseThroughGlobs中的某个位置:
    filesInThisDirectory:= ...
    对于_,file:= range filesInThisDirectory {
        如果thisDirectoryLooksInteresting(file){
            recurseThroughGlobs(append(pathBeforeGlob,file),pathRemaining)
            //砰!
        }
    }

    实际发生的是切片'pathBeforeGlob'只会继续使用曾经拥有的内存。追加到pathBeforeGlob将破坏pathRemaining。

    证明 这里 .
    更简单,更震撼的例子 这里 .

    拉屎。 Go知道pathBeforeGlob受范围限制。而不是破坏pathRemaining slice,而是在范围末尾追加追加复制将是一个非常安全得多的默认值。

一般非结构化投诉

自C语言以来,已有数十年的语言进步,似乎被忽略了……没有操作符重载(字符串除外,字符串显然很重要,值得拥有)。零(不是零,拼写已经被升级了吗?)指针并不比20年前回到C / C ++ / Java领域更好。一些允许列表理解的语法糖(映射,过滤器等)将非常有用。蒂姆·斯威尼(Tim Sweeney)(是个大神)在2006年制作了一张幻灯片,上面有一个片段,我觉得这特别有趣:

"For loops in Unreal:
40%是功能理解
50%是功能性褶皱"

猜猜除了虚幻引擎之外,还有哪些代码库是正确的?他们全部。 golang的理由似乎是声明新列表,迭代,执行转换,将下一个结果写入新列表的5行并不多。而且一次过还是十次过'很多。在我们的(...每个)代码库中,有成百上千个这样的代码,所有代码都增加了一段混乱和潜在的错误。各种其他'编程好习惯'诸如分离关注点,传递最少的信息等确实有鼓励像map这样的结构的趋势&过滤。一种非常危险的选择是仅传递大对象,这导致代码库耦合更紧密(也称为更差)。

即使没有针对地图/过滤器/折叠的语言级别支持,泛型也将以99%的方式解决此问题,因为我可以实现以下目标:

func Map(inputList []<InputType>, fn func(<InputType>) <OutputType>) []<OutputType> {
    outputList:= make([]<OutputType>, len(inputList))
    对于我,elt:= range inputList {
        outputList [i] = fn(elt)
    }
    返回outputList
}

但是我可以't do this either.

对于宏大的结局……Go的最糟糕的事情(甚至比可破坏的片段还糟糕):缺少const正确性,或者除基本常量之外,任何编译器都强制执行不变性。这是一个 可怕 错误,尤其是在尝试广泛使用并发时。当使用具有某些复杂类型的库时,const是确保此特定调用图不会使我的数据结构发生变异的好方法。它可以在可变数据与不可变数据的代码中强制进行某些结构分离。

Go中的切片和地图基本上是通过引用传递的。是的,按值传递参考 正在传递参考。切片可能会引入指针别名,并且映射通过突变不是线程安全的。必须使用互斥锁来保护将使映射发生变化的并发访问,或者通过专用的mutator goroutine序列化,并设置了一些通道供goroutine在它们之间进行交谈。如果数据是不可变的怎么办?也许有一个例程来准备数据结构,然后有其他几个线程在可用时对数据起作用。我想做的是让调用图A准备数据结构,然后将其传递给具有const限定符的调用图B。 B可以根据需要生成尽可能多的goroutine,可以读取所需的数量,从不写入,因此对调用图B中的数据的访问不会'需要互斥体或任何受控的mutator例程。这种基本模式(准备rw,使用线程ro)在我们的代码库中非常常见。现在稍后,一个不熟悉代码库的人意识到他们可以通过对调用图B中的数据结构进行一些小的调整来支持X功能请求。如果没有语言级别的const运算符,则除了繁文tape节之外,没有其他方法可以阻止这种情况的发生。在C / C ++中,调用图B将具有对数据的const限定引用,我们'重做。在Go中,我们只需要 希望 那:

  1. 看到代码的人都会通过阅读注释,询问我们或者只是一眼理解数百行代码的工作原理就可以理解它。
  2. They will have the skill and discipline to do the 对 thing (add flags to A, mutate elsewhere, restructure graph B such that mutation is all in one place, etc)

实际上可能发生的情况:称职的工程师将获得功能请求,然后进行类似于以下内容的思考过程:"这看起来很容易添加。几行...好了。添加一个单元测试,现有测试通过,很酷'将其发布以供审核,完成工作"。这是一种完全合理的方法,但是他们实际上引入了一个错误。不,单元测试不能可靠地捕捉到微妙的比赛条件(但是,我必须承认,我需要使用go比赛检测器进行比赛)。三个月后,该部分开始更频繁地崩溃,然后我们花费了一周的调试时间,决定由于该部分具有竞争条件而需要重新编写该部分,并可能在更改之后引入了回归。

有什么比这更好的了:


"嘿,我想做X,但是你的const实在是太痛苦了。我应该做些什么?"
"放在那里或那里,我'll be all good."
"Ok rad, thanks."
几个月后,就再也没有地雷了。 const使这种事情可以执行,基本上是通过某些限制在代码库上进行更好的设计。长期影响是更好的代码,更少的错误,更高的整体生产率。

结论

Go is a really 不错 language, 与 rich, easily composable, well written libraries. It is easier to write a clean, fast, parallel program in go than in most other languages, and the crucial thing that Go does well is to allow a 球队 相对轻松地编写干净,快速,并行的程序。这里描述的专业人士远远超过了我的为什么可以的嘘声清单't-Go-be-rust(但要认真对待?没有const?)。我不'看不到产品的这一部分需要很长时间进行技术更新。

随意在评论中向我开火,但如果您不这样做,'t喜欢const然后你'绝对是错误的。

自由

白色UpGuard徽标
可供下载的UpGuard免费资源
学到更多

下载我们的免费电子书和白皮书

关于网络安全和供应商风险管理的见解。
白色UpGuard徽标
电子书,报告& Whitepapers
可供下载的UpGuard免费资源
UpGuard客户支持团队UpGuard客户支持团队UpGuard客户支持团队

观看UpGuard的实际应用

与我们的一位网络安全专家预订免费的个性化入职电话。
抽象形状抽象形状

相关文章

了解有关网络安全的最新问题的更多信息。
传送图标

注册我们的时事通讯

每周在收件箱中获取最新精选的网络安全新闻,漏洞,事件和更新。
抽象形状抽象形状
免费即时安全评分

您的组织有多安全?

索取免费的网络安全报告,以发现您的网站,电子邮件,网络和品牌上的主要风险。
  • 检查图标
    您可以立即采取行动的即时见解
  • 检查图标
    13个风险因素,包括电子邮件安全,SSL,DNS运行状况,开放端口和常见漏洞
网站安全扫描结果网站安全扫描等级抽象形状