├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── 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 └── style.md /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/tools" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request_target: 7 | branches: [ '*' ] 8 | 9 | jobs: 10 | stitchmd: 11 | name: Check or update style.md 12 | runs-on: ubuntu-latest 13 | 14 | # Needed to give the job permission 15 | # to push to branches. 16 | permissions: 17 | contents: write 18 | 19 | steps: 20 | - name: Check out repository 21 | uses: actions/checkout@v4 22 | with: 23 | # Check out the pull request repository and branch. 24 | # If the PR is made from a fork, this will check out the fork. 25 | # This is necessary for git-auto-commit-action to update PRs made by forks. 26 | # See 27 | # https://github.com/stefanzweifel/git-auto-commit-action#use-in-forks-from-public-repositories 28 | repository: ${{ github.event.pull_request.head.repo.full_name }} 29 | ref: ${{ github.head_ref }} 30 | 31 | - name: Check or update style.md 32 | uses: abhinav/stitchmd-action@v1 33 | with: 34 | # For pull requests, run in 'write' mode so that edits made 35 | # directly in the GitHub UI get propagated to style.md 36 | # without a local checkout. 37 | # 38 | # Otherwise, run in 'check' mode to fail CI if style.md is out-of-date. 39 | mode: ${{ github.event_name == 'pull_request_target' && 'write' || 'check' }} 40 | summary: src/SUMMARY.md 41 | preface: src/preface.txt 42 | output: style.md 43 | 44 | - uses: stefanzweifel/git-auto-commit-action@v5 45 | if: ${{ github.event_name == 'pull_request_target' }} 46 | with: 47 | file_pattern: style.md 48 | commit_message: 'Auto-update style.md' 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Refer to golangci-lint's example config file for more options and information: 2 | # https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml 3 | 4 | run: 5 | timeout: 5m 6 | modules-download-mode: readonly 7 | 8 | linters: 9 | enable: 10 | - errcheck 11 | - goimports 12 | - golint 13 | - govet 14 | - staticcheck 15 | 16 | issues: 17 | exclude-use-default: false 18 | max-issues-per-linter: 0 19 | max-same-issues: 0 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2023-05-09 2 | 3 | - Test tables: Discourage tables with complex, branching test bodies. 4 | 5 | # 2023-04-13 6 | 7 | - Errors: Add guidance on handling errors only once. 8 | 9 | # 2023-03-03 10 | 11 | - Receivers and Interfaces: Clarify subtlety with pointer receivers and values. 12 | 13 | # 2022-10-18 14 | 15 | - Add guidance on managing goroutine lifecycles. 16 | 17 | # 2022-03-30 18 | 19 | - Add guidance on using field tags in marshaled structs. 20 | 21 | # 2021-11-16 22 | 23 | - Add guidance on use of `%w` vs `%v` with `fmt.Errorf`, and where to use 24 | `errors.New` or custom error types. 25 | 26 | # 2021-11-12 27 | 28 | - Add soft line length limit of 99 characters. 29 | 30 | # 2021-04-19 31 | 32 | - Programs should exit only in `main()`, preferably at most once. 33 | 34 | # 2021-03-15 35 | 36 | - Add guidance on omitting zero-value fields during struct initialization. 37 | - Add guidance on `Foo{}` versus `var` form for initialization of empty 38 | structs. 39 | - Add new section for Initializing Structs, moving relevant guidances into 40 | subsections of it. 41 | 42 | # 2020-06-10 43 | 44 | - Add guidance on avoiding `init()`. 45 | - Add guidance to avoid using built-in names. 46 | - Add reminder that nil slices are not always the same as empty slices. 47 | 48 | # 2020-02-24 49 | 50 | - Add guidance on verifying interface compliance with compile-time checks. 51 | 52 | # 2020-01-30 53 | 54 | - Recommend using the `time` package when dealing with time. 55 | 56 | # 2020-01-25 57 | 58 | - Add guidance against embedding types in public structs. 59 | 60 | # 2019-12-17 61 | 62 | - Functional Options: Recommend struct implementations of `Option` interface 63 | instead of capturing values with a closure. 64 | 65 | # 2019-11-26 66 | 67 | - Add guidance against mutating global variables. 68 | 69 | # 2019-10-21 70 | 71 | - Add section on remaining consistent with existing practices. 72 | - Add guidance on map initialization and size hints. 73 | 74 | # 2019-10-11 75 | 76 | - Suggest succinct context for error messages. 77 | 78 | # 2019-10-10 79 | 80 | - Initial release. 81 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Before making any changes, 4 | please discuss your plans on GitHub 5 | and get agreement on the general direction of the change. 6 | 7 | ## Making changes 8 | 9 | - style.md is generated from the contents of the src/ folder. 10 | All changes must be made to files in the src/ folder. 11 | - For new entries, create a new file with a short name 12 | (see [File names](#file-names)) and add it to [SUMMARY.md](src/SUMMARY.md). 13 | The file must have a single level 1 heading and any number of subsections. 14 | - Use tables for side-by-side code samples. 15 | - Link to other sections with their file names (`[..](foo.md)`). 16 | 17 | ## Writing style 18 | 19 | ### Line breaks 20 | 21 | Use [semantic line breaks](https://sembr.org/) in your writing. 22 | This keeps the Markdown files easily reviewable and editable. 23 | 24 | ### File names 25 | 26 | Files in src/ follow a rough naming convention of: 27 | 28 | {subject}-{desc}.md 29 | 30 | Where `{subject}` is the **singular form** of subject that the entry is about 31 | (e.g `string`, `struct`, `time`, `var`, `error`) 32 | and `{desc}` is a short one or two word description of the topic. 33 | For subjects where their name is enough, the `-{desc}` may be omitted. 34 | 35 | ### Code samples 36 | 37 | Use two spaces to indent code samples. 38 | Horizontal space is limited in side-by-side samples. 39 | 40 | ### Side-by-side samples 41 | 42 | Create side-by-side code samples with the following template: 43 | 44 | ~~~ 45 | 46 | 47 | 48 | 61 |
BadGood
49 | 50 | ```go 51 | BAD CODE GOES HERE 52 | ``` 53 | 54 | 55 | 56 | ```go 57 | GOOD CODE GOES HERE 58 | ``` 59 | 60 |
62 | ~~~ 63 | 64 | The empty lines between the HTML tags and code samples are necessary. 65 | 66 | If you need to add labels or descriptions below the code samples, 67 | add another row before the `` line. 68 | 69 | ~~~ 70 | 71 | DESCRIBE BAD CODE 72 | DESCRIBE GOOD CODE 73 | 74 | ~~~ 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | 3 | # Setting GOBIN makes 'go install' put the binary in the bin/ directory. 4 | export GOBIN ?= $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/bin 5 | 6 | STITCHMD = $(GOBIN)/stitchmd 7 | 8 | # Keep these options in-sync with .github/workflows/ci.yml. 9 | STITCHMD_ARGS = -o style.md -preface src/preface.txt src/SUMMARY.md 10 | 11 | .PHONY: all 12 | all: style.md 13 | 14 | .PHONY: lint 15 | lint: $(STITCHMD) 16 | @DIFF=$$($(STITCHMD) -d $(STITCHMD_ARGS)); \ 17 | if [[ -n "$$DIFF" ]]; then \ 18 | echo "style.md is out of date:"; \ 19 | echo "$$DIFF"; \ 20 | false; \ 21 | fi 22 | 23 | style.md: $(STITCHMD) $(wildcard src/*) 24 | $(STITCHMD) $(STITCHMD_ARGS) 25 | 26 | $(STITCHMD): 27 | go install go.abhg.dev/stitchmd@latest 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository holds the [Uber Go Style Guide](style.md), which documents 2 | patterns and conventions used in Go code at Uber. 3 | 4 | ## Style Guide 5 | 6 | See [Uber Go Style Guide](style.md) for the style guide. 7 | 8 | ## Translations 9 | 10 | We are aware of the following translations of this guide by the Go community. 11 | 12 | - **中文翻译** (Chinese): [xxjwxc/uber_go_guide_cn](https://github.com/xxjwxc/uber_go_guide_cn) 13 | - **繁體中文** (Traditional Chinese):[ianchen0119/uber_go_guide_tw](https://github.com/ianchen0119/uber_go_guide_tw) 14 | - **한국어 번역** (Korean): [TangoEnSkai/uber-go-style-guide-kr](https://github.com/TangoEnSkai/uber-go-style-guide-kr) 15 | - **日本語訳** (Japanese): [knsh14/uber-style-guide-ja](https://github.com/knsh14/uber-style-guide-ja) 16 | - **Traducción al Español** (Spanish): [friendsofgo/uber-go-guide-es](https://github.com/friendsofgo/uber-go-guide-es) 17 | - **แปลภาษาไทย** (Thai): [pallat/uber-go-style-guide-th](https://github.com/pallat/uber-go-style-guide-th) 18 | - **Tradução em português** (Portuguese): [lucassscaravelli/uber-go-guide-pt](https://github.com/lucassscaravelli/uber-go-guide-pt) 19 | - **Tradução em português** (Portuguese BR): [alcir-junior-caju/uber-go-style-guide-pt-br](https://github.com/alcir-junior-caju/uber-go-style-guide-pt-br) 20 | - **Tłumaczenie polskie** (Polish): [DamianSkrzypczak/uber-go-guide-pl](https://github.com/DamianSkrzypczak/uber-go-guide-pl) 21 | - **Русский перевод** (Russian): [sau00/uber-go-guide-ru](https://github.com/sau00/uber-go-guide-ru) 22 | - **Français** (French): [rm3l/uber-go-style-guide-fr](https://github.com/rm3l/uber-go-style-guide-fr) 23 | - **Türkçe** (Turkish): [ksckaan1/uber-go-style-guide-tr](https://github.com/ksckaan1/uber-go-style-guide-tr) 24 | - **Український переклад** (Ukrainian): [vorobeyme/uber-go-style-guide-uk](https://github.com/vorobeyme/uber-go-style-guide-uk) 25 | - **ترجمه فارسی** (Persian): [jamalkaksouri/uber-go-guide-ir](https://github.com/jamalkaksouri/uber-go-guide-ir) 26 | - **Tiếng việt** (Vietnamese): [nc-minh/uber-go-guide-vi](https://github.com/nc-minh/uber-go-guide-vi) 27 | - **العربية** (Arabic): [anqorithm/uber-go-guide-ar](https://github.com/anqorithm/uber-go-guide-ar) 28 | - **Bahasa Indonesia** (Indonesian): [stanleydv12/uber-go-guide-id](https://github.com/stanleydv12/uber-go-guide-id) 29 | 30 | If you have a translation, feel free to submit a PR adding it to the list. 31 | -------------------------------------------------------------------------------- /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 | runtimetrace "runtime/trace" 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 |
103 | --------------------------------------------------------------------------------