
这是一段常见的订单处理逻辑:
gofunc ProcessOrder(order *Order) error {
if order != nil {
if order.Status == "pending" {
if order.Amount > 0 {
if order.User != nil {
if order.User.Level == "vip" {
order.Discount = 0.8
} else {
if order.User.Level == "new" {
order.Discount = 0.9
} else {
order.Discount = 1.0
}
}
return nil
} else {
return errors.New("user is nil")
}
} else {
return errors.New("invalid amount")
}
} else {
return errors.New("invalid status")
}
} else {
return errors.New("order is nil")
}
}5 层嵌套,7 个判断,阅读成本极高。 这不是个例,这是大多数业务代码的日常。
下面逐一拆解简化手段。
卫语句的核心:不满足条件就立刻返回,让主逻辑保持在最外层。
gofunc ProcessOrder(order *Order) error {
// 卫语句:逐层拦截,不满足就退出
if order == nil {
return errors.New("order is nil")
}
if order.Status != "pending" {
return errors.New("invalid status")
}
if order.Amount <= 0 {
return errors.New("invalid amount")
}
if order.User == nil {
return errors.New("user is nil")
}
// ✅ 主逻辑:没有任何嵌套,一目了然
switch order.User.Level {
case "vip":
order.Discount = 0.8
case "new":
order.Discount = 0.9
default:
order.Discount = 1.0
}
return nil
}效果对比:
指标 | 修改前 | 修改后 |
|---|---|---|
嵌套层数 | 5 层 | 0 层 |
可读性 | 需要缩进追踪 | 线性阅读 |
新增校验 | 要找地方塞 if | 顶部加一行卫语句 |
原则:每个函数的主逻辑,缩进不超过一层。 做不到,说明该拆函数了。
当条件判断的本质是"映射"时,if-else 是最差的选择。
govar discount float64
if level == "vip" {
discount = 0.8
} else if level == "new" {
discount = 0.9
} else if level == "member" {
discount = 0.95
} else {
discount = 1.0
}govar discounts = map[string]float64{
"vip": 0.8,
"new": 0.9,
"member": 0.95,
}
discount := discounts[level]
if discount == 0 {
discount = 1.0 // 默认值
}更简洁的写法(Go 1.21+):
godiscount := map[string]float64{
"vip": 0.8, "new": 0.9, "member": 0.95,
}[level]
if discount == 0 {
discount = 1.0
}为什么 Map 更好?
对比项 | if-else | Map |
|---|---|---|
增加新类型 | 改代码逻辑 | 加一行数据 |
性能 | O(n) 逐条判断 | O(1) 哈希查找 |
可测试性 | 要覆盖所有分支 | 测数据就行 |
可读性 | 分支多了就乱 | 一目了然 |
for range + 函数式风格替代手动循环go// 场景:计算所有 VIP 用户的订单总额
var total float64
for i := 0; i < len(orders); i++ {
if orders[i].User.Level == "vip" {
total += orders[i].Amount
}
}range + 过滤 + 聚合gototal := 0.0
for _, o := range orders {
if o.User.Level == "vip" {
total += o.Amount
}
}再进一步,用函数封装:
gofunc Sum(orders []Order, filter func(Order) bool) float64 {
var total float64
for _, o := range orders {
if filter(o) {
total += o.Amount
}
}
return total
}
// 调用
vipTotal := Sum(orders, func(o Order) bool {
return o.User.Level == "vip"
})Go 1.23+ 还可以用 slices 包:
goimport "slices"
vipOrders := slices.DeleteFunc(orders, func(o Order) bool {
return o.User.Level != "vip"
})
vipTotal := slices.SumFunc(vipOrders, func(o Order) float64 {
return o.Amount
})原则:能用
range就别用索引,能用标准库函数就别自己写循环。
当一个函数里有大量 if-else 或 switch 处理不同类型时,用表驱动重构。
gofunc HandlePayment(p Payment) error {
switch p.Type {
case "alipay":
return handleAlipay(p)
case "wechat":
return handleWechat(p)
case "bank":
return handleBank(p)
case "crypto":
return handleCrypto(p)
default:
return errors.New("unknown type")
}
}gotype Handler func(Payment) error
var paymentHandlers = map[string]Handler{
"alipay": handleAlipay,
"wechat": handleWechat,
"bank": handleBank,
"crypto": handleCrypto,
}
func HandlePayment(p Payment) error {
handler, ok := paymentHandlers[p.Type]
if !ok {
return errors.New("unknown payment type: " + p.Type)
}
return handler(p)
}好处:
HandlePayment 逻辑handler,不用走 switchpaymentHandlers 可以从配置文件加载当条件判断不是查数据,而是选行为时,用接口 + 策略模式。
gofunc CalculatePrice(order *Order) float64 {
if order.User.Level == "vip" {
if order.Amount > 1000 {
return order.Amount * 0.7
}
return order.Amount * 0.8
} else if order.User.Level == "new" {
return order.Amount * 0.9
}
return order.Amount
}gotype PricingStrategy interface {
Calculate(amount float64) float64
}
type VIPStrategy struct{}
func (s VIPStrategy) Calculate(a float64) float64 {
if a > 1000 {
return a * 0.7
}
return a * 0.8
}
type NewUserStrategy struct{}
func (s NewUserStrategy) Calculate(a float64) float64 {
return a * 0.9
}
type RegularStrategy struct{}
func (s RegularStrategy) Calculate(a float64) float64 {
return a
}
var strategyMap = map[string]PricingStrategy{
"vip": VIPStrategy{},
"new": NewUserStrategy{},
"regular": RegularStrategy{},
}
func CalculatePrice(order *Order) float64 {
s := strategyMap[order.User.Level]
return s.Calculate(order.Amount)
}核心思想:把"如果是 A 就怎样,如果是 B 就怎样"的逻辑,变成"查表拿策略,策略自己知道怎么做"。
select 替代多条件等待循环当循环的目的是"等某个条件成立"时,别用 for + sleep。
gofunc WaitReady(ch chan bool) {
for {
select {
case <-ch:
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}这段代码其实没问题,但如果是多条件等待:
select 多路复用gofunc WaitAny(ready chan bool, timeout <-chan time.Time) error {
select {
case <-ready:
return nil
case <-timeout:
return errors.New("timeout")
}
}再复杂一点:
goselect {
case result := <-taskChan:
return result
case <-ctx.Done():
return ctx.Err()
case <-time.After(5 * time.Second):
return errors.New("timeout")
}原则:能用
select表述的并发逻辑,别用for + sleep硬轮询。
技巧 | 适用场景 | 简化效果 |
|---|---|---|
卫语句 | 多层前置校验 | 嵌套 → 线性 |
Map 替代 if-else | 值映射、折扣、状态转换 | 分支 → 查表 |
for range | 遍历 + 过滤 | 索引 → 语义化 |
表驱动法 | 多类型分发处理 | switch → 注册表 |
策略模式 | 条件选行为 | if-else → 多态 |
select | 并发等待、多条件分支 | 轮询 → 事件驱动 |
循环和条件语句本身不复杂,复杂的是它们嵌套在一起之后的组合爆炸。
简化的本质不是"少写代码",而是:
下次写业务逻辑时,先问自己:这段代码里,有多少个 if 是可以变成一行 Map 查找的? 答案通常比你想的多。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。