go语言捕获异常 go语言 异常

Python和go语言有什么区别?哪个更有优势?python和go语言go语言捕获异常的区别
1、语法
Pythongo语言捕获异常的语法使用缩进来指示代码块 。Go的语法基于打开和关闭括号 。
2、范例
Python是一种基于面向对象编程的多范式,命令式和函数式编程语言 。它坚持这样一种观点,即如果一种语言在某些情境中表现出某种特定的方式,理想情况下它应该在所有情境中都有相似的作用 。但是,它又不是纯粹的OOP语言,它不支持强封装 , 这是OOP的主要原则之一 。
Go是一种基于并发编程范式的过程编程语言,它与C具有表面相似性 。实际上,Go更像是C的更新版本 。
3、并发
Python没有提供内置的并发机制,而Go有内置的并发机制 。
4、类型化
Python是动态类型语言,而Go是一种静态类型语言,它实际上有助于在编译时捕获错误,这可以进一步减少生产后期的严重错误 。
5、安全性
Python是一种强类型语言 , 它是经过编译的,因此增加了一层安全性 。Go具有分配给每个变量的类型,因此,它提供了安全性 。但是,如果发生任何错误,用户需要自己运行整个代码 。
6、管理内存
Go允许程序员在很大程度上管理内存 。而,Python中的内存管理完全自动化并由Python VM管理;它不允许程序员对内存管理负责 。
7、库
与Go相比,Python提供的库数量要大得多 。然而,Go仍然是新的,并且还没有取得很大进展 。
8、速度go语言捕获异常:
Go的速度远远超过Python 。
GO语言之recover????运行panic异常一旦被引发就会导致程序崩溃,这当然不是我们愿意看到的,go语言提供乐专用于“拦截”运行时panic的内建函数-recover,它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权 。
????注意:recover只有在defer调用的函数中有效
????如果调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使用程序从panic中恢复,并且返回panic value,导致panic异常的函数不会继续执行,但能正常返回 。在未发生panic时调用recover,recover会返回nil 。
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
【go语言捕获异常 go语言 异常】}
与大多数其他编程语言不同,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 格式化日志 - 没有它,云平台可能无法检测到日志级别等某些功能 。
如果您在应用程序中使用调试级别日志(默认情况下通常不记录),请确保您的应用程序可以轻松更改日志级别,而无需更改代码 。更改日志级别还可以暂时使信息级别甚至警告级别的日志静音,以防它们突然变得过于嘈杂并开始淹没错误 。您可以使用在启动时检查以设置日志级别的环境变量来实现这一点 。
原文:
从PHP 到Golang 的笔记 ( 转 ) ———文章来源YamiOdymel/PHP-to-Golang
PHP和模块之间的关系令人感到烦躁 , 假设你要读取 yaml 档案,你需要有一个 yaml 的模块,为此,你还需要将其编译然后将编译后的模块摆放至指定位置 , 之后换了一台伺服器你还要重新编译,这点到现在还是没有改善;顺带一提之后出了PHP 7效能确实提升了许多(比Python 3快了些),但PHP仍令我感到臃肿,我觉得是时候
(转行)了 。
PHP 和Golang 的效能我想毋庸置疑是后者比较快(而且是以倍数来算),也许有的人会认为两种不应该被放在一起比较,但Golang 本身就是偏向Web 开发的,所以这也是为什么我考虑转用Golang 的原因,起初我的考虑有几个:Node.js 和Rust 还有最终被选定的Golang;先谈谈Node.js 吧 。
Node.js的效能可以说是快上PHP3.5倍至6倍左右 ,而且撰写的语言还是JavaScript,蒸蚌,如此一来就不需要学习新语言了!搭配Babel更可以说是万能,不过那跟「跳跳虎」一样的Async逻辑还有那恐怖的Callback Hell,有人认为前者是种优点 , 这点我不否认,但是对学习PHP的我来说太过于"Mind Fuck" , 至于后者的Callback Hell虽然有Promise , 但是那又是另一个「Then Hell」的故事了 。相较于Golang之下,Node.js似乎就没有那么吸引我了 。你确实可以用Node.js写出很多东西,不过那V8引擎的效能仍然有限,而且要学习新的事物,不就应该是「全新」的吗;)?
题外话: 为什么Node.js不适合大型和商业专案?
在抛弃改用Node.js 之后我曾经花了一天的时间尝试Rust 和Iron 框架 , 嗯??Rust 太强大了,强大到让我觉得Rust 不应该用在这里,这想法也许很蠢,但Rust 让我觉得适合更应该拿来用在系统或者是部分底层的地方 , 而不应该是网路服务 。
Golang是我最终的选择 , 主要在于我花了一天的时间来研究的时候意外地发现Golang夭寿简洁( 关键字只有25个 ),相较之下Rust太过于「强大」令我怯步;而且Golang带有许多工具 , 例如 go fmt 会自动帮你整理程式码、 go doc 会自动帮你生产文件、 go test 可以自动单元测试并生产覆盖率报表、也有 go get 套件管理工具(虽然没有版本功能),不过都很实用,而且也不需要加上分号( ; ),真要说不好的地方??大概就是强迫你花括号不能换行放吧(没错,我就是花括号会换行放的人) 。
当我在撰写这份文件的时候 我会先假设你有一定的基础 , 你可以先阅读下列的手册,他们都很不错 。
你能够在PHP 里面想建立一个变数的时候就直接建立 , 夭寿赞,是吗?
蒸蚌!那么Golang 呢?在Golang 中变数分为几类:「新定义」、「预先定义」、「自动新定义」、「覆盖」 。让我们来看看范例:
在PHP中你会很常用到 echo 来显示文字 , 像这样 。
然而在Golang中你会需要 fmt 套件 , 关于「什么是套件」的说明你可以在文章下述了解 。
这很简单,而且两个语言的用法相差甚少,下面这是PHP:
只是Golang 稍微聒噪了一点,你必须在函式后面宣告他最后会回传什么资料型别 。
在PHP 中你要回传多个资料你就会用上阵列,然后将资料放入阵列里面,像这样 。
然而在Golang 中你可以不必用到一个阵列,函式可以一次回传多个值:
两个语言的撰写方式不尽相同 。
主要是PHP 的阵列能做太多事情了,所以在PHP 里面要储存什么用阵列就好了 。
在Golang里??没有这么万能的东西,首先要先了解Golang中有这些型态: array ,slice ,map ,interface ,
你他妈的我到底看了三洨 , 首先你要知道Golang是个强型别语言,意思是你的阵列中 只能有一种型态 ,什么意思?当你决定这个阵列是用来摆放字串资料的时候 , 你就只能在里面放字串 。没有数值、没有布林值,就像你没有女朋友一样 。
先撇开PHP 的「万能阵列」不管,Golang 中的阵列既单纯却又十分脑残,在定义一个阵列的时候,你必须给他一个长度还有其内容存放的资料型态,你的阵列内容不一定要填满其长度,但是你的阵列内容不能超过你当初定义的长度 。
切片??这听起来也许很奇怪 , 但是你确实可以「切」他 , 让我们先谈谈「切片」比起「阵列」要好在哪里:「你不用定义其最大长度,而且你可以直接赋予值」,没了 。
我们刚才有提到你可以「切」他,记得吗?这有点像是PHP中的 array_slice() ,但是Golang直接让Slice「内建」了这个用法,其用法是: slice[开始:结束]。
在PHP中倒是没有那么方便,在下列PHP范例中你需要不断地使用 array_slice()。
你可以把「映照」看成是一个有键名和键值的阵列,但是记?。骸改阈枰孪榷ㄒ迤浼⒓档淖柿闲吞梗馊韵拗颇忝话旆ㄔ谟痴罩写娣哦嘀植煌吞淖柿?。
在Golang里可就没这么简单了,你需要先用 make() 宣告 map。
也许你不喜欢「接口」这个词,但用「介面」我怕会误导大众,所以,是的,接下来我会继续称其为「接口」 。还记得你可以在PHP 的关联阵列里面存放任何型态的资料吗,像下面这样?
现在你有福了!正因为Golang中的 interface{} 可以接受任何内容,所以你可以把它拿来存放任何型态的资料 。
有时候你也许会有个不定值的变数,在PHP 里你可以直接将一个变数定义成字串、数值、空值、就像你那变心的女友一样随时都在变 。
在Golang中你必须给予变数一个指定的资料型别 , 不过还记得刚才提到的:「Golang中有个 interface{} 能够 存放任何事物 」吗( 虽然也不是真的任何事物啦?? )?
当我们程式中不需要继续使用到某个资源或是发生错误的时候,我们索性会将其关闭或是抛弃来节省资源开销 , 例如PHP 里的读取档案:
在Golang中 , 你可以使用 defer 来在函式结束的时候自动执行某些程式(其执行方向为反向) 。所以你就不需要在函式最后面结束最前面的资源 。
defer 可以被称为「推迟执行」,实际上就是在函式结束后会「反序」执行的东西,例如你按照了这样的顺序定义 defer : A-B-C-D ,那么执行的顺序其实会是 D-C-B-A ,这用在程式结束时还蛮有用的,让我们看看Golang如何改善上述范例 。
这东西很邪恶,不是吗?又不是在写BASIC,不过也许有时候你会在PHP 用上呢 。但是拜托,不要 。
Golang中仅有 for 一种回圈但却能够达成 foreach 、 while 、 for 多种用法 。普通 for 回圈写法在两个语言中都十分相近 。
在Golang请记得:如果你的 i 先前并不存在,那么你就需要定义它 , 所以下面这个范例你会看见 i := 0。
在PHP里,foreach() 能够直接给你值和键名,用起来十分简单 。
Golang里面虽然仅有 for() 但却可以使用 range 达成和PHP一样的 foreach 方式 。
一个 while(条件) 回圈在PHP里面可以不断地执行区块中的程式,直到 条件 为 false 为止 。
在Golang里也有相同的做法 , 但仍是透过 for 回圈,请注意这个 for 回圈并没有任何的分号( ; ),而且一个没有条件的 for 回圈会一直被执行 。
PHP中有 do .. while() 回圈可以先做区块中的动作 。
在Golang中则没有相关函式,但是你可以透过一个无止尽的 for 回圈加上条件式来让他结束回圈 。
要是你真的希望完全符合像是PHP那样的设计方式,或者你可以在Golang中使用很邪恶的 goto。
在PHP中我们可以透过 date() 像这样取得目前的日期 。
在Golang就稍微有趣点了,因为Golang中并不是以 Y-m-d 这种格式做为定义,而是 1 、 2 、 3,这令你需要去翻阅文件,才能够知道 1 的定义是代表什么 。
俗话说:「爆炸就是艺术」,可爱的PHP用词真的很大胆 , 像是: explode() (爆炸)、 die() (死掉) , 回归正传 , 如果你想在PHP里面将字串切割成阵列 , 你可以这么做 。
简单的就让一个字串给「爆炸」了 , 那么Golang 呢?
对了,记得引用 strings 套件 。
这真的是很常用到的功能 , 就像物件一样有着键名和键值,在PHP 里面你很简单的就能靠阵列(Array)办到 。
真是太棒了,那么Golang呢?用 map 是差不多啦 。如果有必要的话,你可以稍微复习一下先前提到的「多资料储存型态-Stores」 。
你很常会在PHP里面用 isset() 检查一个索引是否存在 , 不是吗?
在Golang里面很简单的能够这样办到(仅适用于 map ) 。
指针(有时也做参照)是一个像是「变数别名」的方法,这种方法让你不用整天覆盖旧的变数,让我们假设 A = 1; B = A; 这个时候 B 会复制一份 A 且两者不相干,倘若你希望修改 B 的时候实际上也会修改到 A 的值,就会需要指针 。
指针比起复制一个变数 , 他会建立一个指向到某个变数的记忆体位置,这也就是为什么你改变指针,实际上是在改变某个变数 。
在Golang你需要用上 * 还有符号 。
有些时候你会回传一个阵列,这个阵列里面可能有资料还有错误代号,而你会用条件式判断错误代号是否非空值 。
在Golang中函式可以一次回传多个值 。为此,你不需要真的回传一个阵列,不过要注意的是你将会回传一个属于 error 资料型态的错误 , 所以你需要引用 errors 套件来帮助你做这件事 。
该注意的是Golang没有 try .. catch , 因为 Golang推荐这种错误处理方式 , 你应该在每一次执行可能会发生错误的程式时就处理错误,而非后来用 try 到处包覆你的程式 。
在 if 条件式里宣告变数会让你只能在 if 内部使用这个变数,而不会污染到全域范围 。
也许你在PHP中更常用的会是 try .. catch , 在大型商业逻辑时经常看见如此地用法,实际上这种用法令人感到聒噪(因为你会需要一堆 try 区块):
Golang中并没有 try .. catch,实际上Golang也 不鼓励这种行为 (Golang推荐逐一处理错误的方式),倘若你真想办倒像是捕捉异常这样的方式,你确实可以使用Golang中另类处理错误的方式(可以的话尽量避免使用这种方式): panic() ,recover() ,defer。
你可以把 panic() 当作是 throw (丢出错误),而这跟PHP的 exit() 有87%像,一但你执行了 panic() 你的程式就会宣告而终,但是别担心 , 因为程式结束的时候会呼叫 defer ,所以我们接下来要在 defer 停止 panic()。
关于 defer 上述已经有提到了,他是一个反向执行的宣告,会在函式结束后被执行,当你呼叫了 panic() 结束程式的时候,也就会开始执行 defer ,所以我们要在 defer 内使用 recover() 让程式不再继续进行结束动作,这就像是捕捉异常 。
recover() 可以看作 catch (捕捉),我们要在 defer 里面用 recover() 解决 panic(),如此一来程式就会回归正常而不会被结束 。
还记得在PHP里要引用一堆档案的日子吗?到处可见的 require() 或是 include() ?到了Golang这些都不见了,取而代之的是「套件(Package)」 。现在让我们来用PHP解释一下 。
这看起来很正常对吧?但假设你有一堆档案,这马上就成了 Include Hell , 让我们看看Golang怎么透过「套件」解决这个问题 。
「 蛤???杀?。浚浚?」你可能如此地说道 。是的, main.go 中除了引用 fmt 套件( 为了要输出结果用的套件 )之外完全没有引用到 a.go。
「 蛤???杀?。浚浚浚浚浚?」你仿佛回到了几秒钟前的自己 。
既然没有引用其他档案,为什么 main.go 可以输出 foo 呢?注意到了吗,两者都是属于 main 套件 ,因此 他们共享同一个区域,所以接下来要介绍的是什么叫做「套件」 。
套件是每一个 .go 档案都必须声明在Golang原始码中最开端的东西,像下面这样:
这意味着目前的档案是属于 main 套件( 你也可以依照你的喜好命名 ),那么要如何让同个套件之间的函式沟通呢?
接着是Golang;注意!你不需要引用任何档案,因为下列两个档案同属一个套件 。
一个由「套件」所掌握的世界,比起PHP的 include() 和 require() 还要好太多了,对吗?
在Golang 中没有引用单独档案的方式,你必须汇入一整个套件,而且你要记?。骸敢欢慊闳肓? ,你就一定要使用它」,像下面这样 。
假如你不希望使用你汇入的套件,你只是为了要触发那个套件的 main() 函式而引用的话??,那么你可以在前面加上一个底线( _ ) 。
如果你的套件出现了名称冲突,你可以在套件来源前面给他一个新的名称 。
现在你知道可以汇入套件了,那么什么是「汇出」?同个套件内的函式还有共享变数确实可以直接用,但那 并不表示可以给其他套件使用 ,其方法取决于 函式/变数的「开头大小写」。
是的 。Golang依照一个函式/变数的开头大小写决定这个东西是否可供「汇出」。
这用在区别函式的时候格外有用,因为小写开头的任何事物都是不供汇出的,反之,大写开头的任何事物都是用来汇出供其他套件使用的 。
一开始可能会觉得这是什么奇异的规定,但写久之后 , 你就能发现比起JavaScript和Python以「底线为开头的命名方式」还要来得更好;比起成天宣告 public 、 private 、 protected 还要来得更快 。
在Golang 中没有类别 , 但有所谓的「建构体(Struct)」和「接口(Interface)」,这就能够满足几乎所有的需求了 , 这也是为什么我认为Golang 很简洁却又很强大的原因 。
让我们先用PHP 建立一个类别 , 然后看看Golang 怎么解决这个问题 。
虽然Golang没有类别,但是「建构体(Struct)」就十分地堪用了,首先你要知道在Golang中「类别」的成员还有方法都是在「类别」外面所定义的,这跟PHP在类别内定义的方式有所不同,在Golang中还有一点,那就是他们没有 public 、 private 、 protected 的种类 。
在PHP中 , 当有一个类别被 new 的时候会自动执行该类别内的建构子( __construct() ),通常你会用这个来初始化一些类别内部的值 。
但是在Golang 里因为没有类别,也就没有建构子,不巧的是建构体本身也不带有建构子的特性,这个时候你只能自己在外部建立一个建构用函式 。
让我们假设你有两个类别,你会把其中一个类别传入到另一个类别里面使用,废话不多说!先上个PHP 范例(为了简短篇幅我省去了换行) 。
在Golang中你也有相同的用法,但是请记得:「 任何东西都是在「类别」外完成建构的 」 。
在PHP 中没有相关的范例,这部分会以刚才「嵌入」章节中的Golang 范例作为解说对象 。
你可以看见Golang在进行 Foo 嵌入 Bar 的时候,会自动将 Foo 的成员暴露在 Bar 底下,那么假设「双方之间有相同的成员名称」呢?
这个时候被嵌入的成员就会被「遮蔽」 , 下面是个实际范例,还有你如何解决遮蔽问题:
虽然都是呼叫同一个函式,但是这个函式可以针对不同的资料来源做出不同的举动 , 这就是多形 。你也能够把这看作是:「讯息的意义由接收者定义,而不是传送者」 。
目前PHP 中没有真正的「多形」 , 不过你仍可以做出同样的东西 。
嗯??那么Golang呢?实际上更简单而且更有条理了 , 在Golang中有 interface 可以帮忙完成这个工作 。
如果你对Interface还不熟悉,可以试着查看「 解释Golang中的Interface到底是什么 」文章 。
谢谢你看到这里,可惜这篇文章却没有说出Golang 最重要的卖点:「Goroutine」和「Channel」
驳狗屎文 "我为什么放弃Go语言此篇文章流传甚广, 其实里面没啥干货, 而且里面很多观点是有问题的. 这个文章在 golang-china 很早就讨论过了.
最近因为 Rust 1.0 和 1.1 的发布, 导致这个文章又出来毒害读者.
所以写了这篇反驳文章, 指出其中的问题.
有好几次,当我想起来的时候,总是会问自己go语言捕获异常:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题 。
开门见山地说,我当初放弃Go语言(golang) , 就是因为两个“不爽”:第一,对Go语言本身不爽;第二 , 对Go语言社区里的某些人不爽 。毫无疑问,这是非常主观的结论 。但是我有足够详实的客观的论据,用以支撑这个看似主观的结论 。
文末附有本文更新日志 。
确实是非常主观的结论, 因为里面有不少有问题的观点(用来忽悠Go小白还行).
第0节:我的Go语言经历
先说说我的经历吧 , 以避免被无缘无故地当作Go语言的低级黑 。
2009年底,Go语言(golang)第一个公开版本发布,笼罩着“Google公司制造”的光环,吸引了许多慕名而来的尝鲜者,我(Liigo)也身居其中,笼统的看了一些Go语言的资料 , 学习了基础的教程,因对其语法中的分号和花括号不满,很快就遗忘掉了,没拿它当一回事 。
在2009年Go刚发布时, 确实是因为“Google公司制造”的光环而吸引了(包括文章作者和诸多IT采访人员)很多低级的尝鲜者.
还好, 经过5年的发展, 这些纯粹因为光环来的投机者所剩已经不多了(Google趋势).
目前, 真正的Go用户早就将Go用于实际的生产了.
说到 其语法中的分号和花括号不满, 我想说这只是go语言捕获异常你的 个人主观感受, 还有很多人对Go的分号和花括号很满意,
包括水果公司的的 Swift 的语言设计者也很满意这种风格(Swift中的分号和花括号和Go基本相同).
如果只谈 个人主观感受, 我也可以说 Rust 的 fn 缩写也很蛋疼!
两年之后,2011年底,Go语言发布1.0的计划被提上日程 , 相关的报道又多起来,我再次关注它,重新评估之后决定深入参与Go语言 。我订阅了其users、nuts、dev、commits等官方邮件组,坚持每天阅读其中的电子邮件,以及开发者提交的每一次源代码更新,给Go提交了许多改进意见,甚至包括修改Go语言编译器源代码直接参与开发任务 。如此持续了数月时间 。
这个到是事实, 在 golang-china 有不少吵架的帖子, 感兴趣的可以去挖下, 我就不展开说了.
到2012年初,Go 1.0发布,语言和标准库都已经基本定型 , 不可能再有大幅改进,我对Go语言未能在1.0定型之前更上一个台阶、实现自我突破,甚至带着诸多明显缺陷走向1.0,感到非常失望,因而逐渐疏远了它(所以Go 1.0之后的事情我很少关心) 。后来看到即将发布的Go 1.1的Release Note , 发现语言层面没有太大改变,只是在库和工具层面有所修补和改进,感到它尚在幼年就失去成长的动力 , 越发失望 。外加Go语言社区里的某些人,其中也包括Google公司负责开发Go语言的某些人,其态度、言行,让我极度厌恶 , 促使我决绝地离弃Go语言 。
真的不清楚楼主说的可以在 Go1.0 之前短时间内能实现的 重大改进和诸多明显缺陷 是什么.
如果是楼主说前面的 其语法中的分号和花括号不满 之类的重大改进, 我只能说这只是你的 个人主观感受 而已,
你的很多想法只能说服你自己, 没办法说服其go语言捕获异常他绝大部分人(不要以为像C或Rust那样什么特性都有就NB了, 各种NB特性加到一起只能是 要你命3000, 而绝对不会是什么 银弹).
Go 1.1的Release Note,发现语言层面没有太大改变. 语言层没有改变是是因为 Go1 作出的向后兼容的承诺. 对于工业级的语言来说, Go1 这个只能是优点. 如果连语言层在每个版本都会出现诸多大幅改进, 那谁还敢用Go语言来做生产开发呢(我承认Rust的改动很大胆, 但也说明了Rust还处于比较幼稚和任性的阶段)?
说 Go语言社区里的某些人固执 的观点我是同意的. 但是这些 固执 的人是可以讲道理的, 但是他们对很多东西的要求很高(特别是关于Go的设计哲学部分).
只要你给的建议有依据(语言的设计哲学是另外一回事情), 他们绝对不会盲目的拒绝(只是讨论的周期会比较长).
关于楼主提交的给Go文件添加BOM的文章, 需要补充说明下.
在Go1.0发布的时候, Go语言的源文件(.go)明确要求必须是UTF8编码的, 而且是无BOM的UTF8编码的.
注意: 这个 无BOM的UTF8编码 的限制仅仅是 针对 Go语言的源文件(.go).
这个限制并不是说不允许用户处理带BOM的UTF8的txt文件!
我觉得对于写Go程序来说, 这个限制是没有任何问题的, 到目前为止, 我还从来没有使用过带BOM的.go文件.
不仅是因为带BOM的.go文件没有太多的意义, 而且有很多的缺陷.
BOM的原意是用来表示编码是大端还是小端的, 主要用于UTF16和UTF32. 对于 UTF8 来说, BOM 没有任何存在的意义(正是Go的2个作者发明了UTF8, 彻底解决了全球的编码问题).
但是, 在现实中, 因为MS的txt记事本, 对于中文环境会将txt(甚至是C/C源文件)当作GBK编码(GBK是个烂编码),
为了区别到底是GBK还是UTF8, MS的记事本在前面加了BOM这个垃圾(被GBK占了茅坑), 这里的bom已经不是表示字节序本意了. 不知道有没有人用ms的记事本写网页, 然后生成一个带bom的utf8网页肯定很有意思.
这是MS的记事本的BUG: 它不支持生成无BOM的UTF8编码的文本文件!
这些是现实存在的带BOM的UTF8编码的文本文件, 但是它们肯定都不是Go语言源文件!
所以说, Go语言的源文件即使强制限制了无BOM的UTF8编码要求, 也是没有任何问题的(而且我还希望有这个限制).
虽然后来Go源文件接受带BOM的UTF8了, 但是运行 go fmt 之后, 还是会删除掉BOM的(因为BOM就是然并卵). 也就是说 带 BOM 的 Go 源文件是不符合 Go语言的编码风格的, go fmt 会强制删除 BOM 头.
前面说了BOM是MS带来的垃圾, 但是BOM的UTF8除了然并卵之外还有很多问题, 因为BOM在string的开头嵌入了垃圾,
导致正则表达式, string的链接运算等操作都被会被BOM这个垃圾所污染. 对于.go语言, 即使代码完全一样, 有BOM和无BOM会导致文件的MD5之类的校验码不同.
所以, 我觉得Go用户不用纠结BOM这个无关紧要的东西.
在上一个10年,我(Liigo)在我所属的公司里 , 深度参与了两个编程语言项目的开发 。我想,对于如何判断某个编程语言的优劣,或者说至少对于如何判断某个编程语言是否适合于我自己,我应该还是有一点发言权的 。
第1节:我为什么对Go语言不爽?
Go语言有很多让我不爽之处,这里列出我现在还能记起的其中一部分,排名基本上不分先后 。读者们耐心地看完之后 , 还能淡定地说一句“我不在乎”吗?
1.1 不允许左花括号另起一行
关于对花括号的摆放,在C语言、C、Java、C#等社区中,十余年来存在持续争议,从未形成一致意见 。在我看来,这本来就是主观倾向很重的抉择,不违反原则不涉及是非的情况下,不应该搞一刀切,让程序员或团队自己选择就足够了 。编程语言本身强行限制 , 把自己的喜好强加给别人 , 得不偿失 。无论倾向于其中任意一种,必然得罪与其对立的一群人 。虽然我现在已经习惯了把左花括号放在行尾 , 但一想到被禁止其他选择 , 就感到十分不爽 。Go语言这这个问题上,没有做到“团结一切可以团结的力量”不说,还有意给自己树敌,太失败了 。
我觉得Go最伟大的发明是 go fmt, 从此Go用户不会再有花括弧的位置这种无聊争论了(当然也少了不少灌水和上tiobe排名的机会).
是这优点, Swift 语言也使用和 Go 类似的风格(当然楼主也可能鄙视swift的作者).
1.2 编译器莫名其妙地给行尾加上分号
对Go语言本身而言 , 行尾的分号是可以省略的 。但是在其编译器(gc)的实现中,为了方便编译器开发者,却在词法分析阶段强行添加了行尾的分号,反过来又影响到语言规范,对“怎样添加分号”做出特殊规定 。这种变态做法前无古人 。在左花括号被意外放到下一行行首的情况下,它自动在上一行行尾添加的分号,会导致莫名其妙的编译错误(Go 1.0之前),连它自己都解释不明白 。如果实在处理不好分号,干脆不要省略分号得了;或者,Scala和JavaScript的编译器是开源的,跟它们学学怎么处理省略行尾分号可以吗?
又是楼主的 个人主观感受, 不过我很喜欢这个特性. Swift 语言也是类似.
1.3 极度强调编译速度 , 不惜放弃本应提供的功能
程序员是人不是神 , 编码过程中免不了因为大意或疏忽犯一些错 。其中有一些,是大家集体性的很容易就中招的错误(Go语言里的例子我暂时想不起来,C里的例子有“基类析构函数不是虚函数”) 。这时候编译器应该站出来,多做一些检查、约束、核对性工作,尽量阻止常规错误的发生,尽量不让有潜在错误的代码编译通过,必要时给出一些警告或提示,让程序员留意 。编译器不就是机器么,不就是应该多做脏活累活杂活、减少人的心智负担么?编译器多做一项检查,可能会避免数十万程序员今后多年内无数次犯同样的错误,节省的时间不计其数,这是功德无量的好事 。但是Go编译器的作者们可不这么想,他们不愿意自己多花几个小时给编译器增加新功能,觉得那是亏本 , 反而减慢了编译速度 。他们以影响编译速度为由 , 拒绝了很多对编译器改进的要求 。典型的因噎废食 。强调编译速度固然值得赞赏,但如果因此放弃应有的功能,我不赞成 。
编译速度是很重要的, 如果编译速度够慢, 语言再好也不会有人使用的.
比如C/C的增量编译/预编译头文件/并发编译都是为了提高编译速度.
Rust1.1 也号称 比 1.0 的编译时间减少了32% (注意: 不是运行速度).
当然, Go刚面世的时候, 编译速度是其中的一个设计目标.
不过我想楼主, 可能想说的是因为编译器自己添加分号而导致的编译错误的问题.
我觉得Go中 { 不能另起一行是语言特性, 如果修复这个就是引入了新的错误.
其他的我真想不起来还有哪些 调编译速度,不惜放弃本应提供的功能 (不要提泛型, 那是因为还没有好的设计).
1.4 错误处理机制太原始
在Go语言中处理错误的基本模式是:函数通常返回多个值,其中最后一个值是error类型 , 用于表示错误类型极其描述;调用者每次调用完一个函数,都需要检查这个error并进行相应的错误处理:if err != nil { /*这种代码写多了不想吐么*/ } 。此模式跟C语言那种很原始的错误处理相比如出一辙,并无实质性改进 。实际应用中很容易形成多层嵌套的if else语句,可以想一想这个编码场景:先判断文件是否存在,如果存在则打开文件,如果打开成功则读取文件,如果读取成功再写入一段数据 , 最后关闭文件 , 别忘了还要处理每一步骤中出现错误的情况,这代码写出来得有多变态、多丑陋?实践中普遍的做法是,判断操作出错后提前return , 以避免多层花括号嵌套 , 但这么做的后果是,许多错误处理代码被放在前面突出的位置,常规的处理逻辑反而被掩埋到后面去了 , 代码可读性极差 。而且,error对象的标准接口只能返回一个错误文本,有时候调用者为了区分不同的错误类型 , 甚至需要解析该文本 。除此之外 , 你只能手工强制转换error类型到特定子类型(静态类型的优势没了) 。至于panic - recover机制,致命的缺陷是不能跨越库的边界使用,注定是一个半成品,最多只能在自己的pkg里面玩一玩 。Java的异常处理虽然也有自身的问题(比如Checked Exceptions),但总体上还是比Go的错误处理高明很多 。
话说, 软件开发都发展了半个世纪, 还是无实质性改进. 不要以为弄一个异常的语法糖就是革命了.
我只能说错误和异常是2个不同的东西, 将所有错误当作异常那是SB行为.
正因为有异常这个所谓的银弹, 导致很多等着别人帮忙擦屁股的行为(注意 shit 函数抛出的绝对不会是一种类型的 shit, 而被其间接调用的各种 xxx_shit 也可能抛出各种类型的异常, 这就导致 catch 失控了):
int main() {
try {
shit();
} catch( /* 到底有几千种 shit ? */) {
...
}
}
Go的建议是 panic - recover 不跨越边界, 也就是要求正常的错误要由pkg的处理掉.
这是负责任的行为.
再说Go是面向并发的编程语言, 在海量的 goroutine 中使用 try/catch 是不是有一种不伦不类的感觉呢?
1.5 垃圾回收器(GC)不完善、有重大缺陷
在Go 1.0前夕,其垃圾回收器在32位环境下有内存泄漏,一直拖着不肯改进,这且不说 。Go语言垃圾回收器真正致命的缺陷是 , 会导致整个进程不可预知的间歇性停顿 。像某些大型后台服务程序,如游戏服务器、APP容器等,由于占用内存巨大,其内存对象数量极多,GC完成一次回收周期,可能需要数秒甚至更长时间,这段时间内,整个服务进程是阻塞的、停顿的 , 在外界看来就是服务中断、无响应 , 再牛逼的并发机制到了这里统统失效 。垃圾回收器定期启动 , 每次启动就导致短暂的服务中断,这样下去,还有人敢用吗?这可是后台服务器进程 , 是Go语言的重点应用领域 。以上现象可不是我假设出来的,而是事实存在的现实问题,受其严重困扰的也不是一家两家了(2013年底ECUG Con 2013,京东的刘奇提到了Go语言的GC、defer、标准库实现是性能杀手 , 最大的痛苦是GC;美团的沈锋也提到Go语言的GC导致后台服务间隔性停顿是最大的问题 。更早的网络游戏仙侠道开发团队也曾受Go垃圾回收的沉重打击) 。在实践中,你必须努力减少进程中的对象数量,以便把GC导致的间歇性停顿控制在可接受范围内 。除此之外你别无选择(难道你还想自己更换GC算法、甚至砍掉GC?那还是Go语言吗?) 。跳出圈外,我近期一直在思考,一定需要垃圾回收器吗?没有垃圾回收器就一定是历史的倒退吗?(可能会新写一篇博客文章专题探讨 。)
这是说的是32位系统, 这绝对不是Go语言的重点应用领域!! 我可以说Go出生就是面向64位系统和多核心CPU环境设计的. (再说 Rust 目前好像还不支持 XP 吧, 这可不可以算是影响巨大?)
32位当时是有问题, 但是对实际生产影响并不大(请问楼主还是在用32位系统吗, 还只安装4GB的内存吗). 如果是8位单片机环境, 建议就不要用Go语言了, 直接C语言好了.
而且这个问题早就不存在了(大家可以去看Go的发布日志).
Go的出生也就5年时间, GC的完善和改进是一个持续的工作, 2015年8月将发布的 Go1.5将采用并行GC.
关于GC的被人诟病的地方是会导致卡顿, 但是我以为这个主要是因为GC的实现还不够完美而导致的.
如果是完美的并发和增量的GC, 那应该不会出现大的卡顿问题的.
当然, 如果非要实时性, 那用C好了(实时并不表示性能高, 只是响应时间可控).
对于Rust之类没有GC的语言来说, 想很方便的开发并发的后台程序那几乎是不可能的.
不要总是吹Rust能代替底层/中层/上层的开发, 我们要看有谁用Rust真的做了什么.
1.6 禁止未使用变量和多余import
Go编译器不允许存在被未被使用的变量和多余的import,如果存在,必然导致编译错误 。但是现实情况是 , 在代码编写、重构、调试过程中 , 例如,临时性的注释掉一行代码,很容易就会导致同时出现未使用的变量和多余的import,直接编译错误了,你必须相应的把变量定义注释掉,再翻页回到文件首部把多余的import也注释掉,……等事情办完了 , 想把刚才注释的代码找回来,又要好几个麻烦的步骤 。还有一个让人蛋疼的问题,编写数据库相关的代码时,如果你import某数据库驱动的pkg,它编译给你报错,说不需要import这个未被使用的pkg;但如果你听信编译器的话删掉该import,编译是通过了,运行时必然报错,说找不到数据库驱动;你看看程序员被折腾的两边不是人,最后不得不请出大神:import _ 。对待这种问题,一个比较好的解决方案是 , 视其为编译警告而非编译错误 。但是Go语言开发者很固执 , 不容许这种折中方案 。
这个问题我只能说楼主的吐槽真的是没水平.
为何不使用的是错误而不是警告? 这是为了将低级的bug消灭在编译阶段(大家可以想下C/C的那么多警告有什么卵用).
而且, import 即使没有使用的话, 也是用副作用的, 因为 import 会导致 init 和全局变量的初始化.
如果某些代码没有使用, 为何要执行 init 这些初始化呢?
如果是因为调试而添加的变量, 那么调试完删除不是很正常的要求吗?
如果是因为调试而要导入fmt或log之类的包, 删除调试代码后又导致 import 错误的花,
楼主难道不知道在一个独立的文件包装下类似的辅助调试的函数吗?
import (
"fmt"
"log"
)
func logf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
}
func fatalf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
os.Exit(1)
}
import _ 是有明确行为的用法, 就是为了执行包中的 init 等函数(可以做某些注册操作).
将警告当作错误是Go的一个哲学, 当然在楼主看来这是白痴做法.
1.7 创建对象的方式太多令人纠结
创建对象的方式,调用new函数、调用make函数、调用New方法、使用花括号语法直接初始化结构体,你选哪一种?不好选择,因为没有一个固定的模式 。从实践中看,如果要创建一个语言内置类型(如channel、map)的对象 , 通常用make函数创建;如果要创建标准库或第三方库定义的类型的对象,首先要去文档里找一下有没有New方法,如果有就最好调用New方法创建对象,如果没有New方法 , 则退而求其次,用初始化结构体的方式创建其对象 。这个过程颇为周折,不像C、Java、C#那样直接new就行了 。
C的new是狗屎. new导致的问题是构造函数和普通函数的行为不一致, 这个补丁特性真的没啥优越的.
我还是喜欢C语言的 fopen 和 malloc 之类构造函数, 构造函数就是普通函数, Go语言中也是这样.
C中, 除了构造不兼容普通函数, 析构函数也是不兼容普通函数. 这个而引入的坑有很多吧.
1.8 对象没有构造函数和析构函数
没有构造函数还好说 , 毕竟还有自定义的New方法,大致也算是构造函数了 。没有析构函数就比较难受了,没法实现RAII 。额外的人工处理资源清理工作,无疑加重了程序员的心智负担 。没人性?。?还嫌我们程序员加班还少吗?C里有析构函数 , Java里虽然没有析构函数但是有人家finally语句?。珿o呢,什么都没有 。没错 , 你有个defer , 可是那个defer问题更大,详见下文吧 。
defer 可以覆盖析构函数的行为, 当然 defer 还有其他的任务. Swift2.0 也引入了一个简化版的 defer 特性.
1.9 defer语句的语义设定不甚合理
Go语言设计defer语句的出发点是好的,把释放资源的“代码”放在靠近创建资源的地方,但把释放资源的“动作”推迟(defer)到函数返回前执行 。遗憾的是其执行时机的设置似乎有些不甚合理 。设想有一个需要长期运行的函数,其中有无限循环语句,在循环体内不断的创建资源(或分配内存),并用defer语句确保释放 。由于函数一直运行没有返回,所有defer语句都得不到执行 , 循环过程中创建的大量短暂性资源一直积累着,得不到回收 。而且 , 系统为了存储defer列表还要额外占用资源,也是持续增加的 。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃 。像这类长期运行的函数,http.ListenAndServe()就是典型的例子 。在Go语言重点应用领域,可以说几乎每一个后台服务程序都必然有这么一类函数,往往还都是程序的核心部分 。如果程序员不小心在这些函数中使用了defer语句,可以说后患无穷 。如果语言设计者把defer的语义设定为在所属代码块结束时(而非函数返回时)执行,是不是更好一点呢?可是Go 1.0早已发布定型,为了保持向后兼容性,已经不可能改变了 。小心使用defer语句!一不小心就中招 。
前面说到 defer 还有其他的任务, 也就是 defer 中执行的 recover 可以捕获 panic 抛出的异常.
还有 defer 可以在 return 之后修改命名的返回值.
上面2个工作要求 defer 只能在函数退出时来执行.
楼主说的 defer 是类似 Swift2.0 中 defer 的行为, 但是 Swift2.0 中 defer 是没有前面2个特性的.
Go中的defer是以函数作用域作为触发的条件的, 是会导致楼主说的在 for 中执行的错误用法(哪个语言没有坑呢?).
不过 for 中 局部 defer 也是有办法的 (Go中的defer是以函数作用域):
for {
func(){
f, err := os.Open(...)
defer f.Close()
}()
}
在 for 中做一个闭包函数就可以了. 自己不会用不要怪别人没告诉你.
1.10 许多语言内置设施不支持用户定义的类型
for in、make、range、channel、map等都仅支持语言内置类型,不支持用户定义的类型(?) 。用户定义的类型没法支持for in循环,用户不能编写像make、range那样“参数类型和个数”甚至“返回值类型和个数”都可变的函数,不能编写像channel、map那样类似泛型的数据类型 。语言内置的那些东西,处处充斥着斧凿的痕迹 。这体现了语言设计的局限性、封闭性、不完善,可扩展性差,像是新手作品——且不论其设计者和实现者如何权威 。延伸阅读:Go语言是30年前的陈旧设计思想,用户定义的东西几乎都是二等公民(Tikhon Jelvis) 。
说到底, 这个是因为对泛型支持的不完备导致的.
Go语言是没啥NB的特性, 但是Go的特性和工具组合在一起就是好用.
这就是Go语言NB的地方.
1.11 没有泛型支持,常见数据类型接口丑陋
没有泛型的话 , List、Set、Tree这些常见的基础性数据类型的接口就只能很丑陋:放进去的对象是一个具体的类型,取出来之后成了无类型的interface{}(可以视为所有类型的基础类型) , 还得强制类型转换之后才能继续使用,令人无语 。Go语言缺少min、max这类函数 , 求数值绝对值的函数abs只接收/返回双精度小数类型,排序接口只能借助sort.Interface无奈的回避了被比较对象的类型 , 等等等等,都是没有泛型导致的结果 。没有泛型,接口很难优雅起来 。Go开发者没有明确拒绝泛型 , 只是说还没有找到很好的方法实现泛型(能不能学学已经开源的语言呀) 。现实是,Go 1.0已经定型,泛型还没有 , 那些丑陋的接口为了保持向后兼容必须长期存在着 。
Go有自己的哲学, 如果能有和目前哲学不冲突的泛型实现, 他们是不会反对的.
如果只是简单学学(或者叫抄袭)已经开源的语言的语法, 那是C的设计风格(或者说C从来都是这样设计的, 有什么特性就抄什么), 导致了各种脑裂的编程风格.
编译时泛型和运行时泛型可能是无法完全兼容的, 看这个例子:
type AdderT interface {
Add(a, b T) T
}
关于go语言捕获异常和go语言 异常的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。

    推荐阅读