基础语法不是背规则,而是建立手感
很多人学 Go 的第一反应是:语法不难,但总觉得写起来有些规矩。变量声明有好几种方式,类型写在变量名后面,if 没有括号,循环只有 for,switch 默认不贯穿。单独看每条规则都不复杂,真正需要练的是判断:什么时候该显式写类型,什么时候可以让编译器推断,什么时候应该用 switch 让代码更清楚。
Go 的基础语法很强调可读性。它不追求用最短的字符表达最多的东西,而是希望一段代码交给同事时,对方不用猜你的意图。变量名、类型、作用域和控制流都应该服务于这个目标。
这篇文章会从变量和类型讲起,再讲 if、for、switch。示例尽量贴近真实后端代码,比如订单金额、用户状态、配置开关和分页参数。你不需要一次记住所有细节,但要开始形成 Go 的写法习惯。
变量声明的几种方式
Go 最完整的变量声明是:
var name string = "小林"
这行代码同时写出了变量名、类型和值。它当然正确,但在函数内部并不总是必要。因为右侧已经是字符串,编译器能推断类型,所以可以写成:
var name = "小林"
在函数内部,最常见的是短变量声明:
name := "小林"
age := 28
active := true
:= 只能用于函数内部,不能用于包级变量。它适合局部变量,因为局部上下文短,类型通常一眼能看出来。
包级变量一般更谨慎:
var serviceName = "user-api"
var maxRetryCount int = 3
包级状态会影响整个包,使用时要克制。初学阶段不要把所有东西都放到全局变量里。能作为参数传递的,就尽量作为参数传递。
零值:Go 很重视默认状态
Go 变量声明后即使没有赋值,也会有零值:
var name string
var count int
var enabled bool
fmt.Println(name) // ""
fmt.Println(count) // 0
fmt.Println(enabled) // false
零值让 Go 代码少了很多未初始化错误。你可以放心声明一个变量,再根据条件赋值。
func discountRate(level string) float64 {
var rate float64
if level == "gold" {
rate = 0.20
} else if level == "silver" {
rate = 0.10
}
return rate
}
如果不是金牌或银牌用户,rate 保持 0。这段代码是安全的。但安全不代表一定清楚。很多时候直接返回会更明确:
func discountRate(level string) float64 {
if level == "gold" {
return 0.20
}
if level == "silver" {
return 0.10
}
return 0
}
Go 的零值很有用,但不要用它隐藏业务意图。如果 0 表示“没有折扣”,可以接受。如果 0 表示“未知状态”,最好显式建模。
基本类型要选得自然
Go 常用基本类型包括:
string
bool
int
int64
float64
byte
rune
业务代码里,整数默认使用 int 就可以。需要和数据库、自增 ID、时间戳或外部协议对齐时,才更常见地使用 int64。金额不要随便用 float64 表示真实货币,因为浮点数有精度问题。入门示例可以用 float64 表示折扣率、评分、比例,但订单金额更建议用“分”为单位的整数:
type Order struct {
ID int64
TotalCents int64
Paid bool
}
字符串是 string,但要注意中文字符不是一个字节。下面代码输出的长度不是 2:
name := "小林"
fmt.Println(len(name))
len 返回字节数。中文字符在 UTF-8 中通常占多个字节。如果你要按字符遍历,应使用 range:
for _, r := range "小林" {
fmt.Printf("%c\n", r)
}
这里的 r 类型是 rune,本质上是 Unicode 码点。初学者不必深入编码细节,但要记住:处理中文时,不要把字节长度当成字符数量。
常量适合表达稳定规则
常量用 const:
const defaultPageSize = 20
const maxPageSize = 100
它适合表达不会在运行时改变的规则。比如分页默认数量、订单状态值、配置键名。
const (
StatusPending = "pending"
StatusPaid = "paid"
StatusClosed = "closed"
)
Go 里常量没有必须大写的要求。首字母大写意味着导出,能被其他包访问;首字母小写表示包内使用。如果这些状态只在当前包里使用,写成小写也很好:
const (
statusPending = "pending"
statusPaid = "paid"
statusClosed = "closed"
)
不要为了“像常量”就全部大写。Go 社区很少写 MAX_PAGE_SIZE 这种风格,除非你在对接已有协议或环境变量名。
if:让判断贴近业务语言
Go 的 if 没有括号:
if age >= 18 {
fmt.Println("adult")
}
可以带初始化语句:
if discount := discountRate("gold"); discount > 0 {
fmt.Printf("discount: %.0f%%\n", discount*100)
}
这里 discount 只在 if 和对应 else 范围内可见。这个写法适合临时变量,能减少作用域污染。
真实代码里,更常见的是提前返回:
func canCheckout(stock int, userActive bool) bool {
if stock <= 0 {
return false
}
if !userActive {
return false
}
return true
}
这种写法比多层嵌套更容易读。Go 代码经常这样处理错误和边界条件:先把不能继续的情况排除掉,最后留下正常路径。
不太建议写成:
func canCheckout(stock int, userActive bool) bool {
if stock > 0 {
if userActive {
return true
}
}
return false
}
它能运行,但读者要在脑子里穿过两层条件。业务条件越多,嵌套越难维护。
for:Go 只有一种循环
Go 没有 while,循环统一使用 for。
普通计数循环:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
类似 while 的写法:
count := 0
for count < 5 {
fmt.Println(count)
count++
}
无限循环:
for {
fmt.Println("working")
break
}
遍历切片使用 range:
names := []string{"小林", "阿周", "老陈"}
for index, name := range names {
fmt.Printf("%d: %s\n", index, name)
}
如果不需要索引,用 _ 忽略:
for _, name := range names {
fmt.Println(name)
}
遍历 map:
scores := map[string]int{
"小林": 95,
"阿周": 88,
}
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
要注意 map 遍历顺序是不固定的。你不能依赖它每次输出一样顺序。如果需要排序,先取出 key,排序后再访问。
switch:比一串 else if 更像规则表
当判断同一个变量的多个取值时,switch 很清楚:
func statusText(status string) string {
switch status {
case "pending":
return "待支付"
case "paid":
return "已支付"
case "closed":
return "已关闭"
default:
return "未知状态"
}
}
Go 的 switch 默认不会贯穿到下一个 case,所以不需要写 break。这减少了很多传统 C 风格 switch 的错误。
多个值可以放在同一个 case:
func isFinalStatus(status string) bool {
switch status {
case "paid", "closed", "refunded":
return true
default:
return false
}
}
也可以不带表达式,把它当成更清楚的 if else:
func shippingFee(totalCents int64) int64 {
switch {
case totalCents >= 19900:
return 0
case totalCents >= 9900:
return 500
default:
return 1200
}
}
这段代码比嵌套判断更像一张规则表。业务规则一多,switch 往往比连续 else if 更容易维护。
作用域:变量活得越短越好
Go 的作用域规则直接影响代码质量。变量应该尽量靠近使用它的地方声明。
func printOrder(orderID int64) {
orderNo := fmt.Sprintf("NO-%06d", orderID)
fmt.Println(orderNo)
}
不要把局部变量提前声明到很远的地方:
func printOrder(orderID int64) {
var orderNo string
// 中间隔了很多代码
orderNo = fmt.Sprintf("NO-%06d", orderID)
fmt.Println(orderNo)
}
变量作用域越大,读者需要记住的状态越多。Go 的短变量声明、if 初始化语句、for 初始化语句,都是为了让变量留在尽量小的范围内。
也要避免无意中遮蔽变量:
count := 10
if true {
count := 20
fmt.Println(count) // 20
}
fmt.Println(count) // 10
里面的 count := 20 创建了新变量,不是修改外面的 count。这种情况有时是故意的,有时是 bug。初学阶段遇到值没变,先检查是不是用了 :=。
写一个分页参数清洗函数
把前面的知识组合起来,写一个常见函数:清洗分页参数。
package main
import "fmt"
const (
defaultPage = 1
defaultPageSize = 20
maxPageSize = 100
)
func normalizePagination(page int, pageSize int) (int, int) {
if page <= 0 {
page = defaultPage
}
switch {
case pageSize <= 0:
pageSize = defaultPageSize
case pageSize > maxPageSize:
pageSize = maxPageSize
}
return page, pageSize
}
func main() {
page, pageSize := normalizePagination(-1, 500)
fmt.Printf("page=%d pageSize=%d\n", page, pageSize)
}
这段代码包含常量、变量、if、switch 和多返回值。它不复杂,却很像真实后端服务里的参数处理。
为什么不直接在调用处到处写判断?因为分页规则是一件独立事情。把它封装成函数,调用者只需要相信结果已经合法:
page, pageSize := normalizePagination(req.Page, req.PageSize)
学习基础语法时,最好总是问一句:这段语法在真实代码里解决什么问题?当你能把语法和场景连起来,记忆会牢得多。
小结
Go 的变量、类型和流程控制都不难,但它们共同塑造了一种风格:显式、短作用域、少嵌套、规则清楚。变量声明不要炫技,能让读者看懂最重要;类型选择要贴近业务,尤其是金额、ID、状态这类字段;if 适合处理边界,switch 适合表达规则,for 统一处理所有循环。
写 Go 基础代码时,可以先记住几个简单判断:局部变量优先用 :=,包级变量少用;能提前返回就少嵌套;中文字符串不要用字节长度当字符数量;map 遍历不要依赖顺序;变量作用域越小越好。
这些习惯看起来细,但会一直伴随你。后面写函数、结构体、接口和并发代码时,基础语法的清晰度会直接决定程序是否容易维护。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。