阅读更多

2顶
0踩

编程语言

转载新闻 Go 语言的 10 个实用技术

2014-08-27 10:59 by 见习编辑 u012797015 评论(1) 有8296人浏览

这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指:

某个应用需要适配一个灵活的环境。你不希望每过 3 到 4 个月就不得不将它们全部重构一遍。添加新的特性应当很容易。许多人参与开发该应用,它应当可以被理解,且维护简单。许多人使用该应用,bug 应该容易被发现并且可以快速的修复。我用了很长的时间学到了这些事情。其中的一些很微小,但对于许多事情都会有影响。

所有这些都仅仅是建议,具体情况具体对待,并且如果有帮助的话务必告诉我。随时留言:)

 

1. 使用单一的 GOPATH

多个 GOPATH 的情况并不具有弹性。GOPATH 本身就是高度自我完备的(通过导入路径)。有多个 GOPATH 会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个 GOPATH 的情况。所以只使用单一的 GOPATH,这会提升你 Go 的开发进度。

许多人不同意这一观点,接下来我会做一些澄清。像 etcdcamlistore 这样的大项目使用了像 godep 这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的 GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的 GOPATH。如果你认为项目需要一个自己的 GOPATH 目录,那么就创建它,否则不要尝试使用多个 GOPATH。它只会拖慢你的进度。

2. 将 for-select 封装到函数中

如果在某个条件下,你需要从 for-select 中退出,就需要使用标签。例如:

如你所见,需要联合break使用标签。这有其用途,不过我不喜欢。这个例子中的 for 循环看起来很小,但是通常它们会更大,而判断break的条件也更为冗长。

如果需要退出循环,我会将 for-select 封装到函数中:

你还可以返回一个错误(或任何其他值),也是同样漂亮的,只需要:

3. 在初始化结构体时使用带有标签的语法

这是一个无标签语法的例子:

那么如果你添加一个新的字段到T结构体,代码会编译失败:

如果使用了标签语法,Go 的兼容性规则(http://golang.org/doc/go1compat)会处理代码。例如在向net包的类型添加叫做Zone的字段,参见:http://golang.org/doc/go1.1#library。回到我们的例子,使用标签语法:

这个编译起来没问题,而且弹性也好。不论你如何添加其他字段到T结构体。你的代码总是能编译,并且在以后的 Go 的版本也可以保证这一点。只要在代码集中执行go vet,就可以发现所有的无标签的语法。

 

4. 将结构体的初始化拆分到多行

如果有两个以上的字段,那么就用多行。它会让你的代码更加容易阅读,也就是说不要:

这有许多好处,首先它容易阅读,其次它使得允许或屏蔽字段初始化变得容易(只要注释或删除它们),最后添加其他字段也更容易(只要添加一行)。

 

5. 为整数常量添加 String() 方法

如果你利用 iota 来使用自定义的整数枚举类型,务必要为其添加 String() 方法。例如,像这样:

如果你创建了这个类型的一个变量,然后输出,会得到一个整数(http://play.golang.org/p/V5VVFB05HB):

除非你回顾常量定义,否则这里的0看起来毫无意义。只需要为State类型添加String()方法就可以修复这个问题(http://play.golang.org/p/ewMKl6K302):

新的输出是:state: Running。显然现在看起来可读性好了很多。在你调试程序的时候,这会带来更多的便利。同时还可以在实现 MarshalJSON()、UnmarshalJSON() 这类方法的时候使用同样的手段。

 

6. 让 iota 从 a +1 开始增量

在前面的例子中同时也产生了一个我已经遇到过许多次的 bug。假设你有一个新的结构体,有一个State字段:

现在如果基于 T 创建一个新的变量,然后输出,你会得到奇怪的结果(http://play.golang.org/p/LPG2RF3y39):

看到 bug 了吗?State字段没有初始化,Go 默认使用对应类型的零值进行填充。由于State是一个整数,零值也就是0,但在我们的例子中它表示Running

那么如何知道 State 被初始化了?还是它真得是在Running模式?没有办法区分它们,那么这就会产生未知的、不可预测的 bug。不过,修复这个很容易,只要让 iota 从 +1 开始(http://play.golang.org/p/VyAq-3OItv):

现在t变量将默认输出Unknown,不是吗? :)

 

不过让 iota 从零值开始也是一种解决办法。例如,你可以引入一个新的状态叫做Unknown,将其修改为:


 

7. 返回函数调用

我已经看过很多代码例如(http://play.golang.org/p/8Rz1EJwFTZ):



 

然而,你只需要:

更简单也更容易阅读(当然,除非你要对某些内部的值做一些记录)。

8. 把 slice、map 等定义为自定义类型

将 slice 或 map 定义成自定义类型可以让代码维护起来更加容易。假设有一个Server类型和一个返回服务器列表的函数:

现在假设需要获取某些特定名字的服务器。需要对 ListServers() 做一些改动,增加筛选条件:



 

现在可以用这个来筛选有字符串Foo的服务器:



 

显然这个函数能够正常工作。不过它的弹性并不好。如果你想对服务器集合引入其他逻辑的话会如何呢?例如检查所有服务器的状态,为每个服务器创建一个数据库记录,用其他字段进行筛选等等……

现在引入一个叫做Servers的新类型,并且修改原始版本的 ListServers() 返回这个新类型:



 

现在需要做的是只要为Servers类型添加一个新的Filter()方法:



 

现在可以针对字符串Foo筛选服务器:

哈!看到你的代码是多么的简单了吗?还想对服务器的状态进行检查?或者为每个服务器添加一条数据库记录?没问题,添加以下新方法即可:



 

9. withContext 封装函数

有时对于函数会有一些重复劳动,例如锁/解锁,初始化一个新的局部上下文,准备初始化变量等等……这里有一个例子:

如果你想要修改某个内容,你需要对所有的都进行修改。如果它是一个常见的任务,那么最好创建一个叫做withContext的函数。这个函数的输入参数是另一个函数,并用调用者提供的上下文来调用它:
只需要将之前的函数用这个进行封装:


 
不要光想着加锁的情形。对此来说最好的用例是数据库链接。现在对 withContext 函数作一些小小的改动:


 
如你所见,它获取一个连接,然后传递给提供的参数,并且在调用函数的时候返回错误。你需要做的只是:


 

你在考虑一个不同的场景,例如作一些预初始化?没问题,只需要将它们加到withDBContext就可以了。这对于测试也同样有效。

这个方法有个缺陷,它增加了缩进并且更难阅读。再次提示,永远寻找最简单的解决方案。

 

10. 为访问 map 增加 setter,getters

如果你重度使用 map 读写数据,那么就为其添加 getter 和 setter 吧。通过 getter 和 setter 你可以将逻辑封分别装到函数里。这里最常见的错误就是并发访问。如果你在某个 goroutein 里有这样的代码:



 

会发生什么?你们中的大多数应当已经非常熟悉这样的竞态了。简单来说这个竞态是由于 map 默认并非线程安全。不过你可以用互斥量来保护它们:



 

假设你在其他地方也使用这个 map。你必须把互斥量放得到处都是!然而通过 getter 和 setter 函数就可以很容易的避免这个问题:



 

使用接口可以对这一过程做进一步的改进。你可以将实现完全隐藏起来。只使用一个简单的、设计良好的接口,然后让包的用户使用它们:



 

这只是个例子,不过你应该能体会到。对于底层的实现使用什么都没关系。不光是使用接口本身很简单,而且还解决了暴露内部数据结构带来的大量的问题。

但是得承认,有时只是为了同时对若干个变量加锁就使用接口会有些过分。理解你的程序,并且在你需要的时候使用这些改进。

总结

抽象永远都不是容易的事情。有时,最简单的就是你已经实现的方法。要知道,不要让你的代码看起来很聪明。Go 天生就是个简单的语言,在大多数情况下只会有一种方法来作某事。简单是力量的源泉,也是为什么在人的层面它表现的如此有弹性。

如果必要的话,使用这些基数。例如将[]Server转化为Servers是另一种抽象,仅在你有一个合理的理由的情况下这么做。不过有一些技术,如 iota 从 1 开始计数总是有用的。再次提醒,永远保持简单。

特别感谢 Cihangir Savas、Andrew Gerrand、Ben Johnson 和 Damian Gryski 提供的极具价值的反馈和建议。

 

  • 大小: 8.2 KB
  • 大小: 9 KB
  • 大小: 2.8 KB
  • 大小: 6.2 KB
  • 大小: 7.1 KB
  • 大小: 6.9 KB
  • 大小: 8.2 KB
  • 大小: 4.7 KB
  • 大小: 4.4 KB
  • 大小: 12.4 KB
  • 大小: 3 KB
  • 大小: 6.9 KB
  • 大小: 3.7 KB
  • 大小: 7 KB
  • 大小: 4.1 KB
  • 大小: 4.7 KB
  • 大小: 2.6 KB
  • 大小: 7 KB
  • 大小: 18.9 KB
  • 大小: 6.7 KB
  • 大小: 7.4 KB
  • 大小: 10.6 KB
  • 大小: 4.3 KB
  • 大小: 2.9 KB
  • 大小: 10.9 KB
  • 大小: 3.7 KB
  • 大小: 10.4 KB
  • 大小: 5.4 KB
  • 大小: 11.3 KB
  • 大小: 3 KB
  • 大小: 5.3 KB
  • 大小: 7.3 KB
  • 大小: 4 KB
2
0
评论 共 1 条 请登录后发表评论
1 楼 elgs 2014-08-27 21:38
Wonderful, thank you.

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Go语言使用字符串的几个技巧分享

    这真的非常重要,而且影响着下面的其他几个技巧。当你创建一个字符串时,其本质就是一个字节的数组。这意味着你可以像访问数组一样的访问单独的某个字节。例如,下面的代码逐个打印字符串中的每个字节以及对应字节...

  • 学习笔记(2)--RASArchitecture

    google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//<script type="text/javascript"

  • ARM MIPS PowerPC X86 四大常见处理架构比较

    目录 1、ARM 1.1 ARM历史 1.2 ARM内核系列 2、MIPS 应用范围 发展历史 3、PowerPC 三巨头 4、X86架构 X86历史 5、PowerPC架构相比于ARM的优势 6、Powerpc架构与X86架构的区别 1、ARM ARM处理器是英国Acorn有限公司设计的低功耗成本的第一款RISC微处理器。全称为Advanced RISC Mac...

  • 学习笔记(2)--RAS Architecture

    google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//<script type="text/javascript"

  • [GO语言基础] 一.为什么我要学习Golang以及GO语言入门普及

    作为网络安全初学者,会遇到采用Go语言开发的恶意样本。因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识;另一方面是分享与读者,希望大家一起进步。这系列文章入门部分将参考“尚硅谷...

  • Go 语言 入门 && 基于 GoLand 2023.1 创建第一个Go程序

    Go 语言 入门 && 基于 GoLand 2023.1 创建第一个Go程序Go 语言 入门 && 基于 GoLand 2023.1 创建第一个Go程序Go 语言 入门 && 基于 GoLand 2023.1 创建第一个Go程序Go 语言 入门 && 基于 GoLand 2023.1 创建第一个Go...

  • linux下通过go语言获得系统进程cpu使用情况的方法

    主要介绍了linux下通过go语言获得系统进程cpu使用情况的方法,实例分析了Go语言使用linux的系统命令ps来分析cpu使用情况的技巧,需要的朋友可以参考下

  • Go语言学习技巧之如何合理使用Pool

    垃圾回收一直是Go语言的一块心病,在它执行垃圾回收的时间中,你很难做什么。 在垃圾回收压力大的服务中,GC占据的CPU有可能超过2%,造成的Pause经常超过2ms。垃圾严重的时候,秒级的GC也出现过。 如果经常临时...

  • go语言使用scp的方法实例分析

    主要介绍了go语言使用scp的方法,实例分析了go语言调用scp命令的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下

  • 100天精通Golang(基础入门篇)——第10天:Go语言中的数组

    通过阅读本篇文章,读者能够了解到如何在Go语言中定义和使用数组,并掌握一些实用的代码技巧。Go语言是一种现代的、快速、并发的编程语言,由于其简单的语法和高效的执行效率,越来越受到广大开发者的青睐。本篇文章...

  • 100天精通Golang(基础入门篇)——第1天:学习Go语言基本概念

    100天精通Golang(基础入门篇)——第1天:学习Go语言基本概念,欢迎来到100天精通Golang的基础入门篇!在这个系列教程中,我们将带领你逐步掌握Go语言的核心概念和语法。本篇博文将着重介绍Go语言的基本概念,包括...

  • 我为什么放弃Go语言

    我为什么放弃Go语言?...开门见山地说,我当初放弃Go语言,就是因为两个“不爽”:第一,对Go语言本身不爽;第二,对Go语言社区里的某些人不爽。毫无疑问,这是非常主观的结论,但是我有足够详实的客观的论据。

  • Go语言结构体定义和使用方法

    主要介绍了Go语言结构体定义和使用方法,以实例形式分析了Go语言中结构体的定义和使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下

  • go语言通过odbc访问Sql Server数据库的方法

    主要介绍了go语言通过odbc访问Sql Server数据库的方法,实例分析了Go语言通过odbc连接与查SQL Server询数据库的技巧,需要的朋友可以参考下

  • 为什么劝你要学习Golang以及GO语言(Go语言知识普及)

    为什么劝你要学习Golang以及GO语言

  • Go语言用map实现堆栈功能的方法

    主要介绍了Go语言用map实现堆栈功能的方法,实例分析了Go语言使用map操作堆栈的技巧,具有一定参考借鉴价值,需要的朋友可以参考下

  • go语言使用RC4加密的方法

    主要介绍了go语言使用RC4加密的方法,实例分析了RC4加密的技巧与实现方法,具有一定参考借鉴价值,需要的朋友可以参考下

  • 【Golang】go编程语言适合哪些项目开发?

    无论是网络编程、大数据处理、云计算、Web开发还是嵌入式系统,Go语言都能够提供出色的性能和可扩展性。希望本文能够帮助读者了解Go语言的优势,并在项目开发中做出明智的选择。如果大家对相关文章感兴趣,可以关注...

  • 【GoLang】哪些大公司正在使用Go语言

    在这个多元化的编程语境中,Go语言(简称Golang)以其简洁、高效、并发处理能力等特性逐渐受到业界关注。越来越多的大型科技公司纷纷采用Go语言作为其软件开发的首选语言,这种趋势反映了Go语言在构建可靠、高性能和...

Global site tag (gtag.js) - Google Analytics