【Go常见错误】1. 从错误中学习

本文部分内容主要参考 《100 Go Mistackes:How to Avoid Them》。

为什么要读一本关于Go常见错误的书呢?

神经科学家证明当我们面对错误时,正是大脑成长的最佳时期。这一特点是因为错误具有促进作用。其主要思想是我们不仅能记住错误,而且还能记住错误发生的场景。

这就是为什么从错误中学习是如此有效的原因之一。

关于Go

1. 特点:简单而稳定

简单:在特性方面,Go没有类型继承,没有异常,没有宏,没有偏函数,不支持惰性求值或不变性,没有运算法重载,没有模式匹配,没有隐式类型转换等。

举例1 类型继承就是一个非常好的例子。

事实上,如果大多数交互都基于继承,那么开发人员维护的心智模型会很快变的复杂。长期以来,人们一直建议程序员应该更喜欢组合而非继承。因此,继承没有被包含在Go语言中。

举例2 数据结构:Go只有三种标准的数据结构类型:

  • array。可以存储固定数量的且类型相同的元素。

  • slice。数组的动态版本,提供了一种更强大、更方便的方式来存储元素集合。

  • map。Go中用于存储键值对的哈希表的实现。

稳定:是Go的一个基本特征。尽管Go也有频繁的更新(性能改进,安全补丁等),但在过去的很多年Go仍然是一门非常稳定的语言。稳定性是在组织规模上采用一种语言的一个重要方面。人们甚至认为它是语言的最佳特征。

2. 开发者生产力:高效

今天,我们比以往任何时候都更快的构建,测试和部署。软件编程必须顺应这一趋势。Go被认为是开发人员最具有生产力的语言。让我们看看为什么这么说。

简洁性

首先,我们提到的是Go是一种简洁的语言:它只有25个关键字。如果与其他语言相比,Java和Rust有50多个,C++有100多个,等等。

另外,Go的简洁性也体现在对于新手来说Go的学习曲线很浅。在Go中,开发人员可以通过注入tour.golang.org之类的资源来快速学习Go。

富有表现力

我们可以强调Go是富有表现力的。在编程语言中的表现力意味着我们可以自然的和直观的编写和阅读代码。

因此,使用富有表现力的语言工作是至关重要的,尤其是在大型组织中。

此外,与其他语言相比,解决常见问题的方法数量减少也使得大型Go代码库通常更容易处理。

快速编译

开发人员生产力的另一个重要方面是编译时间。

以快速编译为目标一直是Go设计者有意而为的。首先,Go的设计目的是为软件构建提供一个模型,简化依赖性分析,避免C风格的include文件和库的大量开销。因此,为开发人员编译节省了大量时间。

总之,Go被认为是一种高效的语言,主要有三个原因:简洁性,表达性和高效性

3. 安全性

类型安全

首先Go是一门静态类型的语言。类型检查是在编译阶段而非运行时进行的。这样就保证了我们编写的代码在大多数情况下是类型安全的。

内存安全

Go具有垃圾收集器来帮助开发者处理内存管理。直接管理内存不是开发人员的责任。垃圾回收器负责跟踪内存分配并在不需要的时候释放内存。

但在执行期间也增加了一点开销。然而,这是一种假设平衡,因为它显著减少了开发工作并降低了应用程序崩溃或内存泄露的风险。

指针安全

对于开发人员来说,另一个令人害怕的方面是指针。指针是一个包含另一个变量地址的变量。指针是Go语言的一个核心方面。然而,Go中的指针处理起来并不复杂,因为他们是显式的(与引用不同),并且没有指针运算之类的东西。这是什么原因呢?再次是为了降低编写不安全应用程序的风险。

4. 并发

在过去的30年里,CPU设计者主要在三个领域取得了显著的进步:

  • 时钟速度

  • 执行优化

  • 缓存

多年来,通过改进这三个领域导致顺序应用程序的性能(非并行,单线程,单进程)的改进。

从2004年左右开始,单线程执行的速度提升不再是线性的。更糟糕的是,它已经趋于达到上限。

同时,CPU设计人员不再只关注时钟速度和优化。相反,他们开始考虑其他方法,例如多核和吵线程(同一物理核上的多个逻辑核)。

并发性将成为软件开发人员的下一个重大革命,而不是编写顺序应用程序并期望CPU总是变的更快。

Go编程语言在设计时就考虑到了并发性。它的并发模型基于通信顺序进程(CSP)。

CSP模型

下面简单介绍一下Go的CSP模型,以及可以看出其是为何简单且高效的。

CSP模型是一种依赖于消息传递的并发范式。进程不必共享内存,而是通过通道交换消息来进行通信。如图1.2所示:

在图1.2中,我们可以看到基于CSP的两个进程之间的交互。每一个进程都是顺序执行的。没有回调使整个交互更加复杂。第一个进程发送一个消息A,同时在某个时刻等待响应。第二个进程等待消息A,执行一些工作,并作为响应,发送一个消息B。

通过内存共享促进消息传递的基本原理是什么呢?

今天,所有的CPU都有不同级别的缓存来加速对主内存(RAM)的访问。跨不同线程共享的变量可能会重复多次。因此,共享内存是现代CPU提供的一种错觉(我们将在并发章节深入研究这些概念)。

采用消息传递符合现代cpu的构建方式,这在大多数情况下对性能有重大影响。此外,她使复杂的交互更容易推理。我们不必处理复杂的回调链:一切都是按顺序编写的。

Go使用两个原语实现了CSP模型:goroutine和channel。

goroutine可以被看成是一个轻量级的线程。与操作系统调度的线程不同,goroutines是由Go运行时调度的。一个goroutine同一时间只属于一个线程,同时,一个线程能处理多个goroutines,如图1.3所示:

操作系统负责在CPU内核上调度线程。同时,Go运行时根据工作负载确定最合适的Go线程数量,并在这些线程上调度goroutine。与线程相比,创建goroutine的成本在启动时间和内存(只有2KB的栈大小)方面更便宜。从一个goroutine到另一个goroutine的上下文切换操作也比线程的上下文切换更快。因此,看到应用程序同时创建数百个甚至数千个goroutine的情况并不少见。

另一方面,channel是一种允许在不同goroutine之间交换数据的数据结构。发送到channel的每一条消息最多由一个goroutine接收。唯一的广播操作(1对N)是一个channel闭包,它传播被多个goroutine接收的事件。

将这些原语座位核心语言的一部分是一个了不起的特性。无需依赖任何外部库。开发人员可以以整洁的、富有表现力和标准的方式编写并发代码。当然,我们仍然可以使用互斥锁的方式来共享内存。然而,在大多数情况下,我们应该支持消息传递的方法,主要是因为,正如所讨论的,这种方法利用了现代CPU的构建方式。

消息传递是一种强大的并发方法,但它不能防止数据竞争。幸运的是,Go提供了一个强大工具来检测数据竞争。

Go易学难精

简单和容易之间存在者细微的差别。简单地应用一项技术意味着学习或理解起来并不复杂。然而,容易意味着我们可以毫不费力的实现一切。

Go则简单易学,但难于精通。

比如对于并发,有研究表明在传递消息的使用频率低于共享内存方法,而且尽管人们认为传递消息的方法更容易处理且不易出错,但大多数阻塞错误都是由传递消息的不准确使用引起的。这也意味着消息传递虽然在理论上易于学习和使用,但在实践中并不容易掌握。

简单不代表容易:可以推广到Go的很多方面,不仅是并发;例如:

  • 什么时候使用接口?

  • 什么时候使用值接收,什么时候使用指针接收?

  • 如何高效处理切片?

  • 如何干净而富有表现力的处理错误管理?

  • 如何避免内存泄露?

  • 如何编写相关测试和基准测试?

  • 如何使用应用程序做好生产准备?

总结

要成为一名熟练的Go开发者,我们应该对该语言的许多方面都要有透彻的了解,这需要大量的时间,精力和错误。

后续将通过收集和展示Go语言各个方面的常见错误来帮助我们成为一名熟练的开发人员:基础知识、代码组织、数据和控制结构、字符串、函数和方法、错误管理、并发、测试、优化和生产。

通过错误和具体例子学习是一种强大的手段。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值