Go 语言实战 第 9 章 测试和性能

作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。

9.1 单元测试

单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。
测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。

  • 正向路径测试:正常执行的情况下,保证代码不产生错误的测试。

  • 负向路径测试:保证代码不仅会产生错误,而且是预期错误的测试。

  • 基础测试(basic test:只使用一组参数和结果来测试代码。

  • 表组测试(table test:使用多组参数和结果来测试代码。

Go 语言实战 第 8 章 标准库

Go 标准库是一组核心包,用来扩展和增强语言的能力。
这些包为语言增加了大量不同的类型。
开发人员可以直接使用这些类型,而不用再写自己的包或者去下载其他人发布的第三方包。

由于这些包和语言绑在一起发布,它们会得到以下特殊的保证:

  • 每次语言更新,哪怕是小更新,都会带有标准库;
  • 这些标准库会严格遵守向后兼容的承诺;
  • 标准库是 Go 语言开发、构建、发布过程的一部分;
  • 标准库由 Go 的构建者们维护和评审;
  • 每次 Go 语言发布新版本时,标准库都会被测试,并评估性能。
Go 语言实战 第 7 章 并发模式

本章学习 3 个可以在实际工程里使用的包,这 3 个包分别实现了不同的并发模式。

7.1 runner

// Example is provided with help by Gabriel Aszalos.
// Package runner manages the running and lifetime of a process.
package runner

import (
    "errors"
    "os"
    "os/signal"
    "time"
)

// Runner runs a set of tasks within a given timeout and can be
// shut down on an operating system interrupt.
type Runner struct {
    // interrupt channel reports a signal from the
    // operating system.
    interrupt chan os.Signal

    // complete channel reports that processing is done.
    complete chan error

    // timeout reports that time has run out.
    timeout <-chan time.Time

    // tasks holds a set of functions that are executed
    // synchronously in index order.
    tasks []func(int)
}

// ErrTimeout is returned when a value is received on the timeout channel.
var ErrTimeout = errors.New("received timeout")

// ErrInterrupt is returned when an event from the OS is received.
var ErrInterrupt = errors.New("received interrupt")

// New returns a new ready-to-use Runner.
func New(d time.Duration) *Runner {
    return &Runner{
        interrupt: make(chan os.Signal, 1),
        complete:  make(chan error),
        timeout:   time.After(d),
    }
}

// Add attaches tasks to the Runner. A task is a function that
// takes an int ID.
func (r *Runner) Add(tasks ...func(int)) {
    r.tasks = append(r.tasks, tasks...)
}

// Start runs all tasks and monitors channel events.
func (r *Runner) Start() error {
    // We want to receive all interrupt based signals.
    signal.Notify(r.interrupt, os.Interrupt)

    // Run the different tasks on a different goroutine.
    go func() {
        r.complete <- r.run()
    }()

    select {
    // Signaled when processing is done.
    case err := <-r.complete:
        return err

    // Signaled when we run out of time.
    case <-r.timeout:
        return ErrTimeout
    }
}

// run executes each registered task.
func (r *Runner) run() error {
    for id, task := range r.tasks {
        // Check for an interrupt signal from the OS.
        if r.gotInterrupt() {
            return ErrInterrupt
        }

        // Execute the registered task.
        task(id)
    }

    return nil
}

// gotInterrupt verifies if the interrupt signal has been issued.
func (r *Runner) gotInterrupt() bool {
    select {
    // Signaled when an interrupt event is sent.
    case <-r.interrupt:
        // Stop receiving any further signals.
        signal.Stop(r.interrupt)
        return true

    // Continue running as normal.
    default:
        return false
    }
}
Go 语言实战 第 6 章 并发

6.1 并发和并行

Go 语言里的并发指的是能让某个函数独立于其它函数的能力。

Go 语言的并发同步模型来自一个叫做 通信顺序进程(Communicating Sequential Processes,CSP 的泛型(paradigm)。
CSP 是一种消息传递模型,通过在 goroutine 之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。
用于在 goroutine 之间同步和传递数据的关键数据类型叫做 通道(channel

Go 语言实战 第 5 章 Go 语言的类型系统

Go 语言是一种静态类型的编程语言。这就意味着,编译器需要在编译时知晓程序里每个值的类型。

值的类型给编译器提供两个信息:

  1. 需要分配多少内存给这个值(即值的规模)
  2. 这段内存表示什么

5.1 用户定义类型

Go 语言允许用户定义类型。当用户声明一个类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。

使用 struct 关键字声明结构:

type user struct {
    name        string
    email       string
    ext         int
    privileged  bool
}
Go 语言实战 第 4 章 数组、切片和映射

数组

在 Go 语言里,数组是一个长度固定的数据类型,用户存储一段具有相同类型的元素的连续块
数组占用的内存是连续分配的。

声明数组时需要指定内部存储的数据的类型,以及需要存储的元素的数量。
一旦声明,数组里存储的数据类型和数组长度就都不能改变了。

var array [5]int
Go 语言实战 第 3 章 打包和工具链

在 Go 语言中,包是个非常重要的概念。
其设计理念是使用包来封装不同语义单元的功能。

所有的 .go 文件,除了空行和注释,都应该在第一行声明自己所属的包。
每个包都在单独的目录里。
同一个目录下的所有 .go 文件必须声明同一个包名。

给包命名的惯例是使用包所在目录的名字。
给包及其目录命名时,应该使用简洁、清晰且全小写的名字。

main 包

在 Go 语言中,命名为 main 的包具有特殊的含义。
所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。
main() 函数是程序的入口。
程序编译时,会使用声明为 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。

Go 语言实战 第 2 章 快速开始一个 Go 程序

示例代码:

git clone https://github.com/goinaction/code.git

main 包

package main

import (
    "log"
    "os"
    _ "./matchers"
    "./search"s
)

// init is called prior to main.
func init() {
    // Change the device for logging to stdout.
    log.SetOutput(os.Stdout)
}

// main is the entry point for the program.
func main() {
    // Perform the search for the specified term.
    search.Run("president")
}
Go 语言实战 第 1 章 关于 Go 语言的介绍

编译

编译 Go 程序时,编译器只会关注那些直接被引用的库。

JiaJia:

这是不是意味着 Go 不支持反射? Go 语言有提供反射功能的 reflect 包。 => Go 语言如何保证反射的包会被编译?

goroutine

Go 语言对并发的支持是这门语言最重要的特性之一。 goroutine 很像线程,但是它占用的内存远少于线程,使用它需要的代码更少。
goroutine 是可以与其它 goroutine 并行执行的函数,同时也会与主程序(程序的入口)并行执行。