跳过正文

Go 语言基础速查(运维向)

·1462 字·7 分钟·
目录

变量、常量与类型
#

变量声明
#

Go 有三种声明方式,运维脚本里最常用的是短变量声明 :=

package main

import "fmt"

func main() {
    // 显式声明
    var host string = "192.168.1.1"
    var port int = 8080

    // 类型推断
    var timeout = 30 // int
    var ready = true // bool

    // 短变量声明(函数内部)
    addr := fmt.Sprintf("%s:%d", host, port)
    retries := 3

    fmt.Println(addr, timeout, ready, retries)

    // 批量声明
    var (
        maxConn  = 100
        logLevel = "info"
        debug    = false
    )
    fmt.Println(maxConn, logLevel, debug)
}

零值
#

Go 所有变量都有零值,不会出现未初始化的野指针问题。

var i int       // 0
var f float64   // 0.0
var s string    // ""
var b bool      // false
var p *int      // nil
var sl []string // nil(但 len(sl) == 0 是安全的)
var m map[string]int // nil(读 nil map 不 panic,写会 panic)

常量
#

const MaxRetry = 3
const DefaultTimeout = 30 * time.Second // 注意:这是 untyped constant

// iota 枚举
type LogLevel int
const (
    DEBUG LogLevel = iota // 0
    INFO                  // 1
    WARN                  // 2
    ERROR                 // 3
)

控制流
#

for 循环(Go 唯一的循环)
#

// 传统 C 风格
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// while 风格
count := 0
for count < 5 {
    count++
}

// 无限循环
for {
    // 轮询、daemon 场景常用
    time.Sleep(5 * time.Second)
    if shouldStop() {
        break
    }
}

// range 遍历
hosts := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}
for i, host := range hosts {
    fmt.Printf("[%d] checking %s\n", i, host)
}

// 只要 key
for i := range hosts {
    fmt.Println(i)
}

// map 遍历(顺序不确定)
labels := map[string]string{"env": "prod", "app": "nginx"}
for k, v := range labels {
    fmt.Printf("%s=%s\n", k, v)
}

switch
#

env := "production"

switch env {
case "development", "dev":
    fmt.Println("开发环境")
case "staging", "pre":
    fmt.Println("预发布环境")
case "production", "prod":
    fmt.Println("生产环境")
default:
    fmt.Println("未知环境:", env)
}

// 无表达式 switch(等价于 if-else if)
port := 443
switch {
case port < 1024:
    fmt.Println("well-known port")
case port < 49152:
    fmt.Println("registered port")
default:
    fmt.Println("dynamic port")
}

defer
#

defer 语句在函数返回前执行,常用于资源清理。多个 defer 按 LIFO 顺序执行。

func readConfig(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close() // 无论后面怎么 return,都会执行

    return io.ReadAll(f)
}

func connectDB() {
    db := openDB()
    defer db.Close()

    tx := db.Begin()
    defer tx.Rollback() // 如果 Commit 成功,Rollback 是 no-op

    // ... 操作 ...
    tx.Commit()
}

函数
#

多返回值
#

这是 Go 错误处理的核心机制。

func parsePort(s string) (int, error) {
    port, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("invalid port %q: %w", s, err)
    }
    if port < 1 || port > 65535 {
        return 0, fmt.Errorf("port %d out of range [1, 65535]", port)
    }
    return port, nil
}

// 调用时必须处理 error
port, err := parsePort("8080")
if err != nil {
    log.Fatal(err)
}

可变参数(variadic)
#

func logFields(level string, fields ...string) {
    fmt.Printf("[%s]", level)
    for _, f := range fields {
        fmt.Printf(" %s", f)
    }
    fmt.Println()
}

logFields("INFO", "host=10.0.0.1", "port=8080", "status=ok")

// 展开 slice 传入
args := []string{"env=prod", "app=api"}
logFields("DEBUG", args...)

命名返回值
#

适合给返回值加文档,或在 defer 中修改返回值。

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return // naked return,返回命名变量当前值
    }
    result = a / b
    return
}

函数作为值
#

type CheckFn func(host string) bool

func runChecks(hosts []string, check CheckFn) []string {
    var failed []string
    for _, h := range hosts {
        if !check(h) {
            failed = append(failed, h)
        }
    }
    return failed
}

// 闭包
func makeTimeoutChecker(timeout time.Duration) CheckFn {
    return func(host string) bool {
        conn, err := net.DialTimeout("tcp", host+":80", timeout)
        if err != nil {
            return false
        }
        conn.Close()
        return true
    }
}

数组、Slice、Map
#

Slice
#

// 创建
var s []string              // nil slice
s = []string{}              // 空 slice(非 nil)
s = make([]string, 0, 10)  // 预分配容量,避免频繁扩容

// 常用操作
s = append(s, "node1")
s = append(s, "node2", "node3")

other := []string{"node4", "node5"}
s = append(s, other...) // 展开追加

// 切片
fmt.Println(s[1:3])  // [node2 node3]
fmt.Println(s[:2])   // [node1 node2]
fmt.Println(s[2:])   // [node3 node4 node5]

// 长度与容量
fmt.Println(len(s), cap(s))

// 陷阱:slice 共享底层数组
a := []int{1, 2, 3, 4, 5}
b := a[1:3]  // b = [2, 3],与 a 共享内存
b[0] = 99   // a[1] 也变成 99!
// 需要独立副本时用 copy
c := make([]int, len(b))
copy(c, b)

Map
#

// 创建
m := make(map[string]string)
m["env"] = "prod"
m["region"] = "us-west-2"

// 字面量初始化
labels := map[string]string{
    "app":  "nginx",
    "tier": "frontend",
}

// 安全读取(检查 key 是否存在)
val, ok := labels["app"]
if !ok {
    fmt.Println("key not found")
}

// 删除
delete(labels, "tier")

// 遍历
for k, v := range labels {
    fmt.Printf("%s: %s\n", k, v)
}

// map 作为集合使用
seen := make(map[string]struct{})
hosts := []string{"a", "b", "a", "c"}
unique := make([]string, 0)
for _, h := range hosts {
    if _, exists := seen[h]; !exists {
        seen[h] = struct{}{}
        unique = append(unique, h)
    }
}

结构体、方法与接口
#

结构体
#

type Server struct {
    Host    string
    Port    int
    Tags    []string
    Healthy bool
}

// 初始化
s := Server{
    Host:    "10.0.0.1",
    Port:    8080,
    Tags:    []string{"prod", "api"},
    Healthy: true,
}

// 匿名结构体(适合临时数据)
config := struct {
    Timeout int
    Retries int
}{
    Timeout: 30,
    Retries: 3,
}
_ = config

方法
#

// 值接收者:不修改原始数据
func (s Server) Address() string {
    return fmt.Sprintf("%s:%d", s.Host, s.Port)
}

// 指针接收者:修改原始数据,或避免大结构体拷贝
func (s *Server) SetHealthy(healthy bool) {
    s.Healthy = healthy
}

// 使用
srv := &Server{Host: "10.0.0.1", Port: 8080}
fmt.Println(srv.Address())
srv.SetHealthy(false)

接口(鸭子类型)
#

接口由方法签名定义,只要实现了所有方法就满足接口,无需显式声明。

// 定义接口
type HealthChecker interface {
    Check() (bool, error)
    Name() string
}

// HTTP 检查器
type HTTPChecker struct {
    URL     string
    Timeout time.Duration
}

func (h HTTPChecker) Check() (bool, error) {
    client := &http.Client{Timeout: h.Timeout}
    resp, err := client.Get(h.URL)
    if err != nil {
        return false, err
    }
    defer resp.Body.Close()
    return resp.StatusCode == http.StatusOK, nil
}

func (h HTTPChecker) Name() string {
    return "HTTP:" + h.URL
}

// TCP 检查器
type TCPChecker struct {
    Addr    string
    Timeout time.Duration
}

func (t TCPChecker) Check() (bool, error) {
    conn, err := net.DialTimeout("tcp", t.Addr, t.Timeout)
    if err != nil {
        return false, err
    }
    conn.Close()
    return true, nil
}

func (t TCPChecker) Name() string {
    return "TCP:" + t.Addr
}

// 统一处理
func runAllChecks(checkers []HealthChecker) {
    for _, c := range checkers {
        ok, err := c.Check()
        if err != nil {
            fmt.Printf("[ERROR] %s: %v\n", c.Name(), err)
            continue
        }
        status := "UP"
        if !ok {
            status = "DOWN"
        }
        fmt.Printf("[%s] %s\n", status, c.Name())
    }
}

指针基础
#

// 取地址
x := 42
p := &x      // p 是 *int
fmt.Println(*p) // 解引用:42

*p = 100    // 通过指针修改 x
fmt.Println(x) // 100

// 函数参数传指针(修改调用方的变量)
func increment(n *int) {
    *n++
}

count := 0
increment(&count)
fmt.Println(count) // 1

// new() 分配零值
s := new(Server) // s 是 *Server,所有字段是零值
s.Host = "localhost"

包管理(go mod 常用命令)
#

# 初始化模块
go mod init github.com/yourname/ops-tools

# 添加依赖
go get github.com/spf13/cobra@latest

# 整理依赖(删除未用的,补充缺少的)
go mod tidy

# 查看依赖树
go mod graph

# 下载依赖到本地缓存
go mod download

# vendor 模式(CI 环境或离网部署)
go mod vendor
go build -mod=vendor ./...

# 升级依赖
go get -u github.com/spf13/cobra
go get -u ./... # 升级所有

# 查看可用版本
go list -m -versions github.com/spf13/cobra

# 替换依赖(本地开发 / fork)
# go.mod 中添加:
# replace github.com/original/pkg => ../local-pkg

错误处理惯用法
#

// 基本模式:每次调用后检查 error
data, err := os.ReadFile("/etc/hosts")
if err != nil {
    return fmt.Errorf("读取 hosts 文件失败: %w", err)
}

// errors.Is:检查错误链中是否包含特定错误
if errors.Is(err, os.ErrNotExist) {
    // 文件不存在,不是致命错误
    data = []byte{}
}

// errors.As:提取特定类型的错误
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Println("问题路径:", pathErr.Path)
}

// 错误包装(%w 保留错误链)
func loadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("loadConfig %s: %w", path, err)
    }
    // ...
    return nil, nil
}

运维工程师常用标准库一览
#

主要用途关键类型/函数
os文件、目录、环境变量、进程、信号Open, ReadFile, Getenv, Exit, Signal
ioI/O 原语、接口定义Reader, Writer, ReadAll, Copy
bufio带缓冲的 I/O,按行读取Scanner, NewReader, NewWriter
fmt格式化输出、字符串构建Println, Sprintf, Fprintf, Errorf
strings字符串操作Contains, Split, TrimSpace, HasPrefix
strconv类型转换Atoi, Itoa, ParseBool, FormatFloat
time时间、定时器Now, Since, Sleep, Ticker, Format
encoding/jsonJSON 序列化/反序列化Marshal, Unmarshal, Decoder
net/httpHTTP 客户端和服务端Get, Post, ListenAndServe, Client
os/exec执行外部命令Command, Output, CombinedOutput
log简单日志Printf, Fatal, SetFlags
path/filepath路径操作(跨平台)Join, Dir, Base, Walk, Glob
regexp正则表达式Compile, MatchString, FindAll
sync并发同步原语Mutex, WaitGroup, Once
context超时、取消传播WithTimeout, WithCancel, Background
flag命令行参数解析String, Int, Bool, Parse

快速参考:常见陷阱
#

// ❌ 陷阱1:在循环中使用 goroutine 捕获变量
for _, host := range hosts {
    go func() {
        fmt.Println(host) // 所有 goroutine 都会打印最后一个 host
    }()
}

// ✅ 正确方式:传参
for _, host := range hosts {
    go func(h string) {
        fmt.Println(h)
    }(host)
}

// ❌ 陷阱2:nil map 写入
var m map[string]string
m["key"] = "value" // panic: assignment to entry in nil map

// ✅ 正确方式
m := make(map[string]string)
m["key"] = "value"

// ❌ 陷阱3:忽略 error
os.Remove("/tmp/test") // 如果失败,你不会知道

// ✅ 正确方式
if err := os.Remove("/tmp/test"); err != nil {
    log.Printf("删除文件失败: %v", err)
}

// ❌ 陷阱4:slice append 后仍共享底层数组
a := make([]int, 3, 5)
b := a[:2]
b = append(b, 99) // 这会修改 a[2]!

// ✅ 用 full slice expression 限制容量
b = a[:2:2] // cap(b) == 2,append 会强制分配新数组
Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

Go 标准库速查:运维工程师常用

·1761 字·9 分钟
不查文档快速写出对的代码——整理了运维场景最常用的 Go 标准库用法,每节都是可直接复制的代码片段

Go 运维工具开发实战

·1841 字·9 分钟
从零写一个 Go 运维工具:cobra CLI 框架、执行 kubectl 命令、调用 K8s API、配置 zap 日志、viper 配置管理,完整可运行的代码示例