跳转至

1.4 第一个Go程序与代码规范

学习目标

  • 编写并运行第一个Go程序
  • 理解Go程序的基本结构
  • 掌握Go语言代码规范
  • 学会包的概念与使用
  • 了解注释规范与文档编写

1. Hello World程序

1.1 创建第一个程序

创建一个名为 hello.go 的文件:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

1.2 运行程序

# 方法1:直接运行
go run hello.go

# 方法2:编译后运行
go build hello.go
./hello

# 输出:Hello, World!

1.3 程序结构分析

package main           // 1. 包声明
import "fmt"          // 2. 导入语句
func main() {         // 3. 主函数
    fmt.Println("Hello, World!")  // 4. 函数体
}

结构说明

  1. package main:声明这是一个可执行程序
  2. import "fmt":导入fmt包用于格式化输出
  3. func main():程序入口函数
  4. fmt.Println():调用fmt包的Println函数

2. Go程序基本结构

2.1 完整程序示例

// 文件头注释:程序说明
// 这是一个演示Go程序结构的示例

package main

import (
    "fmt"      // 标准库
    "os"       // 标准库
    "strings"  // 标准库

    "github.com/gin-gonic/gin" // 第三方库
)

// 全局变量
var (
    appName    = "MyApp"
    appVersion = "1.0.0"
)

// 全局常量
const (
    MaxRetries = 3
    DefaultPort = 8080
)

// 自定义类型
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

// init函数:包初始化
func init() {
    fmt.Println("Package initialized")
}

// 主函数
func main() {
    fmt.Printf("%s v%s\n", appName, appVersion)

    user := User{ID: 1, Name: "Alice"}
    fmt.Printf("User: %+v\n", user)
}

2.2 程序执行顺序

1. 导入包 → 2. 初始化全局变量 → 3. 调用init()函数 → 4. 调用main()函数
package main

import "fmt"

var globalVar = initGlobalVar()

func initGlobalVar() string {
    fmt.Println("1. 初始化全局变量")
    return "global"
}

func init() {
    fmt.Println("2. 执行init函数")
}

func main() {
    fmt.Println("3. 执行main函数")
}

// 输出:
// 1. 初始化全局变量
// 2. 执行init函数  
// 3. 执行main函数

3. Go语言代码规范

3.1 命名规范

包名规范

// 好的包名
package user
package http
package json

// 不好的包名
package userService  // 避免驼峰命名
package User         // 避免大写开头
package user_service // 避免下划线

变量命名

// 局部变量:驼峰命名
var userName string
var userAge int
var isActive bool

// 全局变量:首字母大写表示导出
var UserCount int
var MaxConnections int

// 常量:全大写或驼峰
const MaxRetries = 3
const DefaultTimeout = 30 * time.Second

// 私有常量
const maxRetries = 3

函数命名

// 导出函数:首字母大写
func GetUser(id int) (*User, error) {
    // ...
}

// 私有函数:首字母小写
func validateUser(user *User) error {
    // ...
}

// 接口命名:通常以-er结尾
type Reader interface {
    Read([]byte) (int, error)
}

type Writer interface {
    Write([]byte) (int, error)
}

结构体命名

// 结构体:首字母大写表示导出
type User struct {
    ID       int    `json:"id"`        // 导出字段
    Name     string `json:"name"`      // 导出字段
    password string `json:"-"`         // 私有字段
}

// 方法命名
func (u *User) GetName() string {
    return u.Name
}

func (u *User) setPassword(pwd string) {
    u.password = pwd
}

3.2 代码格式规范

缩进与空格

// 使用Tab进行缩进,不使用空格
func main() {
    if true {
        fmt.Println("使用Tab缩进")
    }
}

// 操作符前后加空格
result := a + b
if x > 0 && y < 10 {
    // ...
}

// 逗号后加空格
values := []int{1, 2, 3, 4, 5}
fmt.Printf("Hello, %s!\n", name)

行长度与换行

// 行长度控制在80-120字符以内
func processUserData(userID int, userName string, userEmail string, 
    userAge int, isActive bool) error {
    // ...
}

// 长参数列表换行对齐
user := &User{
    ID:    1,
    Name:  "Alice",
    Email: "alice@example.com",
    Age:   25,
}

大括号位置

// 左大括号不换行
if condition {
    // ...
} else {
    // ...
}

func myFunction() {
    // ...
}

type MyStruct struct {
    field1 string
    field2 int
}

3.3 导入规范

package main

import (
    // 1. 标准库
    "context"
    "fmt"
    "net/http"
    "os"
    "time"

    // 2. 第三方库
    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis/v8"

    // 3. 项目内部包
    "myproject/internal/config"
    "myproject/internal/service"
    "myproject/pkg/utils"
)

导入别名

import (
    "database/sql"

    // 别名导入
    mysqlDriver "github.com/go-sql-driver/mysql"

    // 点导入(谨慎使用)
    . "github.com/onsi/ginkgo"

    // 空白导入(仅执行init函数)
    _ "github.com/go-sql-driver/mysql"
)

4. 包的概念与使用

4.1 包的基本概念

// 包声明必须在文件开头
package utils

// 同一个包内的文件可以互相访问私有成员
func publicFunction() {    // 导出函数
    privateFunction()      // 可以调用私有函数
}

func privateFunction() {   // 私有函数
    // ...
}

4.2 包的组织结构

myproject/
├── main.go              // package main
├── utils/               // package utils
│   ├── string.go        // package utils
│   └── math.go          // package utils
├── models/              // package models
│   ├── user.go          // package models
│   └── product.go       // package models
└── handlers/            // package handlers
    ├── user.go          // package handlers
    └── product.go       // package handlers

4.3 包的使用示例

utils/string.go

package utils

import "strings"

// 导出函数:首字母大写
func Capitalize(s string) string {
    if len(s) == 0 {
        return s
    }
    return strings.ToUpper(s[:1]) + s[1:]
}

// 私有函数:首字母小写
func reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

main.go

package main

import (
    "fmt"

    "myproject/utils"  // 导入自定义包
)

func main() {
    result := utils.Capitalize("hello world")
    fmt.Println(result) // 输出:Hello world

    // result := utils.reverse("hello") // 编译错误:无法访问私有函数
}

4.4 init函数

package database

import (
    "database/sql"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB

// init函数在包被导入时自动执行
func init() {
    var err error
    DB, err = sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    if err = DB.Ping(); err != nil {
        log.Fatal("Failed to ping database:", err)
    }

    log.Println("Database connection established")
}

5. 注释规范与文档编写

5.1 注释类型

单行注释

// 这是单行注释
var count int // 行尾注释

// TODO: 实现用户认证功能
// FIXME: 修复并发访问问题
// NOTE: 这里需要特别注意性能

多行注释

/*
这是多行注释
可以跨越多行
通常用于版权声明或大段说明
*/

/*
Package utils provides utility functions for string and math operations.

This package includes commonly used helper functions that can be reused
across different parts of the application.
*/
package utils

5.2 文档注释规范

包文档

// Package user provides user management functionality.
//
// This package includes user registration, authentication,
// and profile management features.
//
// Example usage:
//
//     user := &User{Name: "Alice", Email: "alice@example.com"}
//     if err := user.Save(); err != nil {
//         log.Fatal(err)
//     }
//
package user

函数文档

// GetUser retrieves a user by ID from the database.
//
// It returns the user object if found, or an error if the user
// doesn't exist or if there's a database connection issue.
//
// Example:
//
//     user, err := GetUser(123)
//     if err != nil {
//         log.Printf("Error getting user: %v", err)
//         return
//     }
//     fmt.Printf("User: %s\n", user.Name)
//
func GetUser(id int) (*User, error) {
    // 实现细节...
}

类型文档

// User represents a user in the system.
//
// Each user has a unique ID, name, and email address.
// The password field is not exported for security reasons.
type User struct {
    ID       int    `json:"id" db:"id"`             // 用户唯一标识
    Name     string `json:"name" db:"name"`         // 用户姓名
    Email    string `json:"email" db:"email"`       // 邮箱地址
    password string `json:"-" db:"password"`        // 密码(私有)
}

// String returns a string representation of the user.
func (u User) String() string {
    return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s}", 
        u.ID, u.Name, u.Email)
}

常量和变量文档

// DefaultTimeout is the default timeout for HTTP requests.
const DefaultTimeout = 30 * time.Second

// ErrUserNotFound is returned when a user is not found in the database.
var ErrUserNotFound = errors.New("user not found")

// Configuration holds application configuration.
var Config = struct {
    Port     int    // 服务端口
    Database string // 数据库连接字符串
    Debug    bool   // 调试模式
}{
    Port:     8080,
    Database: "localhost:5432",
    Debug:    false,
}

5.3 生成文档

# 查看包文档
go doc fmt
go doc fmt.Println

# 查看自定义包文档
go doc ./utils
go doc utils.Capitalize

# 生成HTML文档
godoc -http=:6060

# 在浏览器中访问 http://localhost:6060

6. 实践示例

6.1 完整的用户管理包

models/user.go

// Package models provides data models for the application.
package models

import (
    "errors"
    "fmt"
    "regexp"
)

// 常见错误定义
var (
    ErrInvalidEmail = errors.New("invalid email address")
    ErrEmptyName    = errors.New("name cannot be empty")
)

// User represents a user in the system.
//
// Each user must have a valid email address and non-empty name.
// The ID is automatically assigned when the user is saved.
type User struct {
    ID    int    `json:"id" db:"id"`
    Name  string `json:"name" db:"name"`
    Email string `json:"email" db:"email"`
}

// NewUser creates a new user with the given name and email.
//
// It validates the input parameters and returns an error if
// validation fails.
func NewUser(name, email string) (*User, error) {
    user := &User{
        Name:  name,
        Email: email,
    }

    if err := user.Validate(); err != nil {
        return nil, err
    }

    return user, nil
}

// Validate checks if the user data is valid.
//
// It returns an error if the name is empty or the email
// address is not in a valid format.
func (u *User) Validate() error {
    if u.Name == "" {
        return ErrEmptyName
    }

    emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    if !emailRegex.MatchString(u.Email) {
        return ErrInvalidEmail
    }

    return nil
}

// String returns a string representation of the user.
func (u User) String() string {
    return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s}", 
        u.ID, u.Name, u.Email)
}

main.go

package main

import (
    "fmt"
    "log"

    "myproject/models"
)

func main() {
    // 创建用户
    user, err := models.NewUser("Alice", "alice@example.com")
    if err != nil {
        log.Fatal("创建用户失败:", err)
    }

    fmt.Printf("创建用户成功: %s\n", user)

    // 测试无效邮箱
    invalidUser, err := models.NewUser("Bob", "invalid-email")
    if err != nil {
        fmt.Printf("预期的错误: %v\n", err)
    } else {
        fmt.Printf("意外创建了用户: %s\n", invalidUser)
    }
}

7. 代码规范工具

7.1 gofmt - 代码格式化

# 格式化文件
gofmt -w main.go

# 查看格式化差异
gofmt -d main.go

# 格式化整个项目
gofmt -w .

7.2 goimports - 导入管理

# 安装
go install golang.org/x/tools/cmd/goimports@latest

# 自动管理导入并格式化
goimports -w main.go

# 查看差异
goimports -d main.go

7.3 golint - 代码规范检查

# 安装
go install golang.org/x/lint/golint@latest

# 检查代码规范
golint ./...

# 示例输出
# main.go:10:1: exported function GetUser should have comment or be unexported

本节小结

通过本节学习,您应该掌握了Go程序的基本结构、代码规范和文档编写方法。良好的代码规范不仅提高代码可读性,还有助于团队协作和项目维护。

思考题

  1. 为什么Go语言强调代码格式的统一性?
  2. 在什么情况下应该使用init函数?
  3. 如何为开源项目编写高质量的文档注释?