Golang 单元测试与基准测试
前言 “没测试的代码 = 不知道能不能跑的代码”。Go 把测试做成语言级公民:go test 开箱即用,没有选择困难。这篇从规范到进阶一次讲清。 ...
前言 “没测试的代码 = 不知道能不能跑的代码”。Go 把测试做成语言级公民:go test 开箱即用,没有选择困难。这篇从规范到进阶一次讲清。 ...
前言 标准库 net/http 就能写出生产级 Web 服务。这篇从路由到优雅关停走一遍,再聊什么时候需要框架。 一、五分钟起步 package main import ( "encoding/json" "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintln(w, "ok") }) // JSON API mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") // go1.22+ 原生路径参数! json.NewEncoder(w).Encode(map[string]string{ "id": id, "name": "zy", }) }) log.Println("listening :8080") log.Fatal(http.ListenAndServe(":8080", mux)) } curl http://localhost:8080/healthz curl http://localhost:8080/api/users/42 Go 1.22 后 ServeMux 支持方法限定 + 路径参数(GET /users/{id}),日常路由基本不用框架了。 ...
前言 写 Go 几乎不需要"装依赖起步"——标准库覆盖了日常大头。这篇按使用频率盘点高频标准库,都是抄了就能用的片段。 一、strings / strconv / unicode import ("strings"; "strconv"; "unicode") s := "Hello, Golang 世界" strings.Contains(s, "Golang") // true strings.HasPrefix(s, "He") // true strings.Split("a,b,c", ",") // [a b c] strings.Join([]string{"a","b"}, "-") // a-b strings.ToUpper(s) strings.TrimSpace(" hi \n") // hi strings.Replace(s, "o", "0", -1) // 全部替换 strings.Fields(" a b c ") // [a b c](按空白切, 超常用) strings.Repeat("=", 20) fmt.Sprintf("%s=%d", "k", 1) // Builder: 循环拼字符串用它(比 += 快得多) var b strings.Builder for i := 0; i < 1000; i++ { b.WriteString("x") } b.String() // strconv: 字符串与数字互转 strconv.Itoa(42) // "42" n, err := strconv.Atoi("42") f, err := strconv.ParseFloat("3.14", 64) 二、slices / maps / sort(go1.21+ 泛型库) import ("slices"; "maps"; "sort") nums := []int{3, 1, 4, 1, 5} slices.Contains(nums, 4) // true slices.Index(nums, 4) // 2 slices.Sort(nums) slices.Reverse(nums) slices.Equal([]int{1,2}, []int{1,2}) // true slices.Max(nums), slices.Min(nums) // 5, 1 // 结构体排序 type User struct{ Name string; Age int } users := []User{{"b", 30}, {"a", 25}} slices.SortFunc(users, func(x, y User) int { return x.Age - y.Age }) // maps m := map[string]int{"a": 1, "b": 2} keys := slices.Collect(maps.Keys(m)) // 迭代器收集 maps.Clone(m) // 浅拷贝 三、time:时间处理 now := time.Now() now.Format("2006-01-02 15:04:05") // 记住: Go 的格式化模板就是这个参考时间! t, err := time.Parse("2006-01-02", "2024-11-16") now.Add(24 * time.Hour) // 明天 now.Sub(startTime) // time.Duration d := 2*time.Hour + 30*time.Minute d.Seconds() // 9000 // 休眠与超时 time.Sleep(100 * time.Millisecond) // 定时器 ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { fmt.Println("每5秒执行", time.Now()) } // 耗时测量 start := time.Now() heavyWork() log.Printf("cost %v", time.Since(start)) // 1.234ms 四、encoding/json(序列化基石) type Server struct { Host string `json:"host"` Port int `json:"port,omitempty"` // 零值时省略 Tags []string `json:"tags,omitempty"` } // 结构体 -> JSON s := Server{Host: "10.0.0.1", Port: 8080, Tags: []string{"prod"}} data, _ := json.Marshal(s) data2, _ := json.MarshalIndent(s, "", " ") // 带缩进(好读) // JSON -> 结构体 var s2 Server json.Unmarshal([]byte(`{"host":"h1","port":80}`), &s2) // JSON -> map(结构未知时) var m map[string]any json.Unmarshal([]byte(`{"a":1}`), &m) 五、net/http:自带 Web 能力 // 服务端: 十行起一个生产可用的 API http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintln(w, "ok") }) http.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") // ?name=zy if name == "" { http.Error(w, "missing name", http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"msg": "hello " + name}) }) log.Println("listen :8080") log.Fatal(http.ListenAndServe(":8080", nil)) // 客户端 resp, err := http.Get("https://httpbin.org/get") if err != nil { log.Fatal(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(resp.StatusCode, len(body)) // 带超时的 client(生产必配! 默认没有超时) client := &http.Client{Timeout: 5 * time.Second} 六、os / os/exec / filepath import ("os"; "os/exec"; "path/filepath") // 文件 data, err := os.ReadFile("app.conf") os.WriteFile("out.txt", []byte("hi"), 0644) f, _ := os.Open("big.log") defer f.Close() scanner := bufio.NewScanner(f) // 逐行读(大文件) for scanner.Scan() { fmt.Println(scanner.Text()) } // 目录 entries, _ := os.ReadDir("/var/log") for _, e := range entries { fmt.Println(e.Name(), e.IsDir()) } // 路径( filepath 跨平台) filepath.Join("/etc", "app", "a.conf") // /etc/app/a.conf filepath.Ext("a.tar.gz") // .gz filepath.Base("/x/y/z.txt") // z.txt filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if !d.IsDir() && filepath.Ext(path) == ".log" { fmt.Println(path) } return nil }) // 执行命令 out, err := exec.Command("df", "-h").Output() if ee, ok := err.(*exec.ExitError); ok { log.Println("exit code:", ee.ExitCode()) } 七、flag / env:程序配置 import "flag" port := flag.Int("port", 8080, "监听端口") env := flag.String("env", "dev", "运行环境") flag.Parse() fmt.Println(*port, *env) // ./app -port 9090 -env prod // 环境变量 if v := os.Getenv("APP_ENV"); v == "" { os.Setenv("APP_ENV", "dev") // 或 log.Fatal("必须设置 APP_ENV") } 八、context(超时与取消,下篇细讲,先混个脸熟) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) // 请求级超时 resp, err := client.Do(req) 九、综合:一个小工具 // urlcheck.go —— 批量探测 URL 可用性 package main import ( "context"; "fmt"; "net/http"; "os"; "sync"; "time" ) func main() { client := &http.Client{Timeout: 3 * time.Second} urls := os.Args[1:] var wg sync.WaitGroup for _, u := range urls { wg.Add(1) go func(u string) { defer wg.Done() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", u, nil) _, err := client.Do(req) status := "OK" if err != nil { status = "FAIL: " + err.Error() } fmt.Printf("%-40s %s\n", u, status) }(u) } wg.Wait() } // go run urlcheck.go https://a.com https://b.com 十几行标准库 = 并发 + 超时 + CLI 工具,Go 的"自带电池"含金量。 ...
前言 Go 没有 try/catch,错误是返回值,必须逐个处理。初看啰嗦,实则强迫你思考每一个失败路径。这篇把现代 Go(1.13+)的错误处理体系讲全。 ...
前言 Go 从语言层面为并发而生:go 关键字一行起"线程",channel 让协程间安全通信。口号是——不要通过共享内存来通信,而要通过通信来共享内存。 ...
前言 Go 没有 class、没有继承,但通过结构体 + 方法 + 接口实现了更轻量的面向对象。核心哲学一句话:组合优于继承,隐式优于显式。 一、结构体 type Server struct { Host string // 大写开头 = 导出(公开) Port int // 小写 = 包内私有 Tags []string metadata map[string]string } // 字面量初始化(推荐: 带字段名, 可读性好且不怕字段调整) s := Server{ Host: "10.0.0.1", Port: 8080, Tags: []string{"prod", "web"}, } // 零值可用(Go 设计哲学!) var s2 Server // Host="" Port=0 Tags=nil, 直接用不会崩 s2.Port = 9090 // 结构体是值类型:赋值即拷贝 s3 := s2 s3.Port = 1000 fmt.Println(s2.Port) // 9090, 不受影响 构造函数惯例(Go 没有 constructor,用普通函数模拟): ...
前言 Go 的日常编码,八成时间在和 slice 与 map 打交道。这篇把它们的核心语义和著名"陷阱"讲透。 一、数组:定长的值类型 // 长度是类型的一部分: [3]int 和 [4]int 是不同类型! var a [3]int // {0, 0, 0}(零值) b := [3]int{1, 2, 3} c := [...]int{1, 2, 3} // 编译器数长度, 仍是 [3]int // 数组是值类型: 赋值和传参都是完整拷贝! d := b d[0] = 99 fmt.Println(b[0]) // 1 —— b 不受影响 实际开发中很少直接用数组(定长 + 值拷贝太死板),主力是切片。 ...
前言 环境搭好(见上篇),开始正式修炼 Go 语法。Go 的设计哲学是 少即是多——关键字只有 25 个,没有继承、没有异常、没有三元运算符,但每个特性都精打细磨。 ...
前言 决定把 Golang 加入技能树——云原生生态(Docker、Kubernetes、Prometheus)几乎全是 Go 写的,学它性价比极高。第一步,把环境搭顺。 ...