go语言编程勘误 go 编程语言

golang编程中,不属于异常处理的关键词是error 。Go语言又称Golang是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言,在该语言种类中error不属于异常处理的关键词,因为error仅仅是用来提示错误的词 , 和异常处理功能无关 。
「测试开发全栈化-Go」(1) Go语言基本了解作为一个测试,作为一个测试开发,全栈化 管理是我们未来的发展方向 。已经掌握了Java、Python、HTML的你,是不是也想了解下最近异常火爆的Go语言呢?来吧,让我们一起了解下 。
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易 。
Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发 , 后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本 。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区 。这三个人都是计算机界的大神 , 有的参与了C语言的编写,有的还是数学大神,有的还获得了计算机最高荣誉-图灵奖 。
接下来说说Go语言的特色:
简洁、快速、安全
并行、有趣、开源
内存管理、数组安全、编译迅速
Go语言的用途:
Go 语言被设计成一门应用于搭载 Web 服务器 , 存储集群或类似用途的巨型中央服务器的系统编程语言 。
对于高性能分布式系统领域而言 , Go 语言无疑比大多数其它语言有着更高的开发效率 。它提供了海量并行的支持,这对于 游戏 服务端的开发而言是再好不过了 。
Go语言的环境安装:
建议直接打开官方地址因为墙的原因打不开
因为我用的是windows系统,这里主要讲下Windows系统上使用Go语言来编程 。
Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.17.2.windows-amd64.msi)的安装包来安装 。
默认情况下 .msi 文件会安装在 c:Go 目录下 。你可以将 c:Gobin 目录添加到 Path 环境变量中 。添加后你需要重启命令窗口才能生效 。个人建议还是安装到 Program Files文件夹中 。
使用什么开发工具来对Go语言进行编写:
个人建议用VS code, 也可以用Sublime Text来编辑 。如果你之前看了我讲的HTML语言的学习,肯定已经下载了VS code. 那么这时你需要在VS code中下载Go语言的扩展插件 。
这里有一个巨大的坑,就是在下载Go的插件和依赖包时,会提示一些包没有 。主要是因为下载的依赖包部分被墙了,只能想别的办法去下载 。
建议参考网页:
解决vscode中golang插件安装失败方法
在学习go的过程中,使用的是vscode,但是一直提示安装相关插件失败,然后上网查方法,基本上是叫你建立golang.org目录什么的,结果全是错的,而且都是抄袭,很烦 。无意之中看到一位博主分享的方法,他也是饱受上述的垃圾博文困扰,然后找到了解决方法,这里向他致敬,秉着让更多人看到正确解决方法的心,我写下正确的解决方法,希望对你有所帮助,也可以点开原博主链接参考:
Go有一个全球模块代理 , 设置代理再去安装golang的插件 , 就可以安装成功了 。步骤有,首先Windows用户打开Powershell,一个蓝色的界面 , 注意不是cmd!不知道的直接打开window下面的搜索,然后输入powershell , 搜索出来就可以了 。
$env:GO111MODULE=“on”
$env:GOPROXY=“”
go env -w GOPROXY=
go env -w GOPRIVATE=*.corp.example.com
然后我们打开VsCode界面,下面会提示安装插件,我们选择Install ALL,就会安装成功
当你在运行Go语言程序时,提示所有的插件包都已经安装成功了时,就可以正常使用了,要不然一堆报错会让你非常心烦 。
好了,今天先到这里,晚安、下班~
Go 语言的错误处理机制是一个优秀的设计吗这个问题说来话长 , 我先表达一下我的观点,Go语言从语法层面提供区分错误和异常的机制是很好的做法,比自己用单个返回值做值判断要方便很多 。
上面看到很多知乎大牛把异常和错误混在一起说,有认为Go没有异常机制的,有认为Go纯粹只有异常机制的,我觉得这些观点都太片面了 。
具体对于错误和异常的讨论,我转发一下前阵子写的一篇日志抛砖引玉吧 。
============================
最近连续遇到朋友问我项目里错误和异常管理的事情,之前也多次跟团队强调过错误和异常管理的一些概念,所以趁今天有动力就赶紧写一篇Go语言项目错误和异常管理的经验分享 。
首先我们要理清:什么是错误、什么是异常、为什么需要管理 。然后才是怎样管理 。
错误和异常从语言机制上面讲,就是error和panic的区别,放到别的语言也一样,别的语言没有error类型,但是有错误码之类的,没有panic,但是有throw之类的 。
在语言层面它们是两种概念,导致的是两种不同的结果 。如果程序遇到错误不处理,那么可能进一步的产生业务上的错误,比如给用户多扣钱了,或者进一步产生了异常;如果程序遇到异常不处理,那么结果就是进程异常退出 。
在项目里面是不是应该处理所有的错误情况和捕捉所有的异常呢?我只能说,你可以这么做,但是估计效果不会太好 。我的理由是:
如果所有东西都处理和记录,那么重要信息可能被淹没在信息的海洋里 。
不应该处理的错误被处理了,很容易导出BUG暴露不出来,直到出现更严重错误的时候才暴露出问题,到时候排查就很困难了,因为已经不是错误的第一现场 。
所以错误和异常最好能按一定的规则进行分类和管理,在第一时间能暴露错误和还原现场 。
对于错误处理,Erlang有一个很好的概念叫速错,就是有错误第一时间暴露它 。我们的项目从Erlang到Go一直是沿用这一设计原则 。但是应用这个原则的前提是先得区分错误和异常这两个概念 。
错误和异常上面已经提到了,从语言机制层面比较容易区分它们,但是语言取决于人为,什么情况下用错误表达,什么情况下用异常表达,就得有一套规则,否则很容易出现全部靠异常来做错误处理的情况,似乎Java项目特别容易出现这样的设计 。
这里我先假想有这样一个业务:游戏玩家通过购买按钮 , 用铜钱购买宝石 。
在实现这个业务的时候 , 程序逻辑会进一步分化成客户端逻辑和服务端逻辑 , 客户端逻辑又进一步因为设计方式的不同分化成两种结构:胖客户端结构、瘦客户端结构 。
胖客户端结构,有更多的本地数据和懂得更多的业务逻辑,所以在胖客户端结构的应用中 , 以上的业务会实现成这样:客户端检查缓存中的铜钱数量 , 铜钱数量足够的时候购买按钮为可用的亮起状态,用户点击购买按钮后客户端发送购买请求到服务端;服务端收到请求后校验用户的铜钱数量 , 如果铜钱数量不足就抛出异常 , 终止请求过程并断开客户端的连接 , 如果铜钱数量足够就进一步完成宝石购买过程,这里不继续描述正常过程 。
因为正常的客户端是有一步数据校验的过程的 , 所以当服务端收到不合理的请求(铜钱不足以购买宝石)时,抛出异常比返回错误更为合理,因为这个请求只可能来自两种客户端:外挂或者有BUG的客户端 。如果不通过抛出异常来终止业务过程和断开客户端连接,那么程序的错误就很难被第一时间发现,攻击行为也很难被发现 。
我们再回头看瘦客户端结构的设计 , 瘦客户端不会存有太多状态数据和用户数据也不清楚业务逻辑,所以客户端的设计会是这样:用户点击购买按钮,客户端发送购买请求;服务端收到请求后检查铜钱数量,数量不足就返回数量不足的错误码,数量足够就继续完成业务并返回成功信息;客户端收到服务端的处理结果后,在界面上做出反映 。
在这种结构下,铜钱不足就变成了业务逻辑范围内的一种失败情况 , 但不能提升为异常,否则铜钱不足的用户一点购买按钮都会出错掉线 。
所以,异常和错误在不同程序结构下是互相转换的 , 我们没办法一句话的给所有类型所有结构的程序一个统一的异常和错误分类规则 。
但是,异常和错误的分类是有迹可循的 。比如上面提到的痩客户端结构,铜钱不足是业务逻辑范围内的一种失败情况,它属于业务错误 , 再比如程序逻辑上尝试请求某个URL,最多三次,重试三次的过程中请求失败是错误 , 重试到第三次,失败就被提升为异常了 。
所以我们可以这样来归类异常和错误:不会终止程序逻辑运行的归类为错误 , 会终止程序逻辑运行的归类为异常 。
因为错误不会终止逻辑运行,所以错误是逻辑的一部分,比如上面提到的瘦客户端结构 , 铜钱不足的错误就是业务逻辑处理过程中需要考虑和处理的一个逻辑分支 。而异常就是那些不应该出现在业务逻辑中的东西,比如上面提到的胖客户端结构,铜钱不足已经不是业务逻辑需要考虑的一部分了,所以它应该是一个异常 。
错误和异常的分类需要通过一定的思维训练来强化分类能力 , 就类似于面向对象的设计方式一样的,技术实现就摆在那边,但是要用好需要不断的思维训练不断的归类和总结,以上提到的归类方式希望可以作为一个参考,期待大家能发现更多更有效的归类方式 。
接下来我们讲一下速错和Go语言里面怎么做到速错 。
速错我最早接触是在做的时候就体验到的,当然跟Erlang的速错不完全一致,那时候也没有那么高大上的一个名字 , 但是对待异常的理念是一样的 。
在.NET项目开发的时候,有经验的程序员都应该知道 , 不能随便re-throw,就是catch错误再抛出,原因是异常的第一现场会被破坏,堆栈跟踪信息会丢失,因为外部最后拿到异常的堆栈跟踪信息,是最后那次throw的异常的堆栈跟踪信息;其次,不能随便try catch,随便catch很容易导出异常暴露不出来,升级为更严重的业务漏洞 。
到了Erlang时期,大家学到了速错概念,简单来讲就是:让它挂 。只有挂了你才会第一时间知道错误,但是Erlang的挂,只是Erlang进程的异常退出,不会导致整个Erlang节点退出,所以它挂的影响层面比较低 。
在Go语言项目中,虽然有类似Erlang进程的Goroutine,但是Goroutine如果panic了,并且没有recover,那么整个Go进程就会异常退出 。所以我们在Go语言项目中要应用速错的设计理念,就要对Goroutine做一定的管理 。
在我们的游戏服务端项目中 , 我把Goroutine按挂掉后的结果分为两类:1、挂掉后不影响其他业务或功能的;2、挂掉后业务就无法正常进行的 。
第一类Goroutine典型的有:处理各个玩家请求的Goroutine , 因为每个玩家连接各自有一个Goroutine,所以挂掉了只会影响单个玩家,不会影响整体业务进行 。
第二类Goroutine典型的有:数据库同步用的Goroutine , 如果它挂了,数据就无法同步到数据库,游戏如果继续运行下去只会导致数据回档,还不如让整个游戏都异常退出 。
这样一分类,就可以比较清楚哪些Goroutine该做recover处理,哪些不该做recover处理了 。
那么在做recover处理时,要怎样才能尽量保留第一现场来帮组开发者排查问题原因呢?我们项目中通常是会在最外层的recover中把错误和堆栈跟踪信息记进日志,同时把关键的业务信息,比如:用户ID、来源IP、请求数据等也一起记录进去 。
为此,我们还特地设计了一个库,用来格式化输出堆栈跟踪信息和对象信息 , 项目地址:funny/debug · GitHub
通篇写下来发现比我预期的长很多 , 所以这里我做一下归纳总结,帮组大家理解这篇文章所要表达的:
错误和异常需要分类和管理,不能一概而论
错误和异常的分类可以以是否终止业务过程作为标准
错误是业务过程的一部分,异常不是
不要随便捕获异常,更不要随便捕获再重新抛出异常
Go语言项目需要把Goroutine分为两类,区别处理异常
在捕获到异常时,需要尽可能的保留第一现场的关键数据
以上仅为一家之言,抛砖引玉,希望对大家有所帮助 。
Golang 中更好的错误处理:理论和实践技巧 云和安全管理服务专家新钛云服 张春翻译
这种方法有几个缺点 。首先,它可以对程序员隐藏错误处理路径,特别是在捕获异常不是强制性的情况下,例如在 Python 中 。即使在具有必须处理的 Java 风格的检查异常的语言中,如果在与原始调用不同的级别上处理错误 , 也并不总是很明显错误是从哪里引发的 。
我们都见过长长的代码块包装在一个 try-catch 块中 。在这种情况下,catch 块实际上充当 goto 语句,这通常被认为是有害的(奇怪的是,C 中的关键字被认为可以接受的少数用例之一是错误后清理,因为该语言没有 Golang- 样式延迟语句) 。
如果你确实从源头捕获异常,你会得到一个不太优雅的 Go 错误模式版本 。这可能会解决混淆代码的问题,但会遇到另一个问题:性能 。在诸如 Java 之类的语言中,抛出异常可能比函数的常规返回慢数百倍 。
Java 中最大的性能成本是由打印异常的堆栈跟踪造成的,这是昂贵的,因为运行的程序必须检查编译它的源代码。仅仅进入一个 try 块也不是空闲的,因为需要保存 CPU 内存寄存器的先前状态,因为它们可能需要在抛出异常的情况下恢复 。
如果您将异常视为通常不会发生的异常情况 , 那么异常的缺点并不重要 。这可能是传统的单体应用程序的情况,其中大部分代码库不必进行网络调用——一个操作格式良好的数据的函数不太可能遇到错误(除了错误的情况) 。一旦您在代码中添加 I/O,无错误代码的梦想就会破灭:您可以忽略错误 , 但不能假装它们不存在!
try {
doSometing()
} catch (IOException e) {
// ignore it
}
与大多数其他编程语言不同,Golang 接受错误是不可避免的 。如果在单体架构时代还不是这样,那么在今天的模块化后端服务中,服务通常和外部 API 调用、数据库读取和写入以及与其他服务通信。
以上所有方法都可能失败,解析或验证从它们接收到的数据(通常在无模式 JSON 中)也可能失败 。Golang 使可以从这些调用返回的错误显式化,与普通返回值的等级相同 。从函数调用返回多个值的能力支持这一点,这在大多数语言中通常是不可能的 。Golang 的错误处理系统不仅仅是一种语言怪癖,它是一种将错误视为替代返回值的完全不同的方式!
重复 if err != nil
对 Go 错误处理的一个常见批评是被迫重复以下代码块:
res, err := doSomething()
if err != nil {
// Handle error
}
对于新用户来说 , 这可能会觉得没用而且浪费行数:在其他语言中需要 3 行的函数很可能会增长到 12 行:
这么多行代码!这么低效!如果您认为上述内容不优雅或浪费代码,您可能忽略了我们检查代码中的错误的全部原因:我们需要能够以不同的方式处理它们!对 API 或数据库的调用可能会被重试 。
有时事件的顺序很重要:调用外部 API 之前发生的错误可能不是什么大问题(因为数据从未通过发送) , 而 API 调用和写入本地数据库之间的错误可能需要立即注意,因为 这可能意味着系统最终处于不一致的状态 。即使我们只想将错误传播给调用者 , 我们也可能希望用失败的解释来包装它们,或者为每个错误返回一个自定义错误类型 。
并非所有错误都是相同的 , 并且向调用者返回适当的错误是 API 设计的重要部分,无论是对于内部包还是 REST API。
不必担心在你的代码中重复 if err != nil ——这就是 Go 中的代码应该看起来的样子 。
自定义错误类型和错误包装
从导出的方法返回错误时,请考虑指定自定义错误类型 , 而不是单独使用错误字符串 。字符串在意外代码中是可以的,但在导出的函数中,它们成为函数公共 API 的一部分 。更改错误字符串将是一项重大更改——如果没有明确的错误类型,需要检查返回错误类型的单元测试将不得不依赖原始字符串值!事实上,基于字符串的错误也使得在私有方法中测试不同的错误案例变得困难 , 因此您也应该考虑在包中使用它们 。回到错误与异常的争论,返回错误也使代码比抛出异常更容易测试,因为错误只是要检查的返回值 。不需要测试框架或在测试中捕获异常。
可以在 database/sql 包中找到简单自定义错误类型的一个很好的示例 。它定义了一个导出常量列表 , 表示包可以返回的错误类型,最著名的是 sql.ErrNoRows 。虽然从 API 设计的角度来看,这种特定的错误类型有点问题(您可能会争辩说 API 应该返回一个空结构而不是错误),但任何需要检查空行的应用程序都可以导入该常量并在代码中使用它不必担心错误消息本身会改变和破坏代码 。
对于更复杂的错误处理,您可以通过实现返回错误字符串的 Error() 方法来定义自定义错误类型 。自定义错误可以包括元数据,例如错误代码或原始请求参数 。如果您想表示错误类别,它们很有用 。DigitalOcean 的本教程展示了如何使用自定义错误类型来表示可以重试的一类临时错误 。
通常,错误会通过将低级错误与更高级别的解释包装起来,从而在程序的调用堆栈中传播 。例如,数据库错误可能会以下列格式记录在 API 调用处理程序中:调用 CreateUser 端点时出错:查询数据库时出错:pq:检测到死锁 。这很有用,因为它可以帮助我们跟踪错误在系统中传播的过程,向我们展示根本原因(数据库事务引擎中的死锁)以及它对更广泛系统的影响(调用者无法创建新用户) 。
自 Go 1.13 以来,此模式具有特殊的语言支持,并带有错误包装 。通过在创建字符串错误时使用 %w 动词,可以使用 Unwrap() 方法访问底层错误 。除了比较错误相等性的函数 errors.Is() 和 errors.As() 外,程序还可以获取包装错误的原始类型或标识 。这在某些情况下可能很有用 , 尽管我认为在确定如何处理所述错误时最好使用顶级错误的类型 。
Panics
不要 panic()!长时间运行的应用程序应该优雅地处理错误而不是panic 。即使在无法恢复的情况下(例如在启动时验证配置),最好记录一个错误并优雅地退出 。panic比错误消息更难诊断,并且可能会跳过被推迟的重要关闭代码 。
Logging
我还想简要介绍一下日志记录 , 因为它是处理错误的关键部分 。通常你能做的最好的事情就是记录收到的错误并继续下一个请求 。
除非您正在构建简单的命令行工具或个人项目,否则您的应用程序应该使用结构化的日志库,该库可以为日志添加时间戳,并提供对日志级别的控制 。最后一部分特别重要,因为它将允许您突出显示应用程序记录的所有错误和警告 。通过帮助将它们与信息级日志分开,这将为您节省无数时间 。
微服务架构还应该在日志行中包含服务的名称以及机器实例的名称 。默认情况下记录这些时,程序代码不必担心包含它们 。您也可以在日志的结构化部分中记录其他字段,例如收到的错误(如果您不想将其嵌入日志消息本身)或有问题的请求或响应 。只需确保您的日志没有泄露任何敏感数据,例如密码、API 密钥或用户的个人数据!
对于日志库,我过去使用过 logrus 和 zerolog,但您也可以选择其他结构化日志库 。如果您想了解更多信息,互联网上有许多关于如何使用这些的指南 。如果您将应用程序部署到云中,您可能需要日志库上的适配器来根据您的云平台的日志 API 格式化日志 - 没有它,云平台可能无法检测到日志级别等某些功能 。
如果您在应用程序中使用调试级别日志(默认情况下通常不记录) , 请确保您的应用程序可以轻松更改日志级别,而无需更改代码 。更改日志级别还可以暂时使信息级别甚至警告级别的日志静音,以防它们突然变得过于嘈杂并开始淹没错误 。您可以使用在启动时检查以设置日志级别的环境变量来实现这一点 。
原文:
Go语言和其他语言的不同之基本语法Go语言作为出现比较晚的一门编程语言,在其原生支持高并发、云原生等领域的优秀表现,像目前比较流行的容器编排技术Kubernetes、容器技术Docker都是用Go语言写的,像Java等其他面向对象的语言,虽然也能做云原生相关的开发,但是支持的程度远没有Go语言高,凭借其语言特性和简单的编程方式,弥补了其他编程语言一定程度上的不足,一度成为一个热门的编程语言 。
最近在学习Go语言,我之前使用过C#、Java等面向对象编程的语言,发现其中有很多的编程方式和其他语言有区别的地方,好记性不如烂笔头,总结一下 , 和其他语言做个对比 。这里只总结差异的地方,具体的语法不做详细的介绍 。
种一棵树最好的时间是十年前,其次是现在 。
3)变量初始化时候可以和其他语言一样直接在变量后面加等号,等号后面为要初始化的值,也可以使用变量名:=变量值的简单方式
3)变量赋值 Go语言的变量赋值和多数语言一致,但是Go语言提供了多重赋值的功能,比如下面这个交换i、j变量的语句:
在不支持多重赋值的语言中,交换两个变量的值需要引入一个中间变量:
4)匿名变量
在使用其他语言时,有时候要获取一个值 , 却因为该函数返回多个值而不得不定义很多没有的变量 , Go语言可以借助多重返回值和匿名变量来避免这种写法,使代码看起来更优雅 。
假如GetName()函数返回3个值,分别是firstName,lastName和nickName
若指向获得nickName,则函数调用可以这样写
这种写法可以让代码更清晰,从而大幅降低沟通的复杂度和维护的难度 。
1)基本常量
常量使用关键字const 定义 , 可以限定常量类型,但不是必须的,如果没有定义常量的类型,是无类型常量
2)预定义常量
Go语言预定义了这些常量 true、false和iota
iota比较特殊,可以被任务是一个可被编译器修改的常量,在每个const关键字出现时被重置为0 , 然后在下一个const出现之前每出现一个iota,其所代表的数字会自动加1.
3)枚举
1)int 和int32在Go语言中被认为是两种不同类型的类型
2)Go语言定义了两个浮点型float32和float64,其中前者等价于C语言的float类型,后者等价于C语言的double类型
3)go语言支持复数类型
复数实际上是由两个实数(在计算机中使用浮点数表示)构成,一个表示实部(real)、一个表示虚部(imag) 。也就是数学上的那个复数
复数的表示
实部与虚部
对于一个复数z=complex(x,y),就可以通过Go语言内置函数real(z)获得该复数的实部 , 也就是x , 通过imag(z)获得该复数的虚部 , 也就是y
4)数组(值类型,长度在定义后无法再次修改,每次传递都将产生一个副本 。)
5)数组切片(slice)
数组切片(slice)弥补了数组的不足,其数据结构可以抽象为以下三个变量:
6)Map 在go语言中Map不需要引入任何库 , 使用很方便
Go循环语句只支持for关键字 , 不支持while和do-while
goto语句的语义非常简单,就是跳转到本函数内的某个标签
今天就介绍到这里 , 以后我会在总结Go语言在其他方面比如并发编程、面向对象、网络编程等方面的不同及使用方法 。希望对大家有所帮助 。
【go语言编程勘误 go 编程语言】go语言编程勘误的介绍就聊到这里吧 , 感谢你花时间阅读本站内容,更多关于go 编程语言、go语言编程勘误的信息别忘了在本站进行查找喔 。

    推荐阅读