├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md └── src ├── README.md ├── SUMMARY.md ├── atomic.md ├── builtin-name.md ├── channel-size.md ├── consistency.md ├── container-capacity.md ├── container-copy.md ├── decl-group.md ├── defer-clean.md ├── else-unnecessary.md ├── embed-public.md ├── enum-start.md ├── error-name.md ├── error-once.md ├── error-type.md ├── error-wrap.md ├── exit-main.md ├── exit-once.md ├── function-name.md ├── function-order.md ├── functional-option.md ├── global-decl.md ├── global-mut.md ├── global-name.md ├── goroutine-exit.md ├── goroutine-forget.md ├── goroutine-init.md ├── import-alias.md ├── import-group.md ├── init.md ├── interface-compliance.md ├── interface-pointer.md ├── interface-receiver.md ├── intro.md ├── line-length.md ├── lint.md ├── map-init.md ├── mutex-zero-value.md ├── nest-less.md ├── package-name.md ├── panic.md ├── param-naked.md ├── performance.md ├── preface.txt ├── printf-const.md ├── printf-name.md ├── slice-nil.md ├── strconv.md ├── string-byte-slice.md ├── string-escape.md ├── struct-embed.md ├── struct-field-key.md ├── struct-field-zero.md ├── struct-pointer.md ├── struct-tag.md ├── struct-zero.md ├── test-table.md ├── time.md ├── type-assert.md ├── var-decl.md └── var-scope.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 2020 年 1 月 30 日 3 | 4 | - 建议在处理时间时使用 “time” 包。 5 | 6 | # 2020 年 1 月 25 日 7 | 8 | - 添加有关在公共结构中嵌入类型的指导。 9 | 10 | # 2019 年 12 月 17 日 11 | 12 | - 函数选项:推荐 “Option” 接口的结构实现,而不是用闭包捕获值。 13 | 14 | # 2019 年 11 月 26 日 15 | 16 | - 添加针对全局变量变异的指导。 17 | 18 | # 2019 年 10 月 21 日 19 | 20 | - 添加与现有实践保持一致的章节。 21 | - 添加有关地图初始化和大小提示的指导。 22 | 23 | # 2019 年 10 月 11 日 24 | 25 | - 为错误消息建议简洁的上下文。 26 | 27 | # 2019 年 10 月 10 日 28 | 29 | - 初次发布。 30 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at oss-conduct@uber.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 52 | 53 | 114 | ## [uber-go/guide](https://github.com/uber-go/guide) 的中文翻译 115 | 116 | ## [English](https://github.com/uber-go/guide/blob/master/style.md) 117 | 118 | ## Uber Go 语言编码规范 119 | 120 | [Uber](https://www.uber.com/) 是一家美国硅谷的科技公司,也是 Go 语言的早期 adopter。其开源了很多 golang 项目,诸如被 Gopher 圈熟知的 [zap](https://github.com/uber-go/zap)、[jaeger](https://github.com/jaegertracing/jaeger) 等。2018 年年末 Uber 将内部的 [Go 风格规范](https://github.com/uber-go/guide) 开源到 GitHub,经过一年的积累和更新,该规范已经初具规模,并受到广大 Gopher 的关注。本文是该规范的中文版本。本版本会根据原版实时更新。 121 | 122 | ## 版本 123 | 124 | - 当前更新版本:2024-08-10 版本地址:[commit:#217](https://github.com/uber-go/guide/commit/a66b53bed4ec57c695992b14c4ccaafd49bd5296) 125 | - 如果您发现任何更新、问题或改进,请随时 fork 和 PR 126 | - Please feel free to fork and PR if you find any updates, issues or improvement. 127 | 128 | ## 目录 129 | 130 | - [uber-go/guide 的中文翻译](#uber-goguide-的中文翻译) 131 | - [English](#english) 132 | - [Uber Go 语言编码规范](#uber-go-语言编码规范) 133 | - [版本](#版本) 134 | - [目录](#目录) 135 | - [介绍](#介绍) 136 | - [指导原则](#指导原则) 137 | - [指向 interface 的指针](#指向-interface-的指针) 138 | - [Interface 合理性验证](#interface-合理性验证) 139 | - [接收器 (receiver) 与接口](#接收器-receiver-与接口) 140 | - [零值 Mutex 是有效的](#零值-mutex-是有效的) 141 | - [在边界处拷贝 Slices 和 Maps](#在边界处拷贝-slices-和-maps) 142 | - [接收 Slices 和 Maps](#接收-slices-和-maps) 143 | - [返回 slices 或 maps](#返回-slices-或-maps) 144 | - [使用 defer 释放资源](#使用-defer-释放资源) 145 | - [Channel 的 size 要么是 1,要么是无缓冲的](#channel-的-size-要么是-1要么是无缓冲的) 146 | - [枚举从 1 开始](#枚举从-1-开始) 147 | - [使用 time 处理时间](#使用-time-处理时间) 148 | - [使用 `time.Time` 表达瞬时时间](#使用-timetime-表达瞬时时间) 149 | - [使用 `time.Duration` 表达时间段](#使用-timeduration-表达时间段) 150 | - [对外部系统使用 `time.Time` 和 `time.Duration`](#对外部系统使用-timetime-和-timeduration) 151 | - [Errors](#errors) 152 | - [错误类型](#错误类型) 153 | - [错误包装](#错误包装) 154 | - [错误命名](#错误命名) 155 | - [一次处理错误](#一次处理错误) 156 | - [处理断言失败](#处理断言失败) 157 | - [不要使用 panic](#不要使用-panic) 158 | - [使用 go.uber.org/atomic](#使用-gouberorgatomic) 159 | - [避免可变全局变量](#避免可变全局变量) 160 | - [避免在公共结构中嵌入类型](#避免在公共结构中嵌入类型) 161 | - [避免使用内置名称](#避免使用内置名称) 162 | - [避免使用 `init()`](#避免使用-init) 163 | - [追加时优先指定切片容量](#追加时优先指定切片容量) 164 | - [主函数退出方式 (Exit)](#主函数退出方式-exit) 165 | - [一次性退出](#一次性退出) 166 | - [在序列化结构中使用字段标记](#在序列化结构中使用字段标记) 167 | - [不要一劳永逸地使用 goroutine](#不要一劳永逸地使用-goroutine) 168 | - [等待 goroutines 退出](#等待-goroutines-退出) 169 | - [不要在 `init()` 使用 goroutines](#不要在-init-使用-goroutines) 170 | - [性能](#性能) 171 | - [优先使用 strconv 而不是 fmt](#优先使用-strconv-而不是-fmt) 172 | - [避免字符串到字节的转换](#避免字符串到字节的转换) 173 | - [指定容器容量](#指定容器容量) 174 | - [指定 Map 容量提示](#指定-map-容量提示) 175 | - [指定切片容量](#指定切片容量) 176 | - [规范](#规范) 177 | - [避免过长的行](#避免过长的行) 178 | - [一致性](#一致性) 179 | - [相似的声明放在一组](#相似的声明放在一组) 180 | - [import 分组](#import-分组) 181 | - [包名](#包名) 182 | - [函数名](#函数名) 183 | - [导入别名](#导入别名) 184 | - [函数分组与顺序](#函数分组与顺序) 185 | - [减少嵌套](#减少嵌套) 186 | - [不必要的 else](#不必要的-else) 187 | - [顶层变量声明](#顶层变量声明) 188 | - [对于未导出的顶层常量和变量,使用\_作为前缀](#对于未导出的顶层常量和变量使用_作为前缀) 189 | - [结构体中的嵌入](#结构体中的嵌入) 190 | - [本地变量声明](#本地变量声明) 191 | - [nil 是一个有效的 slice](#nil-是一个有效的-slice) 192 | - [缩小变量作用域](#缩小变量作用域) 193 | - [避免参数语义不明确 (Avoid Naked Parameters)](#避免参数语义不明确-avoid-naked-parameters) 194 | - [使用原始字符串字面值,避免转义](#使用原始字符串字面值避免转义) 195 | - [初始化结构体](#初始化结构体) 196 | - [使用字段名初始化结构](#使用字段名初始化结构) 197 | - [省略结构中的零值字段](#省略结构中的零值字段) 198 | - [对零值结构使用 `var`](#对零值结构使用-var) 199 | - [初始化 Struct 引用](#初始化-struct-引用) 200 | - [初始化 Maps](#初始化-maps) 201 | - [字符串 string format](#字符串-string-format) 202 | - [命名 Printf 样式的函数](#命名-printf-样式的函数) 203 | - [编程模式](#编程模式) 204 | - [表驱动测试](#表驱动测试) 205 | - [功能选项](#功能选项) 206 | - [Linting](#linting) 207 | - [Lint Runners](#lint-runners) 208 | - [Stargazers over time](#stargazers-over-time) 209 | 210 | ## 介绍 211 | 212 | 样式 (style) 是支配我们代码的惯例。术语`样式`有点用词不当,因为这些约定涵盖的范围不限于由 gofmt 替我们处理的源文件格式。 213 | 214 | 本指南的目的是通过详细描述在 Uber 编写 Go 代码的注意事项来管理这种复杂性。这些规则的存在是为了使代码库易于管理,同时仍然允许工程师更有效地使用 Go 语言功能。 215 | 216 | 该指南最初由 [Prashant Varanasi] 和 [Simon Newton] 编写,目的是使一些同事能快速使用 Go。多年来,该指南已根据其他人的反馈进行了修改。 217 | 218 | [Prashant Varanasi]: https://github.com/prashantv 219 | [Simon Newton]: https://github.com/nomis52 220 | 221 | 本文档记录了我们在 Uber 遵循的 Go 代码中的惯用约定。其中许多是 Go 的通用准则,而其他扩展准则依赖于下面外部的指南: 222 | 223 | 1. [Effective Go](https://golang.org/doc/effective_go.html) 224 | 2. [Go Common Mistakes](https://go.dev/wiki/CommonMistakes) 225 | 3. [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) 226 | 227 | 我们的目标是使代码示例能够准确地用于 Go 的两个发布版本 [releases](https://go.dev/doc/devel/release). 228 | 229 | 所有代码都应该通过`golint`和`go vet`的检查并无错误。我们建议您将编辑器设置为: 230 | 231 | - 保存时运行 `goimports` 232 | - 运行 `golint` 和 `go vet` 检查错误 233 | 234 | 您可以在以下 Go 编辑器工具支持页面中找到更为详细的信息: 235 | 236 | 237 | ## 指导原则 238 | 239 | ### 指向 interface 的指针 240 | 241 | 您几乎不需要指向接口类型的指针。您应该将接口作为值进行传递,在这样的传递过程中,实质上传递的底层数据仍然可以是指针。 242 | 243 | 接口实质上在底层用两个字段表示: 244 | 245 | 1. 一个指向某些特定类型信息的指针。您可以将其视为"type"。 246 | 2. 数据指针。如果存储的数据是指针,则直接存储。如果存储的数据是一个值,则存储指向该值的指针。 247 | 248 | 如果希望接口方法修改基础数据,则必须使用指针传递 (将对象指针赋值给接口变量)。 249 | 250 | ```go 251 | type F interface { 252 | f() 253 | } 254 | 255 | type S1 struct{} 256 | 257 | func (s S1) f() {} 258 | 259 | type S2 struct{} 260 | 261 | func (s *S2) f() {} 262 | 263 | // f1.f() 无法修改底层数据 264 | // f2.f() 可以修改底层数据,给接口变量 f2 赋值时使用的是对象指针 265 | var f1 F = S1{} 266 | var f2 F = &S2{} 267 | ``` 268 | 永远不要使用指向 interface 的指针,这个是没有意义的。在 go 语言中,接口本身就是引用类型,换句话说,接口类型本身就是一个指针。对于我的需求,其实 test 的参数只要是 myinterface 就可以了,只需要在传值的时候,传*mystruct 类型(也只能传*mystruct 类型) 269 | ```go 270 | type myinterface interface{ 271 | print() 272 | } 273 | func test(value *myinterface){ 274 | //someting to do ... 275 | } 276 | 277 | type mystruct struct { 278 | i int 279 | } 280 | //实现接口 281 | func (this *mystruct) print(){ 282 | fmt.Println(this.i) 283 | this.i=1 284 | } 285 | func main(){ 286 | m := &mystruct{0} 287 | test(m) // 错误 288 | test(*m) // 错误 289 | } 290 | ``` 291 | 292 | ### Interface 合理性验证 293 | 294 | 在编译时验证接口的符合性。这包括: 295 | 296 | - 将实现特定接口的导出类型作为接口 API 的一部分进行检查 297 | - 实现同一接口的 (导出和非导出) 类型属于实现类型的集合 298 | - 任何违反接口合理性检查的场景,都会终止编译,并通知给用户 299 | 300 | 补充:上面 3 条是编译器对接口的检查机制, 301 | 大体意思是错误使用接口会在编译期报错。 302 | 所以可以利用这个机制让部分问题在编译期暴露。 303 | 304 | 305 | 306 | 307 | 340 |
BadGood
308 | 309 | ```go 310 | // 如果 Handler 没有实现 http.Handler,会在运行时报错 311 | type Handler struct { 312 | // ... 313 | } 314 | func (h *Handler) ServeHTTP( 315 | w http.ResponseWriter, 316 | r *http.Request, 317 | ) { 318 | ... 319 | } 320 | ``` 321 | 322 | 323 | 324 | ```go 325 | type Handler struct { 326 | // ... 327 | } 328 | // 用于触发编译期的接口的合理性检查机制 329 | // 如果 Handler 没有实现 http.Handler,会在编译期报错 330 | var _ http.Handler = (*Handler)(nil) 331 | func (h *Handler) ServeHTTP( 332 | w http.ResponseWriter, 333 | r *http.Request, 334 | ) { 335 | // ... 336 | } 337 | ``` 338 | 339 |
341 | 342 | 如果 `*Handler` 与 `http.Handler` 的接口不匹配, 343 | 那么语句 `var _ http.Handler = (*Handler)(nil)` 将无法编译通过。 344 | 345 | 赋值的右边应该是断言类型的零值。 346 | 对于指针类型(如 `*Handler`)、切片和映射,这是 `nil`; 347 | 对于结构类型,这是空结构。 348 | 349 | ```go 350 | type LogHandler struct { 351 | h http.Handler 352 | log *zap.Logger 353 | } 354 | var _ http.Handler = LogHandler{} 355 | func (h LogHandler) ServeHTTP( 356 | w http.ResponseWriter, 357 | r *http.Request, 358 | ) { 359 | // ... 360 | } 361 | ``` 362 | 363 | ### 接收器 (receiver) 与接口 364 | 365 | 使用值接收器的方法既可以通过值调用,也可以通过指针调用。 366 | 367 | 带指针接收器的方法只能通过指针或 [addressable values] 调用。 368 | 369 | [addressable values]: https://golang.org/ref/spec#Method_values 370 | 371 | 例如, 372 | 373 | ```go 374 | type S struct { 375 | data string 376 | } 377 | 378 | func (s S) Read() string { 379 | return s.data 380 | } 381 | 382 | func (s *S) Write(str string) { 383 | s.data = str 384 | } 385 | 386 | sVals := map[int]S{1: {"A"}} 387 | 388 | // 你通过值只能调用 Read 389 | sVals[1].Read() 390 | 391 | // 这不能编译通过: 392 | // sVals[1].Write("test") 393 | 394 | sPtrs := map[int]*S{1: {"A"}} 395 | 396 | // 通过指针既可以调用 Read,也可以调用 Write 方法 397 | sPtrs[1].Read() 398 | sPtrs[1].Write("test") 399 | ``` 400 | 401 | 类似的,即使方法有了值接收器,也同样可以用指针接收器来满足接口。 402 | 403 | ```go 404 | type F interface { 405 | f() 406 | } 407 | 408 | type S1 struct{} 409 | 410 | func (s S1) f() {} 411 | 412 | type S2 struct{} 413 | 414 | func (s *S2) f() {} 415 | 416 | s1Val := S1{} 417 | s1Ptr := &S1{} 418 | s2Val := S2{} 419 | s2Ptr := &S2{} 420 | 421 | var i F 422 | i = s1Val 423 | i = s1Ptr 424 | i = s2Ptr 425 | 426 | // 下面代码无法通过编译。因为 s2Val 是一个值,而 S2 的 f 方法中没有使用值接收器 427 | // i = s2Val 428 | ``` 429 | 430 | [Effective Go](https://golang.org/doc/effective_go.html) 中有一段关于 [pointers vs. values](https://golang.org/doc/effective_go.html#pointers_vs_values) 的精彩讲解。 431 | 432 | 补充: 433 | 434 | - 一个类型可以有值接收器方法集和指针接收器方法集 435 | - 值接收器方法集是指针接收器方法集的子集,反之不是 436 | - 规则 437 | - 值对象只可以使用值接收器方法集 438 | - 指针对象可以使用 值接收器方法集 + 指针接收器方法集 439 | - 接口的匹配 (或者叫实现) 440 | - 类型实现了接口的所有方法,叫匹配 441 | - 具体的讲,要么是类型的值方法集匹配接口,要么是指针方法集匹配接口 442 | 443 | 具体的匹配分两种: 444 | 445 | - 值方法集和接口匹配 446 | - 给接口变量赋值的不管是值还是指针对象,都 ok,因为都包含值方法集 447 | - 指针方法集和接口匹配 448 | - 只能将指针对象赋值给接口变量,因为只有指针方法集和接口匹配 449 | - 如果将值对象赋值给接口变量,会在编译期报错 (会触发接口合理性检查机制) 450 | 451 | 为啥 i = s2Val 会报错,因为值方法集和接口不匹配。 452 | 453 | ### 零值 Mutex 是有效的 454 | 455 | 零值 `sync.Mutex` 和 `sync.RWMutex` 是有效的。所以指向 mutex 的指针基本是不必要的。 456 | 457 | 458 | 459 | 460 | 475 |
BadGood
461 | 462 | ```go 463 | mu := new(sync.Mutex) 464 | mu.Lock() 465 | ``` 466 | 467 | 468 | 469 | ```go 470 | var mu sync.Mutex 471 | mu.Lock() 472 | ``` 473 | 474 |
476 | 477 | 如果你使用结构体指针,mutex 应该作为结构体的非指针字段。即使该结构体不被导出,也不要直接把 mutex 嵌入到结构体中。 478 | 479 | 480 | 481 | 482 | 529 | 538 |
BadGood
483 | 484 | ```go 485 | type SMap struct { 486 | sync.Mutex 487 | 488 | data map[string]string 489 | } 490 | 491 | func NewSMap() *SMap { 492 | return &SMap{ 493 | data: make(map[string]string), 494 | } 495 | } 496 | 497 | func (m *SMap) Get(k string) string { 498 | m.Lock() 499 | defer m.Unlock() 500 | 501 | return m.data[k] 502 | } 503 | ``` 504 | 505 | 506 | 507 | ```go 508 | type SMap struct { 509 | mu sync.Mutex 510 | 511 | data map[string]string 512 | } 513 | 514 | func NewSMap() *SMap { 515 | return &SMap{ 516 | data: make(map[string]string), 517 | } 518 | } 519 | 520 | func (m *SMap) Get(k string) string { 521 | m.mu.Lock() 522 | defer m.mu.Unlock() 523 | 524 | return m.data[k] 525 | } 526 | ``` 527 | 528 |
530 | 531 | `Mutex` 字段,`Lock` 和 `Unlock` 方法是 `SMap` 导出的 API 中不刻意说明的一部分。 532 | 533 | 534 | 535 | mutex 及其方法是 `SMap` 的实现细节,对其调用者不可见。 536 | 537 |
539 | 540 | ### 在边界处拷贝 Slices 和 Maps 541 | 542 | slices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。 543 | 544 | #### 接收 Slices 和 Maps 545 | 546 | 请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改。 547 | 548 | 549 | 550 | 551 | 552 | 567 | 583 | 584 | 585 | 586 |
Bad Good
553 | 554 | ```go 555 | func (d *Driver) SetTrips(trips []Trip) { 556 | d.trips = trips 557 | } 558 | 559 | trips := ... 560 | d1.SetTrips(trips) 561 | 562 | // 你是要修改 d1.trips 吗? 563 | trips[0] = ... 564 | ``` 565 | 566 | 568 | 569 | ```go 570 | func (d *Driver) SetTrips(trips []Trip) { 571 | d.trips = make([]Trip, len(trips)) 572 | copy(d.trips, trips) 573 | } 574 | 575 | trips := ... 576 | d1.SetTrips(trips) 577 | 578 | // 这里我们修改 trips[0],但不会影响到 d1.trips 579 | trips[0] = ... 580 | ``` 581 | 582 |
587 | 588 | #### 返回 slices 或 maps 589 | 590 | 同样,请注意用户对暴露内部状态的 map 或 slice 的修改。 591 | 592 | 593 | 594 | 595 | 643 |
BadGood
596 | 597 | ```go 598 | type Stats struct { 599 | mu sync.Mutex 600 | 601 | counters map[string]int 602 | } 603 | 604 | // Snapshot 返回当前状态。 605 | func (s *Stats) Snapshot() map[string]int { 606 | s.mu.Lock() 607 | defer s.mu.Unlock() 608 | 609 | return s.counters 610 | } 611 | 612 | // snapshot 不再受互斥锁保护 613 | // 因此对 snapshot 的任何访问都将受到数据竞争的影响 614 | // 影响 stats.counters 615 | snapshot := stats.Snapshot() 616 | ``` 617 | 618 | 619 | 620 | ```go 621 | type Stats struct { 622 | mu sync.Mutex 623 | 624 | counters map[string]int 625 | } 626 | 627 | func (s *Stats) Snapshot() map[string]int { 628 | s.mu.Lock() 629 | defer s.mu.Unlock() 630 | 631 | result := make(map[string]int, len(s.counters)) 632 | for k, v := range s.counters { 633 | result[k] = v 634 | } 635 | return result 636 | } 637 | 638 | // snapshot 现在是一个拷贝 639 | snapshot := stats.Snapshot() 640 | ``` 641 | 642 |
644 | 645 | ### 使用 defer 释放资源 646 | 647 | 使用 defer 释放资源,诸如文件和锁。 648 | 649 | 650 | 651 | 652 | 687 |
BadGood
653 | 654 | ```go 655 | p.Lock() 656 | if p.count < 10 { 657 | p.Unlock() 658 | return p.count 659 | } 660 | 661 | p.count++ 662 | newCount := p.count 663 | p.Unlock() 664 | 665 | return newCount 666 | 667 | // 当有多个 return 分支时,很容易遗忘 unlock 668 | ``` 669 | 670 | 671 | 672 | ```go 673 | p.Lock() 674 | defer p.Unlock() 675 | 676 | if p.count < 10 { 677 | return p.count 678 | } 679 | 680 | p.count++ 681 | return p.count 682 | 683 | // 更可读 684 | ``` 685 | 686 |
688 | 689 | Defer 的开销非常小,只有在您可以证明函数执行时间处于纳秒级的程度时,才应避免这样做。使用 defer 提升可读性是值得的,因为使用它们的成本微不足道。尤其适用于那些不仅仅是简单内存访问的较大的方法,在这些方法中其他计算的资源消耗远超过 `defer`。 690 | 691 | ### Channel 的 size 要么是 1,要么是无缓冲的 692 | 693 | channel 通常 size 应为 1 或是无缓冲的。默认情况下,channel 是无缓冲的,其 size 为零。任何其他尺寸都必须经过严格的审查。我们需要考虑如何确定大小,考虑是什么阻止了 channel 在高负载下和阻塞写时的写入,以及当这种情况发生时系统逻辑有哪些变化。(翻译解释:按照原文意思是需要界定通道边界,竞态条件,以及逻辑上下文梳理) 694 | 695 | 696 | 697 | 698 | 715 |
BadGood
699 | 700 | ```go 701 | // 应该足以满足任何情况! 702 | c := make(chan int, 64) 703 | ``` 704 | 705 | 706 | 707 | ```go 708 | // 大小:1 709 | c := make(chan int, 1) // 或者 710 | // 无缓冲 channel,大小为 0 711 | c := make(chan int) 712 | ``` 713 | 714 |
716 | 717 | ### 枚举从 1 开始 718 | 719 | 在 Go 中引入枚举的标准方法是声明一个自定义类型和一个使用了 iota 的 const 组。由于变量的默认值为 0,因此通常应以非零值开头枚举。 720 | 721 | 722 | 723 | 724 | 753 |
BadGood
725 | 726 | ```go 727 | type Operation int 728 | 729 | const ( 730 | Add Operation = iota 731 | Subtract 732 | Multiply 733 | ) 734 | 735 | // Add=0, Subtract=1, Multiply=2 736 | ``` 737 | 738 | 739 | 740 | ```go 741 | type Operation int 742 | 743 | const ( 744 | Add Operation = iota + 1 745 | Subtract 746 | Multiply 747 | ) 748 | 749 | // Add=1, Subtract=2, Multiply=3 750 | ``` 751 | 752 |
754 | 755 | 在某些情况下,使用零值是有意义的(枚举从零开始),例如,当零值是理想的默认行为时。 756 | 757 | ```go 758 | type LogOutput int 759 | 760 | const ( 761 | LogToStdout LogOutput = iota 762 | LogToFile 763 | LogToRemote 764 | ) 765 | 766 | // LogToStdout=0, LogToFile=1, LogToRemote=2 767 | ``` 768 | 769 | ### 使用 time 处理时间 770 | 771 | 时间处理很复杂。关于时间的错误假设通常包括以下几点。 772 | 773 | 1. 一天有 24 小时 774 | 2. 一小时有 60 分钟 775 | 3. 一周有七天 776 | 4. 一年 365 天 777 | 5. [还有更多](https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time) 778 | 779 | 例如,*1* 表示在一个时间点上加上 24 小时并不总是产生一个新的日历日。 780 | 781 | 因此,在处理时间时始终使用 [`"time"`] 包,因为它有助于以更安全、更准确的方式处理这些不正确的假设。 782 | 783 | [`"time"`]: https://pkg.go.dev/time/ 784 | 785 | #### 使用 `time.Time` 表达瞬时时间 786 | 787 | 在处理时间的瞬间时使用 [`time.Time`],在比较、添加或减去时间时使用 `time.Time` 中的方法。 788 | 789 | [`time.Time`]: https://pkg.go.dev/time/#Time 790 | 791 | 792 | 793 | 794 | 811 |
BadGood
795 | 796 | ```go 797 | func isActive(now, start, stop int) bool { 798 | return start <= now && now < stop 799 | } 800 | ``` 801 | 802 | 803 | 804 | ```go 805 | func isActive(now, start, stop time.Time) bool { 806 | return (start.Before(now) || start.Equal(now)) && now.Before(stop) 807 | } 808 | ``` 809 | 810 |
812 | 813 | #### 使用 `time.Duration` 表达时间段 814 | 815 | 在处理时间段时使用 [`time.Duration`] . 816 | 817 | [`time.Duration`]: https://pkg.go.dev/time/#Duration 818 | 819 | 820 | 821 | 822 | 847 |
BadGood
823 | 824 | ```go 825 | func poll(delay int) { 826 | for { 827 | // ... 828 | time.Sleep(time.Duration(delay) * time.Millisecond) 829 | } 830 | } 831 | poll(10) // 是几秒钟还是几毫秒? 832 | ``` 833 | 834 | 835 | 836 | ```go 837 | func poll(delay time.Duration) { 838 | for { 839 | // ... 840 | time.Sleep(delay) 841 | } 842 | } 843 | poll(10*time.Second) 844 | ``` 845 | 846 |
848 | 849 | 回到第一个例子,在一个时间瞬间加上 24 小时,我们用于添加时间的方法取决于意图。如果我们想要下一个日历日 (当前天的下一天) 的同一个时间点,我们应该使用 [`Time.AddDate`]。但是,如果我们想保证某一时刻比前一时刻晚 24 小时,我们应该使用 [`Time.Add`]。 850 | 851 | [`Time.AddDate`]: https://pkg.go.dev/time/#Time.AddDate 852 | [`Time.Add`]: https://pkg.go.dev/time/#Time.Add 853 | 854 | ```go 855 | newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */) 856 | maybeNewDay := t.Add(24 * time.Hour) 857 | ``` 858 | 859 | #### 对外部系统使用 `time.Time` 和 `time.Duration` 860 | 861 | 尽可能在与外部系统的交互中使用 `time.Duration` 和 `time.Time` 例如 : 862 | 863 | - Command-line 标志:[`flag`] 通过 [`time.ParseDuration`] 支持 `time.Duration` 864 | - JSON: [`encoding/json`] 通过其 [`UnmarshalJSON` method] 方法支持将 `time.Time` 编码为 [RFC 3339] 字符串 865 | - SQL: [`database/sql`] 支持将 `DATETIME` 或 `TIMESTAMP` 列转换为 `time.Time`,如果底层驱动程序支持则返回 866 | - YAML: [`gopkg.in/yaml.v2`] 支持将 `time.Time` 作为 [RFC 3339] 字符串,并通过 [`time.ParseDuration`] 支持 `time.Duration`。 867 | 868 | [`flag`]: https://pkg.go.dev/flag/ 869 | [`time.ParseDuration`]: https://pkg.go.dev/time/#ParseDuration 870 | [`encoding/json`]: https://pkg.go.dev/encoding/json/ 871 | [RFC 3339]: https://tools.ietf.org/html/rfc3339 872 | [`UnmarshalJSON` method]: https://pkg.go.dev/time/#Time.UnmarshalJSON 873 | [`database/sql`]: https://pkg.go.dev/database/sql/ 874 | [`gopkg.in/yaml.v2`]: https://pkg.go.dev/gopkg.in/yaml.v2 875 | 876 | 当不能在这些交互中使用 `time.Duration` 时,请使用 `int` 或 `float64`,并在字段名称中包含单位。 877 | 878 | 例如,由于 `encoding/json` 不支持 `time.Duration`,因此该单位包含在字段的名称中。 879 | 880 | 881 | 882 | 883 | 902 |
BadGood
884 | 885 | ```go 886 | // {"interval": 2} 887 | type Config struct { 888 | Interval int `json:"interval"` 889 | } 890 | ``` 891 | 892 | 893 | 894 | ```go 895 | // {"intervalMillis": 2000} 896 | type Config struct { 897 | IntervalMillis int `json:"intervalMillis"` 898 | } 899 | ``` 900 | 901 |
903 | 904 | 当在这些交互中不能使用 `time.Time` 时,除非达成一致,否则使用 `string` 和 [RFC 3339] 中定义的格式时间戳。默认情况下,[`Time.UnmarshalText`] 使用此格式,并可通过 [`time.RFC3339`] 在 `Time.Format` 和 `time.Parse` 中使用。 905 | 906 | [`Time.UnmarshalText`]: https://pkg.go.dev/time/#Time.UnmarshalText 907 | [`time.RFC3339`]: https://pkg.go.dev/time/#RFC3339 908 | 909 | 尽管这在实践中并不成问题,但请记住,`"time"` 包不支持解析闰秒时间戳([8728]),也不在计算中考虑闰秒([15190])。如果您比较两个时间瞬间,则差异将不包括这两个瞬间之间可能发生的闰秒。 910 | 911 | [8728]: https://go.dev/issues/8728 912 | [15190]: https://go.dev/issues/15190 913 | 914 | 915 | 916 | 917 | ### Errors 918 | 919 | #### 错误类型 920 | 921 | 声明错误的选项很少。 922 | 在选择最适合您的用例的选项之前,请考虑以下事项。 923 | 924 | - 调用者是否需要匹配错误以便他们可以处理它? 925 | 如果是,我们必须通过声明顶级错误变量或自定义类型来支持 [`errors.Is`] 或 [`errors.As`] 函数。 926 | - 错误消息是否为静态字符串,还是需要上下文信息的动态字符串? 927 | 如果是静态字符串,我们可以使用 [`errors.New`],但对于后者,我们必须使用 [`fmt.Errorf`] 或自定义错误类型。 928 | - 我们是否正在传递由下游函数返回的新错误? 929 | 如果是这样,请参阅[错误包装部分](#错误包装)。 930 | 931 | [`errors.Is`]: https://pkg.go.dev/errors/#Is 932 | [`errors.As`]: https://pkg.go.dev/errors/#As 933 | 934 | | 错误匹配?| 错误消息 | 指导 | 935 | |-----------------|---------------|-------------------------------------| 936 | | No | static | [`errors.New`] | 937 | | No | dynamic | [`fmt.Errorf`] | 938 | | Yes | static | top-level `var` with [`errors.New`] | 939 | | Yes | dynamic | custom `error` type | 940 | 941 | [`errors.New`]: https://pkg.go.dev/errors/#New 942 | [`fmt.Errorf`]: https://pkg.go.dev/fmt/#Errorf 943 | 944 | 例如, 945 | 使用 [`errors.New`] 表示带有静态字符串的错误。 946 | 如果调用者需要匹配并处理此错误,则将此错误导出为变量以支持将其与 `errors.Is` 匹配。 947 | 948 | 949 | 950 | 951 | 991 |
无错误匹配错误匹配
952 | 953 | ```go 954 | // package foo 955 | 956 | func Open() error { 957 | return errors.New("could not open") 958 | } 959 | 960 | // package bar 961 | 962 | if err := foo.Open(); err != nil { 963 | // Can't handle the error. 964 | panic("unknown error") 965 | } 966 | ``` 967 | 968 | 969 | 970 | ```go 971 | // package foo 972 | 973 | var ErrCouldNotOpen = errors.New("could not open") 974 | 975 | func Open() error { 976 | return ErrCouldNotOpen 977 | } 978 | 979 | // package bar 980 | 981 | if err := foo.Open(); err != nil { 982 | if errors.Is(err, foo.ErrCouldNotOpen) { 983 | // handle the error 984 | } else { 985 | panic("unknown error") 986 | } 987 | } 988 | ``` 989 | 990 |
992 | 993 | 对于动态字符串的错误, 994 | 如果调用者不需要匹配它,则使用 [`fmt.Errorf`], 995 | 如果调用者确实需要匹配它,则自定义 `error`。 996 | 997 | 998 | 999 | 1000 | 1048 |
无错误匹配错误匹配
1001 | 1002 | ```go 1003 | // package foo 1004 | 1005 | func Open(file string) error { 1006 | return fmt.Errorf("file %q not found", file) 1007 | } 1008 | 1009 | // package bar 1010 | 1011 | if err := foo.Open("testfile.txt"); err != nil { 1012 | // Can't handle the error. 1013 | panic("unknown error") 1014 | } 1015 | ``` 1016 | 1017 | 1018 | 1019 | ```go 1020 | // package foo 1021 | 1022 | type NotFoundError struct { 1023 | File string 1024 | } 1025 | 1026 | func (e *NotFoundError) Error() string { 1027 | return fmt.Sprintf("file %q not found", e.File) 1028 | } 1029 | 1030 | func Open(file string) error { 1031 | return &NotFoundError{File: file} 1032 | } 1033 | 1034 | 1035 | // package bar 1036 | 1037 | if err := foo.Open("testfile.txt"); err != nil { 1038 | var notFound *NotFoundError 1039 | if errors.As(err, ¬Found) { 1040 | // handle the error 1041 | } else { 1042 | panic("unknown error") 1043 | } 1044 | } 1045 | ``` 1046 | 1047 |
1049 | 1050 | 请注意,如果您从包中导出错误变量或类型, 1051 | 它们将成为包的公共 API 的一部分。 1052 | 1053 | #### 错误包装 1054 | 1055 | 如果调用其他方法时出现错误,通常有三种处理方式可以选择: 1056 | 1057 | - 将原始错误原样返回 1058 | - 使用 `fmt.Errorf` 搭配 `%w` 将错误添加进上下文后返回 1059 | - 使用 `fmt.Errorf` 搭配 `%v` 将错误添加进上下文后返回 1060 | 1061 | 如果没有要添加的其他上下文,则按原样返回原始错误。 1062 | 这将保留原始错误类型和消息。 1063 | 这非常适合底层错误消息有足够的信息来追踪它来自哪里的错误。 1064 | 1065 | 否则,尽可能在错误消息中添加上下文 1066 | 这样就不会出现诸如“连接被拒绝”之类的模糊错误, 1067 | 您会收到更多有用的错误,例如“调用服务 foo:连接被拒绝”。 1068 | 1069 | 使用 `fmt.Errorf` 为你的错误添加上下文, 1070 | 根据调用者是否应该能够匹配和提取根本原因,在 `%w` 或 `%v` 动词之间进行选择。 1071 | 1072 | - 如果调用者应该可以访问底层错误,请使用 `%w`。 1073 | 对于大多数包装错误,这是一个很好的默认值, 1074 | 但请注意,调用者可能会开始依赖此行为。因此,对于包装错误是已知`var`或类型的情况,请将其作为函数契约的一部分进行记录和测试。 1075 | - 使用 `%v` 来混淆底层错误。 1076 | 调用者将无法匹配它,但如果需要,您可以在将来切换到 `%w`。 1077 | 1078 | 在为返回的错误添加上下文时,通过避免使用"failed to"之类的短语来保持上下文简洁,当错误通过堆栈向上渗透时,它会一层一层被堆积起来: 1079 | 1080 | 1081 | 1082 | 1083 | 1116 |
BadGood
1084 | 1085 | ```go 1086 | s, err := store.New() 1087 | if err != nil { 1088 | return fmt.Errorf( 1089 | "failed to create new store: %w", err) 1090 | } 1091 | ``` 1092 | 1093 | 1094 | 1095 | ```go 1096 | s, err := store.New() 1097 | if err != nil { 1098 | return fmt.Errorf( 1099 | "new store: %w", err) 1100 | } 1101 | ``` 1102 | 1103 |
1104 | 1105 | ```plain 1106 | failed to x: failed to y: failed to create new store: the error 1107 | ``` 1108 | 1109 | 1110 | 1111 | ```plain 1112 | x: y: new store: the error 1113 | ``` 1114 | 1115 |
1117 | 1118 | 然而,一旦错误被发送到另一个系统,应该清楚消息是一个错误(例如`err` 标签或日志中的"Failed"前缀)。 1119 | 1120 | 1121 | 另见 [不要只检查错误,优雅地处理它们]。 1122 | 1123 | [`"pkg/errors".Cause`]: https://pkg.go.dev/github.com/pkg/errors#Cause 1124 | [不要只检查错误,优雅地处理它们]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 1125 | 1126 | #### 错误命名 1127 | 1128 | 对于存储为全局变量的错误值, 1129 | 根据是否导出,使用前缀 `Err` 或 `err`。 1130 | 请看指南 [对于未导出的顶层常量和变量,使用_作为前缀](#对于未导出的顶层常量和变量使用_作为前缀)。 1131 | 1132 | ```go 1133 | var ( 1134 | // 导出以下两个错误,以便此包的用户可以将它们与 errors.Is 进行匹配。 1135 | 1136 | ErrBrokenLink = errors.New("link is broken") 1137 | ErrCouldNotOpen = errors.New("could not open") 1138 | 1139 | // 这个错误没有被导出,因为我们不想让它成为我们公共 API 的一部分。我们可能仍然在带有错误的包内使用它。 1140 | 1141 | errNotFound = errors.New("not found") 1142 | ) 1143 | ``` 1144 | 1145 | 对于自定义错误类型,请改用后缀 `Error`。 1146 | 1147 | ```go 1148 | // 同样,这个错误被导出,以便这个包的用户可以将它与 errors.As 匹配。 1149 | 1150 | type NotFoundError struct { 1151 | File string 1152 | } 1153 | 1154 | func (e *NotFoundError) Error() string { 1155 | return fmt.Sprintf("file %q not found", e.File) 1156 | } 1157 | 1158 | // 并且这个错误没有被导出,因为我们不想让它成为公共 API 的一部分。我们仍然可以在带有 errors.As 的包中使用它。 1159 | type resolveError struct { 1160 | Path string 1161 | } 1162 | 1163 | func (e *resolveError) Error() string { 1164 | return fmt.Sprintf("resolve %q", e.Path) 1165 | } 1166 | ``` 1167 | #### 一次处理错误 1168 | 1169 | 当调用方从被调用方接收到错误时,它可以根据对错误的了解,以各种不同的方式进行处理。 1170 | 1171 | 其中包括但不限于: 1172 | 1173 | - 如果被调用者约定定义了特定的错误,则将错误与`errors.Is`或`errors.As`匹配,并以不同的方式处理分支 1174 | - 如果错误是可恢复的,则记录错误并正常降级 1175 | - 如果该错误表示特定于域的故障条件,则返回定义明确的错误 1176 | - 返回错误,无论是 [wrapped](#错误包装) 还是逐字逐句 1177 | 1178 | 无论调用方如何处理错误,它通常都应该只处理每个错误一次。例如,调用方不应该记录错误然后返回,因为*its*调用方也可能处理错误。 1179 | 1180 | 例如,考虑以下情况: 1181 | 1182 | 1183 | 1184 | 1185 | 1203 | 1221 | 1239 | 1262 |
DescriptionCode
1186 | 1187 | **Bad**: 记录错误并将其返回 1188 | 1189 | 堆栈中的调用程序可能会对该错误采取类似的操作。这样做会在应用程序日志中造成大量噪音,但收效甚微。 1190 | 1191 | 1192 | 1193 | ```go 1194 | u, err := getUser(id) 1195 | if err != nil { 1196 | // BAD: See description 1197 | log.Printf("Could not get user %q: %v", id, err) 1198 | return err 1199 | } 1200 | ``` 1201 | 1202 |
1204 | 1205 | **Good**: 将错误换行并返回 1206 | 1207 | 1208 | 1209 | 堆栈中更靠上的调用程序将处理该错误。使用`%w`可确保它们可以将错误与`errors.Is`或`errors.As`相匹配(如果相关)。 1210 | 1211 | 1212 | 1213 | ```go 1214 | u, err := getUser(id) 1215 | if err != nil { 1216 | return fmt.Errorf("get user %q: %w", id, err) 1217 | } 1218 | ``` 1219 | 1220 |
1222 | 1223 | **Good**: 记录错误并正常降级 1224 | 1225 | 如果操作不是绝对必要的,我们可以通过从中恢复来提供降级但不间断的体验。 1226 | 1227 | 1228 | 1229 | ```go 1230 | if err := emitMetrics(); err != nil { 1231 | // Failure to write metrics should not 1232 | // break the application. 1233 | log.Printf("Could not emit metrics: %v", err) 1234 | } 1235 | 1236 | ``` 1237 | 1238 |
1240 | 1241 | **Good**: 匹配错误并适当降级 1242 | 1243 | 如果被调用者在其约定中定义了一个特定的错误,并且失败是可恢复的,则匹配该错误案例并正常降级。对于所有其他案例,请包装错误并返回。 1244 | 1245 | 堆栈中更靠上的调用程序将处理其他错误。 1246 | 1247 | 1248 | 1249 | ```go 1250 | tz, err := getUserTimeZone(id) 1251 | if err != nil { 1252 | if errors.Is(err, ErrUserNotFound) { 1253 | // User doesn't exist. Use UTC. 1254 | tz = time.UTC 1255 | } else { 1256 | return fmt.Errorf("get user %q: %w", id, err) 1257 | } 1258 | } 1259 | ``` 1260 | 1261 |
1263 | 1264 | ### 处理断言失败 1265 | 1266 | [类型断言] 将会在检测到不正确的类型时,以单一返回值形式返回 panic。因此,请始终使用“逗号 ok”习语。 1267 | 1268 | [类型断言]: https://golang.org/ref/spec#Type_assertions 1269 | 1270 | 1271 | 1272 | 1273 | 1289 |
BadGood
1274 | 1275 | ```go 1276 | t := i.(string) 1277 | ``` 1278 | 1279 | 1280 | 1281 | ```go 1282 | t, ok := i.(string) 1283 | if !ok { 1284 | // 优雅地处理错误 1285 | } 1286 | ``` 1287 | 1288 |
1290 | 1291 | 1293 | 1294 | ### 不要使用 panic 1295 | 1296 | 在生产环境中运行的代码必须避免出现 panic。panic 是 [级联失败] 的主要根源。如果发生错误,该函数必须返回错误,并允许调用方决定如何处理它。 1297 | 1298 | [级联失败]: https://en.wikipedia.org/wiki/Cascading_failure 1299 | 1300 | 1301 | 1302 | 1303 | 1338 |
BadGood
1304 | 1305 | ```go 1306 | func run(args []string) { 1307 | if len(args) == 0 { 1308 | panic("an argument is required") 1309 | } 1310 | // ... 1311 | } 1312 | 1313 | func main() { 1314 | run(os.Args[1:]) 1315 | } 1316 | ``` 1317 | 1318 | 1319 | 1320 | ```go 1321 | func run(args []string) error { 1322 | if len(args) == 0 { 1323 | return errors.New("an argument is required") 1324 | } 1325 | // ... 1326 | return nil 1327 | } 1328 | 1329 | func main() { 1330 | if err := run(os.Args[1:]); err != nil { 1331 | fmt.Fprintln(os.Stderr, err) 1332 | os.Exit(1) 1333 | } 1334 | } 1335 | ``` 1336 | 1337 |
1339 | 1340 | panic/recover 不是错误处理策略。仅当发生不可恢复的事情(例如:nil 引用)时,程序才必须 panic。程序初始化是一个例外:程序启动时应使程序中止的不良情况可能会引起 panic。 1341 | 1342 | ```go 1343 | var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML")) 1344 | ``` 1345 | 1346 | 即使在测试代码中,也优先使用`t.Fatal`或者`t.FailNow`而不是 panic 来确保失败被标记。 1347 | 1348 | 1349 | 1350 | 1351 | 1374 |
BadGood
1352 | 1353 | ```go 1354 | // func TestFoo(t *testing.T) 1355 | 1356 | f, err := os.CreateTemp("", "test") 1357 | if err != nil { 1358 | panic("failed to set up test") 1359 | } 1360 | ``` 1361 | 1362 | 1363 | 1364 | ```go 1365 | // func TestFoo(t *testing.T) 1366 | 1367 | f, err := os.CreateTemp("", "test") 1368 | if err != nil { 1369 | t.Fatal("failed to set up test") 1370 | } 1371 | ``` 1372 | 1373 |
1375 | 1376 | 1377 | 1378 | ### 使用 go.uber.org/atomic 1379 | 1380 | 使用 [sync/atomic] 包的原子操作对原始类型 (`int32`, `int64`等)进行操作,因为很容易忘记使用原子操作来读取或修改变量。 1381 | 1382 | [go.uber.org/atomic] 通过隐藏基础类型为这些操作增加了类型安全性。此外,它包括一个方便的`atomic.Bool`类型。 1383 | 1384 | [go.uber.org/atomic]: https://pkg.go.dev/go.uber.org/atomic 1385 | [sync/atomic]: https://pkg.go.dev/sync/atomic/ 1386 | 1387 | 1388 | 1389 | 1390 | 1431 |
BadGood
1391 | 1392 | ```go 1393 | type foo struct { 1394 | running int32 // atomic 1395 | } 1396 | 1397 | func (f* foo) start() { 1398 | if atomic.SwapInt32(&f.running, 1) == 1 { 1399 | // already running… 1400 | return 1401 | } 1402 | // start the Foo 1403 | } 1404 | 1405 | func (f *foo) isRunning() bool { 1406 | return f.running == 1 // race! 1407 | } 1408 | ``` 1409 | 1410 | 1411 | 1412 | ```go 1413 | type foo struct { 1414 | running atomic.Bool 1415 | } 1416 | 1417 | func (f *foo) start() { 1418 | if f.running.Swap(true) { 1419 | // already running… 1420 | return 1421 | } 1422 | // start the Foo 1423 | } 1424 | 1425 | func (f *foo) isRunning() bool { 1426 | return f.running.Load() 1427 | } 1428 | ``` 1429 | 1430 |
1432 | 1433 | ### 避免可变全局变量 1434 | 1435 | 使用选择依赖注入方式避免改变全局变量。 1436 | 既适用于函数指针又适用于其他值类型 1437 | 1438 | 1439 | 1440 | 1441 | 1470 | 1498 |
BadGood
1442 | 1443 | ```go 1444 | // sign.go 1445 | var _timeNow = time.Now 1446 | func sign(msg string) string { 1447 | now := _timeNow() 1448 | return signWithTime(msg, now) 1449 | } 1450 | ``` 1451 | 1452 | 1453 | 1454 | ```go 1455 | // sign.go 1456 | type signer struct { 1457 | now func() time.Time 1458 | } 1459 | func newSigner() *signer { 1460 | return &signer{ 1461 | now: time.Now, 1462 | } 1463 | } 1464 | func (s *signer) Sign(msg string) string { 1465 | now := s.now() 1466 | return signWithTime(msg, now) 1467 | } 1468 | ``` 1469 |
1471 | 1472 | ```go 1473 | // sign_test.go 1474 | func TestSign(t *testing.T) { 1475 | oldTimeNow := _timeNow 1476 | _timeNow = func() time.Time { 1477 | return someFixedTime 1478 | } 1479 | defer func() { _timeNow = oldTimeNow }() 1480 | assert.Equal(t, want, sign(give)) 1481 | } 1482 | ``` 1483 | 1484 | 1485 | 1486 | ```go 1487 | // sign_test.go 1488 | func TestSigner(t *testing.T) { 1489 | s := newSigner() 1490 | s.now = func() time.Time { 1491 | return someFixedTime 1492 | } 1493 | assert.Equal(t, want, s.Sign(give)) 1494 | } 1495 | ``` 1496 | 1497 |
1499 | 1500 | ### 避免在公共结构中嵌入类型 1501 | 1502 | 这些嵌入的类型泄漏实现细节、禁止类型演化和模糊的文档。 1503 | 1504 | 假设您使用共享的 `AbstractList` 实现了多种列表类型,请避免在具体的列表实现中嵌入 `AbstractList`。 1505 | 相反,只需手动将方法写入具体的列表,该列表将委托给抽象列表。 1506 | 1507 | ```go 1508 | type AbstractList struct {} 1509 | // 添加将实体添加到列表中。 1510 | func (l *AbstractList) Add(e Entity) { 1511 | // ... 1512 | } 1513 | // 移除从列表中移除实体。 1514 | func (l *AbstractList) Remove(e Entity) { 1515 | // ... 1516 | } 1517 | ``` 1518 | 1519 | 1520 | 1521 | 1522 | 1549 |
BadGood
1523 | 1524 | ```go 1525 | // ConcreteList 是一个实体列表。 1526 | type ConcreteList struct { 1527 | *AbstractList 1528 | } 1529 | ``` 1530 | 1531 | 1532 | 1533 | ```go 1534 | // ConcreteList 是一个实体列表。 1535 | type ConcreteList struct { 1536 | list *AbstractList 1537 | } 1538 | // 添加将实体添加到列表中。 1539 | func (l *ConcreteList) Add(e Entity) { 1540 | l.list.Add(e) 1541 | } 1542 | // 移除从列表中移除实体。 1543 | func (l *ConcreteList) Remove(e Entity) { 1544 | l.list.Remove(e) 1545 | } 1546 | ``` 1547 | 1548 |
1550 | 1551 | Go 允许 [类型嵌入](https://golang.org/doc/effective_go.html#embedding) 作为继承和组合之间的折衷。外部类型获取嵌入类型的方法的隐式副本。默认情况下,这些方法委托给嵌入实例的同一方法。 1552 | 1553 | 结构还获得与类型同名的字段。 1554 | 所以,如果嵌入的类型是 public,那么字段是 public。为了保持向后兼容性,外部类型的每个未来版本都必须保留嵌入类型。 1555 | 1556 | 很少需要嵌入类型。 1557 | 这是一种方便,可以帮助您避免编写冗长的委托方法。 1558 | 1559 | 即使嵌入兼容的抽象列表 *interface*,而不是结构体,这将为开发人员提供更大的灵活性来改变未来,但仍然泄露了具体列表使用抽象实现的细节。 1560 | 1561 | 1562 | 1563 | 1564 | 1596 |
BadGood
1565 | 1566 | ```go 1567 | // AbstractList 是各种实体列表的通用实现。 1568 | type AbstractList interface { 1569 | Add(Entity) 1570 | Remove(Entity) 1571 | } 1572 | // ConcreteList 是一个实体列表。 1573 | type ConcreteList struct { 1574 | AbstractList 1575 | } 1576 | ``` 1577 | 1578 | 1579 | 1580 | ```go 1581 | // ConcreteList 是一个实体列表。 1582 | type ConcreteList struct { 1583 | list AbstractList 1584 | } 1585 | // 添加将实体添加到列表中。 1586 | func (l *ConcreteList) Add(e Entity) { 1587 | l.list.Add(e) 1588 | } 1589 | // 移除从列表中移除实体。 1590 | func (l *ConcreteList) Remove(e Entity) { 1591 | l.list.Remove(e) 1592 | } 1593 | ``` 1594 | 1595 |
1597 | 1598 | 无论是使用嵌入结构还是嵌入接口,都会限制类型的演化。 1599 | 1600 | - 向嵌入接口添加方法是一个破坏性的改变。 1601 | - 从嵌入结构体删除方法是一个破坏性改变。 1602 | - 删除嵌入类型是一个破坏性的改变。 1603 | - 即使使用满足相同接口的类型替换嵌入类型,也是一个破坏性的改变。 1604 | 1605 | 尽管编写这些委托方法是乏味的,但是额外的工作隐藏了实现细节,留下了更多的更改机会,还消除了在文档中发现完整列表接口的间接性操作。 1606 | 1607 | ### 避免使用内置名称 1608 | 1609 | Go [语言规范] 概述了几个内置的, 1610 | 不应在 Go 项目中使用的 [预先声明的标识符]。 1611 | 1612 | 根据上下文的不同,将这些标识符作为名称重复使用, 1613 | 将在当前作用域(或任何嵌套作用域)中隐藏原始标识符,或者混淆代码。 1614 | 在最好的情况下,编译器会报错;在最坏的情况下,这样的代码可能会引入潜在的、难以恢复的错误。 1615 | 1616 | [语言规范]: https://golang.org/ref/spec 1617 | [预先声明的标识符]: https://golang.org/ref/spec#Predeclared_identifiers 1618 | 1619 | 1620 | 1621 | 1622 | 1649 | 1687 |
BadGood
1623 | 1624 | ```go 1625 | var error string 1626 | // `error` 作用域隐式覆盖 1627 | 1628 | // or 1629 | 1630 | func handleErrorMessage(error string) { 1631 | // `error` 作用域隐式覆盖 1632 | } 1633 | ``` 1634 | 1635 | 1636 | 1637 | ```go 1638 | var errorMessage string 1639 | // `error` 指向内置的非覆盖 1640 | 1641 | // or 1642 | 1643 | func handleErrorMessage(msg string) { 1644 | // `error` 指向内置的非覆盖 1645 | } 1646 | ``` 1647 | 1648 |
1650 | 1651 | ```go 1652 | type Foo struct { 1653 | // 虽然这些字段在技术上不构成阴影,但`error`或`string`字符串的重映射现在是不明确的。 1654 | error error 1655 | string string 1656 | } 1657 | 1658 | func (f Foo) Error() error { 1659 | // `error` 和 `f.error` 在视觉上是相似的 1660 | return f.error 1661 | } 1662 | 1663 | func (f Foo) String() string { 1664 | // `string` and `f.string` 在视觉上是相似的 1665 | return f.string 1666 | } 1667 | ``` 1668 | 1669 | 1670 | 1671 | ```go 1672 | type Foo struct { 1673 | // `error` and `string` 现在是明确的。 1674 | err error 1675 | str string 1676 | } 1677 | 1678 | func (f Foo) Error() error { 1679 | return f.err 1680 | } 1681 | 1682 | func (f Foo) String() string { 1683 | return f.str 1684 | } 1685 | ``` 1686 |
1688 | 1689 | 注意,编译器在使用预先分隔的标识符时不会生成错误, 1690 | 但是诸如`go vet`之类的工具会正确地指出这些和其他情况下的隐式问题。 1691 | 1692 | ### 避免使用 `init()` 1693 | 1694 | 尽可能避免使用`init()`。当`init()`是不可避免或可取的,代码应先尝试: 1695 | 1696 | 1. 无论程序环境或调用如何,都要完全确定。 1697 | 2. 避免依赖于其他`init()`函数的顺序或副作用。虽然`init()`顺序是明确的,但代码可以更改, 1698 | 因此`init()`函数之间的关系可能会使代码变得脆弱和容易出错。 1699 | 3. 避免访问或操作全局或环境状态,如机器信息、环境变量、工作目录、程序参数/输入等。 1700 | 4. 避免`I/O`,包括文件系统、网络和系统调用。 1701 | 1702 | 不能满足这些要求的代码可能属于要作为`main()`调用的一部分(或程序生命周期中的其他地方), 1703 | 或者作为`main()`本身的一部分写入。特别是,打算由其他程序使用的库应该特别注意完全确定性, 1704 | 而不是执行“init magic” 1705 | 1706 | 1707 | 1708 | 1709 | 1739 | 1777 |
BadGood
1710 | 1711 | ```go 1712 | type Foo struct { 1713 | // ... 1714 | } 1715 | var _defaultFoo Foo 1716 | func init() { 1717 | _defaultFoo = Foo{ 1718 | // ... 1719 | } 1720 | } 1721 | ``` 1722 | 1723 | 1724 | 1725 | ```go 1726 | var _defaultFoo = Foo{ 1727 | // ... 1728 | } 1729 | // or,为了更好的可测试性: 1730 | var _defaultFoo = defaultFoo() 1731 | func defaultFoo() Foo { 1732 | return Foo{ 1733 | // ... 1734 | } 1735 | } 1736 | ``` 1737 | 1738 |
1740 | 1741 | ```go 1742 | type Config struct { 1743 | // ... 1744 | } 1745 | var _config Config 1746 | func init() { 1747 | // Bad: 基于当前目录 1748 | cwd, _ := os.Getwd() 1749 | // Bad: I/O 1750 | raw, _ := os.ReadFile( 1751 | path.Join(cwd, "config", "config.yaml"), 1752 | ) 1753 | yaml.Unmarshal(raw, &_config) 1754 | } 1755 | ``` 1756 | 1757 | 1758 | 1759 | ```go 1760 | type Config struct { 1761 | // ... 1762 | } 1763 | func loadConfig() Config { 1764 | cwd, err := os.Getwd() 1765 | // handle err 1766 | raw, err := os.ReadFile( 1767 | path.Join(cwd, "config", "config.yaml"), 1768 | ) 1769 | // handle err 1770 | var config Config 1771 | yaml.Unmarshal(raw, &config) 1772 | return config 1773 | } 1774 | ``` 1775 | 1776 |
1778 | 1779 | 考虑到上述情况,在某些情况下,`init()`可能更可取或是必要的,可能包括: 1780 | 1781 | - 不能表示为单个赋值的复杂表达式。 1782 | - 可插入的钩子,如`database/sql`、编码类型注册表等。 1783 | - 对 [Google Cloud Functions] 和其他形式的确定性预计算的优化。 1784 | 1785 | [Google Cloud Functions]: https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations 1786 | 1787 | ### 追加时优先指定切片容量 1788 | 1789 | 追加时优先指定切片容量 1790 | 1791 | 在尽可能的情况下,在初始化要追加的切片时为`make()`提供一个容量值。 1792 | 1793 | 1794 | 1795 | 1796 | 1819 | 1832 |
BadGood
1797 | 1798 | ```go 1799 | for n := 0; n < b.N; n++ { 1800 | data := make([]int, 0) 1801 | for k := 0; k < size; k++{ 1802 | data = append(data, k) 1803 | } 1804 | } 1805 | ``` 1806 | 1807 | 1808 | 1809 | ```go 1810 | for n := 0; n < b.N; n++ { 1811 | data := make([]int, 0, size) 1812 | for k := 0; k < size; k++{ 1813 | data = append(data, k) 1814 | } 1815 | } 1816 | ``` 1817 | 1818 |
1820 | 1821 | ``` 1822 | BenchmarkBad-4 100000000 2.48s 1823 | ``` 1824 | 1825 | 1826 | 1827 | ``` 1828 | BenchmarkGood-4 100000000 0.21s 1829 | ``` 1830 | 1831 |
1833 | 1834 | ### 主函数退出方式 (Exit) 1835 | 1836 | Go 程序使用 [`os.Exit`] 或者 [`log.Fatal*`] 立即退出 (使用`panic`不是退出程序的好方法,请 [不要使用 panic](#不要使用-panic)。) 1837 | 1838 | [`os.Exit`]: https://pkg.go.dev/os/#Exit 1839 | [`log.Fatal*`]: https://pkg.go.dev/log/#Fatal 1840 | 1841 | **仅在`main()`** 中调用其中一个 `os.Exit` 或者 `log.Fatal*`。所有其他函数应将错误返回到信号失败中。 1842 | 1843 | 1844 | 1845 | 1846 | 1890 |
BadGood
1847 | 1848 | ```go 1849 | func main() { 1850 | body := readFile(path) 1851 | fmt.Println(body) 1852 | } 1853 | func readFile(path string) string { 1854 | f, err := os.Open(path) 1855 | if err != nil { 1856 | log.Fatal(err) 1857 | } 1858 | b, err := os.ReadAll(f) 1859 | if err != nil { 1860 | log.Fatal(err) 1861 | } 1862 | return string(b) 1863 | } 1864 | ``` 1865 | 1866 | 1867 | 1868 | ```go 1869 | func main() { 1870 | body, err := readFile(path) 1871 | if err != nil { 1872 | log.Fatal(err) 1873 | } 1874 | fmt.Println(body) 1875 | } 1876 | func readFile(path string) (string, error) { 1877 | f, err := os.Open(path) 1878 | if err != nil { 1879 | return "", err 1880 | } 1881 | b, err := os.ReadAll(f) 1882 | if err != nil { 1883 | return "", err 1884 | } 1885 | return string(b), nil 1886 | } 1887 | ``` 1888 | 1889 |
1891 | 1892 | 原则上:退出的具有多种功能的程序存在一些问题: 1893 | 1894 | - 不明显的控制流:任何函数都可以退出程序,因此很难对控制流进行推理。 1895 | - 难以测试:退出程序的函数也将退出调用它的测试。这使得函数很难测试,并引入了跳过 `go test` 尚未运行的其他测试的风险。 1896 | - 跳过清理:当函数退出程序时,会跳过已经进入`defer`队列里的函数调用。这增加了跳过重要清理任务的风险。 1897 | #### 一次性退出 1898 | 1899 | 如果可能的话,你的`main()`函数中 **最多一次** 调用 `os.Exit`或者`log.Fatal`。如果有多个错误场景停止程序执行,请将该逻辑放在单独的函数下并从中返回错误。 1900 | 这会缩短 `main()` 函数,并将所有关键业务逻辑放入一个单独的、可测试的函数中。 1901 | 1902 | 1903 | 1904 | 1905 | 1959 |
BadGood
1906 | 1907 | ```go 1908 | package main 1909 | func main() { 1910 | args := os.Args[1:] 1911 | if len(args) != 1 { 1912 | log.Fatal("missing file") 1913 | } 1914 | name := args[0] 1915 | f, err := os.Open(name) 1916 | if err != nil { 1917 | log.Fatal(err) 1918 | } 1919 | defer f.Close() 1920 | // 如果我们调用 log.Fatal 在这条线之后 1921 | // f.Close 将会被执行。 1922 | b, err := os.ReadAll(f) 1923 | if err != nil { 1924 | log.Fatal(err) 1925 | } 1926 | // ... 1927 | } 1928 | ``` 1929 | 1930 | 1931 | 1932 | ```go 1933 | package main 1934 | func main() { 1935 | if err := run(); err != nil { 1936 | log.Fatal(err) 1937 | } 1938 | } 1939 | func run() error { 1940 | args := os.Args[1:] 1941 | if len(args) != 1 { 1942 | return errors.New("missing file") 1943 | } 1944 | name := args[0] 1945 | f, err := os.Open(name) 1946 | if err != nil { 1947 | return err 1948 | } 1949 | defer f.Close() 1950 | b, err := os.ReadAll(f) 1951 | if err != nil { 1952 | return err 1953 | } 1954 | // ... 1955 | } 1956 | ``` 1957 | 1958 |
1960 | 1961 | 上面的示例使用`log.Fatal`,但该指南也适用于`os.Exit`或任何调用`os.Exit`的库代码。 1962 | 1963 | ```go 1964 | func main() { 1965 | if err := run(); err != nil { 1966 | fmt.Fprintln(os.Stderr, err) 1967 | os.Exit(1) 1968 | } 1969 | } 1970 | ``` 1971 | 1972 | 您可以根据需要更改`run()`的签名。例如,如果您的程序必须使用特定的失败退出代码退出,`run()`可能会返回退出代码而不是错误。这也允许单元测试直接验证此行为。 1973 | 1974 | ```go 1975 | func main() { 1976 | os.Exit(run(args)) 1977 | } 1978 | 1979 | func run() (exitCode int) { 1980 | // ... 1981 | } 1982 | ``` 1983 | 请注意,这些示例中使用的`run()`函数并不是强制性的。 1984 | `run()`函数的名称、签名和设置具有灵活性。除其他外,您可以: 1985 | 1986 | - 接受未分析的命令行参数 (e.g., `run(os.Args[1:])`) 1987 | - 解析`main()`中的命令行参数并将其传递到`run` 1988 | - 使用自定义错误类型将退出代码传回`main()` 1989 | - 将业务逻辑置于不同的抽象层 `package main` 1990 | 1991 | 本指南只要求在`main()`中有一个位置负责实际的退出流程。 1992 | 1993 | ### 在序列化结构中使用字段标记 1994 | 1995 | 任何序列化到 JSON、YAML、, 1996 | 或其他支持基于标记的字段命名的格式应使用相关标记进行注释。 1997 | 1998 | 1999 | 2000 | 2001 | 2029 |
BadGood
2002 | 2003 | ```go 2004 | type Stock struct { 2005 | Price int 2006 | Name string 2007 | } 2008 | bytes, err := json.Marshal(Stock{ 2009 | Price: 137, 2010 | Name: "UBER", 2011 | }) 2012 | ``` 2013 | 2014 | 2015 | 2016 | ```go 2017 | type Stock struct { 2018 | Price int `json:"price"` 2019 | Name string `json:"name"` 2020 | // Safe to rename Name to Symbol. 2021 | } 2022 | bytes, err := json.Marshal(Stock{ 2023 | Price: 137, 2024 | Name: "UBER", 2025 | }) 2026 | ``` 2027 | 2028 |
2030 | 2031 | 理论上: 2032 | 结构的序列化形式是不同系统之间的契约。 2033 | 对序列化表单结构(包括字段名)的更改会破坏此约定。在标记中指定字段名使约定明确, 2034 | 它还可以通过重构或重命名字段来防止意外违反约定。 2035 | 2036 | ### 不要一劳永逸地使用 goroutine 2037 | 2038 | Goroutines 是轻量级的,但它们不是免费的: 2039 | 至少,它们会为堆栈和 CPU 的调度消耗内存。 2040 | 虽然这些成本对于 Goroutines 的使用来说很小,但当它们在没有受控生命周期的情况下大量生成时会导致严重的性能问题。 2041 | 具有非托管生命周期的 Goroutines 也可能导致其他问题,例如防止未使用的对象被垃圾回收并保留不再使用的资源。 2042 | 2043 | 因此,不要在代码中泄漏 goroutine。 2044 | 使用 [go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak) 2045 | 来测试可能产生 goroutine 的包内的 goroutine 泄漏。 2046 | 2047 | 一般来说,每个 goroutine: 2048 | 2049 | - 必须有一个可预测的停止运行时间;或者 2050 | - 必须有一种方法可以向 goroutine 发出信号它应该停止 2051 | 2052 | 在这两种情况下,都必须有一种方式代码来阻塞并等待 goroutine 完成。 2053 | 2054 | For example: 2055 | 2056 | 2057 | 2058 | 2059 | 2096 | 2106 |
BadGood
2060 | 2061 | ```go 2062 | go func() { 2063 | for { 2064 | flush() 2065 | time.Sleep(delay) 2066 | } 2067 | }() 2068 | ``` 2069 | 2070 | 2071 | 2072 | ```go 2073 | var ( 2074 | stop = make(chan struct{}) // 告诉 goroutine 停止 2075 | done = make(chan struct{}) // 告诉我们 goroutine 退出了 2076 | ) 2077 | go func() { 2078 | defer close(done) 2079 | ticker := time.NewTicker(delay) 2080 | defer ticker.Stop() 2081 | for { 2082 | select { 2083 | case <-tick.C: 2084 | flush() 2085 | case <-stop: 2086 | return 2087 | } 2088 | } 2089 | }() 2090 | // 其它... 2091 | close(stop) // 指示 goroutine 停止 2092 | <-done // and wait for it to exit 2093 | ``` 2094 | 2095 |
2097 | 2098 | 没有办法阻止这个 goroutine。这将一直运行到应用程序退出。 2099 | 2100 | 2101 | 2102 | 这个 goroutine 可以用 `close(stop)`, 2103 | 我们可以等待它退出 `<-done`. 2104 | 2105 |
2107 | 2108 | #### 等待 goroutines 退出 2109 | 2110 | 给定一个由系统生成的 goroutine, 2111 | 必须有一种方案能等待 goroutine 的退出。 2112 | 有两种常用的方法可以做到这一点: 2113 | 2114 | - 使用 `sync.WaitGroup`. 2115 | 如果您要等待多个 goroutine,请执行此操作 2116 | 2117 | ```go 2118 | var wg sync.WaitGroup 2119 | for i := 0; i < N; i++ { 2120 | wg.Add(1) 2121 | go func() { 2122 | defer wg.Done() 2123 | // ... 2124 | }() 2125 | } 2126 | 2127 | // To wait for all to finish: 2128 | wg.Wait() 2129 | ``` 2130 | 2131 | - 添加另一个 `chan struct{}`,goroutine 完成后会关闭它。 2132 | 如果只有一个 goroutine,请执行此操作。 2133 | 2134 | ```go 2135 | done := make(chan struct{}) 2136 | go func() { 2137 | defer close(done) 2138 | // ... 2139 | }() 2140 | 2141 | // To wait for the goroutine to finish: 2142 | <-done 2143 | ``` 2144 | 2145 | #### 不要在 `init()` 使用 goroutines 2146 | 2147 | `init()` 函数不应该产生 goroutines。 2148 | 另请参阅 [避免使用 init()](#避免使用-init)。 2149 | 2150 | 如果一个包需要一个后台 goroutine, 2151 | 它必须公开一个负责管理 goroutine 生命周期的对象。 2152 | 该对象必须提供一个方法(`Close`、`Stop`、`Shutdown` 等)来指示后台 goroutine 停止并等待它的退出。 2153 | 2154 | 2155 | 2156 | 2157 | 2199 | 2214 |
BadGood
2158 | 2159 | ```go 2160 | func init() { 2161 | go doWork() 2162 | } 2163 | func doWork() { 2164 | for { 2165 | // ... 2166 | } 2167 | } 2168 | ``` 2169 | 2170 | 2171 | 2172 | ```go 2173 | type Worker struct{ /* ... */ } 2174 | func NewWorker(...) *Worker { 2175 | w := &Worker{ 2176 | stop: make(chan struct{}), 2177 | done: make(chan struct{}), 2178 | // ... 2179 | } 2180 | go w.doWork() 2181 | } 2182 | func (w *Worker) doWork() { 2183 | defer close(w.done) 2184 | for { 2185 | // ... 2186 | case <-w.stop: 2187 | return 2188 | } 2189 | } 2190 | // Shutdown 告诉 worker 停止 2191 | // 并等待它完成。 2192 | func (w *Worker) Shutdown() { 2193 | close(w.stop) 2194 | <-w.done 2195 | } 2196 | ``` 2197 | 2198 |
2200 | 2201 | 当用户导出这个包时,无条件地生成一个后台 goroutine。 2202 | 用户无法控制 goroutine 或停止它的方法。 2203 | 2204 | 2205 | 2206 | 仅当用户请求时才生成工作人员。 2207 | 提供一种关闭工作器的方法,以便用户可以释放工作器使用的资源。 2208 | 2209 | 请注意,如果工作人员管理多个 goroutine,则应使用`WaitGroup`。 2210 | 请参阅 [等待 goroutines 退出](#等待-goroutines-退出)。 2211 | 2212 | 2213 |
2215 | 2216 | ## 性能 2217 | 2218 | 性能方面的特定准则只适用于高频场景。 2219 | 2220 | ### 优先使用 strconv 而不是 fmt 2221 | 2222 | 将原语转换为字符串或从字符串转换时,`strconv`速度比`fmt`快。 2223 | 2224 | 2225 | 2226 | 2227 | 2244 | 2257 |
BadGood
2228 | 2229 | ```go 2230 | for i := 0; i < b.N; i++ { 2231 | s := fmt.Sprint(rand.Int()) 2232 | } 2233 | ``` 2234 | 2235 | 2236 | 2237 | ```go 2238 | for i := 0; i < b.N; i++ { 2239 | s := strconv.Itoa(rand.Int()) 2240 | } 2241 | ``` 2242 | 2243 |
2245 | 2246 | ```plain 2247 | BenchmarkFmtSprint-4 143 ns/op 2 allocs/op 2248 | ``` 2249 | 2250 | 2251 | 2252 | ```plain 2253 | BenchmarkStrconv-4 64.2 ns/op 1 allocs/op 2254 | ``` 2255 | 2256 |
2258 | 2259 | 2260 | 2261 | ### 避免字符串到字节的转换 2262 | 2263 | 不要反复从固定字符串创建字节 slice。相反,请执行一次转换并捕获结果。 2264 | 2265 | 2266 | 2267 | 2268 | 2286 | 2299 |
BadGood
2269 | 2270 | ```go 2271 | for i := 0; i < b.N; i++ { 2272 | w.Write([]byte("Hello world")) 2273 | } 2274 | ``` 2275 | 2276 | 2277 | 2278 | ```go 2279 | data := []byte("Hello world") 2280 | for i := 0; i < b.N; i++ { 2281 | w.Write(data) 2282 | } 2283 | ``` 2284 | 2285 |
2287 | 2288 | ```plain 2289 | BenchmarkBad-4 50000000 22.2 ns/op 2290 | ``` 2291 | 2292 | 2293 | 2294 | ```plain 2295 | BenchmarkGood-4 500000000 3.25 ns/op 2296 | ``` 2297 | 2298 |
2300 | 2301 | ### 指定容器容量 2302 | 2303 | 尽可能指定容器容量,以便为容器预先分配内存。这将在添加元素时最小化后续分配(通过复制和调整容器大小)。 2304 | 2305 | #### 指定 Map 容量提示 2306 | 2307 | 在尽可能的情况下,在使用 `make()` 初始化的时候提供容量信息 2308 | 2309 | ```go 2310 | make(map[T1]T2, hint) 2311 | ``` 2312 | 2313 | 向`make()`提供容量提示会在初始化时尝试调整 map 的大小,这将减少在将元素添加到 map 时为 map 重新分配内存。 2314 | 2315 | 2316 | 注意,与 slices 不同。map 容量提示并不保证完全的、预先的分配,而是用于估计所需的 hashmap bucket 的数量。 2317 | 因此,在将元素添加到 map 时,甚至在指定 map 容量时,仍可能发生分配。 2318 | 2319 | 2320 | 2321 | 2322 | 2346 | 2355 |
BadGood
2323 | 2324 | ```go 2325 | m := make(map[string]os.FileInfo) 2326 | 2327 | files, _ := os.ReadDir("./files") 2328 | for _, f := range files { 2329 | m[f.Name()] = f 2330 | } 2331 | ``` 2332 | 2333 | 2334 | 2335 | ```go 2336 | 2337 | files, _ := os.ReadDir("./files") 2338 | 2339 | m := make(map[string]os.FileInfo, len(files)) 2340 | for _, f := range files { 2341 | m[f.Name()] = f 2342 | } 2343 | ``` 2344 | 2345 |
2347 | 2348 | `m` 是在没有大小提示的情况下创建的;在运行时可能会有更多分配。 2349 | 2350 | 2351 | 2352 | `m` 是有大小提示创建的;在运行时可能会有更少的分配。 2353 | 2354 |
2356 | 2357 | #### 指定切片容量 2358 | 2359 | 在尽可能的情况下,在使用`make()`初始化切片时提供容量信息,特别是在追加切片时。 2360 | 2361 | ```go 2362 | make([]T, length, capacity) 2363 | ``` 2364 | 2365 | 与 maps 不同,slice capacity 不是一个提示:编译器将为提供给`make()`的 slice 的容量分配足够的内存, 2366 | 这意味着后续的`append()`操作将导致零分配(直到 slice 的长度与容量匹配,在此之后,任何 append 都可能调整大小以容纳其他元素)。 2367 | 2368 | 2369 | 2370 | 2371 | 2394 | 2407 |
BadGood
2372 | 2373 | ```go 2374 | for n := 0; n < b.N; n++ { 2375 | data := make([]int, 0) 2376 | for k := 0; k < size; k++{ 2377 | data = append(data, k) 2378 | } 2379 | } 2380 | ``` 2381 | 2382 | 2383 | 2384 | ```go 2385 | for n := 0; n < b.N; n++ { 2386 | data := make([]int, 0, size) 2387 | for k := 0; k < size; k++{ 2388 | data = append(data, k) 2389 | } 2390 | } 2391 | ``` 2392 | 2393 |
2395 | 2396 | ``` 2397 | BenchmarkBad-4 100000000 2.48s 2398 | ``` 2399 | 2400 | 2401 | 2402 | ``` 2403 | BenchmarkGood-4 100000000 0.21s 2404 | ``` 2405 | 2406 |
2408 | 2409 | ## 规范 2410 | ### 避免过长的行 2411 | 2412 | 避免使用需要读者水平滚动或过度转动头部的代码行。 2413 | 2414 | 我们建议将行长度限制为 **99 characters** (99 个字符). 2415 | 作者应该在达到这个限制之前换行, 2416 | 但这不是硬性限制。 2417 | 允许代码超过此限制。 2418 | ### 一致性 2419 | 2420 | 本文中概述的一些标准都是客观性的评估,是根据场景、上下文、或者主观性的判断; 2421 | 2422 | 但是最重要的是,**保持一致**. 2423 | 2424 | 一致性的代码更容易维护、是更合理的、需要更少的学习成本、并且随着新的约定出现或者出现错误后更容易迁移、更新、修复 bug 2425 | 2426 | 相反,在一个代码库中包含多个完全不同或冲突的代码风格会导致维护成本开销、不确定性和认知偏差。所有这些都会直接导致速度降低、代码审查痛苦、而且增加 bug 数量。 2427 | 2428 | 将这些标准应用于代码库时,建议在 package(或更大)级别进行更改,子包级别的应用程序通过将多个样式引入到同一代码中,违反了上述关注点。 2429 | 2430 | ### 相似的声明放在一组 2431 | 2432 | Go 语言支持将相似的声明放在一个组内。 2433 | 2434 | 2435 | 2436 | 2437 | 2454 |
BadGood
2438 | 2439 | ```go 2440 | import "a" 2441 | import "b" 2442 | ``` 2443 | 2444 | 2445 | 2446 | ```go 2447 | import ( 2448 | "a" 2449 | "b" 2450 | ) 2451 | ``` 2452 | 2453 |
2455 | 2456 | 这同样适用于常量、变量和类型声明: 2457 | 2458 | 2459 | 2460 | 2461 | 2495 |
BadGood
2462 | 2463 | ```go 2464 | 2465 | const a = 1 2466 | const b = 2 2467 | 2468 | var a = 1 2469 | var b = 2 2470 | 2471 | type Area float64 2472 | type Volume float64 2473 | ``` 2474 | 2475 | 2476 | 2477 | ```go 2478 | const ( 2479 | a = 1 2480 | b = 2 2481 | ) 2482 | 2483 | var ( 2484 | a = 1 2485 | b = 2 2486 | ) 2487 | 2488 | type ( 2489 | Area float64 2490 | Volume float64 2491 | ) 2492 | ``` 2493 | 2494 |
2496 | 2497 | 仅将相关的声明放在一组。不要将不相关的声明放在一组。 2498 | 2499 | 2500 | 2501 | 2502 | 2530 |
BadGood
2503 | 2504 | ```go 2505 | type Operation int 2506 | 2507 | const ( 2508 | Add Operation = iota + 1 2509 | Subtract 2510 | Multiply 2511 | EnvVar = "MY_ENV" 2512 | ) 2513 | ``` 2514 | 2515 | 2516 | 2517 | ```go 2518 | type Operation int 2519 | 2520 | const ( 2521 | Add Operation = iota + 1 2522 | Subtract 2523 | Multiply 2524 | ) 2525 | 2526 | const EnvVar = "MY_ENV" 2527 | ``` 2528 | 2529 |
2531 | 2532 | 分组使用的位置没有限制,例如:你可以在函数内部使用它们: 2533 | 2534 | 2535 | 2536 | 2537 | 2564 |
BadGood
2538 | 2539 | ```go 2540 | func f() string { 2541 | red := color.New(0xff0000) 2542 | green := color.New(0x00ff00) 2543 | blue := color.New(0x0000ff) 2544 | 2545 | ... 2546 | } 2547 | ``` 2548 | 2549 | 2550 | 2551 | ```go 2552 | func f() string { 2553 | var ( 2554 | red = color.New(0xff0000) 2555 | green = color.New(0x00ff00) 2556 | blue = color.New(0x0000ff) 2557 | ) 2558 | 2559 | ... 2560 | } 2561 | ``` 2562 | 2563 |
2565 | 2566 | 例外:如果变量声明与其他变量相邻,则应将变量声明(尤其是函数内部的声明)分组在一起。对一起声明的变量执行此操作,即使它们不相关。 2567 | 2568 | 2569 | 2570 | 2571 | 2598 |
BadGood
2572 | 2573 | ```go 2574 | func (c *client) request() { 2575 | caller := c.name 2576 | format := "json" 2577 | timeout := 5*time.Second 2578 | var err error 2579 | // ... 2580 | } 2581 | ``` 2582 | 2583 | 2584 | 2585 | ```go 2586 | func (c *client) request() { 2587 | var ( 2588 | caller = c.name 2589 | format = "json" 2590 | timeout = 5*time.Second 2591 | err error 2592 | ) 2593 | // ... 2594 | } 2595 | ``` 2596 | 2597 |
2599 | 2600 | ### import 分组 2601 | 2602 | 导入应该分为两组: 2603 | 2604 | - 标准库 2605 | - 其他库 2606 | 2607 | 默认情况下,这是 goimports 应用的分组。 2608 | 2609 | 2610 | 2611 | 2612 | 2636 |
BadGood
2613 | 2614 | ```go 2615 | import ( 2616 | "fmt" 2617 | "os" 2618 | "go.uber.org/atomic" 2619 | "golang.org/x/sync/errgroup" 2620 | ) 2621 | ``` 2622 | 2623 | 2624 | 2625 | ```go 2626 | import ( 2627 | "fmt" 2628 | "os" 2629 | 2630 | "go.uber.org/atomic" 2631 | "golang.org/x/sync/errgroup" 2632 | ) 2633 | ``` 2634 | 2635 |
2637 | 2638 | ### 包名 2639 | 2640 | 当命名包时,请按下面规则选择一个名称: 2641 | 2642 | - 全部小写。没有大写或下划线。 2643 | - 大多数使用命名导入的情况下,不需要重命名。 2644 | - 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。 2645 | - 不用复数。例如`net/url`,而不是`net/urls`。 2646 | - 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。 2647 | 2648 | 另请参阅 [Go 包命名规则] 和 [Go 包样式指南]. 2649 | 2650 | [Go 包命名规则]: https://go.dev/blog/package-names 2651 | [Go 包样式指南]: https://rakyll.org/style-packages/ 2652 | 2653 | ### 函数名 2654 | 2655 | 我们遵循 Go 社区关于使用 [MixedCaps 作为函数名] 的约定。有一个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:`TestMyFunction_WhatIsBeingTested`. 2656 | 2657 | [MixedCaps 作为函数名]: https://golang.org/doc/effective_go.html#mixed-caps 2658 | 2659 | ### 导入别名 2660 | 2661 | 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。 2662 | 2663 | ```go 2664 | import ( 2665 | "net/http" 2666 | 2667 | client "example.com/client-go" 2668 | trace "example.com/trace/v2" 2669 | ) 2670 | ``` 2671 | 2672 | 在所有其他情况下,除非导入之间有直接冲突,否则应避免导入别名。 2673 | 2674 | 2675 | 2676 | 2677 | 2701 |
BadGood
2678 | 2679 | ```go 2680 | import ( 2681 | "fmt" 2682 | "os" 2683 | 2684 | nettrace "golang.net/x/trace" 2685 | ) 2686 | ``` 2687 | 2688 | 2689 | 2690 | ```go 2691 | import ( 2692 | "fmt" 2693 | "os" 2694 | "runtime/trace" 2695 | 2696 | nettrace "golang.net/x/trace" 2697 | ) 2698 | ``` 2699 | 2700 |
2702 | 2703 | ### 函数分组与顺序 2704 | 2705 | - 函数应按粗略的调用顺序排序。 2706 | - 同一文件中的函数应按接收者分组。 2707 | 2708 | 因此,导出的函数应先出现在文件中,放在`struct`, `const`, `var`定义的后面。 2709 | 2710 | 在定义类型之后,但在接收者的其余方法之前,可能会出现一个 `newXYZ()`/`NewXYZ()` 2711 | 2712 | 由于函数是按接收者分组的,因此普通工具函数应在文件末尾出现。 2713 | 2714 | 2715 | 2716 | 2717 | 2754 |
BadGood
2718 | 2719 | ```go 2720 | func (s *something) Cost() { 2721 | return calcCost(s.weights) 2722 | } 2723 | 2724 | type something struct{ ... } 2725 | 2726 | func calcCost(n []int) int {...} 2727 | 2728 | func (s *something) Stop() {...} 2729 | 2730 | func newSomething() *something { 2731 | return &something{} 2732 | } 2733 | ``` 2734 | 2735 | 2736 | 2737 | ```go 2738 | type something struct{ ... } 2739 | 2740 | func newSomething() *something { 2741 | return &something{} 2742 | } 2743 | 2744 | func (s *something) Cost() { 2745 | return calcCost(s.weights) 2746 | } 2747 | 2748 | func (s *something) Stop() {...} 2749 | 2750 | func calcCost(n []int) int {...} 2751 | ``` 2752 | 2753 |
2755 | 2756 | ### 减少嵌套 2757 | 2758 | 代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。 2759 | 2760 | 2761 | 2762 | 2763 | 2798 |
BadGood
2764 | 2765 | ```go 2766 | for _, v := range data { 2767 | if v.F1 == 1 { 2768 | v = process(v) 2769 | if err := v.Call(); err == nil { 2770 | v.Send() 2771 | } else { 2772 | return err 2773 | } 2774 | } else { 2775 | log.Printf("Invalid v: %v", v) 2776 | } 2777 | } 2778 | ``` 2779 | 2780 | 2781 | 2782 | ```go 2783 | for _, v := range data { 2784 | if v.F1 != 1 { 2785 | log.Printf("Invalid v: %v", v) 2786 | continue 2787 | } 2788 | 2789 | v = process(v) 2790 | if err := v.Call(); err != nil { 2791 | return err 2792 | } 2793 | v.Send() 2794 | } 2795 | ``` 2796 | 2797 |
2799 | 2800 | ### 不必要的 else 2801 | 2802 | 如果在 if 的两个分支中都设置了变量,则可以将其替换为单个 if。 2803 | 2804 | 2805 | 2806 | 2807 | 2828 |
BadGood
2808 | 2809 | ```go 2810 | var a int 2811 | if b { 2812 | a = 100 2813 | } else { 2814 | a = 10 2815 | } 2816 | ``` 2817 | 2818 | 2819 | 2820 | ```go 2821 | a := 10 2822 | if b { 2823 | a = 100 2824 | } 2825 | ``` 2826 | 2827 |
2829 | 2830 | ### 顶层变量声明 2831 | 2832 | 在顶层,使用标准`var`关键字。请勿指定类型,除非它与表达式的类型不同。 2833 | 2834 | 2835 | 2836 | 2837 | 2856 |
BadGood
2838 | 2839 | ```go 2840 | var _s string = F() 2841 | 2842 | func F() string { return "A" } 2843 | ``` 2844 | 2845 | 2846 | 2847 | ```go 2848 | var _s = F() 2849 | // 由于 F 已经明确了返回一个字符串类型,因此我们没有必要显式指定_s 的类型 2850 | // 还是那种类型 2851 | 2852 | func F() string { return "A" } 2853 | ``` 2854 | 2855 |
2857 | 2858 | 如果表达式的类型与所需的类型不完全匹配,请指定类型。 2859 | 2860 | ```go 2861 | type myError struct{} 2862 | 2863 | func (myError) Error() string { return "error" } 2864 | 2865 | func F() myError { return myError{} } 2866 | 2867 | var _e error = F() 2868 | // F 返回一个 myError 类型的实例,但是我们要 error 类型 2869 | ``` 2870 | 2871 | ### 对于未导出的顶层常量和变量,使用_作为前缀 2872 | 2873 | 在未导出的顶级`vars`和`consts`,前面加上前缀_,以使它们在使用时明确表示它们是全局符号。 2874 | 2875 | 基本依据:顶级变量和常量具有包范围作用域。使用通用名称可能很容易在其他文件中意外使用错误的值。 2876 | 2877 | 2878 | 2879 | 2880 | 2914 |
BadGood
2881 | 2882 | ```go 2883 | // foo.go 2884 | 2885 | const ( 2886 | defaultPort = 8080 2887 | defaultUser = "user" 2888 | ) 2889 | 2890 | // bar.go 2891 | 2892 | func Bar() { 2893 | defaultPort := 9090 2894 | ... 2895 | fmt.Println("Default port", defaultPort) 2896 | 2897 | // We will not see a compile error if the first line of 2898 | // Bar() is deleted. 2899 | } 2900 | ``` 2901 | 2902 | 2903 | 2904 | ```go 2905 | // foo.go 2906 | 2907 | const ( 2908 | _defaultPort = 8080 2909 | _defaultUser = "user" 2910 | ) 2911 | ``` 2912 | 2913 |
2915 | 2916 | **例外**:未导出的错误值可以使用不带下划线的前缀 `err`。参见[错误命名](#错误命名)。 2917 | 2918 | ### 结构体中的嵌入 2919 | 2920 | 嵌入式类型(例如 mutex)应位于结构体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。 2921 | 2922 | 2923 | 2924 | 2925 | 2945 |
BadGood
2926 | 2927 | ```go 2928 | type Client struct { 2929 | version int 2930 | http.Client 2931 | } 2932 | ``` 2933 | 2934 | 2935 | 2936 | ```go 2937 | type Client struct { 2938 | http.Client 2939 | 2940 | version int 2941 | } 2942 | ``` 2943 | 2944 |
2946 | 2947 | 内嵌应该提供切实的好处,比如以语义上合适的方式添加或增强功能。 2948 | 它应该在对用户没有任何不利影响的情况下使用。(另请参见:[避免在公共结构中嵌入类型])。 2949 | 2950 | 例外:即使在未导出类型中,Mutex 也不应该作为内嵌字段。另请参见:[零值 Mutex 是有效的]。 2951 | 2952 | [避免在公共结构中嵌入类型]: #避免在公共结构中嵌入类型 2953 | [零值 Mutex 是有效的]: #零值-mutex-是有效的 2954 | 2955 | 嵌入 **不应该**: 2956 | 2957 | - 纯粹是为了美观或方便。 2958 | - 使外部类型更难构造或使用。 2959 | - 影响外部类型的零值。如果外部类型有一个有用的零值,则在嵌入内部类型之后应该仍然有一个有用的零值。 2960 | - 作为嵌入内部类型的副作用,从外部类型公开不相关的函数或字段。 2961 | - 公开未导出的类型。 2962 | - 影响外部类型的复制形式。 2963 | - 更改外部类型的 API 或类型语义。 2964 | - 嵌入内部类型的非规范形式。 2965 | - 公开外部类型的实现详细信息。 2966 | - 允许用户观察或控制类型内部。 2967 | - 通过包装的方式改变内部函数的一般行为,这种包装方式会给用户带来一些意料之外情况。 2968 | 2969 | 简单地说,有意识地和有目的地嵌入。一种很好的测试体验是, 2970 | "是否所有这些导出的内部方法/字段都将直接添加到外部类型" 2971 | 如果答案是`some`或`no`,不要嵌入内部类型 - 而是使用字段。 2972 | 2973 | 2974 | 2975 | 2976 | 3002 | 3033 | 3056 |
BadGood
2977 | 2978 | ```go 2979 | type A struct { 2980 | // Bad: A.Lock() and A.Unlock() 现在可用 2981 | // 不提供任何功能性好处,并允许用户控制有关 A 的内部细节。 2982 | sync.Mutex 2983 | } 2984 | ``` 2985 | 2986 | 2987 | 2988 | ```go 2989 | type countingWriteCloser struct { 2990 | // Good: Write() 在外层提供用于特定目的, 2991 | // 并且委托工作到内部类型的 Write() 中。 2992 | io.WriteCloser 2993 | count int 2994 | } 2995 | func (w *countingWriteCloser) Write(bs []byte) (int, error) { 2996 | w.count += len(bs) 2997 | return w.WriteCloser.Write(bs) 2998 | } 2999 | ``` 3000 | 3001 |
3003 | 3004 | ```go 3005 | type Book struct { 3006 | // Bad: 指针更改零值的有用性 3007 | io.ReadWriter 3008 | // other fields 3009 | } 3010 | // later 3011 | var b Book 3012 | b.Read(...) // panic: nil pointer 3013 | b.String() // panic: nil pointer 3014 | b.Write(...) // panic: nil pointer 3015 | ``` 3016 | 3017 | 3018 | 3019 | ```go 3020 | type Book struct { 3021 | // Good: 有用的零值 3022 | bytes.Buffer 3023 | // other fields 3024 | } 3025 | // later 3026 | var b Book 3027 | b.Read(...) // ok 3028 | b.String() // ok 3029 | b.Write(...) // ok 3030 | ``` 3031 | 3032 |
3034 | 3035 | ```go 3036 | type Client struct { 3037 | sync.Mutex 3038 | sync.WaitGroup 3039 | bytes.Buffer 3040 | url.URL 3041 | } 3042 | ``` 3043 | 3044 | 3045 | 3046 | ```go 3047 | type Client struct { 3048 | mtx sync.Mutex 3049 | wg sync.WaitGroup 3050 | buf bytes.Buffer 3051 | url url.URL 3052 | } 3053 | ``` 3054 | 3055 |
3057 | 3058 | ### 本地变量声明 3059 | 3060 | 如果将变量明确设置为某个值,则应使用短变量声明形式 (`:=`)。 3061 | 3062 | 3063 | 3064 | 3065 | 3078 |
BadGood
3066 | 3067 | ```go 3068 | var s = "foo" 3069 | ``` 3070 | 3071 | 3072 | 3073 | ```go 3074 | s := "foo" 3075 | ``` 3076 | 3077 |
3079 | 3080 | 但是,在某些情况下,`var` 使用关键字时默认值会更清晰。例如,[声明空切片]。 3081 | 3082 | [声明空切片]: https://go.dev/wiki/CodeReviewComments#declaring-empty-slices 3083 | 3084 | 3085 | 3086 | 3087 | 3114 |
BadGood
3088 | 3089 | ```go 3090 | func f(list []int) { 3091 | filtered := []int{} 3092 | for _, v := range list { 3093 | if v > 10 { 3094 | filtered = append(filtered, v) 3095 | } 3096 | } 3097 | } 3098 | ``` 3099 | 3100 | 3101 | 3102 | ```go 3103 | func f(list []int) { 3104 | var filtered []int 3105 | for _, v := range list { 3106 | if v > 10 { 3107 | filtered = append(filtered, v) 3108 | } 3109 | } 3110 | } 3111 | ``` 3112 | 3113 |
3115 | 3116 | ### nil 是一个有效的 slice 3117 | 3118 | `nil` 是一个有效的长度为 0 的 slice,这意味着, 3119 | 3120 | - 您不应明确返回长度为零的切片。应该返回`nil` 来代替。 3121 | 3122 | 3123 | 3124 | 3125 | 3142 |
BadGood
3126 | 3127 | ```go 3128 | if x == "" { 3129 | return []int{} 3130 | } 3131 | ``` 3132 | 3133 | 3134 | 3135 | ```go 3136 | if x == "" { 3137 | return nil 3138 | } 3139 | ``` 3140 | 3141 |
3143 | 3144 | - 要检查切片是否为空,请始终使用`len(s) == 0`。而非 `nil`。 3145 | 3146 | 3147 | 3148 | 3149 | 3166 |
BadGood
3150 | 3151 | ```go 3152 | func isEmpty(s []string) bool { 3153 | return s == nil 3154 | } 3155 | ``` 3156 | 3157 | 3158 | 3159 | ```go 3160 | func isEmpty(s []string) bool { 3161 | return len(s) == 0 3162 | } 3163 | ``` 3164 | 3165 |
3167 | 3168 | - 零值切片(用`var`声明的切片)可立即使用,无需调用`make()`创建。 3169 | 3170 | 3171 | 3172 | 3173 | 3203 |
BadGood
3174 | 3175 | ```go 3176 | nums := []int{} 3177 | // or, nums := make([]int) 3178 | 3179 | if add1 { 3180 | nums = append(nums, 1) 3181 | } 3182 | 3183 | if add2 { 3184 | nums = append(nums, 2) 3185 | } 3186 | ``` 3187 | 3188 | 3189 | 3190 | ```go 3191 | var nums []int 3192 | 3193 | if add1 { 3194 | nums = append(nums, 1) 3195 | } 3196 | 3197 | if add2 { 3198 | nums = append(nums, 2) 3199 | } 3200 | ``` 3201 | 3202 |
3204 | 3205 | 记住,虽然 nil 切片是有效的切片,但它不等于长度为 0 的切片(一个为 nil,另一个不是),并且在不同的情况下(例如序列化),这两个切片的处理方式可能不同。 3206 | 3207 | ### 缩小变量作用域 3208 | 3209 | 如果有可能,尽量缩小变量作用范围。除非它与 [减少嵌套](#减少嵌套)的规则冲突。 3210 | 3211 | 3212 | 3213 | 3214 | 3232 |
BadGood
3215 | 3216 | ```go 3217 | err := os.WriteFile(name, data, 0644) 3218 | if err != nil { 3219 | return err 3220 | } 3221 | ``` 3222 | 3223 | 3224 | 3225 | ```go 3226 | if err := os.WriteFile(name, data, 0644); err != nil { 3227 | return err 3228 | } 3229 | ``` 3230 | 3231 |
3233 | 3234 | 如果需要在 if 之外使用函数调用的结果,则不应尝试缩小范围。 3235 | 3236 | 3237 | 3238 | 3239 | 3272 |
BadGood
3240 | 3241 | ```go 3242 | if data, err := os.ReadFile(name); err == nil { 3243 | err = cfg.Decode(data) 3244 | if err != nil { 3245 | return err 3246 | } 3247 | 3248 | fmt.Println(cfg) 3249 | return nil 3250 | } else { 3251 | return err 3252 | } 3253 | ``` 3254 | 3255 | 3256 | 3257 | ```go 3258 | data, err := os.ReadFile(name) 3259 | if err != nil { 3260 | return err 3261 | } 3262 | 3263 | if err := cfg.Decode(data); err != nil { 3264 | return err 3265 | } 3266 | 3267 | fmt.Println(cfg) 3268 | return nil 3269 | ``` 3270 | 3271 |
3273 | 3274 | ### 避免参数语义不明确 (Avoid Naked Parameters) 3275 | 3276 | 函数调用中的`意义不明确的参数`可能会损害可读性。当参数名称的含义不明显时,请为参数添加 C 样式注释 (`/* ... */`) 3277 | 3278 | 3279 | 3280 | 3281 | 3298 |
BadGood
3282 | 3283 | ```go 3284 | // func printInfo(name string, isLocal, done bool) 3285 | 3286 | printInfo("foo", true, true) 3287 | ``` 3288 | 3289 | 3290 | 3291 | ```go 3292 | // func printInfo(name string, isLocal, done bool) 3293 | 3294 | printInfo("foo", true /* isLocal */, true /* done */) 3295 | ``` 3296 | 3297 |
3299 | 3300 | 对于上面的示例代码,还有一种更好的处理方式是将上面的 `bool` 类型换成自定义类型。将来,该参数可以支持不仅仅局限于两个状态(true/false)。 3301 | 3302 | ```go 3303 | type Region int 3304 | 3305 | const ( 3306 | UnknownRegion Region = iota 3307 | Local 3308 | ) 3309 | 3310 | type Status int 3311 | 3312 | const ( 3313 | StatusReady Status= iota + 1 3314 | StatusDone 3315 | // Maybe we will have a StatusInProgress in the future. 3316 | ) 3317 | 3318 | func printInfo(name string, region Region, status Status) 3319 | ``` 3320 | 3321 | ### 使用原始字符串字面值,避免转义 3322 | 3323 | Go 支持使用 [原始字符串字面值](https://golang.org/ref/spec#raw_string_lit),也就是 " ` " 来表示原生字符串,在需要转义的场景下,我们应该尽量使用这种方案来替换。 3324 | 3325 | 可以跨越多行并包含引号。使用这些字符串可以避免更难阅读的手工转义的字符串。 3326 | 3327 | 3328 | 3329 | 3330 | 3343 |
BadGood
3331 | 3332 | ```go 3333 | wantError := "unknown name:\"test\"" 3334 | ``` 3335 | 3336 | 3337 | 3338 | ```go 3339 | wantError := `unknown error:"test"` 3340 | ``` 3341 | 3342 |
3344 | 3345 | ### 初始化结构体 3346 | 3347 | #### 使用字段名初始化结构 3348 | 3349 | 初始化结构时,几乎应该始终指定字段名。目前由 [`go vet`] 强制执行。 3350 | 3351 | [`go vet`]: https://golang.org/cmd/vet/ 3352 | 3353 | 3354 | 3355 | 3356 | 3373 |
BadGood
3357 | 3358 | ```go 3359 | k := User{"John", "Doe", true} 3360 | ``` 3361 | 3362 | 3363 | 3364 | ```go 3365 | k := User{ 3366 | FirstName: "John", 3367 | LastName: "Doe", 3368 | Admin: true, 3369 | } 3370 | ``` 3371 | 3372 |
3374 | 3375 | 例外:当有 3 个或更少的字段时,测试表中的字段名可以省略。 3376 | 3377 | ```go 3378 | tests := []struct{ 3379 | op Operation 3380 | want string 3381 | }{ 3382 | {Add, "add"}, 3383 | {Subtract, "subtract"}, 3384 | } 3385 | ``` 3386 | #### 省略结构中的零值字段 3387 | 3388 | 初始化具有字段名的结构时,除非提供有意义的上下文,否则忽略值为零的字段。 3389 | 也就是,让我们自动将这些设置为零值 3390 | 3391 | 3392 | 3393 | 3394 | 3415 |
BadGood
3395 | 3396 | ```go 3397 | user := User{ 3398 | FirstName: "John", 3399 | LastName: "Doe", 3400 | MiddleName: "", 3401 | Admin: false, 3402 | } 3403 | ``` 3404 | 3405 | 3406 | 3407 | ```go 3408 | user := User{ 3409 | FirstName: "John", 3410 | LastName: "Doe", 3411 | } 3412 | ``` 3413 | 3414 |
3416 | 3417 | 这有助于通过省略该上下文中的默认值来减少阅读的障碍。只指定有意义的值。 3418 | 3419 | 在字段名提供有意义上下文的地方包含零值。例如,[表驱动测试](#表驱动测试) 中的测试用例可以受益于字段的名称,即使它们是零值的。 3420 | 3421 | ```go 3422 | tests := []struct{ 3423 | give string 3424 | want int 3425 | }{ 3426 | {give: "0", want: 0}, 3427 | // ... 3428 | } 3429 | ``` 3430 | #### 对零值结构使用 `var` 3431 | 3432 | 如果在声明中省略了结构的所有字段,请使用 `var` 声明结构。 3433 | 3434 | 3435 | 3436 | 3437 | 3450 |
BadGood
3438 | 3439 | ```go 3440 | user := User{} 3441 | ``` 3442 | 3443 | 3444 | 3445 | ```go 3446 | var user User 3447 | ``` 3448 | 3449 |
3451 | 3452 | 这将零值结构与那些具有类似于为 [初始化 Maps](#初始化-maps) 创建的,区别于非零值字段的结构区分开来, 3453 | 我们倾向于[声明一个空切片](https://go.dev/wiki/CodeReviewComments#declaring-empty-slices) 3454 | 3455 | #### 初始化 Struct 引用 3456 | 3457 | 在初始化结构引用时,请使用`&T{}`代替`new(T)`,以使其与结构体初始化一致。 3458 | 3459 | 3460 | 3461 | 3462 | 3481 |
BadGood
3463 | 3464 | ```go 3465 | sval := T{Name: "foo"} 3466 | 3467 | // inconsistent 3468 | sptr := new(T) 3469 | sptr.Name = "bar" 3470 | ``` 3471 | 3472 | 3473 | 3474 | ```go 3475 | sval := T{Name: "foo"} 3476 | 3477 | sptr := &T{Name: "bar"} 3478 | ``` 3479 | 3480 |
3482 | 3483 | ### 初始化 Maps 3484 | 3485 | 对于空 map 请使用 `make(..)` 初始化,并且 map 是通过编程方式填充的。 3486 | 这使得 map 初始化在表现上不同于声明,并且它还可以方便地在 make 后添加大小提示。 3487 | 3488 | 3489 | 3490 | 3491 | 3514 | 3523 |
BadGood
3492 | 3493 | ```go 3494 | var ( 3495 | // m1 读写安全; 3496 | // m2 在写入时会 panic 3497 | m1 = map[T1]T2{} 3498 | m2 map[T1]T2 3499 | ) 3500 | ``` 3501 | 3502 | 3503 | 3504 | ```go 3505 | var ( 3506 | // m1 读写安全; 3507 | // m2 在写入时会 panic 3508 | m1 = make(map[T1]T2) 3509 | m2 map[T1]T2 3510 | ) 3511 | ``` 3512 | 3513 |
3515 | 3516 | 声明和初始化看起来非常相似的。 3517 | 3518 | 3519 | 3520 | 声明和初始化看起来差别非常大。 3521 | 3522 |
3524 | 3525 | 在尽可能的情况下,请在初始化时提供 map 容量大小,详细请看 [指定 Map 容量提示](#指定Map容量提示)。 3526 | 3527 | 3528 | 另外,如果 map 包含固定的元素列表,则使用 map literals(map 初始化列表) 初始化映射。 3529 | 3530 | 3531 | 3532 | 3533 | 3534 | 3554 |
BadGood
3535 | 3536 | ```go 3537 | m := make(map[T1]T2, 3) 3538 | m[k1] = v1 3539 | m[k2] = v2 3540 | m[k3] = v3 3541 | ``` 3542 | 3543 | 3544 | 3545 | ```go 3546 | m := map[T1]T2{ 3547 | k1: v1, 3548 | k2: v2, 3549 | k3: v3, 3550 | } 3551 | ``` 3552 | 3553 |
3555 | 3556 | 基本准则是:在初始化时使用 map 初始化列表 来添加一组固定的元素。否则使用 `make` (如果可以,请尽量指定 map 容量)。 3557 | 3558 | ### 字符串 string format 3559 | 3560 | 如果你在函数外声明`Printf`-style 函数的格式字符串,请将其设置为`const`常量。 3561 | 3562 | 这有助于`go vet`对格式字符串执行静态分析。 3563 | 3564 | 3565 | 3566 | 3567 | 3582 |
BadGood
3568 | 3569 | ```go 3570 | msg := "unexpected values %v, %v\n" 3571 | fmt.Printf(msg, 1, 2) 3572 | ``` 3573 | 3574 | 3575 | 3576 | ```go 3577 | const msg = "unexpected values %v, %v\n" 3578 | fmt.Printf(msg, 1, 2) 3579 | ``` 3580 | 3581 |
3583 | 3584 | ### 命名 Printf 样式的函数 3585 | 3586 | 声明`Printf`-style 函数时,请确保`go vet`可以检测到它并检查格式字符串。 3587 | 3588 | 这意味着您应尽可能使用预定义的`Printf`-style 函数名称。`go vet`将默认检查这些。有关更多信息,请参见 [Printf 系列]。 3589 | 3590 | [Printf 系列]: https://golang.org/cmd/vet/#hdr-Printf_family 3591 | 3592 | 如果不能使用预定义的名称,请以 f 结束选择的名称:`Wrapf`,而不是`Wrap`。`go vet`可以要求检查特定的 Printf 样式名称,但名称必须以`f`结尾。 3593 | 3594 | ```shell 3595 | go vet -printfuncs=wrapf,statusf 3596 | ``` 3597 | 3598 | 另请参阅 [go vet: Printf family check]. 3599 | 3600 | [go vet: Printf family check]: https://kuzminva.wordpress.com/2017/11/07/go-vet-printf-family-check/ 3601 | 3602 | ## 编程模式 3603 | 3604 | ### 表驱动测试 3605 | 3606 | 当测试逻辑是重复的时候,通过 [subtests] 使用 table 驱动的方式编写 case 代码看上去会更简洁。 3607 | 3608 | [subtests]: https://go.dev/blog/subtests 3609 | 3610 | 3611 | 3612 | 3613 | 3682 |
BadGood
3614 | 3615 | ```go 3616 | // func TestSplitHostPort(t *testing.T) 3617 | 3618 | host, port, err := net.SplitHostPort("192.0.2.0:8000") 3619 | require.NoError(t, err) 3620 | assert.Equal(t, "192.0.2.0", host) 3621 | assert.Equal(t, "8000", port) 3622 | 3623 | host, port, err = net.SplitHostPort("192.0.2.0:http") 3624 | require.NoError(t, err) 3625 | assert.Equal(t, "192.0.2.0", host) 3626 | assert.Equal(t, "http", port) 3627 | 3628 | host, port, err = net.SplitHostPort(":8000") 3629 | require.NoError(t, err) 3630 | assert.Equal(t, "", host) 3631 | assert.Equal(t, "8000", port) 3632 | 3633 | host, port, err = net.SplitHostPort("1:8") 3634 | require.NoError(t, err) 3635 | assert.Equal(t, "1", host) 3636 | assert.Equal(t, "8", port) 3637 | ``` 3638 | 3639 | 3640 | 3641 | ```go 3642 | // func TestSplitHostPort(t *testing.T) 3643 | 3644 | tests := []struct{ 3645 | give string 3646 | wantHost string 3647 | wantPort string 3648 | }{ 3649 | { 3650 | give: "192.0.2.0:8000", 3651 | wantHost: "192.0.2.0", 3652 | wantPort: "8000", 3653 | }, 3654 | { 3655 | give: "192.0.2.0:http", 3656 | wantHost: "192.0.2.0", 3657 | wantPort: "http", 3658 | }, 3659 | { 3660 | give: ":8000", 3661 | wantHost: "", 3662 | wantPort: "8000", 3663 | }, 3664 | { 3665 | give: "1:8", 3666 | wantHost: "1", 3667 | wantPort: "8", 3668 | }, 3669 | } 3670 | 3671 | for _, tt := range tests { 3672 | t.Run(tt.give, func(t *testing.T) { 3673 | host, port, err := net.SplitHostPort(tt.give) 3674 | require.NoError(t, err) 3675 | assert.Equal(t, tt.wantHost, host) 3676 | assert.Equal(t, tt.wantPort, port) 3677 | }) 3678 | } 3679 | ``` 3680 | 3681 |
3683 | 3684 | 很明显,使用 test table 的方式在代码逻辑扩展的时候,比如新增 test case,都会显得更加的清晰。 3685 | 3686 | 我们遵循这样的约定:将结构体切片称为`tests`。每个测试用例称为`tt`。此外,我们鼓励使用`give`和`want`前缀说明每个测试用例的输入和输出值。 3687 | 3688 | ```go 3689 | tests := []struct{ 3690 | give string 3691 | wantHost string 3692 | wantPort string 3693 | }{ 3694 | // ... 3695 | } 3696 | 3697 | for _, tt := range tests { 3698 | // ... 3699 | } 3700 | ``` 3701 | 3702 | 并行测试,比如一些专门的循环(例如,生成 goroutine 或捕获引用作为循环体的一部分的那些循环) 3703 | 必须注意在循环的范围内显式地分配循环变量,以确保它们保持预期的值。 3704 | 3705 | ```go 3706 | tests := []struct{ 3707 | give string 3708 | // ... 3709 | }{ 3710 | // ... 3711 | } 3712 | for _, tt := range tests { 3713 | tt := tt // for t.Parallel 3714 | t.Run(tt.give, func(t *testing.T) { 3715 | t.Parallel() 3716 | // ... 3717 | }) 3718 | } 3719 | ``` 3720 | 3721 | 在上面的例子中,由于下面使用了`t.Parallel()`,我们必须声明一个作用域为循环迭代的`tt`变量。 3722 | 如果我们不这样做,大多数或所有测试都会收到一个意外的`tt`值,或者一个在运行时发生变化的值。 3723 | 3724 | ### 功能选项 3725 | 3726 | 功能选项是一种模式,您可以在其中声明一个不透明 Option 类型,该类型在某些内部结构中记录信息。您接受这些选项的可变编号,并根据内部结构上的选项记录的全部信息采取行动。 3727 | 3728 | 将此模式用于您需要扩展的构造函数和其他公共 API 中的可选参数,尤其是在这些功能上已经具有三个或更多参数的情况下。 3729 | 3730 | 3731 | 3732 | 3733 | 3774 | 3801 |
BadGood
3734 | 3735 | ```go 3736 | // package db 3737 | 3738 | func Open( 3739 | addr string, 3740 | cache bool, 3741 | logger *zap.Logger 3742 | ) (*Connection, error) { 3743 | // ... 3744 | } 3745 | ``` 3746 | 3747 | 3748 | 3749 | ```go 3750 | // package db 3751 | 3752 | type Option interface { 3753 | // ... 3754 | } 3755 | 3756 | func WithCache(c bool) Option { 3757 | // ... 3758 | } 3759 | 3760 | func WithLogger(log *zap.Logger) Option { 3761 | // ... 3762 | } 3763 | 3764 | // Open creates a connection. 3765 | func Open( 3766 | addr string, 3767 | opts ...Option, 3768 | ) (*Connection, error) { 3769 | // ... 3770 | } 3771 | ``` 3772 | 3773 |
3775 | 3776 | 必须始终提供缓存和记录器参数,即使用户希望使用默认值。 3777 | 3778 | ```go 3779 | db.Open(addr, db.DefaultCache, zap.NewNop()) 3780 | db.Open(addr, db.DefaultCache, log) 3781 | db.Open(addr, false /* cache */, zap.NewNop()) 3782 | db.Open(addr, false /* cache */, log) 3783 | ``` 3784 | 3785 | 3786 | 3787 | 只有在需要时才提供选项。 3788 | 3789 | ```go 3790 | db.Open(addr) 3791 | db.Open(addr, db.WithLogger(log)) 3792 | db.Open(addr, db.WithCache(false)) 3793 | db.Open( 3794 | addr, 3795 | db.WithCache(false), 3796 | db.WithLogger(log), 3797 | ) 3798 | ``` 3799 | 3800 |
3802 | 3803 | 我们建议实现此模式的方法是使用一个 `Option` 接口,该接口保存一个未导出的方法,在一个未导出的 `options` 结构上记录选项。 3804 | 3805 | ```go 3806 | type options struct { 3807 | cache bool 3808 | logger *zap.Logger 3809 | } 3810 | 3811 | type Option interface { 3812 | apply(*options) 3813 | } 3814 | 3815 | type cacheOption bool 3816 | 3817 | func (c cacheOption) apply(opts *options) { 3818 | opts.cache = bool(c) 3819 | } 3820 | 3821 | func WithCache(c bool) Option { 3822 | return cacheOption(c) 3823 | } 3824 | 3825 | type loggerOption struct { 3826 | Log *zap.Logger 3827 | } 3828 | 3829 | func (l loggerOption) apply(opts *options) { 3830 | opts.logger = l.Log 3831 | } 3832 | 3833 | func WithLogger(log *zap.Logger) Option { 3834 | return loggerOption{Log: log} 3835 | } 3836 | 3837 | // Open creates a connection. 3838 | func Open( 3839 | addr string, 3840 | opts ...Option, 3841 | ) (*Connection, error) { 3842 | options := options{ 3843 | cache: defaultCache, 3844 | logger: zap.NewNop(), 3845 | } 3846 | 3847 | for _, o := range opts { 3848 | o.apply(&options) 3849 | } 3850 | 3851 | // ... 3852 | } 3853 | ``` 3854 | 3855 | 注意:还有一种使用闭包实现这个模式的方法,但是我们相信上面的模式为作者提供了更多的灵活性,并且更容易对用户进行调试和测试。特别是,我们的这种方式允许在测试和模拟中比较选项,这在闭包实现中几乎是不可能的。此外,它还允许选项实现其他接口,包括 `fmt.Stringer`,允许用户读取选项的字符串表示形式。 3856 | 3857 | 还可以参考下面资料: 3858 | 3859 | - [Self-referential functions and the design of options] 3860 | - [Functional options for friendly APIs] 3861 | 3862 | [Self-referential functions and the design of options]: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 3863 | [Functional options for friendly APIs]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 3864 | 3865 | 3867 | 3868 | ## Linting 3869 | 3870 | 比任何 "blessed" linter 集更重要的是,lint 在一个代码库中始终保持一致。 3871 | 3872 | 我们建议至少使用以下 linters,因为我认为它们有助于发现最常见的问题,并在不需要规定的情况下为代码质量建立一个高标准: 3873 | 3874 | - [errcheck] 以确保错误得到处理 3875 | - [goimports] 格式化代码和管理 imports 3876 | - [golint] 指出常见的文体错误 3877 | - [govet] 分析代码中的常见错误 3878 | - [staticcheck] 各种静态分析检查 3879 | 3880 | [errcheck]: https://github.com/kisielk/errcheck 3881 | [goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports 3882 | [golint]: https://github.com/golang/lint 3883 | [govet]: https://golang.org/cmd/vet/ 3884 | [staticcheck]: https://staticcheck.io/ 3885 | 3886 | 3887 | ### Lint Runners 3888 | 3889 | 我们推荐 [golangci-lint] 作为 go-to lint 的运行程序,这主要是因为它在较大的代码库中的性能以及能够同时配置和使用许多规范。这个 repo 有一个示例配置文件 [.golangci.yml] 和推荐的 linter 设置。 3890 | 3891 | golangci-lint 有 [various-linters] 可供使用。建议将上述 linters 作为基本 set,我们鼓励团队添加对他们的项目有意义的任何附加 linters。 3892 | 3893 | [golangci-lint]: https://github.com/golangci/golangci-lint 3894 | [.golangci.yml]: https://github.com/uber-go/guide/blob/master/.golangci.yml 3895 | [various-linters]: https://golangci-lint.run/usage/linters/ 3896 | 3897 | 3898 | ## Stargazers over time 3899 | 3900 | [![Stargazers over time](https://starchart.cc/xxjwxc/uber_go_guide_cn.svg)](https://starchart.cc/xxjwxc/uber_go_guide_cn) 3901 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | The contents of this directory are used to generate the top-level style.md. 2 | The layout is controlled by SUMMARY.md. 3 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Uber Go Style Guide 2 | 3 | - [Introduction](intro.md) 4 | - Guidelines 5 | - [Pointers to Interfaces](interface-pointer.md) 6 | - [Verify Interface Compliance](interface-compliance.md) 7 | - [Receivers and Interfaces](interface-receiver.md) 8 | - [Zero-value Mutexes are Valid](mutex-zero-value.md) 9 | - [Copy Slices and Maps at Boundaries](container-copy.md) 10 | - [Defer to Clean Up](defer-clean.md) 11 | - [Channel Size is One or None](channel-size.md) 12 | - [Start Enums at One](enum-start.md) 13 | - [Use `"time"` to handle time](time.md) 14 | - Errors 15 | - [Error Types](error-type.md) 16 | - [Error Wrapping](error-wrap.md) 17 | - [Error Naming](error-name.md) 18 | - [Handle Errors Once](error-once.md) 19 | - [Handle Type Assertion Failures](type-assert.md) 20 | - [Don't Panic](panic.md) 21 | - [Use go.uber.org/atomic](atomic.md) 22 | - [Avoid Mutable Globals](global-mut.md) 23 | - [Avoid Embedding Types in Public Structs](embed-public.md) 24 | - [Avoid Using Built-In Names](builtin-name.md) 25 | - [Avoid `init()`](init.md) 26 | - [Exit in Main](exit-main.md) 27 | - [Exit Once](exit-once.md) 28 | - [Use field tags in marshaled structs](struct-tag.md) 29 | - [Don't fire-and-forget goroutines](goroutine-forget.md) 30 | - [Wait for goroutines to exit](goroutine-exit.md) 31 | - [No goroutines in `init()`](goroutine-init.md) 32 | - [Performance](performance.md) 33 | - [Prefer strconv over fmt](strconv.md) 34 | - [Avoid repeated string-to-byte conversions](string-byte-slice.md) 35 | - [Prefer Specifying Container Capacity](container-capacity.md) 36 | - Style 37 | - [Avoid overly long lines](line-length.md) 38 | - [Be Consistent](consistency.md) 39 | - [Group Similar Declarations](decl-group.md) 40 | - [Import Group Ordering](import-group.md) 41 | - [Package Names](package-name.md) 42 | - [Function Names](function-name.md) 43 | - [Import Aliasing](import-alias.md) 44 | - [Function Grouping and Ordering](function-order.md) 45 | - [Reduce Nesting](nest-less.md) 46 | - [Unnecessary Else](else-unnecessary.md) 47 | - [Top-level Variable Declarations](global-decl.md) 48 | - [Prefix Unexported Globals with _](global-name.md) 49 | - [Embedding in Structs](struct-embed.md) 50 | - [Local Variable Declarations](var-decl.md) 51 | - [nil is a valid slice](slice-nil.md) 52 | - [Reduce Scope of Variables](var-scope.md) 53 | - [Avoid Naked Parameters](param-naked.md) 54 | - [Use Raw String Literals to Avoid Escaping](string-escape.md) 55 | - Initializing Structs 56 | - [Use Field Names to Initialize Structs](struct-field-key.md) 57 | - [Omit Zero Value Fields in Structs](struct-field-zero.md) 58 | - [Use `var` for Zero Value Structs](struct-zero.md) 59 | - [Initializing Struct References](struct-pointer.md) 60 | - [Initializing Maps](map-init.md) 61 | - [Format Strings outside Printf](printf-const.md) 62 | - [Naming Printf-style Functions](printf-name.md) 63 | - Patterns 64 | - [Test Tables](test-table.md) 65 | - [Functional Options](functional-option.md) 66 | - [Linting](lint.md) 67 | -------------------------------------------------------------------------------- /src/atomic.md: -------------------------------------------------------------------------------- 1 | # Use go.uber.org/atomic 2 | 3 | Atomic operations with the [sync/atomic] package operate on the raw types 4 | (`int32`, `int64`, etc.) so it is easy to forget to use the atomic operation to 5 | read or modify the variables. 6 | 7 | [go.uber.org/atomic] adds type safety to these operations by hiding the 8 | underlying type. Additionally, it includes a convenient `atomic.Bool` type. 9 | 10 | [go.uber.org/atomic]: https://pkg.go.dev/go.uber.org/atomic 11 | [sync/atomic]: https://pkg.go.dev/sync/atomic 12 | 13 | 14 | 15 | 16 | 57 |
BadGood
17 | 18 | ```go 19 | type foo struct { 20 | running int32 // atomic 21 | } 22 | 23 | func (f* foo) start() { 24 | if atomic.SwapInt32(&f.running, 1) == 1 { 25 | // already running… 26 | return 27 | } 28 | // start the Foo 29 | } 30 | 31 | func (f *foo) isRunning() bool { 32 | return f.running == 1 // race! 33 | } 34 | ``` 35 | 36 | 37 | 38 | ```go 39 | type foo struct { 40 | running atomic.Bool 41 | } 42 | 43 | func (f *foo) start() { 44 | if f.running.Swap(true) { 45 | // already running… 46 | return 47 | } 48 | // start the Foo 49 | } 50 | 51 | func (f *foo) isRunning() bool { 52 | return f.running.Load() 53 | } 54 | ``` 55 | 56 |
58 | -------------------------------------------------------------------------------- /src/builtin-name.md: -------------------------------------------------------------------------------- 1 | # Avoid Using Built-In Names 2 | 3 | The Go [language specification] outlines several built-in, 4 | [predeclared identifiers] that should not be used as names within Go programs. 5 | 6 | Depending on context, reusing these identifiers as names will either shadow 7 | the original within the current lexical scope (and any nested scopes) or make 8 | affected code confusing. In the best case, the compiler will complain; in the 9 | worst case, such code may introduce latent, hard-to-grep bugs. 10 | 11 | [language specification]: https://go.dev/ref/spec 12 | [predeclared identifiers]: https://go.dev/ref/spec#Predeclared_identifiers 13 | 14 | 15 | 16 | 17 | 44 | 89 |
BadGood
18 | 19 | ```go 20 | var error string 21 | // `error` shadows the builtin 22 | 23 | // or 24 | 25 | func handleErrorMessage(error string) { 26 | // `error` shadows the builtin 27 | } 28 | ``` 29 | 30 | 31 | 32 | ```go 33 | var errorMessage string 34 | // `error` refers to the builtin 35 | 36 | // or 37 | 38 | func handleErrorMessage(msg string) { 39 | // `error` refers to the builtin 40 | } 41 | ``` 42 | 43 |
45 | 46 | ```go 47 | type Foo struct { 48 | // While these fields technically don't 49 | // constitute shadowing, grepping for 50 | // `error` or `string` strings is now 51 | // ambiguous. 52 | error error 53 | string string 54 | } 55 | 56 | func (f Foo) Error() error { 57 | // `error` and `f.error` are 58 | // visually similar 59 | return f.error 60 | } 61 | 62 | func (f Foo) String() string { 63 | // `string` and `f.string` are 64 | // visually similar 65 | return f.string 66 | } 67 | ``` 68 | 69 | 70 | 71 | ```go 72 | type Foo struct { 73 | // `error` and `string` strings are 74 | // now unambiguous. 75 | err error 76 | str string 77 | } 78 | 79 | func (f Foo) Error() error { 80 | return f.err 81 | } 82 | 83 | func (f Foo) String() string { 84 | return f.str 85 | } 86 | ``` 87 | 88 |
90 | 91 | Note that the compiler will not generate errors when using predeclared 92 | identifiers, but tools such as `go vet` should correctly point out these and 93 | other cases of shadowing. 94 | -------------------------------------------------------------------------------- /src/channel-size.md: -------------------------------------------------------------------------------- 1 | # Channel Size is One or None 2 | 3 | Channels should usually have a size of one or be unbuffered. By default, 4 | channels are unbuffered and have a size of zero. Any other size 5 | must be subject to a high level of scrutiny. Consider how the size is 6 | determined, what prevents the channel from filling up under load and blocking 7 | writers, and what happens when this occurs. 8 | 9 | 10 | 11 | 12 | 29 |
BadGood
13 | 14 | ```go 15 | // Ought to be enough for anybody! 16 | c := make(chan int, 64) 17 | ``` 18 | 19 | 20 | 21 | ```go 22 | // Size of one 23 | c := make(chan int, 1) // or 24 | // Unbuffered channel, size of zero 25 | c := make(chan int) 26 | ``` 27 | 28 |
30 | -------------------------------------------------------------------------------- /src/consistency.md: -------------------------------------------------------------------------------- 1 | # Be Consistent 2 | 3 | Some of the guidelines outlined in this document can be evaluated objectively; 4 | others are situational, contextual, or subjective. 5 | 6 | Above all else, **be consistent**. 7 | 8 | Consistent code is easier to maintain, is easier to rationalize, requires less 9 | cognitive overhead, and is easier to migrate or update as new conventions emerge 10 | or classes of bugs are fixed. 11 | 12 | Conversely, having multiple disparate or conflicting styles within a single 13 | codebase causes maintenance overhead, uncertainty, and cognitive dissonance, 14 | all of which can directly contribute to lower velocity, painful code reviews, 15 | and bugs. 16 | 17 | When applying these guidelines to a codebase, it is recommended that changes 18 | are made at a package (or larger) level: application at a sub-package level 19 | violates the above concern by introducing multiple styles into the same code. 20 | -------------------------------------------------------------------------------- /src/container-capacity.md: -------------------------------------------------------------------------------- 1 | # Prefer Specifying Container Capacity 2 | 3 | Specify container capacity where possible in order to allocate memory for the 4 | container up front. This minimizes subsequent allocations (by copying and 5 | resizing of the container) as elements are added. 6 | 7 | ## Specifying Map Capacity Hints 8 | 9 | Where possible, provide capacity hints when initializing 10 | maps with `make()`. 11 | 12 | ```go 13 | make(map[T1]T2, hint) 14 | ``` 15 | 16 | Providing a capacity hint to `make()` tries to right-size the 17 | map at initialization time, which reduces the need for growing 18 | the map and allocations as elements are added to the map. 19 | 20 | Note that, unlike slices, map capacity hints do not guarantee complete, 21 | preemptive allocation, but are used to approximate the number of hashmap buckets 22 | required. Consequently, allocations may still occur when adding elements to the 23 | map, even up to the specified capacity. 24 | 25 | 26 | 27 | 28 | 52 | 63 |
BadGood
29 | 30 | ```go 31 | m := make(map[string]os.FileInfo) 32 | 33 | files, _ := os.ReadDir("./files") 34 | for _, f := range files { 35 | m[f.Name()] = f 36 | } 37 | ``` 38 | 39 | 40 | 41 | ```go 42 | 43 | files, _ := os.ReadDir("./files") 44 | 45 | m := make(map[string]os.DirEntry, len(files)) 46 | for _, f := range files { 47 | m[f.Name()] = f 48 | } 49 | ``` 50 | 51 |
53 | 54 | `m` is created without a size hint; there may be more 55 | allocations at assignment time. 56 | 57 | 58 | 59 | `m` is created with a size hint; there may be fewer 60 | allocations at assignment time. 61 | 62 |
64 | 65 | ## Specifying Slice Capacity 66 | 67 | Where possible, provide capacity hints when initializing slices with `make()`, 68 | particularly when appending. 69 | 70 | ```go 71 | make([]T, length, capacity) 72 | ``` 73 | 74 | Unlike maps, slice capacity is not a hint: the compiler will allocate enough 75 | memory for the capacity of the slice as provided to `make()`, which means that 76 | subsequent `append()` operations will incur zero allocations (until the length 77 | of the slice matches the capacity, after which any appends will require a resize 78 | to hold additional elements). 79 | 80 | 81 | 82 | 83 | 106 | 119 |
BadGood
84 | 85 | ```go 86 | for n := 0; n < b.N; n++ { 87 | data := make([]int, 0) 88 | for k := 0; k < size; k++{ 89 | data = append(data, k) 90 | } 91 | } 92 | ``` 93 | 94 | 95 | 96 | ```go 97 | for n := 0; n < b.N; n++ { 98 | data := make([]int, 0, size) 99 | for k := 0; k < size; k++{ 100 | data = append(data, k) 101 | } 102 | } 103 | ``` 104 | 105 |
107 | 108 | ```plain 109 | BenchmarkBad-4 100000000 2.48s 110 | ``` 111 | 112 | 113 | 114 | ```plain 115 | BenchmarkGood-4 100000000 0.21s 116 | ``` 117 | 118 |
120 | -------------------------------------------------------------------------------- /src/container-copy.md: -------------------------------------------------------------------------------- 1 | # Copy Slices and Maps at Boundaries 2 | 3 | Slices and maps contain pointers to the underlying data so be wary of scenarios 4 | when they need to be copied. 5 | 6 | ## Receiving Slices and Maps 7 | 8 | Keep in mind that users can modify a map or slice you received as an argument 9 | if you store a reference to it. 10 | 11 | 12 | 13 | 14 | 15 | 30 | 46 | 47 | 48 | 49 |
Bad Good
16 | 17 | ```go 18 | func (d *Driver) SetTrips(trips []Trip) { 19 | d.trips = trips 20 | } 21 | 22 | trips := ... 23 | d1.SetTrips(trips) 24 | 25 | // Did you mean to modify d1.trips? 26 | trips[0] = ... 27 | ``` 28 | 29 | 31 | 32 | ```go 33 | func (d *Driver) SetTrips(trips []Trip) { 34 | d.trips = make([]Trip, len(trips)) 35 | copy(d.trips, trips) 36 | } 37 | 38 | trips := ... 39 | d1.SetTrips(trips) 40 | 41 | // We can now modify trips[0] without affecting d1.trips. 42 | trips[0] = ... 43 | ``` 44 | 45 |
50 | 51 | ## Returning Slices and Maps 52 | 53 | Similarly, be wary of user modifications to maps or slices exposing internal 54 | state. 55 | 56 | 57 | 58 | 59 | 104 |
BadGood
60 | 61 | ```go 62 | type Stats struct { 63 | mu sync.Mutex 64 | counters map[string]int 65 | } 66 | 67 | // Snapshot returns the current stats. 68 | func (s *Stats) Snapshot() map[string]int { 69 | s.mu.Lock() 70 | defer s.mu.Unlock() 71 | 72 | return s.counters 73 | } 74 | 75 | // snapshot is no longer protected by the mutex, so any 76 | // access to the snapshot is subject to data races. 77 | snapshot := stats.Snapshot() 78 | ``` 79 | 80 | 81 | 82 | ```go 83 | type Stats struct { 84 | mu sync.Mutex 85 | counters map[string]int 86 | } 87 | 88 | func (s *Stats) Snapshot() map[string]int { 89 | s.mu.Lock() 90 | defer s.mu.Unlock() 91 | 92 | result := make(map[string]int, len(s.counters)) 93 | for k, v := range s.counters { 94 | result[k] = v 95 | } 96 | return result 97 | } 98 | 99 | // Snapshot is now a copy. 100 | snapshot := stats.Snapshot() 101 | ``` 102 | 103 |
105 | 106 | 107 | -------------------------------------------------------------------------------- /src/decl-group.md: -------------------------------------------------------------------------------- 1 | # Group Similar Declarations 2 | 3 | Go supports grouping similar declarations. 4 | 5 | 6 | 7 | 8 | 25 |
BadGood
9 | 10 | ```go 11 | import "a" 12 | import "b" 13 | ``` 14 | 15 | 16 | 17 | ```go 18 | import ( 19 | "a" 20 | "b" 21 | ) 22 | ``` 23 | 24 |
26 | 27 | This also applies to constants, variables, and type declarations. 28 | 29 | 30 | 31 | 32 | 70 |
BadGood
33 | 34 | ```go 35 | 36 | const a = 1 37 | const b = 2 38 | 39 | 40 | 41 | var a = 1 42 | var b = 2 43 | 44 | 45 | 46 | type Area float64 47 | type Volume float64 48 | ``` 49 | 50 | 51 | 52 | ```go 53 | const ( 54 | a = 1 55 | b = 2 56 | ) 57 | 58 | var ( 59 | a = 1 60 | b = 2 61 | ) 62 | 63 | type ( 64 | Area float64 65 | Volume float64 66 | ) 67 | ``` 68 | 69 |
71 | 72 | Only group related declarations. Do not group declarations that are unrelated. 73 | 74 | 75 | 76 | 77 | 105 |
BadGood
78 | 79 | ```go 80 | type Operation int 81 | 82 | const ( 83 | Add Operation = iota + 1 84 | Subtract 85 | Multiply 86 | EnvVar = "MY_ENV" 87 | ) 88 | ``` 89 | 90 | 91 | 92 | ```go 93 | type Operation int 94 | 95 | const ( 96 | Add Operation = iota + 1 97 | Subtract 98 | Multiply 99 | ) 100 | 101 | const EnvVar = "MY_ENV" 102 | ``` 103 | 104 |
106 | 107 | Groups are not limited in where they can be used. For example, you can use them 108 | inside of functions. 109 | 110 | 111 | 112 | 113 | 140 |
BadGood
114 | 115 | ```go 116 | func f() string { 117 | red := color.New(0xff0000) 118 | green := color.New(0x00ff00) 119 | blue := color.New(0x0000ff) 120 | 121 | // ... 122 | } 123 | ``` 124 | 125 | 126 | 127 | ```go 128 | func f() string { 129 | var ( 130 | red = color.New(0xff0000) 131 | green = color.New(0x00ff00) 132 | blue = color.New(0x0000ff) 133 | ) 134 | 135 | // ... 136 | } 137 | ``` 138 | 139 |
141 | 142 | Exception: Variable declarations, particularly inside functions, should be 143 | grouped together if declared adjacent to other variables. Do this for variables 144 | declared together even if they are unrelated. 145 | 146 | 147 | 148 | 149 | 178 |
BadGood
150 | 151 | ```go 152 | func (c *client) request() { 153 | caller := c.name 154 | format := "json" 155 | timeout := 5*time.Second 156 | var err error 157 | 158 | // ... 159 | } 160 | ``` 161 | 162 | 163 | 164 | ```go 165 | func (c *client) request() { 166 | var ( 167 | caller = c.name 168 | format = "json" 169 | timeout = 5*time.Second 170 | err error 171 | ) 172 | 173 | // ... 174 | } 175 | ``` 176 | 177 |
179 | -------------------------------------------------------------------------------- /src/defer-clean.md: -------------------------------------------------------------------------------- 1 | # Defer to Clean Up 2 | 3 | Use defer to clean up resources such as files and locks. 4 | 5 | 6 | 7 | 8 | 43 |
BadGood
9 | 10 | ```go 11 | p.Lock() 12 | if p.count < 10 { 13 | p.Unlock() 14 | return p.count 15 | } 16 | 17 | p.count++ 18 | newCount := p.count 19 | p.Unlock() 20 | 21 | return newCount 22 | 23 | // easy to miss unlocks due to multiple returns 24 | ``` 25 | 26 | 27 | 28 | ```go 29 | p.Lock() 30 | defer p.Unlock() 31 | 32 | if p.count < 10 { 33 | return p.count 34 | } 35 | 36 | p.count++ 37 | return p.count 38 | 39 | // more readable 40 | ``` 41 | 42 |
44 | 45 | Defer has an extremely small overhead and should be avoided only if you can 46 | prove that your function execution time is in the order of nanoseconds. The 47 | readability win of using defers is worth the miniscule cost of using them. This 48 | is especially true for larger methods that have more than simple memory 49 | accesses, where the other computations are more significant than the `defer`. 50 | -------------------------------------------------------------------------------- /src/else-unnecessary.md: -------------------------------------------------------------------------------- 1 | # Unnecessary Else 2 | 3 | If a variable is set in both branches of an if, it can be replaced with a 4 | single if. 5 | 6 | 7 | 8 | 9 | 30 |
BadGood
10 | 11 | ```go 12 | var a int 13 | if b { 14 | a = 100 15 | } else { 16 | a = 10 17 | } 18 | ``` 19 | 20 | 21 | 22 | ```go 23 | a := 10 24 | if b { 25 | a = 100 26 | } 27 | ``` 28 | 29 |
31 | -------------------------------------------------------------------------------- /src/embed-public.md: -------------------------------------------------------------------------------- 1 | # Avoid Embedding Types in Public Structs 2 | 3 | These embedded types leak implementation details, inhibit type evolution, and 4 | obscure documentation. 5 | 6 | Assuming you have implemented a variety of list types using a shared 7 | `AbstractList`, avoid embedding the `AbstractList` in your concrete list 8 | implementations. 9 | Instead, hand-write only the methods to your concrete list that will delegate 10 | to the abstract list. 11 | 12 | ```go 13 | type AbstractList struct {} 14 | 15 | // Add adds an entity to the list. 16 | func (l *AbstractList) Add(e Entity) { 17 | // ... 18 | } 19 | 20 | // Remove removes an entity from the list. 21 | func (l *AbstractList) Remove(e Entity) { 22 | // ... 23 | } 24 | ``` 25 | 26 | 27 | 28 | 29 | 58 |
BadGood
30 | 31 | ```go 32 | // ConcreteList is a list of entities. 33 | type ConcreteList struct { 34 | *AbstractList 35 | } 36 | ``` 37 | 38 | 39 | 40 | ```go 41 | // ConcreteList is a list of entities. 42 | type ConcreteList struct { 43 | list *AbstractList 44 | } 45 | 46 | // Add adds an entity to the list. 47 | func (l *ConcreteList) Add(e Entity) { 48 | l.list.Add(e) 49 | } 50 | 51 | // Remove removes an entity from the list. 52 | func (l *ConcreteList) Remove(e Entity) { 53 | l.list.Remove(e) 54 | } 55 | ``` 56 | 57 |
59 | 60 | Go allows [type embedding] as a compromise between inheritance and composition. 61 | The outer type gets implicit copies of the embedded type's methods. 62 | These methods, by default, delegate to the same method of the embedded 63 | instance. 64 | 65 | [type embedding]: https://go.dev/doc/effective_go#embedding 66 | 67 | The struct also gains a field by the same name as the type. 68 | So, if the embedded type is public, the field is public. 69 | To maintain backward compatibility, every future version of the outer type must 70 | keep the embedded type. 71 | 72 | An embedded type is rarely necessary. 73 | It is a convenience that helps you avoid writing tedious delegate methods. 74 | 75 | Even embedding a compatible AbstractList *interface*, instead of the struct, 76 | would offer the developer more flexibility to change in the future, but still 77 | leak the detail that the concrete lists use an abstract implementation. 78 | 79 | 80 | 81 | 82 | 118 |
BadGood
83 | 84 | ```go 85 | // AbstractList is a generalized implementation 86 | // for various kinds of lists of entities. 87 | type AbstractList interface { 88 | Add(Entity) 89 | Remove(Entity) 90 | } 91 | 92 | // ConcreteList is a list of entities. 93 | type ConcreteList struct { 94 | AbstractList 95 | } 96 | ``` 97 | 98 | 99 | 100 | ```go 101 | // ConcreteList is a list of entities. 102 | type ConcreteList struct { 103 | list AbstractList 104 | } 105 | 106 | // Add adds an entity to the list. 107 | func (l *ConcreteList) Add(e Entity) { 108 | l.list.Add(e) 109 | } 110 | 111 | // Remove removes an entity from the list. 112 | func (l *ConcreteList) Remove(e Entity) { 113 | l.list.Remove(e) 114 | } 115 | ``` 116 | 117 |
119 | 120 | Either with an embedded struct or an embedded interface, the embedded type 121 | places limits on the evolution of the type. 122 | 123 | - Adding methods to an embedded interface is a breaking change. 124 | - Removing methods from an embedded struct is a breaking change. 125 | - Removing the embedded type is a breaking change. 126 | - Replacing the embedded type, even with an alternative that satisfies the same 127 | interface, is a breaking change. 128 | 129 | Although writing these delegate methods is tedious, the additional effort hides 130 | an implementation detail, leaves more opportunities for change, and also 131 | eliminates indirection for discovering the full List interface in 132 | documentation. 133 | -------------------------------------------------------------------------------- /src/enum-start.md: -------------------------------------------------------------------------------- 1 | # Start Enums at One 2 | 3 | The standard way of introducing enumerations in Go is to declare a custom type 4 | and a `const` group with `iota`. Since variables have a 0 default value, you 5 | should usually start your enums on a non-zero value. 6 | 7 | 8 | 9 | 10 | 39 |
BadGood
11 | 12 | ```go 13 | type Operation int 14 | 15 | const ( 16 | Add Operation = iota 17 | Subtract 18 | Multiply 19 | ) 20 | 21 | // Add=0, Subtract=1, Multiply=2 22 | ``` 23 | 24 | 25 | 26 | ```go 27 | type Operation int 28 | 29 | const ( 30 | Add Operation = iota + 1 31 | Subtract 32 | Multiply 33 | ) 34 | 35 | // Add=1, Subtract=2, Multiply=3 36 | ``` 37 | 38 |
40 | 41 | There are cases where using the zero value makes sense, for example when the 42 | zero value case is the desirable default behavior. 43 | 44 | ```go 45 | type LogOutput int 46 | 47 | const ( 48 | LogToStdout LogOutput = iota 49 | LogToFile 50 | LogToRemote 51 | ) 52 | 53 | // LogToStdout=0, LogToFile=1, LogToRemote=2 54 | ``` 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/error-name.md: -------------------------------------------------------------------------------- 1 | # Error Naming 2 | 3 | For error values stored as global variables, 4 | use the prefix `Err` or `err` depending on whether they're exported. 5 | This guidance supersedes the [Prefix Unexported Globals with _](global-name.md). 6 | 7 | ```go 8 | var ( 9 | // The following two errors are exported 10 | // so that users of this package can match them 11 | // with errors.Is. 12 | 13 | ErrBrokenLink = errors.New("link is broken") 14 | ErrCouldNotOpen = errors.New("could not open") 15 | 16 | // This error is not exported because 17 | // we don't want to make it part of our public API. 18 | // We may still use it inside the package 19 | // with errors.Is. 20 | 21 | errNotFound = errors.New("not found") 22 | ) 23 | ``` 24 | 25 | For custom error types, use the suffix `Error` instead. 26 | 27 | ```go 28 | // Similarly, this error is exported 29 | // so that users of this package can match it 30 | // with errors.As. 31 | 32 | type NotFoundError struct { 33 | File string 34 | } 35 | 36 | func (e *NotFoundError) Error() string { 37 | return fmt.Sprintf("file %q not found", e.File) 38 | } 39 | 40 | // And this error is not exported because 41 | // we don't want to make it part of the public API. 42 | // We can still use it inside the package 43 | // with errors.As. 44 | 45 | type resolveError struct { 46 | Path string 47 | } 48 | 49 | func (e *resolveError) Error() string { 50 | return fmt.Sprintf("resolve %q", e.Path) 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /src/error-once.md: -------------------------------------------------------------------------------- 1 | # Handle Errors Once 2 | 3 | When a caller receives an error from a callee, 4 | it can handle it in a variety of different ways 5 | depending on what it knows about the error. 6 | 7 | These include, but not are limited to: 8 | 9 | - if the callee contract defines specific errors, 10 | matching the error with `errors.Is` or `errors.As` 11 | and handling the branches differently 12 | - if the error is recoverable, 13 | logging the error and degrading gracefully 14 | - if the error represents a domain-specific failure condition, 15 | returning a well-defined error 16 | - returning the error, either [wrapped](error-wrap.md) or verbatim 17 | 18 | Regardless of how the caller handles the error, 19 | it should typically handle each error only once. 20 | The caller should not, for example, log the error and then return it, 21 | because *its* callers may handle the error as well. 22 | 23 | For example, consider the following cases: 24 | 25 | 26 | 27 | 28 | 47 | 65 | 85 | 111 |
DescriptionCode
29 | 30 | **Bad**: Log the error and return it 31 | 32 | Callers further up the stack will likely take a similar action with the error. 33 | Doing so causing a lot of noise in the application logs for little value. 34 | 35 | 36 | 37 | ```go 38 | u, err := getUser(id) 39 | if err != nil { 40 | // BAD: See description 41 | log.Printf("Could not get user %q: %v", id, err) 42 | return err 43 | } 44 | ``` 45 | 46 |
48 | 49 | **Good**: Wrap the error and return it 50 | 51 | Callers further up the stack will handle the error. 52 | Use of `%w` ensures they can match the error with `errors.Is` or `errors.As` 53 | if relevant. 54 | 55 | 56 | 57 | ```go 58 | u, err := getUser(id) 59 | if err != nil { 60 | return fmt.Errorf("get user %q: %w", id, err) 61 | } 62 | ``` 63 | 64 |
66 | 67 | **Good**: Log the error and degrade gracefully 68 | 69 | If the operation isn't strictly necessary, 70 | we can provide a degraded but unbroken experience 71 | by recovering from it. 72 | 73 | 74 | 75 | ```go 76 | if err := emitMetrics(); err != nil { 77 | // Failure to write metrics should not 78 | // break the application. 79 | log.Printf("Could not emit metrics: %v", err) 80 | } 81 | 82 | ``` 83 | 84 |
86 | 87 | **Good**: Match the error and degrade gracefully 88 | 89 | If the callee defines a specific error in its contract, 90 | and the failure is recoverable, 91 | match on that error case and degrade gracefully. 92 | For all other cases, wrap the error and return it. 93 | 94 | Callers further up the stack will handle other errors. 95 | 96 | 97 | 98 | ```go 99 | tz, err := getUserTimeZone(id) 100 | if err != nil { 101 | if errors.Is(err, ErrUserNotFound) { 102 | // User doesn't exist. Use UTC. 103 | tz = time.UTC 104 | } else { 105 | return fmt.Errorf("get user %q: %w", id, err) 106 | } 107 | } 108 | ``` 109 | 110 |
112 | -------------------------------------------------------------------------------- /src/error-type.md: -------------------------------------------------------------------------------- 1 | # Error Types 2 | 3 | There are few options for declaring errors. 4 | Consider the following before picking the option best suited for your use case. 5 | 6 | - Does the caller need to match the error so that they can handle it? 7 | If yes, we must support the [`errors.Is`] or [`errors.As`] functions 8 | by declaring a top-level error variable or a custom type. 9 | - Is the error message a static string, 10 | or is it a dynamic string that requires contextual information? 11 | For the former, we can use [`errors.New`], but for the latter we must 12 | use [`fmt.Errorf`] or a custom error type. 13 | - Are we propagating a new error returned by a downstream function? 14 | If so, see the [section on error wrapping](error-wrap.md). 15 | 16 | [`errors.Is`]: https://pkg.go.dev/errors#Is 17 | [`errors.As`]: https://pkg.go.dev/errors#As 18 | 19 | | Error matching? | Error Message | Guidance | 20 | |-----------------|---------------|-------------------------------------| 21 | | No | static | [`errors.New`] | 22 | | No | dynamic | [`fmt.Errorf`] | 23 | | Yes | static | top-level `var` with [`errors.New`] | 24 | | Yes | dynamic | custom `error` type | 25 | 26 | [`errors.New`]: https://pkg.go.dev/errors#New 27 | [`fmt.Errorf`]: https://pkg.go.dev/fmt#Errorf 28 | 29 | For example, 30 | use [`errors.New`] for an error with a static string. 31 | Export this error as a variable to support matching it with `errors.Is` 32 | if the caller needs to match and handle this error. 33 | 34 | 35 | 36 | 37 | 77 |
No error matchingError matching
38 | 39 | ```go 40 | // package foo 41 | 42 | func Open() error { 43 | return errors.New("could not open") 44 | } 45 | 46 | // package bar 47 | 48 | if err := foo.Open(); err != nil { 49 | // Can't handle the error. 50 | panic("unknown error") 51 | } 52 | ``` 53 | 54 | 55 | 56 | ```go 57 | // package foo 58 | 59 | var ErrCouldNotOpen = errors.New("could not open") 60 | 61 | func Open() error { 62 | return ErrCouldNotOpen 63 | } 64 | 65 | // package bar 66 | 67 | if err := foo.Open(); err != nil { 68 | if errors.Is(err, foo.ErrCouldNotOpen) { 69 | // handle the error 70 | } else { 71 | panic("unknown error") 72 | } 73 | } 74 | ``` 75 | 76 |
78 | 79 | For an error with a dynamic string, 80 | use [`fmt.Errorf`] if the caller does not need to match it, 81 | and a custom `error` if the caller does need to match it. 82 | 83 | 84 | 85 | 86 | 134 |
No error matchingError matching
87 | 88 | ```go 89 | // package foo 90 | 91 | func Open(file string) error { 92 | return fmt.Errorf("file %q not found", file) 93 | } 94 | 95 | // package bar 96 | 97 | if err := foo.Open("testfile.txt"); err != nil { 98 | // Can't handle the error. 99 | panic("unknown error") 100 | } 101 | ``` 102 | 103 | 104 | 105 | ```go 106 | // package foo 107 | 108 | type NotFoundError struct { 109 | File string 110 | } 111 | 112 | func (e *NotFoundError) Error() string { 113 | return fmt.Sprintf("file %q not found", e.File) 114 | } 115 | 116 | func Open(file string) error { 117 | return &NotFoundError{File: file} 118 | } 119 | 120 | 121 | // package bar 122 | 123 | if err := foo.Open("testfile.txt"); err != nil { 124 | var notFound *NotFoundError 125 | if errors.As(err, ¬Found) { 126 | // handle the error 127 | } else { 128 | panic("unknown error") 129 | } 130 | } 131 | ``` 132 | 133 |
135 | 136 | Note that if you export error variables or types from a package, 137 | they will become part of the public API of the package. 138 | -------------------------------------------------------------------------------- /src/error-wrap.md: -------------------------------------------------------------------------------- 1 | # Error Wrapping 2 | 3 | There are three main options for propagating errors if a call fails: 4 | 5 | - return the original error as-is 6 | - add context with `fmt.Errorf` and the `%w` verb 7 | - add context with `fmt.Errorf` and the `%v` verb 8 | 9 | Return the original error as-is if there is no additional context to add. 10 | This maintains the original error type and message. 11 | This is well suited for cases when the underlying error message 12 | has sufficient information to track down where it came from. 13 | 14 | Otherwise, add context to the error message where possible 15 | so that instead of a vague error such as "connection refused", 16 | you get more useful errors such as "call service foo: connection refused". 17 | 18 | Use `fmt.Errorf` to add context to your errors, 19 | picking between the `%w` or `%v` verbs 20 | based on whether the caller should be able to 21 | match and extract the underlying cause. 22 | 23 | - Use `%w` if the caller should have access to the underlying error. 24 | This is a good default for most wrapped errors, 25 | but be aware that callers may begin to rely on this behavior. 26 | So for cases where the wrapped error is a known `var` or type, 27 | document and test it as part of your function's contract. 28 | - Use `%v` to obfuscate the underlying error. 29 | Callers will be unable to match it, 30 | but you can switch to `%w` in the future if needed. 31 | 32 | When adding context to returned errors, keep the context succinct by avoiding 33 | phrases like "failed to", which state the obvious and pile up as the error 34 | percolates up through the stack: 35 | 36 | 37 | 38 | 39 | 72 |
BadGood
40 | 41 | ```go 42 | s, err := store.New() 43 | if err != nil { 44 | return fmt.Errorf( 45 | "failed to create new store: %w", err) 46 | } 47 | ``` 48 | 49 | 50 | 51 | ```go 52 | s, err := store.New() 53 | if err != nil { 54 | return fmt.Errorf( 55 | "new store: %w", err) 56 | } 57 | ``` 58 | 59 |
60 | 61 | ```plain 62 | failed to x: failed to y: failed to create new store: the error 63 | ``` 64 | 65 | 66 | 67 | ```plain 68 | x: y: new store: the error 69 | ``` 70 | 71 |
73 | 74 | However once the error is sent to another system, it should be clear the 75 | message is an error (e.g. an `err` tag or "Failed" prefix in logs). 76 | 77 | See also [Don't just check errors, handle them gracefully]. 78 | 79 | [Don't just check errors, handle them gracefully]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 80 | -------------------------------------------------------------------------------- /src/exit-main.md: -------------------------------------------------------------------------------- 1 | # Exit in Main 2 | 3 | Go programs use [`os.Exit`] or [`log.Fatal*`] to exit immediately. (Panicking 4 | is not a good way to exit programs, please [don't panic](panic.md).) 5 | 6 | [`os.Exit`]: https://pkg.go.dev/os#Exit 7 | [`log.Fatal*`]: https://pkg.go.dev/log#Fatal 8 | 9 | Call one of `os.Exit` or `log.Fatal*` **only in `main()`**. All other 10 | functions should return errors to signal failure. 11 | 12 | 13 | 14 | 15 | 65 |
BadGood
16 | 17 | ```go 18 | func main() { 19 | body := readFile(path) 20 | fmt.Println(body) 21 | } 22 | 23 | func readFile(path string) string { 24 | f, err := os.Open(path) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | b, err := io.ReadAll(f) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | return string(b) 35 | } 36 | ``` 37 | 38 | 39 | 40 | ```go 41 | func main() { 42 | body, err := readFile(path) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | fmt.Println(body) 47 | } 48 | 49 | func readFile(path string) (string, error) { 50 | f, err := os.Open(path) 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | b, err := io.ReadAll(f) 56 | if err != nil { 57 | return "", err 58 | } 59 | 60 | return string(b), nil 61 | } 62 | ``` 63 | 64 |
66 | 67 | Rationale: Programs with multiple functions that exit present a few issues: 68 | 69 | - Non-obvious control flow: Any function can exit the program so it becomes 70 | difficult to reason about the control flow. 71 | - Difficult to test: A function that exits the program will also exit the test 72 | calling it. This makes the function difficult to test and introduces risk of 73 | skipping other tests that have not yet been run by `go test`. 74 | - Skipped cleanup: When a function exits the program, it skips function calls 75 | enqueued with `defer` statements. This adds risk of skipping important 76 | cleanup tasks. 77 | -------------------------------------------------------------------------------- /src/exit-once.md: -------------------------------------------------------------------------------- 1 | # Exit Once 2 | 3 | If possible, prefer to call `os.Exit` or `log.Fatal` **at most once** in your 4 | `main()`. If there are multiple error scenarios that halt program execution, 5 | put that logic under a separate function and return errors from it. 6 | 7 | This has the effect of shortening your `main()` function and putting all key 8 | business logic into a separate, testable function. 9 | 10 | 11 | 12 | 13 | 77 |
BadGood
14 | 15 | ```go 16 | package main 17 | 18 | func main() { 19 | args := os.Args[1:] 20 | if len(args) != 1 { 21 | log.Fatal("missing file") 22 | } 23 | name := args[0] 24 | 25 | f, err := os.Open(name) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | defer f.Close() 30 | 31 | // If we call log.Fatal after this line, 32 | // f.Close will not be called. 33 | 34 | b, err := io.ReadAll(f) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // ... 40 | } 41 | ``` 42 | 43 | 44 | 45 | ```go 46 | package main 47 | 48 | func main() { 49 | if err := run(); err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | 54 | func run() error { 55 | args := os.Args[1:] 56 | if len(args) != 1 { 57 | return errors.New("missing file") 58 | } 59 | name := args[0] 60 | 61 | f, err := os.Open(name) 62 | if err != nil { 63 | return err 64 | } 65 | defer f.Close() 66 | 67 | b, err := io.ReadAll(f) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | // ... 73 | } 74 | ``` 75 | 76 |
78 | 79 | The example above uses `log.Fatal`, but the guidance also applies to 80 | `os.Exit` or any library code that calls `os.Exit`. 81 | 82 | ```go 83 | func main() { 84 | if err := run(); err != nil { 85 | fmt.Fprintln(os.Stderr, err) 86 | os.Exit(1) 87 | } 88 | } 89 | ``` 90 | 91 | You may alter the signature of `run()` to fit your needs. 92 | For example, if your program must exit with specific exit codes for failures, 93 | `run()` may return the exit code instead of an error. 94 | This allows unit tests to verify this behavior directly as well. 95 | 96 | ```go 97 | func main() { 98 | os.Exit(run(args)) 99 | } 100 | 101 | func run() (exitCode int) { 102 | // ... 103 | } 104 | ``` 105 | 106 | More generally, note that the `run()` function used in these examples 107 | is not intended to be prescriptive. 108 | There's flexibility in the name, signature, and setup of the `run()` function. 109 | Among other things, you may: 110 | 111 | - accept unparsed command line arguments (e.g., `run(os.Args[1:])`) 112 | - parse command line arguments in `main()` and pass them onto `run` 113 | - use a custom error type to carry the exit code back to `main()` 114 | - put business logic in a different layer of abstraction from `package main` 115 | 116 | This guidance only requires that there's a single place in your `main()` 117 | responsible for actually exiting the process. 118 | -------------------------------------------------------------------------------- /src/function-name.md: -------------------------------------------------------------------------------- 1 | # Function Names 2 | 3 | We follow the Go community's convention of using [MixedCaps for function 4 | names]. An exception is made for test functions, which may contain underscores 5 | for the purpose of grouping related test cases, e.g., 6 | `TestMyFunction_WhatIsBeingTested`. 7 | 8 | [MixedCaps for function names]: https://go.dev/doc/effective_go#mixed-caps 9 | -------------------------------------------------------------------------------- /src/function-order.md: -------------------------------------------------------------------------------- 1 | # Function Grouping and Ordering 2 | 3 | - Functions should be sorted in rough call order. 4 | - Functions in a file should be grouped by receiver. 5 | 6 | Therefore, exported functions should appear first in a file, after 7 | `struct`, `const`, `var` definitions. 8 | 9 | A `newXYZ()`/`NewXYZ()` may appear after the type is defined, but before the 10 | rest of the methods on the receiver. 11 | 12 | Since functions are grouped by receiver, plain utility functions should appear 13 | towards the end of the file. 14 | 15 | 16 | 17 | 18 | 55 |
BadGood
19 | 20 | ```go 21 | func (s *something) Cost() { 22 | return calcCost(s.weights) 23 | } 24 | 25 | type something struct{ ... } 26 | 27 | func calcCost(n []int) int {...} 28 | 29 | func (s *something) Stop() {...} 30 | 31 | func newSomething() *something { 32 | return &something{} 33 | } 34 | ``` 35 | 36 | 37 | 38 | ```go 39 | type something struct{ ... } 40 | 41 | func newSomething() *something { 42 | return &something{} 43 | } 44 | 45 | func (s *something) Cost() { 46 | return calcCost(s.weights) 47 | } 48 | 49 | func (s *something) Stop() {...} 50 | 51 | func calcCost(n []int) int {...} 52 | ``` 53 | 54 |
56 | -------------------------------------------------------------------------------- /src/functional-option.md: -------------------------------------------------------------------------------- 1 | # Functional Options 2 | 3 | Functional options is a pattern in which you declare an opaque `Option` type 4 | that records information in some internal struct. You accept a variadic number 5 | of these options and act upon the full information recorded by the options on 6 | the internal struct. 7 | 8 | Use this pattern for optional arguments in constructors and other public APIs 9 | that you foresee needing to expand, especially if you already have three or 10 | more arguments on those functions. 11 | 12 | 13 | 14 | 15 | 56 | 84 |
BadGood
16 | 17 | ```go 18 | // package db 19 | 20 | func Open( 21 | addr string, 22 | cache bool, 23 | logger *zap.Logger 24 | ) (*Connection, error) { 25 | // ... 26 | } 27 | ``` 28 | 29 | 30 | 31 | ```go 32 | // package db 33 | 34 | type Option interface { 35 | // ... 36 | } 37 | 38 | func WithCache(c bool) Option { 39 | // ... 40 | } 41 | 42 | func WithLogger(log *zap.Logger) Option { 43 | // ... 44 | } 45 | 46 | // Open creates a connection. 47 | func Open( 48 | addr string, 49 | opts ...Option, 50 | ) (*Connection, error) { 51 | // ... 52 | } 53 | ``` 54 | 55 |
57 | 58 | The cache and logger parameters must always be provided, even if the user 59 | wants to use the default. 60 | 61 | ```go 62 | db.Open(addr, db.DefaultCache, zap.NewNop()) 63 | db.Open(addr, db.DefaultCache, log) 64 | db.Open(addr, false /* cache */, zap.NewNop()) 65 | db.Open(addr, false /* cache */, log) 66 | ``` 67 | 68 | 69 | 70 | Options are provided only if needed. 71 | 72 | ```go 73 | db.Open(addr) 74 | db.Open(addr, db.WithLogger(log)) 75 | db.Open(addr, db.WithCache(false)) 76 | db.Open( 77 | addr, 78 | db.WithCache(false), 79 | db.WithLogger(log), 80 | ) 81 | ``` 82 | 83 |
85 | 86 | Our suggested way of implementing this pattern is with an `Option` interface 87 | that holds an unexported method, recording options on an unexported `options` 88 | struct. 89 | 90 | ```go 91 | type options struct { 92 | cache bool 93 | logger *zap.Logger 94 | } 95 | 96 | type Option interface { 97 | apply(*options) 98 | } 99 | 100 | type cacheOption bool 101 | 102 | func (c cacheOption) apply(opts *options) { 103 | opts.cache = bool(c) 104 | } 105 | 106 | func WithCache(c bool) Option { 107 | return cacheOption(c) 108 | } 109 | 110 | type loggerOption struct { 111 | Log *zap.Logger 112 | } 113 | 114 | func (l loggerOption) apply(opts *options) { 115 | opts.logger = l.Log 116 | } 117 | 118 | func WithLogger(log *zap.Logger) Option { 119 | return loggerOption{Log: log} 120 | } 121 | 122 | // Open creates a connection. 123 | func Open( 124 | addr string, 125 | opts ...Option, 126 | ) (*Connection, error) { 127 | options := options{ 128 | cache: defaultCache, 129 | logger: zap.NewNop(), 130 | } 131 | 132 | for _, o := range opts { 133 | o.apply(&options) 134 | } 135 | 136 | // ... 137 | } 138 | ``` 139 | 140 | Note that there's a method of implementing this pattern with closures but we 141 | believe that the pattern above provides more flexibility for authors and is 142 | easier to debug and test for users. In particular, it allows options to be 143 | compared against each other in tests and mocks, versus closures where this is 144 | impossible. Further, it lets options implement other interfaces, including 145 | `fmt.Stringer` which allows for user-readable string representations of the 146 | options. 147 | 148 | See also, 149 | 150 | - [Self-referential functions and the design of options] 151 | - [Functional options for friendly APIs] 152 | 153 | [Self-referential functions and the design of options]: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 154 | [Functional options for friendly APIs]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 155 | 156 | 158 | -------------------------------------------------------------------------------- /src/global-decl.md: -------------------------------------------------------------------------------- 1 | # Top-level Variable Declarations 2 | 3 | At the top level, use the standard `var` keyword. Do not specify the type, 4 | unless it is not the same type as the expression. 5 | 6 | 7 | 8 | 9 | 28 |
BadGood
10 | 11 | ```go 12 | var _s string = F() 13 | 14 | func F() string { return "A" } 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | var _s = F() 21 | // Since F already states that it returns a string, we don't need to specify 22 | // the type again. 23 | 24 | func F() string { return "A" } 25 | ``` 26 | 27 |
29 | 30 | Specify the type if the type of the expression does not match the desired type 31 | exactly. 32 | 33 | ```go 34 | type myError struct{} 35 | 36 | func (myError) Error() string { return "error" } 37 | 38 | func F() myError { return myError{} } 39 | 40 | var _e error = F() 41 | // F returns an object of type myError but we want error. 42 | ``` 43 | -------------------------------------------------------------------------------- /src/global-mut.md: -------------------------------------------------------------------------------- 1 | # Avoid Mutable Globals 2 | 3 | Avoid mutating global variables, instead opting for dependency injection. 4 | This applies to function pointers as well as other kinds of values. 5 | 6 | 7 | 8 | 9 | 44 | 76 |
BadGood
10 | 11 | ```go 12 | // sign.go 13 | 14 | var _timeNow = time.Now 15 | 16 | func sign(msg string) string { 17 | now := _timeNow() 18 | return signWithTime(msg, now) 19 | } 20 | ``` 21 | 22 | 23 | 24 | ```go 25 | // sign.go 26 | 27 | type signer struct { 28 | now func() time.Time 29 | } 30 | 31 | func newSigner() *signer { 32 | return &signer{ 33 | now: time.Now, 34 | } 35 | } 36 | 37 | func (s *signer) Sign(msg string) string { 38 | now := s.now() 39 | return signWithTime(msg, now) 40 | } 41 | ``` 42 | 43 |
45 | 46 | ```go 47 | // sign_test.go 48 | 49 | func TestSign(t *testing.T) { 50 | oldTimeNow := _timeNow 51 | _timeNow = func() time.Time { 52 | return someFixedTime 53 | } 54 | defer func() { _timeNow = oldTimeNow }() 55 | 56 | assert.Equal(t, want, sign(give)) 57 | } 58 | ``` 59 | 60 | 61 | 62 | ```go 63 | // sign_test.go 64 | 65 | func TestSigner(t *testing.T) { 66 | s := newSigner() 67 | s.now = func() time.Time { 68 | return someFixedTime 69 | } 70 | 71 | assert.Equal(t, want, s.Sign(give)) 72 | } 73 | ``` 74 | 75 |
77 | -------------------------------------------------------------------------------- /src/global-name.md: -------------------------------------------------------------------------------- 1 | # Prefix Unexported Globals with _ 2 | 3 | Prefix unexported top-level `var`s and `const`s with `_` to make it clear when 4 | they are used that they are global symbols. 5 | 6 | Rationale: Top-level variables and constants have a package scope. Using a 7 | generic name makes it easy to accidentally use the wrong value in a different 8 | file. 9 | 10 | 11 | 12 | 13 | 47 |
BadGood
14 | 15 | ```go 16 | // foo.go 17 | 18 | const ( 19 | defaultPort = 8080 20 | defaultUser = "user" 21 | ) 22 | 23 | // bar.go 24 | 25 | func Bar() { 26 | defaultPort := 9090 27 | ... 28 | fmt.Println("Default port", defaultPort) 29 | 30 | // We will not see a compile error if the first line of 31 | // Bar() is deleted. 32 | } 33 | ``` 34 | 35 | 36 | 37 | ```go 38 | // foo.go 39 | 40 | const ( 41 | _defaultPort = 8080 42 | _defaultUser = "user" 43 | ) 44 | ``` 45 | 46 |
48 | 49 | **Exception**: Unexported error values may use the prefix `err` without the underscore. 50 | See [Error Naming](error-name.md). 51 | -------------------------------------------------------------------------------- /src/goroutine-exit.md: -------------------------------------------------------------------------------- 1 | # Wait for goroutines to exit 2 | 3 | Given a goroutine spawned by the system, 4 | there must be a way to wait for the goroutine to exit. 5 | There are two popular ways to do this: 6 | 7 | - Use a `sync.WaitGroup`. 8 | Do this if there are multiple goroutines that you want to wait for 9 | 10 | ```go 11 | var wg sync.WaitGroup 12 | for i := 0; i < N; i++ { 13 | wg.Add(1) 14 | go func() { 15 | defer wg.Done() 16 | // ... 17 | }() 18 | } 19 | 20 | // To wait for all to finish: 21 | wg.Wait() 22 | ``` 23 | 24 | - Add another `chan struct{}` that the goroutine closes when it's done. 25 | Do this if there's only one goroutine. 26 | 27 | ```go 28 | done := make(chan struct{}) 29 | go func() { 30 | defer close(done) 31 | // ... 32 | }() 33 | 34 | // To wait for the goroutine to finish: 35 | <-done 36 | ``` 37 | -------------------------------------------------------------------------------- /src/goroutine-forget.md: -------------------------------------------------------------------------------- 1 | # Don't fire-and-forget goroutines 2 | 3 | Goroutines are lightweight, but they're not free: 4 | at minimum, they cost memory for their stack and CPU to be scheduled. 5 | While these costs are small for typical uses of goroutines, 6 | they can cause significant performance issues 7 | when spawned in large numbers without controlled lifetimes. 8 | Goroutines with unmanaged lifetimes can also cause other issues 9 | like preventing unused objects from being garbage collected 10 | and holding onto resources that are otherwise no longer used. 11 | 12 | Therefore, do not leak goroutines in production code. 13 | Use [go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak) 14 | to test for goroutine leaks inside packages that may spawn goroutines. 15 | 16 | In general, every goroutine: 17 | 18 | - must have a predictable time at which it will stop running; or 19 | - there must be a way to signal to the goroutine that it should stop 20 | 21 | In both cases, there must be a way code to block and wait for the goroutine to 22 | finish. 23 | 24 | For example: 25 | 26 | 27 | 28 | 29 | 68 | 79 |
BadGood
30 | 31 | ```go 32 | go func() { 33 | for { 34 | flush() 35 | time.Sleep(delay) 36 | } 37 | }() 38 | ``` 39 | 40 | 41 | 42 | ```go 43 | var ( 44 | stop = make(chan struct{}) // tells the goroutine to stop 45 | done = make(chan struct{}) // tells us that the goroutine exited 46 | ) 47 | go func() { 48 | defer close(done) 49 | 50 | ticker := time.NewTicker(delay) 51 | defer ticker.Stop() 52 | for { 53 | select { 54 | case <-ticker.C: 55 | flush() 56 | case <-stop: 57 | return 58 | } 59 | } 60 | }() 61 | 62 | // Elsewhere... 63 | close(stop) // signal the goroutine to stop 64 | <-done // and wait for it to exit 65 | ``` 66 | 67 |
69 | 70 | There's no way to stop this goroutine. 71 | This will run until the application exits. 72 | 73 | 74 | 75 | This goroutine can be stopped with `close(stop)`, 76 | and we can wait for it to exit with `<-done`. 77 | 78 |
80 | -------------------------------------------------------------------------------- /src/goroutine-init.md: -------------------------------------------------------------------------------- 1 | # No goroutines in `init()` 2 | 3 | `init()` functions should not spawn goroutines. 4 | See also [Avoid init()](init.md). 5 | 6 | If a package has need of a background goroutine, 7 | it must expose an object that is responsible for managing a goroutine's 8 | lifetime. 9 | The object must provide a method (`Close`, `Stop`, `Shutdown`, etc) 10 | that signals the background goroutine to stop, and waits for it to exit. 11 | 12 | 13 | 14 | 15 | 61 | 77 |
BadGood
16 | 17 | ```go 18 | func init() { 19 | go doWork() 20 | } 21 | 22 | func doWork() { 23 | for { 24 | // ... 25 | } 26 | } 27 | ``` 28 | 29 | 30 | 31 | ```go 32 | type Worker struct{ /* ... */ } 33 | 34 | func NewWorker(...) *Worker { 35 | w := &Worker{ 36 | stop: make(chan struct{}), 37 | done: make(chan struct{}), 38 | // ... 39 | } 40 | go w.doWork() 41 | } 42 | 43 | func (w *Worker) doWork() { 44 | defer close(w.done) 45 | for { 46 | // ... 47 | case <-w.stop: 48 | return 49 | } 50 | } 51 | 52 | // Shutdown tells the worker to stop 53 | // and waits until it has finished. 54 | func (w *Worker) Shutdown() { 55 | close(w.stop) 56 | <-w.done 57 | } 58 | ``` 59 | 60 |
62 | 63 | Spawns a background goroutine unconditionally when the user exports this package. 64 | The user has no control over the goroutine or a means of stopping it. 65 | 66 | 67 | 68 | Spawns the worker only if the user requests it. 69 | Provides a means of shutting down the worker so that the user can free up 70 | resources used by the worker. 71 | 72 | Note that you should use `WaitGroup`s if the worker manages multiple 73 | goroutines. 74 | See [Wait for goroutines to exit](goroutine-exit.md). 75 | 76 |
78 | -------------------------------------------------------------------------------- /src/import-alias.md: -------------------------------------------------------------------------------- 1 | # Import Aliasing 2 | 3 | Import aliasing must be used if the package name does not match the last 4 | element of the import path. 5 | 6 | ```go 7 | import ( 8 | "net/http" 9 | 10 | client "example.com/client-go" 11 | trace "example.com/trace/v2" 12 | ) 13 | ``` 14 | 15 | In all other scenarios, import aliases should be avoided unless there is a 16 | direct conflict between imports. 17 | 18 | 19 | 20 | 21 | 46 |
BadGood
22 | 23 | ```go 24 | import ( 25 | "fmt" 26 | "os" 27 | 28 | 29 | nettrace "golang.net/x/trace" 30 | ) 31 | ``` 32 | 33 | 34 | 35 | ```go 36 | import ( 37 | "fmt" 38 | "os" 39 | "runtime/trace" 40 | 41 | nettrace "golang.net/x/trace" 42 | ) 43 | ``` 44 | 45 |
47 | -------------------------------------------------------------------------------- /src/import-group.md: -------------------------------------------------------------------------------- 1 | # Import Group Ordering 2 | 3 | There should be two import groups: 4 | 5 | - Standard library 6 | - Everything else 7 | 8 | This is the grouping applied by goimports by default. 9 | 10 | 11 | 12 | 13 | 37 |
BadGood
14 | 15 | ```go 16 | import ( 17 | "fmt" 18 | "os" 19 | "go.uber.org/atomic" 20 | "golang.org/x/sync/errgroup" 21 | ) 22 | ``` 23 | 24 | 25 | 26 | ```go 27 | import ( 28 | "fmt" 29 | "os" 30 | 31 | "go.uber.org/atomic" 32 | "golang.org/x/sync/errgroup" 33 | ) 34 | ``` 35 | 36 |
38 | -------------------------------------------------------------------------------- /src/init.md: -------------------------------------------------------------------------------- 1 | # Avoid `init()` 2 | 3 | Avoid `init()` where possible. When `init()` is unavoidable or desirable, code 4 | should attempt to: 5 | 6 | 1. Be completely deterministic, regardless of program environment or invocation. 7 | 2. Avoid depending on the ordering or side-effects of other `init()` functions. 8 | While `init()` ordering is well-known, code can change, and thus 9 | relationships between `init()` functions can make code brittle and 10 | error-prone. 11 | 3. Avoid accessing or manipulating global or environment state, such as machine 12 | information, environment variables, working directory, program 13 | arguments/inputs, etc. 14 | 4. Avoid I/O, including both filesystem, network, and system calls. 15 | 16 | Code that cannot satisfy these requirements likely belongs as a helper to be 17 | called as part of `main()` (or elsewhere in a program's lifecycle), or be 18 | written as part of `main()` itself. In particular, libraries that are intended 19 | to be used by other programs should take special care to be completely 20 | deterministic and not perform "init magic". 21 | 22 | 23 | 24 | 25 | 60 | 106 |
BadGood
26 | 27 | ```go 28 | type Foo struct { 29 | // ... 30 | } 31 | 32 | var _defaultFoo Foo 33 | 34 | func init() { 35 | _defaultFoo = Foo{ 36 | // ... 37 | } 38 | } 39 | ``` 40 | 41 | 42 | 43 | ```go 44 | var _defaultFoo = Foo{ 45 | // ... 46 | } 47 | 48 | // or, better, for testability: 49 | 50 | var _defaultFoo = defaultFoo() 51 | 52 | func defaultFoo() Foo { 53 | return Foo{ 54 | // ... 55 | } 56 | } 57 | ``` 58 | 59 |
61 | 62 | ```go 63 | type Config struct { 64 | // ... 65 | } 66 | 67 | var _config Config 68 | 69 | func init() { 70 | // Bad: based on current directory 71 | cwd, _ := os.Getwd() 72 | 73 | // Bad: I/O 74 | raw, _ := os.ReadFile( 75 | path.Join(cwd, "config", "config.yaml"), 76 | ) 77 | 78 | yaml.Unmarshal(raw, &_config) 79 | } 80 | ``` 81 | 82 | 83 | 84 | ```go 85 | type Config struct { 86 | // ... 87 | } 88 | 89 | func loadConfig() Config { 90 | cwd, err := os.Getwd() 91 | // handle err 92 | 93 | raw, err := os.ReadFile( 94 | path.Join(cwd, "config", "config.yaml"), 95 | ) 96 | // handle err 97 | 98 | var config Config 99 | yaml.Unmarshal(raw, &config) 100 | 101 | return config 102 | } 103 | ``` 104 | 105 |
107 | 108 | Considering the above, some situations in which `init()` may be preferable or 109 | necessary might include: 110 | 111 | - Complex expressions that cannot be represented as single assignments. 112 | - Pluggable hooks, such as `database/sql` dialects, encoding type registries, etc. 113 | - Optimizations to [Google Cloud Functions] and other forms of deterministic 114 | precomputation. 115 | 116 | [Google Cloud Functions]: https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations 117 | -------------------------------------------------------------------------------- /src/interface-compliance.md: -------------------------------------------------------------------------------- 1 | # Verify Interface Compliance 2 | 3 | Verify interface compliance at compile time where appropriate. This includes: 4 | 5 | - Exported types that are required to implement specific interfaces as part of 6 | their API contract 7 | - Exported or unexported types that are part of a collection of types 8 | implementing the same interface 9 | - Other cases where violating an interface would break users 10 | 11 | 12 | 13 | 14 | 49 |
BadGood
15 | 16 | ```go 17 | type Handler struct { 18 | // ... 19 | } 20 | 21 | 22 | 23 | func (h *Handler) ServeHTTP( 24 | w http.ResponseWriter, 25 | r *http.Request, 26 | ) { 27 | ... 28 | } 29 | ``` 30 | 31 | 32 | 33 | ```go 34 | type Handler struct { 35 | // ... 36 | } 37 | 38 | var _ http.Handler = (*Handler)(nil) 39 | 40 | func (h *Handler) ServeHTTP( 41 | w http.ResponseWriter, 42 | r *http.Request, 43 | ) { 44 | // ... 45 | } 46 | ``` 47 | 48 |
50 | 51 | The statement `var _ http.Handler = (*Handler)(nil)` will fail to compile if 52 | `*Handler` ever stops matching the `http.Handler` interface. 53 | 54 | The right hand side of the assignment should be the zero value of the asserted 55 | type. This is `nil` for pointer types (like `*Handler`), slices, and maps, and 56 | an empty struct for struct types. 57 | 58 | ```go 59 | type LogHandler struct { 60 | h http.Handler 61 | log *zap.Logger 62 | } 63 | 64 | var _ http.Handler = LogHandler{} 65 | 66 | func (h LogHandler) ServeHTTP( 67 | w http.ResponseWriter, 68 | r *http.Request, 69 | ) { 70 | // ... 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /src/interface-pointer.md: -------------------------------------------------------------------------------- 1 | # Pointers to Interfaces 2 | 3 | You almost never need a pointer to an interface. You should be passing 4 | interfaces as values—the underlying data can still be a pointer. 5 | 6 | An interface is two fields: 7 | 8 | 1. A pointer to some type-specific information. You can think of this as 9 | "type." 10 | 2. Data pointer. If the data stored is a pointer, it’s stored directly. If 11 | the data stored is a value, then a pointer to the value is stored. 12 | 13 | If you want interface methods to modify the underlying data, you must use a 14 | pointer. 15 | -------------------------------------------------------------------------------- /src/interface-receiver.md: -------------------------------------------------------------------------------- 1 | # Receivers and Interfaces 2 | 3 | Methods with value receivers can be called on pointers as well as values. 4 | Methods with pointer receivers can only be called on pointers or [addressable values]. 5 | 6 | [addressable values]: https://go.dev/ref/spec#Method_values 7 | 8 | For example, 9 | 10 | ```go 11 | type S struct { 12 | data string 13 | } 14 | 15 | func (s S) Read() string { 16 | return s.data 17 | } 18 | 19 | func (s *S) Write(str string) { 20 | s.data = str 21 | } 22 | 23 | // We cannot get pointers to values stored in maps, because they are not 24 | // addressable values. 25 | sVals := map[int]S{1: {"A"}} 26 | 27 | // We can call Read on values stored in the map because Read 28 | // has a value receiver, which does not require the value to 29 | // be addressable. 30 | sVals[1].Read() 31 | 32 | // We cannot call Write on values stored in the map because Write 33 | // has a pointer receiver, and it's not possible to get a pointer 34 | // to a value stored in a map. 35 | // 36 | // sVals[1].Write("test") 37 | 38 | sPtrs := map[int]*S{1: {"A"}} 39 | 40 | // You can call both Read and Write if the map stores pointers, 41 | // because pointers are intrinsically addressable. 42 | sPtrs[1].Read() 43 | sPtrs[1].Write("test") 44 | ``` 45 | 46 | Similarly, an interface can be satisfied by a pointer, even if the method has a 47 | value receiver. 48 | 49 | ```go 50 | type F interface { 51 | f() 52 | } 53 | 54 | type S1 struct{} 55 | 56 | func (s S1) f() {} 57 | 58 | type S2 struct{} 59 | 60 | func (s *S2) f() {} 61 | 62 | s1Val := S1{} 63 | s1Ptr := &S1{} 64 | s2Val := S2{} 65 | s2Ptr := &S2{} 66 | 67 | var i F 68 | i = s1Val 69 | i = s1Ptr 70 | i = s2Ptr 71 | 72 | // The following doesn't compile, since s2Val is a value, and there is no value receiver for f. 73 | // i = s2Val 74 | ``` 75 | 76 | Effective Go has a good write up on [Pointers vs. Values]. 77 | 78 | [Pointers vs. Values]: https://go.dev/doc/effective_go#pointers_vs_values 79 | -------------------------------------------------------------------------------- /src/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Styles are the conventions that govern our code. The term style is a bit of a 4 | misnomer, since these conventions cover far more than just source file 5 | formatting—gofmt handles that for us. 6 | 7 | The goal of this guide is to manage this complexity by describing in detail the 8 | Dos and Don'ts of writing Go code at Uber. These rules exist to keep the code 9 | base manageable while still allowing engineers to use Go language features 10 | productively. 11 | 12 | This guide was originally created by [Prashant Varanasi] and [Simon Newton] as 13 | a way to bring some colleagues up to speed with using Go. Over the years it has 14 | been amended based on feedback from others. 15 | 16 | [Prashant Varanasi]: https://github.com/prashantv 17 | [Simon Newton]: https://github.com/nomis52 18 | 19 | This documents idiomatic conventions in Go code that we follow at Uber. A lot 20 | of these are general guidelines for Go, while others extend upon external 21 | resources: 22 | 23 | 1. [Effective Go](https://go.dev/doc/effective_go) 24 | 2. [Go Common Mistakes](https://go.dev/wiki/CommonMistakes) 25 | 3. [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) 26 | 27 | We aim for the code samples to be accurate for the two most recent minor versions 28 | of Go [releases](https://go.dev/doc/devel/release). 29 | 30 | All code should be error-free when run through `golint` and `go vet`. We 31 | recommend setting up your editor to: 32 | 33 | - Run `goimports` on save 34 | - Run `golint` and `go vet` to check for errors 35 | 36 | You can find information in editor support for Go tools here: 37 | 38 | -------------------------------------------------------------------------------- /src/line-length.md: -------------------------------------------------------------------------------- 1 | # Avoid overly long lines 2 | 3 | Avoid lines of code that require readers to scroll horizontally 4 | or turn their heads too much. 5 | 6 | We recommend a soft line length limit of **99 characters**. 7 | Authors should aim to wrap lines before hitting this limit, 8 | but it is not a hard limit. 9 | Code is allowed to exceed this limit. 10 | -------------------------------------------------------------------------------- /src/lint.md: -------------------------------------------------------------------------------- 1 | # Linting 2 | 3 | More importantly than any "blessed" set of linters, lint consistently across a 4 | codebase. 5 | 6 | We recommend using the following linters at a minimum, because we feel that they 7 | help to catch the most common issues and also establish a high bar for code 8 | quality without being unnecessarily prescriptive: 9 | 10 | - [errcheck] to ensure that errors are handled 11 | - [goimports] to format code and manage imports 12 | - [golint] to point out common style mistakes 13 | - [govet] to analyze code for common mistakes 14 | - [staticcheck] to do various static analysis checks 15 | 16 | [errcheck]: https://github.com/kisielk/errcheck 17 | [goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports 18 | [golint]: https://github.com/golang/lint 19 | [govet]: https://pkg.go.dev/cmd/vet 20 | [staticcheck]: https://staticcheck.dev 21 | 22 | ## Lint Runners 23 | 24 | We recommend [golangci-lint] as the go-to lint runner for Go code, largely due 25 | to its performance in larger codebases and ability to configure and use many 26 | canonical linters at once. This repo has an example [.golangci.yml] config file 27 | with recommended linters and settings. 28 | 29 | golangci-lint has [various linters] available for use. The above linters are 30 | recommended as a base set, and we encourage teams to add any additional linters 31 | that make sense for their projects. 32 | 33 | [golangci-lint]: https://github.com/golangci/golangci-lint 34 | [.golangci.yml]: https://github.com/uber-go/guide/blob/master/.golangci.yml 35 | [various linters]: https://golangci-lint.run/usage/linters/ 36 | -------------------------------------------------------------------------------- /src/map-init.md: -------------------------------------------------------------------------------- 1 | # Initializing Maps 2 | 3 | Prefer `make(..)` for empty maps, and maps populated 4 | programmatically. This makes map initialization visually 5 | distinct from declaration, and it makes it easy to add size 6 | hints later if available. 7 | 8 | 9 | 10 | 11 | 34 | 43 |
BadGood
12 | 13 | ```go 14 | var ( 15 | // m1 is safe to read and write; 16 | // m2 will panic on writes. 17 | m1 = map[T1]T2{} 18 | m2 map[T1]T2 19 | ) 20 | ``` 21 | 22 | 23 | 24 | ```go 25 | var ( 26 | // m1 is safe to read and write; 27 | // m2 will panic on writes. 28 | m1 = make(map[T1]T2) 29 | m2 map[T1]T2 30 | ) 31 | ``` 32 | 33 |
35 | 36 | Declaration and initialization are visually similar. 37 | 38 | 39 | 40 | Declaration and initialization are visually distinct. 41 | 42 |
44 | 45 | Where possible, provide capacity hints when initializing 46 | maps with `make()`. See 47 | [Specifying Map Capacity Hints](container-capacity.md#specifying-map-capacity-hints) 48 | for more information. 49 | 50 | On the other hand, if the map holds a fixed list of elements, 51 | use map literals to initialize the map. 52 | 53 | 54 | 55 | 56 | 76 |
BadGood
57 | 58 | ```go 59 | m := make(map[T1]T2, 3) 60 | m[k1] = v1 61 | m[k2] = v2 62 | m[k3] = v3 63 | ``` 64 | 65 | 66 | 67 | ```go 68 | m := map[T1]T2{ 69 | k1: v1, 70 | k2: v2, 71 | k3: v3, 72 | } 73 | ``` 74 | 75 |
77 | 78 | The basic rule of thumb is to use map literals when adding a fixed set of 79 | elements at initialization time, otherwise use `make` (and specify a size hint 80 | if available). 81 | -------------------------------------------------------------------------------- /src/mutex-zero-value.md: -------------------------------------------------------------------------------- 1 | # Zero-value Mutexes are Valid 2 | 3 | The zero-value of `sync.Mutex` and `sync.RWMutex` is valid, so you almost 4 | never need a pointer to a mutex. 5 | 6 | 7 | 8 | 9 | 24 |
BadGood
10 | 11 | ```go 12 | mu := new(sync.Mutex) 13 | mu.Lock() 14 | ``` 15 | 16 | 17 | 18 | ```go 19 | var mu sync.Mutex 20 | mu.Lock() 21 | ``` 22 | 23 |
25 | 26 | If you use a struct by pointer, then the mutex should be a non-pointer field on 27 | it. Do not embed the mutex on the struct, even if the struct is not exported. 28 | 29 | 30 | 31 | 32 | 79 | 80 | 91 |
BadGood
33 | 34 | ```go 35 | type SMap struct { 36 | sync.Mutex 37 | 38 | data map[string]string 39 | } 40 | 41 | func NewSMap() *SMap { 42 | return &SMap{ 43 | data: make(map[string]string), 44 | } 45 | } 46 | 47 | func (m *SMap) Get(k string) string { 48 | m.Lock() 49 | defer m.Unlock() 50 | 51 | return m.data[k] 52 | } 53 | ``` 54 | 55 | 56 | 57 | ```go 58 | type SMap struct { 59 | mu sync.Mutex 60 | 61 | data map[string]string 62 | } 63 | 64 | func NewSMap() *SMap { 65 | return &SMap{ 66 | data: make(map[string]string), 67 | } 68 | } 69 | 70 | func (m *SMap) Get(k string) string { 71 | m.mu.Lock() 72 | defer m.mu.Unlock() 73 | 74 | return m.data[k] 75 | } 76 | ``` 77 | 78 |
81 | 82 | The `Mutex` field, and the `Lock` and `Unlock` methods are unintentionally part 83 | of the exported API of `SMap`. 84 | 85 | 86 | 87 | The mutex and its methods are implementation details of `SMap` hidden from its 88 | callers. 89 | 90 |
92 | -------------------------------------------------------------------------------- /src/nest-less.md: -------------------------------------------------------------------------------- 1 | # Reduce Nesting 2 | 3 | Code should reduce nesting where possible by handling error cases/special 4 | conditions first and returning early or continuing the loop. Reduce the amount 5 | of code that is nested multiple levels. 6 | 7 | 8 | 9 | 10 | 45 |
BadGood
11 | 12 | ```go 13 | for _, v := range data { 14 | if v.F1 == 1 { 15 | v = process(v) 16 | if err := v.Call(); err == nil { 17 | v.Send() 18 | } else { 19 | return err 20 | } 21 | } else { 22 | log.Printf("Invalid v: %v", v) 23 | } 24 | } 25 | ``` 26 | 27 | 28 | 29 | ```go 30 | for _, v := range data { 31 | if v.F1 != 1 { 32 | log.Printf("Invalid v: %v", v) 33 | continue 34 | } 35 | 36 | v = process(v) 37 | if err := v.Call(); err != nil { 38 | return err 39 | } 40 | v.Send() 41 | } 42 | ``` 43 | 44 |
46 | -------------------------------------------------------------------------------- /src/package-name.md: -------------------------------------------------------------------------------- 1 | # Package Names 2 | 3 | When naming packages, choose a name that is: 4 | 5 | - All lower-case. No capitals or underscores. 6 | - Does not need to be renamed using named imports at most call sites. 7 | - Short and succinct. Remember that the name is identified in full at every call 8 | site. 9 | - Not plural. For example, `net/url`, not `net/urls`. 10 | - Not "common", "util", "shared", or "lib". These are bad, uninformative names. 11 | 12 | See also [Package Names] and [Style guideline for Go packages]. 13 | 14 | [Package Names]: https://go.dev/blog/package-names 15 | [Style guideline for Go packages]: https://rakyll.org/style-packages/ 16 | -------------------------------------------------------------------------------- /src/panic.md: -------------------------------------------------------------------------------- 1 | # Don't Panic 2 | 3 | Code running in production must avoid panics. Panics are a major source of 4 | [cascading failures]. If an error occurs, the function must return an error and 5 | allow the caller to decide how to handle it. 6 | 7 | [cascading failures]: https://en.wikipedia.org/wiki/Cascading_failure 8 | 9 | 10 | 11 | 12 | 47 |
BadGood
13 | 14 | ```go 15 | func run(args []string) { 16 | if len(args) == 0 { 17 | panic("an argument is required") 18 | } 19 | // ... 20 | } 21 | 22 | func main() { 23 | run(os.Args[1:]) 24 | } 25 | ``` 26 | 27 | 28 | 29 | ```go 30 | func run(args []string) error { 31 | if len(args) == 0 { 32 | return errors.New("an argument is required") 33 | } 34 | // ... 35 | return nil 36 | } 37 | 38 | func main() { 39 | if err := run(os.Args[1:]); err != nil { 40 | fmt.Fprintln(os.Stderr, err) 41 | os.Exit(1) 42 | } 43 | } 44 | ``` 45 | 46 |
48 | 49 | Panic/recover is not an error handling strategy. A program must panic only when 50 | something irrecoverable happens such as a nil dereference. An exception to this is 51 | program initialization: bad things at program startup that should abort the 52 | program may cause panic. 53 | 54 | ```go 55 | var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML")) 56 | ``` 57 | 58 | Even in tests, prefer `t.Fatal` or `t.FailNow` over panics to ensure that the 59 | test is marked as failed. 60 | 61 | 62 | 63 | 64 | 87 |
BadGood
65 | 66 | ```go 67 | // func TestFoo(t *testing.T) 68 | 69 | f, err := os.CreateTemp("", "test") 70 | if err != nil { 71 | panic("failed to set up test") 72 | } 73 | ``` 74 | 75 | 76 | 77 | ```go 78 | // func TestFoo(t *testing.T) 79 | 80 | f, err := os.CreateTemp("", "test") 81 | if err != nil { 82 | t.Fatal("failed to set up test") 83 | } 84 | ``` 85 | 86 |
88 | -------------------------------------------------------------------------------- /src/param-naked.md: -------------------------------------------------------------------------------- 1 | # Avoid Naked Parameters 2 | 3 | Naked parameters in function calls can hurt readability. Add C-style comments 4 | (`/* ... */`) for parameter names when their meaning is not obvious. 5 | 6 | 7 | 8 | 9 | 26 |
BadGood
10 | 11 | ```go 12 | // func printInfo(name string, isLocal, done bool) 13 | 14 | printInfo("foo", true, true) 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | // func printInfo(name string, isLocal, done bool) 21 | 22 | printInfo("foo", true /* isLocal */, true /* done */) 23 | ``` 24 | 25 |
27 | 28 | Better yet, replace naked `bool` types with custom types for more readable and 29 | type-safe code. This allows more than just two states (true/false) for that 30 | parameter in the future. 31 | 32 | ```go 33 | type Region int 34 | 35 | const ( 36 | UnknownRegion Region = iota 37 | Local 38 | ) 39 | 40 | type Status int 41 | 42 | const ( 43 | StatusReady Status = iota + 1 44 | StatusDone 45 | // Maybe we will have a StatusInProgress in the future. 46 | ) 47 | 48 | func printInfo(name string, region Region, status Status) 49 | ``` 50 | -------------------------------------------------------------------------------- /src/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | Performance-specific guidelines apply only to the hot path. 4 | -------------------------------------------------------------------------------- /src/preface.txt: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/printf-const.md: -------------------------------------------------------------------------------- 1 | # Format Strings outside Printf 2 | 3 | If you declare format strings for `Printf`-style functions outside a string 4 | literal, make them `const` values. 5 | 6 | This helps `go vet` perform static analysis of the format string. 7 | 8 | 9 | 10 | 11 | 26 |
BadGood
12 | 13 | ```go 14 | msg := "unexpected values %v, %v\n" 15 | fmt.Printf(msg, 1, 2) 16 | ``` 17 | 18 | 19 | 20 | ```go 21 | const msg = "unexpected values %v, %v\n" 22 | fmt.Printf(msg, 1, 2) 23 | ``` 24 | 25 |
27 | -------------------------------------------------------------------------------- /src/printf-name.md: -------------------------------------------------------------------------------- 1 | # Naming Printf-style Functions 2 | 3 | When you declare a `Printf`-style function, make sure that `go vet` can detect 4 | it and check the format string. 5 | 6 | This means that you should use predefined `Printf`-style function 7 | names if possible. `go vet` will check these by default. See [Printf family] 8 | for more information. 9 | 10 | [Printf family]: https://pkg.go.dev/cmd/vet#hdr-Printf_family 11 | 12 | If using the predefined names is not an option, end the name you choose with 13 | f: `Wrapf`, not `Wrap`. `go vet` can be asked to check specific `Printf`-style 14 | names but they must end with f. 15 | 16 | ```shell 17 | go vet -printfuncs=wrapf,statusf 18 | ``` 19 | 20 | See also [go vet: Printf family check]. 21 | 22 | [go vet: Printf family check]: https://kuzminva.wordpress.com/2017/11/07/go-vet-printf-family-check/ 23 | -------------------------------------------------------------------------------- /src/slice-nil.md: -------------------------------------------------------------------------------- 1 | # nil is a valid slice 2 | 3 | `nil` is a valid slice of length 0. This means that, 4 | 5 | - You should not return a slice of length zero explicitly. Return `nil` 6 | instead. 7 | 8 | 9 | 10 | 11 | 28 |
BadGood
12 | 13 | ```go 14 | if x == "" { 15 | return []int{} 16 | } 17 | ``` 18 | 19 | 20 | 21 | ```go 22 | if x == "" { 23 | return nil 24 | } 25 | ``` 26 | 27 |
29 | 30 | - To check if a slice is empty, always use `len(s) == 0`. Do not check for 31 | `nil`. 32 | 33 | 34 | 35 | 36 | 53 |
BadGood
37 | 38 | ```go 39 | func isEmpty(s []string) bool { 40 | return s == nil 41 | } 42 | ``` 43 | 44 | 45 | 46 | ```go 47 | func isEmpty(s []string) bool { 48 | return len(s) == 0 49 | } 50 | ``` 51 | 52 |
54 | 55 | - The zero value (a slice declared with `var`) is usable immediately without 56 | `make()`. 57 | 58 | 59 | 60 | 61 | 91 |
BadGood
62 | 63 | ```go 64 | nums := []int{} 65 | // or, nums := make([]int) 66 | 67 | if add1 { 68 | nums = append(nums, 1) 69 | } 70 | 71 | if add2 { 72 | nums = append(nums, 2) 73 | } 74 | ``` 75 | 76 | 77 | 78 | ```go 79 | var nums []int 80 | 81 | if add1 { 82 | nums = append(nums, 1) 83 | } 84 | 85 | if add2 { 86 | nums = append(nums, 2) 87 | } 88 | ``` 89 | 90 |
92 | 93 | Remember that, while it is a valid slice, a nil slice is not equivalent to an 94 | allocated slice of length 0 - one is nil and the other is not - and the two may 95 | be treated differently in different situations (such as serialization). 96 | -------------------------------------------------------------------------------- /src/strconv.md: -------------------------------------------------------------------------------- 1 | # Prefer strconv over fmt 2 | 3 | When converting primitives to/from strings, `strconv` is faster than 4 | `fmt`. 5 | 6 | 7 | 8 | 9 | 26 | 39 |
BadGood
10 | 11 | ```go 12 | for i := 0; i < b.N; i++ { 13 | s := fmt.Sprint(rand.Int()) 14 | } 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | for i := 0; i < b.N; i++ { 21 | s := strconv.Itoa(rand.Int()) 22 | } 23 | ``` 24 | 25 |
27 | 28 | ```plain 29 | BenchmarkFmtSprint-4 143 ns/op 2 allocs/op 30 | ``` 31 | 32 | 33 | 34 | ```plain 35 | BenchmarkStrconv-4 64.2 ns/op 1 allocs/op 36 | ``` 37 | 38 |
40 | -------------------------------------------------------------------------------- /src/string-byte-slice.md: -------------------------------------------------------------------------------- 1 | # Avoid repeated string-to-byte conversions 2 | 3 | Do not create byte slices from a fixed string repeatedly. Instead, perform the 4 | conversion once and capture the result. 5 | 6 | 7 | 8 | 9 | 27 | 40 |
BadGood
10 | 11 | ```go 12 | for i := 0; i < b.N; i++ { 13 | w.Write([]byte("Hello world")) 14 | } 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | data := []byte("Hello world") 21 | for i := 0; i < b.N; i++ { 22 | w.Write(data) 23 | } 24 | ``` 25 | 26 |
28 | 29 | ```plain 30 | BenchmarkBad-4 50000000 22.2 ns/op 31 | ``` 32 | 33 | 34 | 35 | ```plain 36 | BenchmarkGood-4 500000000 3.25 ns/op 37 | ``` 38 | 39 |
41 | -------------------------------------------------------------------------------- /src/string-escape.md: -------------------------------------------------------------------------------- 1 | # Use Raw String Literals to Avoid Escaping 2 | 3 | Go supports [raw string literals](https://go.dev/ref/spec#raw_string_lit), 4 | which can span multiple lines and include quotes. Use these to avoid 5 | hand-escaped strings which are much harder to read. 6 | 7 | 8 | 9 | 10 | 23 |
BadGood
11 | 12 | ```go 13 | wantError := "unknown name:\"test\"" 14 | ``` 15 | 16 | 17 | 18 | ```go 19 | wantError := `unknown error:"test"` 20 | ``` 21 | 22 |
24 | -------------------------------------------------------------------------------- /src/struct-embed.md: -------------------------------------------------------------------------------- 1 | # Embedding in Structs 2 | 3 | Embedded types should be at the top of the field list of a 4 | struct, and there must be an empty line separating embedded fields from regular 5 | fields. 6 | 7 | 8 | 9 | 10 | 30 |
BadGood
11 | 12 | ```go 13 | type Client struct { 14 | version int 15 | http.Client 16 | } 17 | ``` 18 | 19 | 20 | 21 | ```go 22 | type Client struct { 23 | http.Client 24 | 25 | version int 26 | } 27 | ``` 28 | 29 |
31 | 32 | Embedding should provide tangible benefit, like adding or augmenting 33 | functionality in a semantically-appropriate way. It should do this with zero 34 | adverse user-facing effects (see also: [Avoid Embedding Types in Public Structs](embed-public.md)). 35 | 36 | Exception: Mutexes should not be embedded, even on unexported types. See also: [Zero-value Mutexes are Valid](mutex-zero-value.md). 37 | 38 | Embedding **should not**: 39 | 40 | - Be purely cosmetic or convenience-oriented. 41 | - Make outer types more difficult to construct or use. 42 | - Affect outer types' zero values. If the outer type has a useful zero value, it 43 | should still have a useful zero value after embedding the inner type. 44 | - Expose unrelated functions or fields from the outer type as a side-effect of 45 | embedding the inner type. 46 | - Expose unexported types. 47 | - Affect outer types' copy semantics. 48 | - Change the outer type's API or type semantics. 49 | - Embed a non-canonical form of the inner type. 50 | - Expose implementation details of the outer type. 51 | - Allow users to observe or control type internals. 52 | - Change the general behavior of inner functions through wrapping in a way that 53 | would reasonably surprise users. 54 | 55 | Simply put, embed consciously and intentionally. A good litmus test is, "would 56 | all of these exported inner methods/fields be added directly to the outer type"; 57 | if the answer is "some" or "no", don't embed the inner type - use a field 58 | instead. 59 | 60 | 61 | 62 | 63 | 96 | 133 | 156 |
BadGood
64 | 65 | ```go 66 | type A struct { 67 | // Bad: A.Lock() and A.Unlock() are 68 | // now available, provide no 69 | // functional benefit, and allow 70 | // users to control details about 71 | // the internals of A. 72 | sync.Mutex 73 | } 74 | ``` 75 | 76 | 77 | 78 | ```go 79 | type countingWriteCloser struct { 80 | // Good: Write() is provided at this 81 | // outer layer for a specific 82 | // purpose, and delegates work 83 | // to the inner type's Write(). 84 | io.WriteCloser 85 | 86 | count int 87 | } 88 | 89 | func (w *countingWriteCloser) Write(bs []byte) (int, error) { 90 | w.count += len(bs) 91 | return w.WriteCloser.Write(bs) 92 | } 93 | ``` 94 | 95 |
97 | 98 | ```go 99 | type Book struct { 100 | // Bad: pointer changes zero value usefulness 101 | io.ReadWriter 102 | 103 | // other fields 104 | } 105 | 106 | // later 107 | 108 | var b Book 109 | b.Read(...) // panic: nil pointer 110 | b.String() // panic: nil pointer 111 | b.Write(...) // panic: nil pointer 112 | ``` 113 | 114 | 115 | 116 | ```go 117 | type Book struct { 118 | // Good: has useful zero value 119 | bytes.Buffer 120 | 121 | // other fields 122 | } 123 | 124 | // later 125 | 126 | var b Book 127 | b.Read(...) // ok 128 | b.String() // ok 129 | b.Write(...) // ok 130 | ``` 131 | 132 |
134 | 135 | ```go 136 | type Client struct { 137 | sync.Mutex 138 | sync.WaitGroup 139 | bytes.Buffer 140 | url.URL 141 | } 142 | ``` 143 | 144 | 145 | 146 | ```go 147 | type Client struct { 148 | mtx sync.Mutex 149 | wg sync.WaitGroup 150 | buf bytes.Buffer 151 | url url.URL 152 | } 153 | ``` 154 | 155 |
157 | -------------------------------------------------------------------------------- /src/struct-field-key.md: -------------------------------------------------------------------------------- 1 | # Use Field Names to Initialize Structs 2 | 3 | You should almost always specify field names when initializing structs. This is 4 | now enforced by [`go vet`]. 5 | 6 | [`go vet`]: https://pkg.go.dev/cmd/vet 7 | 8 | 9 | 10 | 11 | 28 |
BadGood
12 | 13 | ```go 14 | k := User{"John", "Doe", true} 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | k := User{ 21 | FirstName: "John", 22 | LastName: "Doe", 23 | Admin: true, 24 | } 25 | ``` 26 | 27 |
29 | 30 | Exception: Field names *may* be omitted in test tables when there are 3 or 31 | fewer fields. 32 | 33 | ```go 34 | tests := []struct{ 35 | op Operation 36 | want string 37 | }{ 38 | {Add, "add"}, 39 | {Subtract, "subtract"}, 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /src/struct-field-zero.md: -------------------------------------------------------------------------------- 1 | # Omit Zero Value Fields in Structs 2 | 3 | When initializing structs with field names, omit fields that have zero values 4 | unless they provide meaningful context. Otherwise, let Go set these to zero 5 | values automatically. 6 | 7 | 8 | 9 | 10 | 31 |
BadGood
11 | 12 | ```go 13 | user := User{ 14 | FirstName: "John", 15 | LastName: "Doe", 16 | MiddleName: "", 17 | Admin: false, 18 | } 19 | ``` 20 | 21 | 22 | 23 | ```go 24 | user := User{ 25 | FirstName: "John", 26 | LastName: "Doe", 27 | } 28 | ``` 29 | 30 |
32 | 33 | This helps reduce noise for readers by omitting values that are default in 34 | that context. Only meaningful values are specified. 35 | 36 | Include zero values where field names provide meaningful context. For example, 37 | test cases in [Test Tables](test-table.md) can benefit from names of fields 38 | even when they are zero-valued. 39 | 40 | ```go 41 | tests := []struct{ 42 | give string 43 | want int 44 | }{ 45 | {give: "0", want: 0}, 46 | // ... 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /src/struct-pointer.md: -------------------------------------------------------------------------------- 1 | # Initializing Struct References 2 | 3 | Use `&T{}` instead of `new(T)` when initializing struct references so that it 4 | is consistent with the struct initialization. 5 | 6 | 7 | 8 | 9 | 28 |
BadGood
10 | 11 | ```go 12 | sval := T{Name: "foo"} 13 | 14 | // inconsistent 15 | sptr := new(T) 16 | sptr.Name = "bar" 17 | ``` 18 | 19 | 20 | 21 | ```go 22 | sval := T{Name: "foo"} 23 | 24 | sptr := &T{Name: "bar"} 25 | ``` 26 | 27 |
29 | -------------------------------------------------------------------------------- /src/struct-tag.md: -------------------------------------------------------------------------------- 1 | # Use field tags in marshaled structs 2 | 3 | Any struct field that is marshaled into JSON, YAML, 4 | or other formats that support tag-based field naming 5 | should be annotated with the relevant tag. 6 | 7 | 8 | 9 | 10 | 40 |
BadGood
11 | 12 | ```go 13 | type Stock struct { 14 | Price int 15 | Name string 16 | } 17 | 18 | bytes, err := json.Marshal(Stock{ 19 | Price: 137, 20 | Name: "UBER", 21 | }) 22 | ``` 23 | 24 | 25 | 26 | ```go 27 | type Stock struct { 28 | Price int `json:"price"` 29 | Name string `json:"name"` 30 | // Safe to rename Name to Symbol. 31 | } 32 | 33 | bytes, err := json.Marshal(Stock{ 34 | Price: 137, 35 | Name: "UBER", 36 | }) 37 | ``` 38 | 39 |
41 | 42 | Rationale: 43 | The serialized form of the structure is a contract between different systems. 44 | Changes to the structure of the serialized form--including field names--break 45 | this contract. Specifying field names inside tags makes the contract explicit, 46 | and it guards against accidentally breaking the contract by refactoring or 47 | renaming fields. 48 | -------------------------------------------------------------------------------- /src/struct-zero.md: -------------------------------------------------------------------------------- 1 | # Use `var` for Zero Value Structs 2 | 3 | When all the fields of a struct are omitted in a declaration, use the `var` 4 | form to declare the struct. 5 | 6 | 7 | 8 | 9 | 22 |
BadGood
10 | 11 | ```go 12 | user := User{} 13 | ``` 14 | 15 | 16 | 17 | ```go 18 | var user User 19 | ``` 20 | 21 |
23 | 24 | This differentiates zero valued structs from those with non-zero fields 25 | similar to the distinction created for [map initialization](map-init.md), and matches how 26 | we prefer to [declare empty slices]. 27 | 28 | [declare empty slices]: https://go.dev/wiki/CodeReviewComments#declaring-empty-slices 29 | -------------------------------------------------------------------------------- /src/test-table.md: -------------------------------------------------------------------------------- 1 | # Test Tables 2 | 3 | Table-driven tests with [subtests] can be a helpful pattern for writing tests 4 | to avoid duplicating code when the core test logic is repetitive. 5 | 6 | If a system under test needs to be tested against _multiple conditions_ where 7 | certain parts of the the inputs and outputs change, a table-driven test should 8 | be used to reduce redundancy and improve readability. 9 | 10 | [subtests]: https://go.dev/blog/subtests 11 | 12 | 13 | 14 | 15 | 84 |
BadGood
16 | 17 | ```go 18 | // func TestSplitHostPort(t *testing.T) 19 | 20 | host, port, err := net.SplitHostPort("192.0.2.0:8000") 21 | require.NoError(t, err) 22 | assert.Equal(t, "192.0.2.0", host) 23 | assert.Equal(t, "8000", port) 24 | 25 | host, port, err = net.SplitHostPort("192.0.2.0:http") 26 | require.NoError(t, err) 27 | assert.Equal(t, "192.0.2.0", host) 28 | assert.Equal(t, "http", port) 29 | 30 | host, port, err = net.SplitHostPort(":8000") 31 | require.NoError(t, err) 32 | assert.Equal(t, "", host) 33 | assert.Equal(t, "8000", port) 34 | 35 | host, port, err = net.SplitHostPort("1:8") 36 | require.NoError(t, err) 37 | assert.Equal(t, "1", host) 38 | assert.Equal(t, "8", port) 39 | ``` 40 | 41 | 42 | 43 | ```go 44 | // func TestSplitHostPort(t *testing.T) 45 | 46 | tests := []struct{ 47 | give string 48 | wantHost string 49 | wantPort string 50 | }{ 51 | { 52 | give: "192.0.2.0:8000", 53 | wantHost: "192.0.2.0", 54 | wantPort: "8000", 55 | }, 56 | { 57 | give: "192.0.2.0:http", 58 | wantHost: "192.0.2.0", 59 | wantPort: "http", 60 | }, 61 | { 62 | give: ":8000", 63 | wantHost: "", 64 | wantPort: "8000", 65 | }, 66 | { 67 | give: "1:8", 68 | wantHost: "1", 69 | wantPort: "8", 70 | }, 71 | } 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.give, func(t *testing.T) { 75 | host, port, err := net.SplitHostPort(tt.give) 76 | require.NoError(t, err) 77 | assert.Equal(t, tt.wantHost, host) 78 | assert.Equal(t, tt.wantPort, port) 79 | }) 80 | } 81 | ``` 82 | 83 |
85 | 86 | Test tables make it easier to add context to error messages, reduce duplicate 87 | logic, and add new test cases. 88 | 89 | We follow the convention that the slice of structs is referred to as `tests` 90 | and each test case `tt`. Further, we encourage explicating the input and output 91 | values for each test case with `give` and `want` prefixes. 92 | 93 | ```go 94 | tests := []struct{ 95 | give string 96 | wantHost string 97 | wantPort string 98 | }{ 99 | // ... 100 | } 101 | 102 | for _, tt := range tests { 103 | // ... 104 | } 105 | ``` 106 | 107 | ## Avoid Unnecessary Complexity in Table Tests 108 | 109 | Table tests can be difficult to read and maintain if the subtests contain conditional 110 | assertions or other branching logic. Table tests should **NOT** be used whenever 111 | there needs to be complex or conditional logic inside subtests (i.e. complex logic inside the `for` loop). 112 | 113 | Large, complex table tests harm readability and maintainability because test readers may 114 | have difficulty debugging test failures that occur. 115 | 116 | Table tests like this should be split into either multiple test tables or multiple 117 | individual `Test...` functions. 118 | 119 | Some ideals to aim for are: 120 | 121 | * Focus on the narrowest unit of behavior 122 | * Minimize "test depth", and avoid conditional assertions (see below) 123 | * Ensure that all table fields are used in all tests 124 | * Ensure that all test logic runs for all table cases 125 | 126 | In this context, "test depth" means "within a given test, the number of 127 | successive assertions that require previous assertions to hold" (similar 128 | to cyclomatic complexity). 129 | Having "shallower" tests means that there are fewer relationships between 130 | assertions and, more importantly, that those assertions are less likely 131 | to be conditional by default. 132 | 133 | Concretely, table tests can become confusing and difficult to read if they use multiple branching 134 | pathways (e.g. `shouldError`, `expectCall`, etc.), use many `if` statements for 135 | specific mock expectations (e.g. `shouldCallFoo`), or place functions inside the 136 | table (e.g. `setupMocks func(*FooMock)`). 137 | 138 | However, when testing behavior that only 139 | changes based on changed input, it may be preferable to group similar cases 140 | together in a table test to better illustrate how behavior changes across all inputs, 141 | rather than splitting otherwise comparable units into separate tests 142 | and making them harder to compare and contrast. 143 | 144 | If the test body is short and straightforward, 145 | it's acceptable to have a single branching pathway for success versus failure cases 146 | with a table field like `shouldErr` to specify error expectations. 147 | 148 | 149 | 150 | 151 | 230 |
BadGood
152 | 153 | ```go 154 | func TestComplicatedTable(t *testing.T) { 155 | tests := []struct { 156 | give string 157 | want string 158 | wantErr error 159 | shouldCallX bool 160 | shouldCallY bool 161 | giveXResponse string 162 | giveXErr error 163 | giveYResponse string 164 | giveYErr error 165 | }{ 166 | // ... 167 | } 168 | 169 | for _, tt := range tests { 170 | t.Run(tt.give, func(t *testing.T) { 171 | // setup mocks 172 | ctrl := gomock.NewController(t) 173 | xMock := xmock.NewMockX(ctrl) 174 | if tt.shouldCallX { 175 | xMock.EXPECT().Call().Return( 176 | tt.giveXResponse, tt.giveXErr, 177 | ) 178 | } 179 | yMock := ymock.NewMockY(ctrl) 180 | if tt.shouldCallY { 181 | yMock.EXPECT().Call().Return( 182 | tt.giveYResponse, tt.giveYErr, 183 | ) 184 | } 185 | 186 | got, err := DoComplexThing(tt.give, xMock, yMock) 187 | 188 | // verify results 189 | if tt.wantErr != nil { 190 | require.EqualError(t, err, tt.wantErr) 191 | return 192 | } 193 | require.NoError(t, err) 194 | assert.Equal(t, want, got) 195 | }) 196 | } 197 | } 198 | ``` 199 | 200 | 201 | 202 | ```go 203 | func TestShouldCallX(t *testing.T) { 204 | // setup mocks 205 | ctrl := gomock.NewController(t) 206 | xMock := xmock.NewMockX(ctrl) 207 | xMock.EXPECT().Call().Return("XResponse", nil) 208 | 209 | yMock := ymock.NewMockY(ctrl) 210 | 211 | got, err := DoComplexThing("inputX", xMock, yMock) 212 | 213 | require.NoError(t, err) 214 | assert.Equal(t, "want", got) 215 | } 216 | 217 | func TestShouldCallYAndFail(t *testing.T) { 218 | // setup mocks 219 | ctrl := gomock.NewController(t) 220 | xMock := xmock.NewMockX(ctrl) 221 | 222 | yMock := ymock.NewMockY(ctrl) 223 | yMock.EXPECT().Call().Return("YResponse", nil) 224 | 225 | _, err := DoComplexThing("inputY", xMock, yMock) 226 | assert.EqualError(t, err, "Y failed") 227 | } 228 | ``` 229 |
231 | 232 | This complexity makes it more difficult to change, understand, and prove the 233 | correctness of the test. 234 | 235 | While there are no strict guidelines, readability and maintainability should 236 | always be top-of-mind when deciding between Table Tests versus separate tests 237 | for multiple inputs/outputs to a system. 238 | 239 | ## Parallel Tests 240 | 241 | Parallel tests, like some specialized loops (for example, those that spawn 242 | goroutines or capture references as part of the loop body), 243 | must take care to explicitly assign loop variables within the loop's scope to 244 | ensure that they hold the expected values. 245 | 246 | ```go 247 | tests := []struct{ 248 | give string 249 | // ... 250 | }{ 251 | // ... 252 | } 253 | 254 | for _, tt := range tests { 255 | tt := tt // for t.Parallel 256 | t.Run(tt.give, func(t *testing.T) { 257 | t.Parallel() 258 | // ... 259 | }) 260 | } 261 | ``` 262 | 263 | In the example above, we must declare a `tt` variable scoped to the loop 264 | iteration because of the use of `t.Parallel()` below. 265 | If we do not do that, most or all tests will receive an unexpected value for 266 | `tt`, or a value that changes as they're running. 267 | 268 | 269 | -------------------------------------------------------------------------------- /src/time.md: -------------------------------------------------------------------------------- 1 | # Use `"time"` to handle time 2 | 3 | Time is complicated. Incorrect assumptions often made about time include the 4 | following. 5 | 6 | 1. A day has 24 hours 7 | 2. An hour has 60 minutes 8 | 3. A week has 7 days 9 | 4. A year has 365 days 10 | 5. [And a lot more](https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time) 11 | 12 | For example, *1* means that adding 24 hours to a time instant will not always 13 | yield a new calendar day. 14 | 15 | Therefore, always use the [`"time"`] package when dealing with time because it 16 | helps deal with these incorrect assumptions in a safer, more accurate manner. 17 | 18 | [`"time"`]: https://pkg.go.dev/time 19 | 20 | ## Use `time.Time` for instants of time 21 | 22 | Use [`time.Time`] when dealing with instants of time, and the methods on 23 | `time.Time` when comparing, adding, or subtracting time. 24 | 25 | [`time.Time`]: https://pkg.go.dev/time#Time 26 | 27 | 28 | 29 | 30 | 47 |
BadGood
31 | 32 | ```go 33 | func isActive(now, start, stop int) bool { 34 | return start <= now && now < stop 35 | } 36 | ``` 37 | 38 | 39 | 40 | ```go 41 | func isActive(now, start, stop time.Time) bool { 42 | return (start.Before(now) || start.Equal(now)) && now.Before(stop) 43 | } 44 | ``` 45 | 46 |
48 | 49 | ## Use `time.Duration` for periods of time 50 | 51 | Use [`time.Duration`] when dealing with periods of time. 52 | 53 | [`time.Duration`]: https://pkg.go.dev/time#Duration 54 | 55 | 56 | 57 | 58 | 85 |
BadGood
59 | 60 | ```go 61 | func poll(delay int) { 62 | for { 63 | // ... 64 | time.Sleep(time.Duration(delay) * time.Millisecond) 65 | } 66 | } 67 | 68 | poll(10) // was it seconds or milliseconds? 69 | ``` 70 | 71 | 72 | 73 | ```go 74 | func poll(delay time.Duration) { 75 | for { 76 | // ... 77 | time.Sleep(delay) 78 | } 79 | } 80 | 81 | poll(10*time.Second) 82 | ``` 83 | 84 |
86 | 87 | Going back to the example of adding 24 hours to a time instant, the method we 88 | use to add time depends on intent. If we want the same time of the day, but on 89 | the next calendar day, we should use [`Time.AddDate`]. However, if we want an 90 | instant of time guaranteed to be 24 hours after the previous time, we should 91 | use [`Time.Add`]. 92 | 93 | [`Time.AddDate`]: https://pkg.go.dev/time#Time.AddDate 94 | [`Time.Add`]: https://pkg.go.dev/time#Time.Add 95 | 96 | ```go 97 | newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */) 98 | maybeNewDay := t.Add(24 * time.Hour) 99 | ``` 100 | 101 | ## Use `time.Time` and `time.Duration` with external systems 102 | 103 | Use `time.Duration` and `time.Time` in interactions with external systems when 104 | possible. For example: 105 | 106 | - Command-line flags: [`flag`] supports `time.Duration` via 107 | [`time.ParseDuration`] 108 | - JSON: [`encoding/json`] supports encoding `time.Time` as an [RFC 3339] 109 | string via its [`UnmarshalJSON` method] 110 | - SQL: [`database/sql`] supports converting `DATETIME` or `TIMESTAMP` columns 111 | into `time.Time` and back if the underlying driver supports it 112 | - YAML: [`gopkg.in/yaml.v2`] supports `time.Time` as an [RFC 3339] string, and 113 | `time.Duration` via [`time.ParseDuration`]. 114 | 115 | [`flag`]: https://pkg.go.dev/flag 116 | [`time.ParseDuration`]: https://pkg.go.dev/time#ParseDuration 117 | [`encoding/json`]: https://pkg.go.dev/encoding/json 118 | [RFC 3339]: https://tools.ietf.org/html/rfc3339 119 | [`UnmarshalJSON` method]: https://pkg.go.dev/time#Time.UnmarshalJSON 120 | [`database/sql`]: https://pkg.go.dev/database/sql 121 | [`gopkg.in/yaml.v2`]: https://pkg.go.dev/gopkg.in/yaml.v2 122 | 123 | When it is not possible to use `time.Duration` in these interactions, use 124 | `int` or `float64` and include the unit in the name of the field. 125 | 126 | For example, since `encoding/json` does not support `time.Duration`, the unit 127 | is included in the name of the field. 128 | 129 | 130 | 131 | 132 | 151 |
BadGood
133 | 134 | ```go 135 | // {"interval": 2} 136 | type Config struct { 137 | Interval int `json:"interval"` 138 | } 139 | ``` 140 | 141 | 142 | 143 | ```go 144 | // {"intervalMillis": 2000} 145 | type Config struct { 146 | IntervalMillis int `json:"intervalMillis"` 147 | } 148 | ``` 149 | 150 |
152 | 153 | When it is not possible to use `time.Time` in these interactions, unless an 154 | alternative is agreed upon, use `string` and format timestamps as defined in 155 | [RFC 3339]. This format is used by default by [`Time.UnmarshalText`] and is 156 | available for use in `Time.Format` and `time.Parse` via [`time.RFC3339`]. 157 | 158 | [`Time.UnmarshalText`]: https://pkg.go.dev/time#Time.UnmarshalText 159 | [`time.RFC3339`]: https://pkg.go.dev/time#RFC3339 160 | 161 | Although this tends to not be a problem in practice, keep in mind that the 162 | `"time"` package does not support parsing timestamps with leap seconds 163 | ([8728]), nor does it account for leap seconds in calculations ([15190]). If 164 | you compare two instants of time, the difference will not include the leap 165 | seconds that may have occurred between those two instants. 166 | 167 | [8728]: https://github.com/golang/go/issues/8728 168 | [15190]: https://github.com/golang/go/issues/15190 169 | -------------------------------------------------------------------------------- /src/type-assert.md: -------------------------------------------------------------------------------- 1 | # Handle Type Assertion Failures 2 | 3 | The single return value form of a [type assertion] will panic on an incorrect 4 | type. Therefore, always use the "comma ok" idiom. 5 | 6 | [type assertion]: https://go.dev/ref/spec#Type_assertions 7 | 8 | 9 | 10 | 11 | 27 |
BadGood
12 | 13 | ```go 14 | t := i.(string) 15 | ``` 16 | 17 | 18 | 19 | ```go 20 | t, ok := i.(string) 21 | if !ok { 22 | // handle the error gracefully 23 | } 24 | ``` 25 | 26 |
28 | 29 | 31 | -------------------------------------------------------------------------------- /src/var-decl.md: -------------------------------------------------------------------------------- 1 | # Local Variable Declarations 2 | 3 | Short variable declarations (`:=`) should be used if a variable is being set to 4 | some value explicitly. 5 | 6 | 7 | 8 | 9 | 22 |
BadGood
10 | 11 | ```go 12 | var s = "foo" 13 | ``` 14 | 15 | 16 | 17 | ```go 18 | s := "foo" 19 | ``` 20 | 21 |
23 | 24 | However, there are cases where the default value is clearer when the `var` 25 | keyword is used. [Declaring Empty Slices], for example. 26 | 27 | [Declaring Empty Slices]: https://go.dev/wiki/CodeReviewComments#declaring-empty-slices 28 | 29 | 30 | 31 | 32 | 59 |
BadGood
33 | 34 | ```go 35 | func f(list []int) { 36 | filtered := []int{} 37 | for _, v := range list { 38 | if v > 10 { 39 | filtered = append(filtered, v) 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | 46 | 47 | ```go 48 | func f(list []int) { 49 | var filtered []int 50 | for _, v := range list { 51 | if v > 10 { 52 | filtered = append(filtered, v) 53 | } 54 | } 55 | } 56 | ``` 57 | 58 |
60 | -------------------------------------------------------------------------------- /src/var-scope.md: -------------------------------------------------------------------------------- 1 | # Reduce Scope of Variables 2 | 3 | Where possible, reduce scope of variables and constants. Do not reduce the scope if it 4 | conflicts with [Reduce Nesting](nest-less.md). 5 | 6 | 7 | 8 | 9 | 27 |
BadGood
10 | 11 | ```go 12 | err := os.WriteFile(name, data, 0644) 13 | if err != nil { 14 | return err 15 | } 16 | ``` 17 | 18 | 19 | 20 | ```go 21 | if err := os.WriteFile(name, data, 0644); err != nil { 22 | return err 23 | } 24 | ``` 25 | 26 |
28 | 29 | If you need a result of a function call outside of the if, then you should not 30 | try to reduce the scope. 31 | 32 | 33 | 34 | 35 | 68 |
BadGood
36 | 37 | ```go 38 | if data, err := os.ReadFile(name); err == nil { 39 | err = cfg.Decode(data) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | fmt.Println(cfg) 45 | return nil 46 | } else { 47 | return err 48 | } 49 | ``` 50 | 51 | 52 | 53 | ```go 54 | data, err := os.ReadFile(name) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if err := cfg.Decode(data); err != nil { 60 | return err 61 | } 62 | 63 | fmt.Println(cfg) 64 | return nil 65 | ``` 66 | 67 |
69 | 70 | Constants do not need to be global unless they are used in multiple functions or files 71 | or are part of an external contract of the package. 72 | 73 | 74 | 75 | 76 | 102 |
BadGood
77 | 78 | ```go 79 | const ( 80 | _defaultPort = 8080 81 | _defaultUser = "user" 82 | ) 83 | 84 | func Bar() { 85 | fmt.Println("Default port", _defaultPort) 86 | } 87 | ``` 88 | 89 | 90 | 91 | ```go 92 | func Bar() { 93 | const ( 94 | defaultPort = 8080 95 | defaultUser = "user" 96 | ) 97 | fmt.Println("Default port", defaultPort) 98 | } 99 | ``` 100 | 101 |
--------------------------------------------------------------------------------