首页 > 其他分享 >为什么不建议使用goto

为什么不建议使用goto

时间:2024-11-08 23:40:53浏览次数:1  
标签:为什么 建议 goto fmt 订单 Step error Println

前提

最近在公司代码review过程中, 看到同事的代码中大量使用了goto, 我给出了"不用 goto"的建议. 但其给出的理由是使用goto更简单. 确实, 使用goto可以使得逻辑更简单直接, 但前提是不乱用goto, 而在公司的项目中又很难保证这一点.

问题

使用goto带来的最直观的问题就是逻辑的复杂度直线升高. 举个例子来展现goto是如何一步步导致逻辑破败不堪的. (当然, 这个例子是我臆想出来的场景)

首先, 我们有一个创建订单并验证支付的需求:

package main

import "fmt"

func main() {
	fmt.Println("处理订单开始")

	fmt.Println("Step 1: 创建订单")

	fmt.Println("Step 2: 验证订单")

	fmt.Println("Step 3: 验证付款信息")

	fmt.Println("Step 4: 订单完成")

	fmt.Println("处理订单结束")
}

此时逻辑很清楚吧. 现在, 我们要对验证订单的结果进行处理, 如果验证失败, 则进行错误处理, 很合理吧:

package main

import "fmt"

func main() {
	fmt.Println("处理订单开始")

	fmt.Println("Step 1: 创建订单")

	var validErr error
	fmt.Println("Step 2: 验证订单")
	if validErr != nil {
		goto Fail
	}

	fmt.Println("Step 3: 验证付款信息")

	fmt.Println("Step 4: 订单完成")
	goto End
Fail:
	fmt.Println("验证付款失败")
End:
	fmt.Println("处理订单结束")
}

现在, 新的需求来了:

  1. 付款信息处理可能因各种原因失败, 需要重试, 最多重试3次
  2. 验证订单也可能失败(异步接口验证, 网络抖动等), 需要重试, 最多重试3次
  3. 若订单验证失败, 需要提示并重新创建订单
package main

import "fmt"

func main() {
	fmt.Println("处理订单开始")

CreatOrder:
	fmt.Println("Step 1: 创建订单")

	validRetryNum := 0
ValidOrder:
	var validErr error
	fmt.Println("Step 2: 验证订单")
	if validErr != nil {
		validRetryNum++
		if validRetryNum <= 3 {
			goto ValidOrder
		}
		fmt.Println("订单验证失败")
		goto CreatOrder
	}

	checkRetryNum := 0
CheckOrder:
	var checkErr error
	fmt.Println("Step 3: 验证付款信息")
	if checkErr != nil {
		checkRetryNum++
		if checkRetryNum <= 3 {
			goto CheckOrder
		}
		goto CheckErr
	}

	fmt.Println("Step 4: 订单完成")
	goto End

CheckErr:
	fmt.Println("付款信息验证失败")
End:
	fmt.Println("处理订单结束")
}

再来:

  1. 验证付款信息失败, 可能是因为没有付款等, 需要进行付款处理的逻辑
  2. 验证订单付款时, 订单可能认为取消支付, 需要处理资源清理等
package main

import "fmt"

func main() {
	fmt.Println("处理订单开始")

CreatOrder:
	fmt.Println("Step 1: 创建订单")

	validRetryNum := 0
ValidOrder:
	var validErr error
	fmt.Println("Step 2: 验证订单")
	if validErr != nil {
		validRetryNum++
		if validRetryNum <= 3 {
			goto ValidOrder
		}
		fmt.Println("订单验证失败")
		goto CreatOrder
	}

	checkRetryNum := 0
CheckOrder:
	var checkErr error
	var paid, cancel bool
	fmt.Println("Step 3: 验证付款信息")
	if checkErr != nil {
		checkRetryNum++
		if checkRetryNum <= 3 {
			goto CheckOrder
		}
		goto CheckErr
	}
	if cancel {
		goto CancelOrder
	}
	if !paid {
		goto ProcessOrder
	}

	fmt.Println("Step 4: 订单完成")
	goto End

CheckErr:
	fmt.Println("付款信息验证失败")
	goto End

CancelOrder:
	fmt.Println("取消订单支付")
	goto End

ProcessOrder:
	fmt.Println("处理付款信息")
	goto CheckOrder

End:
	fmt.Println("处理订单结束")
}

再来

  1. 增加处理失败的日志记录
  2. 增加订单验证失败的日志记录
  3. 不管是取消订单支付, 还是付款处理失败, 都需要进行一些清理工作
package main

import "fmt"

func main() {
	fmt.Println("处理订单开始")

CreatOrder:
	fmt.Println("Step 1: 创建订单")

	validRetryNum := 0
	checkRetryNum := 0
	var checkErr error
	var validErr error
	var paid, cancel bool
ValidOrder:
	fmt.Println("Step 2: 验证订单")
	if validErr != nil {
		validRetryNum++
		if validRetryNum <= 3 {
			goto ValidOrderErrorLog
		}
		fmt.Println("订单验证失败")
		goto CreatOrder
	}

CheckOrder:
	fmt.Println("Step 3: 验证付款信息")
	if checkErr != nil {
		checkRetryNum++
		if checkRetryNum <= 3 {
			goto CheckOrderErrorLog
		}
		goto CheckErr
	}
	if cancel {
		goto CancelOrder
	}
	if !paid {
		goto ProcessOrder
	}

	fmt.Println("Step 4: 订单完成")
	goto End

ValidOrderErrorLog:
	fmt.Println("记录订单验证失败")
	goto ValidOrder

CheckOrderErrorLog:
	fmt.Println("记录付款验证失败")
	goto CheckOrder

CheckErr:
	fmt.Println("付款信息验证失败")
	goto CleanOrder

CancelOrder:
	fmt.Println("取消订单支付")
	goto CleanOrder

ProcessOrder:
	fmt.Println("处理付款信息")
	goto CheckOrder

CleanOrder:
	fmt.Println("订单关闭的清理工作")
	goto End

End:
	fmt.Println("处理订单结束")
}

现在, 如果你还觉得逻辑清晰, 那我只能说一句"牛".

代码演进到现在, 逻辑已经十分混乱了, 逻辑的混乱会导致一系列问题:

  1. 难以理解, 逐步增加后续迭代的成本
  2. 造成额外的心智负担
  3. 追踪困难
  4. 如果是if for 在逻辑上是自上而下的, 但引入 goto会导致逻辑上下横跳
  5. 等等

可能有人会觉得我举的例子有些极端, 实际中没有人会这么做. 那是因为例子总是简单化的, 现实中的场景实际上要更加复杂:

  1. 大段逻辑分散: 例子中的所有单条print语句, 在实际项目中都可能会对应一大段的逻辑
  2. 最小改动原则: 对现有项目进行改动的时候(尤其是需求要的比较急, 要求改动最小实现功能), 对现有代码的改动越小, 则风险越小. 因此逻辑会越堆越难以理解, 直至最后无法使用
  3. 每个人的水平不同: 同一份代码会由团队中的不同人在不同时间维护, 即使你自信自己的水平, 也无法保证代码在未来不会向着这个方向发展

使用场景

当然, 我也不是把goto一棒子打死, 同事在反驳我的时候也给出了强有力的理由"Go 标准库也存在大量使用goto的地方". 比如:

func ParseMAC(s string) (hw HardwareAddr, err error) {
	if len(s) < 14 {
		goto error
	}

	if s[2] == ':' || s[2] == '-' {
		if (len(s)+1)%3 != 0 {
			goto error
		}
		n := (len(s) + 1) / 3
		if n != 6 && n != 8 && n != 20 {
			goto error
		}
		// ...
	} else if s[4] == '.' {
		if (len(s)+1)%5 != 0 {
			goto error
		}
		n := 2 * (len(s) + 1) / 5
		if n != 6 && n != 8 && n != 20 {
			goto error
		}
		// ...
	} else {
		goto error
	}
	return hw, nil

error:
	return nil, &AddrError{Err: "invalid MAC address", Addr: s}
}

这种其实是可以接受的, 能够简化流程, 但是, 但是, 不要忘记我不建议使用goto最重要的一点:

  1. 你无法保证自己拥有掌控goto的实力
  2. 即使你自信, 也无法保证同事有掌控goto的实力

一旦在使用过程中产生破窗效应, 使用goto破坏的速度一定是比不用要快的多. 代码很快就会脱离掌控.

因此, 为了避免这种情况, 最好的方式就是在最开始杜绝掉.

最后的最后, goto如果能够好好用的话, 确实能够带来一定的便利性, 前提是项目由你一人开发, 或你拥有掌控权可以拒绝某些腐败代码进入代码库.

goto并不可怕, 可怕的是不加限制的乱用goto.


欢迎来辩...

标签:为什么,建议,goto,fmt,订单,Step,error,Println
From: https://www.cnblogs.com/hujingnb/p/18536135

相关文章

  • 为什么工控界面开发大都选C#,还有替代方案吗
    一、引言在工业控制领域,工控界面的开发至关重要。它不仅是操作人员与工业控制系统进行交互的窗口,更是实现高效生产和精确控制的关键环节。在众多的编程语言中,C#在工控界面开发中备受青睐。然而,随着技术的不断发展,人们也在思考是否有其他更好的替代方案。本文将深入探讨为什么......
  • 小电容为什么可以通高频阻低频?
    用手持电桥100k档测试各电容的实际值,保证100k频率下电容值不会有太大变化。电容标称值          实际值10uF                  -31uF47uF                 -7.981uF3.3uF                 3.0632uF1uF  ......
  • vue,for循环为什么不提倡放主键id
    在Vue.js中,v-for循环用于渲染列表时,推荐为每个列表项提供一个key属性,以帮助Vue更高效地更新和复用DOM元素。但是,使用主键id作为key有时并不推荐,原因如下:1.id不一定稳定主键id通常是在数据库中生成的唯一标识符,虽然在数据库中它是唯一的,但在前端应用中,尤其是在......
  • 为什么找不到vcruntime140_1.dll,无法继续执行代码的原因及五种有效解决方法
    vcruntime140_1.dll是微软VisualC++RedistributableforVisualStudio的一个动态链接库(DLL)文件。它是运行由VisualStudio2015及更高版本编译的C++应用程序所必需的。该DLL文件包含了支持C++标准库和Microsoft特定扩展功能的运行时函数,对于Windows应用程序......
  • 我的博客网站为什么又回归Blazor了
    引言在博客网站的开发征程中,站长可谓是一路披荆斩棘。从最初的构思到实践,先后涉足了多种开发技术,包括[MVC](ASP.NETCoreMVC概述|MicrosoftLearn)、[RazorPages](ASP.NETCore中的RazorPages介绍|MicrosoftLearn)、[Vue](Vue.js-渐进式JavaScript框架|Vue.js......
  • 博客园 ----LaTex使用教程,内附各种公式,建议收藏和备忘!!
    前提听说要在下面这个位置加入下面红框里这些代码,才能启用LaTex的公式编辑形式。。<scripttype="text/x-mathjax-config">MathJax.Hub.Config({extensions:["tex2jax.js"],jax:["input/TeX","output/HTML-CSS"],tex2jax:{inlineMath:[['$'......
  • 为什么编号应该从 0 开始
    在常见的编程语言如Python、Go、Java中,序列的下标都是从0开始的,为什么不是从1开始呢?迪杰斯特拉在1982年的时候就思考过编号起点的问题,那个时候还没有上面这3门语言呢。大概思路如下:序列下标是连续的整数,首先要考虑的就是怎么用区间范围表示连续的整数,形如a<=i<......
  • Mac压缩工具首选?FastZip为什么那么受欢迎
    Mac上的压缩工具,各有各的好,也各有各的不好FastZip,Mac上的一款免费多功能压缩工具速度快,支持自定义压缩线程分配,最高支持8线程压缩,将近10GB的文件压缩只需二十秒左右,大文件压缩不再需要长时间等待格式齐全,压缩格式支持7Z、Zip,解压格式支持7Z、ZIP、RAR、TAR、GZIP、BZIP2、X......
  • Reviewbot 开源 | 为什么我们要打造自己的代码审查服务?
    Reviewbot是七牛云开源的一个项目,旨在提供一个自托管的代码审查服务,方便做codereview/静态检查,以及自定义工程规范的落地。静态检查不是个新鲜事。我记得早在几年前,我们就调研并使用过sonarqube做静态检查,但当时并没有大范围的推广。主要原因在于,一是发现的问题多......
  • 程序员为什么要转行做大模型?中年危机?职场发展?升职加薪?
    最近研究了一下大模型相关的内容,决定从互联网的推荐算法转行做大模型推理工程化相关的工作。所以简单说说我在这个决定中的思考过程。1.推荐算法岗的现状我本来是一个在大厂做推荐算法的工程师。收入在行业里面算是中游水平,就这么一直干着似乎也没什么问题。但是互......