十五条有用的Golang编程经验

本文作者在很短的时间内就从对Golang一无所知到开发出真正的产品。在学习Golang的过程中,他总结出十五条编程经验以分享给读者。以下是译文。

像许多其他的开发者一样,我听到过很多有关Golang的传闻。也许你还不熟悉它,那么我告诉你,它是Google开发的开源语言。我之所以对Golang感兴趣,是因为它是一种静态类型编译的现代语言。

长久以来,这就是我所知道的有关Golang的所有信息。我本打算在有空的时候详细了解一下的,但一直都有其他优先级更高的事情要做。大约4个月前,我意识到Golang也许可以用来解决我在CapsuleCD中遇到的一个问题,CapsuleCD是一款我写的可用于任何语言的通用自动化软件包发布工具(npm,cookbooks,gems,pip,jar等)。

我遇到了这么一个问题:CapsuleCD是一个基于Ruby的可执行文件,这意味着任何想要使用CapsuleCD的人都需要在他们的机器上安装Ruby解释器,即使他们想做的只是打包一个Python库。这使得我的Docker容器更加臃肿,开发起来更加复杂。如果只需要把单个二进制文件下载到容器中就好了。所以,把项目移植到Golang这个想法在那一刻突然出现在我的脑海里。

在接下来的几个月里,我反复思考这个想法,几周后,我终于坐了下来,开始把我的3000行Ruby应用程序移植到Golang。虽然我刚刚买了一本类似于Golang傻瓜教程这样的书,但我还是决定直接进行编码,只有在遇到问题卡住的时候才去找博客帖子和stack overflow。

我已经听到一些人劝我放弃的声音。说实话,虽然我玩得很开心,但我最开始开发得非常慢。我是在不知道任何约定的情况下尝试着用一门新的语言来编写这个应用程序。事实是,我喜欢它。那些“啊哈!”的时刻,以及在一此巨大的重构之后再次编译成功的喜悦是一种令人难以置信的动力。

下面是我在把应用程序移植到Golang的过程中学到的一些意想不到的以及非常规的事情。

请注意,这些是我在写Golang代码的过程中遇到的未曾料想到的东西,我以前使用的都是流行语言以及动态类型语言(C ++,C#,Java,Ruby,Python和NodeJS)。这些观点不一定就是对Golang的批评。我能够在两周内从对这个语言零基础到发布应用程序,我真是太牛逼了。

在写第一行代码之前

包的布局

虽然这对于需要编译的语言来说并不是必需的,但Golang需要,只是我并没有找到一个像Ruby、Chef或Node那样的标准目录结构。有一些比较流行的社区,但我本人还是最喜欢Peter Bourgon的建议。

不支持循环依赖

当你发现Golang不支持包之间的循环依赖时,包的布局就变得尤为重要。如果A导入B,B导入A,Golang将会报错。我开始有点喜欢上它了,因为这迫使我更多地去思考应用程序的领域模型。

依赖管理

npm、pypi和bundler,这每一个包管理器都是他们对应编程语言的代名词。然而,Golang还没有官方的包管理器。社区提供了一些不错的选择,但问题是他们都很好,要选出一个合适的博爱管理器有点困难。我最终选择了Glide,因为感觉它跟bundler和npm有点类似。

文档

这应该是Golang做得最好的事情之一了。 go docs和godoc.org这两个网站都非常得棒,并且所有的库的文档进行了标准化。这比包文档都是自定义和自托管的NodeJS社区前进了一大步。

GOROOT, GOPATH

Golang的import机制有点奇特。与大多数其他的语言不同,Golang要求把源代码放在预先配置好的文件夹中。我没有深入研究这个细节,但你应该知道这需要做一些设置,你要习惯这个。 Dmitri Shuralyov的我如何在多个工作区中使用GOPATH是一个很好的资源。

挠痒痒

伪结构体继承

在设计继承模型时,Golang开发人员做了一些有趣的事情。Golang遵循类似于Ruby的多重组合模式, 而不是使用类型语言规定的更为传统的继承模型(多继承或经典继承)。 如果不能完全理解Method-Shadowing,可能会出现一些意想不到的结果。

鸭式接口

这是Golang的另一个很酷的意想不到的功能。我仅在动态类型语言中看到过鸭式接口。这个鸭式与struct密切相关。

结构体中可以定义字段,但接口不行

不幸的是,structs与interfaces不能具有相同的API,因为interfaces无法定义字段。这个问题并算很大,因为可以在接口中定义getter和setter方法,虽然这有点混乱。我相信,应该有一个技术上或者计算机理论上的解释能够回答为什么要这么做。

Public和Private命名

Golang将Python的public和private方法命名方案做了进一步发展。当我最初发现以大写字母开头的函数、结构体是public,而小写开头的则是private的时候,我哑口无言。但老实说,在用Golang开发了两个星期之后,我真的很喜欢这种习惯。

defer

这是另外一个非常有用的Golang特性。我形象这是Golang实现并行处理和错误模型的结果,但defer可以很容易地让源代码看起来更清晰。从某种意义上来说,我把它看做是try-catch-finally模式下的finally方法,或是C#/Java中的using代码块。但我相信它还有更多更有创造性的用法。

go fmt很不错

你不会跟一个Golang开发者进行有关“Tab vs 空格”的争论。Golang有标准的代码风格,go fmt会对代码进行重新格式化。通过阅读它的源代码,我了解到了强大的parser和ast库。

GOARCH、GOOS、CGO和交叉编译

我创建CapsuleCD独立二进制文件的目的是要将端口启动到Golang上。但是,很明显,简单的静态二进制文件并不是Golang的内在特性。如果你的代码及其相关的依赖全部是用Golang写的,那么你可以用GOOS和GOARCH来构建静态二进制文件。但是,如果你像我一样不幸运,存在某个依赖需要在底层调用C代码的话,那么你将会陷入痛苦之中。不要误会我,创建一个动态链接库还是比较容易的。但是,要生成一个没有外部依赖关系的静态二进制文件,需要确保所有的C依赖项(及其依赖项)都是静态链接的。GOOS和GOARCH支持的对应值组合表可以在Golang文档中找到。

如何进行测试?

藏在眼皮底下

测试文件的后缀为_test.go,并且应该跟被测试的代码放在同一个目录中,而不要放在某个特殊的测试目录中。这还好,虽然一开始看着有点混乱。

测试数据放在一个特殊的testdata目录中。 使用go build时,testdata目录和_test.go文件都会被编译器忽略。

go list和vendor目录

依赖关系管理对于Golang来说是相当新鲜的,并不是所有的工具都能理解特殊的vendor文件夹。因此,当你运行go test时,默认情况下会发现它运行了所有依赖项的测试。使用go list | grep -v /vendor可以让Golang忽略vendor目录。

if err != nil

我是一个很看重代码覆盖率的人。我一直在努力让我的开源项目达到80%以上的代码覆盖率,但是,在使用Golang的时候,我感觉这很困难。那些已经熟悉Golang的人可能会说,Golang是一个能达到较高代码覆盖率的语言之一。Golang将所有错误都视为标准对象,而不是为错误创建一个独立的执行路径(try-catch-finally)。 Golang约定,对于可能产生错误的函数,应该在最后的return参数中返回这个错误对象。

这是一个非常有意思的模型,这让我想起了Node的内置函数。然而,就像Node一样,把会生成错误的单元测试写入到内置函数中可能会很困难。当你按照编码模式抛出错误,然后在上层处理错误时,就会变得很烦人,如下所示:

这会很快弄乱你的代码。在这一点上,有些人可能会认为interfaces和mock能解决这些问题。虽然在某些情况下是这样,但是针对内置的库(如os和ioutil)来编写大量的interfaces,或者将这些库作为参数来传递,我认为并不合适,这样做只是让我们能够合理地生成ioutil.WriteFile和os.MkdirAll的错误而已。

我认为这绝对是我心智上一个缺陷。我已经阅读了大量关于如何对Golang进行单元测试和提高代码覆盖的文档和博客文章,但是我还是没有找到一个不需要依赖注入的模式。Golang似乎很讨厌过于繁琐。

结论

我很乐意听到你的想法。我刚刚用Golang工作了几个星期,但这是一个令人难以置信的受教育并且很愉快的经历。我能够在很短的时间内从没有任何经验到开发出一个真正的产品,而不是教科书上的一个例子。我知道我并不是Golang专家,而且对于Golang的了解还存在理论上的差距,但是,当我写下这篇文章的时候,我发现自己走的比预想的要远得多。

Golang按照我原来设想的做了,给了我一个二进制文件,我不再需要Ruby解释器,可以很轻松地下载到Docker容器中。如果你还在用其他语言维护可执行程序,我建议你考虑一下Golang,给Golang一次尝试。

原文发布于微信公众号 - CSDN技术头条(CSDN_Tech)

原文发表时间:2017-08-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法修养

PAT 1026

程序运行时间(15) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard ...

35611
来自专栏spring源码深度学习

设计模式——代码如若初相见

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

922
来自专栏landv

我的第一个Java程序和Java简介

1212
来自专栏精讲JAVA

Gof设计模式之七大设计原则(六)

前言 今天的设计原则重理论,举例子只能是文字形式,没法使用代码解释,希望大家可以多读几遍。真正体会到设计原则的好处。每一个设计原则,我都尽量用非...

21410
来自专栏tkokof 的技术,小趣及杂念

随便再聊一点点Coroutine(确实只是一点点~)

  之前写过一点Coroutine相关的东西(这里和这里),大致讲了些自己关于Unity协程的理解,自己在平日的工作中也确实用到了不少相关的知识,遂而引发了一个...

891
来自专栏Crossin的编程教室

【Python 第7课】if

感觉又一次被微信坑了。前两天刚说改变课程发送方式,今天微信就突然服务器升级,暂时不能新增接收文章的关键字了。所以这两天,还是用回老方式,直接推送。过去的课程0~...

2926
来自专栏Java学习网

Java虚拟机工作原理之JVM用到的3大计算机核心功能,重点是方法调用

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模...

1713
来自专栏华章科技

入门科普:什么时候要用Python?用哪个版本?什么时候不能用?

Python使用面向对象编程(object-oriented programming,OOP)和构造,你可以像任何其它面向对象的语言一样使用它,譬如Java。

1222
来自专栏算法与数据结构

PTA 银行排队问题之单队列多窗口服务

假设银行有K个窗口提供服务,窗口前设一条黄线,所有顾客按到达时间在黄线后排成一条长龙。当有窗口空闲时,下一位顾客即去该窗口处理事务。当有多个窗口可选择时,假设顾...

30910
来自专栏窗户

shell编程/字库裁剪(3)——验证

  程序写完了,必须要验证,这是重要的方法论。因为如果不验证,则不会知道程序写的对还是不对。学过人工智能或者控制论都知道,反馈非常重要,反馈形成闭环,可以用来指...

21610

扫码关注云+社区