Go语言必备技能——加快你的工作效率

简介: 无论是Go语言小白,还是有经验的开发者,都可能从中获取灵感。

原文链接

一句话技巧

把你面向对象的大脑扔到家里吧,去拥抱接口。
学习如何使用Go的方式做事,不要把别的的编程风格强行用在Go里面。
多用接口总比少用好。
拥抱这种简洁、并行、工整的语言。
阅读官网golang.org上所有的文档,真是棒呆了。
别忘了用gofmt。
多读源代码。
学习工具和组件,然后创造你自己的!码代码和学代码一样对成功必不可少。
学而不思则罔,思而不学则殆。《论语》

引入package的多种方式

有几种非常规方式来引入包(package)。接下来我会使用fmt来作为例子:

import format "fmt" - 为fmt创造一个别名。把代码中所有使用到fmt的内容用format.代替fmt.
import . "fmt" - 允许包内的内容不加fmt前缀而被被直接引用
import _ "fmt" - 阻止编译器为引入fmt却不使用里面的内容做引发的警告,执行package中的初始化函数。提醒一句,在这种情况下fmt是不可调用的
看这篇博客来了解更多细节。

Goimports
命令goimports可以更新您的Go导入行,添加缺少的行,并删除未引用的引导行。

它拥有和gofmt(插入式替换)相同的能力,但是goimports额外增加了修复imports的功能。

组织
Go是一种相对来说易学习的编程语言,但对于开发者来说,起初接触这门语言最困难的事情就是如何组织代码。scaffolding是人们喜欢Rails的原因之一,它可以给新晋的开发者清晰的方向,让他们明白在哪里插入代码,应该遵循怎样的编程风格。

作为扩展,Go使用go fmt这样的工具来提供开发者相同的功能。同样地,Go的编译器非常严格,它不会去编译没有使用的变量,或者没有使用的import声明。

自定义构造函数
我经常听到别人问,“我什么时候应该使用像NewJob这样的自定义构造函数?”,我的回答是“大多数情形下你没必要这么做”。然而,当你需要在初始化的时候就设置值,且你有一些默认值的时候,这就最好使用一个构造函数。在这个例子中,构造函数就比较有意义了,因此我们用如下的代码可以构建一个默认的logger:

package main
import (
    "log"
    "os"
)
type Job struct {
    Command string
    *log.Logger
}
func NewJob(command string) *Job {
    return &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)}
}
func main() {
    NewJob("demo").Print("starting now...")
}

把代码分解到不同的package中
参考这篇博客重构Go代码,第一部分就讲了package的组织。

以工程Gobot为例,它可以被分割为一个核心package和一些其他package。gobot的开发者们准备每个部分放在自己的package里。经过讨论,他们选择把所有的官方库放在同一个repository下,让import路径变得干净而富有逻辑。

所以,他们不打算把路径设置为:

github.com/hybridgroup/gobot
github.com/hybridgroup/gobot-sphero
github.com/hybridgroup/gobot-...

而是设置为

github.com/hybridgroup/gobot
github.com/hybridgroup/gobot/sphero
github.com/hybridgroup/gobot/...

现在package的名字不再是冗长的gobot-sphero,而变成了简要的sphero。

集合(Sets)
在其他的程序语言中,经常会有一种数据结构叫做sets,它允许把元素存入,但是不允许重复。Go并不直接支持这种结构,但是这个结构在Go里面的实现并不困难。

// UniqStr returns a copy if the passed slice with only unique string results.
func UniqStr(col []string) []string {
    m := map[string]struct{}{}
    for _, v := range col {
        if _, ok := m[v]; !ok {
            m[v] = struct{}{}
        }
    }
    list := make([]string, len(m))
    i := 0
    for v := range m {
        list[i] = v
        i++
    }
    return list
}

Playground链接

在这里,我会使用一些非常有意思的花招。首先,对空结构的映射:

m := map[string]struct{}{}

我们创建了一个map,这可以确保key是独一无二的,而相关联的value其实是我们不关心的。
我们当然可以使用:

m := map[string]bool{}

但是,使用空结构体可以达到同样的效率,同时不会占用额外的内存。

第二个花招的意味更为深远:

if _, ok := m[v]; !ok {
  m[v] = struct{}{}
}

这里做的事情就是确认map m中的某个值是否存在,而不关心value本身。如果发现没有对应的值,就去加一个。当然,不去验证直接加好像也没有什么区别。

一旦我们拥有了一个充满独一无二key的map以后,就可以把他们放到一个切片里,返回结果了。

这里有一段测试代码,正如你所见,这里使用了一个符合Go语言单元测试风格的表格测试:

func TestUniqStr(t *testing.T) {
    data := []struct{ in, out []string }{
        {[]string{}, []string{}},
        {[]string{"", "", ""}, []string{""}},
        {[]string{"a", "a"}, []string{"a"}},
        {[]string{"a", "b", "a"}, []string{"a", "b"}},
        {[]string{"a", "b", "a", "b"}, []string{"a", "b"}},
        {[]string{"a", "b", "b", "a", "b"}, []string{"a", "b"}},
        {[]string{"a", "a", "b", "b", "a", "b"}, []string{"a", "b"}},
        {[]string{"a", "b", "c", "a", "b", "c"}, []string{"a", "b", "c"}},
    }
    for _, exp := range data {
        res := UniqStr(exp.in)
        if !reflect.DeepEqual(res, exp.out) {
            t.Fatalf("%q didn't match %q\n", res, exp.out)
        }
    }
}

经过测试发现,并非每次都能够成功,而是有概率的。因为map是使用hashmap实现的,使用range进行遍历的时候,其遍历顺序和字符串的内容没有必然联系,因此此test有可能失败。在进行DeapEqual比对的时候,可能会爆出类似于["b" "c" "a"] didn't match ["a" "b" "c"]的错误。当然,在Playground中,每次执行的上下文环境一模一样,因此这里的test是总能通过的。

依赖包管理
很遗憾,Go语言官方并不提供依赖包管理系统。这很可能是因为go语言植根于C语言的文化,因此它没有办法引入特定版本的包。

这会带来一些严重的问题:

1.当多个开发者共同维护一个项目时,不同开发者的依赖版本可能不同。
2.依赖也会有他们自身的依赖,所以很难确保所有的依赖都使用同一个版本。
3.你的多个项目基于了同一个依赖的不同版本。

对于最后一种情形,可以通过搭建一个_持续集成环境(Continuousintegration)来解决,但是前两者就相对困难。

钉钉扫码进群,与阿里云等各界大佬一同学习Go语言的相关知识
image.png

相关文章
|
1天前
|
存储 Go 索引
go语言使用for循环遍历
go语言使用for循环遍历
14 7
|
4天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
17 2
|
5天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
5天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
14 3
|
5天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
15 3
|
7天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
6天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
20 3
|
10天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
34 4
|
10天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
44 1
|
8天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
26 0