Skip to content

Commit

Permalink
style.md: Split into separate files (uber-go#168)
Browse files Browse the repository at this point in the history
* src: Copy contents from style.md
* Add CONTRIBUTING
* style.md: Generate from src/SUMAMRY.md
* {make, ci}: Set up machinery
  • Loading branch information
abhinav authored Mar 2, 2023
1 parent f66d881 commit 9242f02
Show file tree
Hide file tree
Showing 69 changed files with 4,058 additions and 231 deletions.
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/tools"
schedule:
interval: "daily"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ '*' ]

jobs:

build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.20.x
cache: true

- name: Lint
run: make lint
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/bin
74 changes: 74 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Contributing

Before making any changes,
please discuss your plans on GitHub
and get agreement on the general direction of the change.

## Making changes

- style.md is generated from the contents of the src/ folder.
All changes must be made to files in the src/ folder.
- For new entries, create a new file with a short name
(see [File names](#file-names)) and add it to [SUMMARY.md](src/SUMMARY.md).
The file must have a single level 1 heading and any number of subsections.
- Use tables for side-by-side code samples.
- Link to other sections with their file names (`[..](foo.md)`).

## Writing style

### Line breaks

Use [semantic line breaks](https://sembr.org/) in your writing.
This keeps the Markdown files easily reviewable and editable.

### File names

Files in src/ follow a rough naming convention of:

{subject}-{desc}.md

Where `{subject}` is the **singular form** of subject that the entry is about
(e.g `string`, `struct`, `time`, `var`, `error`)
and `{desc}` is a short one or two word description of the topic.
For subjects where their name is enough, the `-{desc}` may be omitted.

### Code samples

Use two spaces to indent code samples.
Horizontal space is limited in side-by-side samples.

### Side-by-side samples

Create side-by-side code samples with the following template:

~~~
<table>
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
<tbody>
<tr><td>
```go
BAD CODE GOES HERE
```
</td><td>
```go
GOOD CODE GOES HERE
```
</td></tr>
</tbody></table>
~~~

The empty lines between the HTML tags and code samples are necessary.

If you need to add labels or descriptions below the code samples,
add another row before the `</tbody></table>` line.

~~~
<tr>
<td>DESCRIBE BAD CODE</td>
<td>DESCRIBE GOOD CODE</td>
</tr>
~~~
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Setting GOBIN makes 'go install' put the binary in the bin/ directory.
export GOBIN ?= $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/bin

STITCHMD = bin/stitchmd

.PHONY: all
all: style.md

.PHONY: lint
lint:
@DIFF=$$($(STITCHMD) -o style.md -d src/SUMMARY.md); \
if [[ -n "$$DIFF" ]]; then \
echo "style.md is out of date:"; \
echo "$$DIFF"; \
false; \
fi

style.md: $(STITCHMD) $(wildcard src/*.md)
$(STITCHMD) -o $@ src/SUMMARY.md

$(STITCHMD): tools/go.mod
go install -C tools go.abhg.dev/stitchmd
2 changes: 2 additions & 0 deletions src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The contents of this directory are used to generate the top-level style.md.
The layout is controlled by SUMMARY.md.
65 changes: 65 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Uber Go Style Guide

- [Introduction](intro.md)
- Guidelines
- [Pointers to Interfaces](interface-pointer.md)
- [Verify Interface Compliance](interface-compliance.md)
- [Receivers and Interfaces](interface-receiver.md)
- [Zero-value Mutexes are Valid](mutex-zero-value.md)
- [Copy Slices and Maps at Boundaries](container-copy.md)
- [Defer to Clean Up](defer-clean.md)
- [Channel Size is One or None](channel-size.md)
- [Start Enums at One](enum-start.md)
- [Use `"time"` to handle time](time.md)
- Errors
- [Error Types](error-type.md)
- [Error Wrapping](error-wrap.md)
- [Error Naming](error-name.md)
- [Handle Type Assertion Failures](type-assert.md)
- [Don't Panic](panic.md)
- [Use go.uber.org/atomic](atomic.md)
- [Avoid Mutable Globals](global-mut.md)
- [Avoid Embedding Types in Public Structs](embed-public.md)
- [Avoid Using Built-In Names](builtin-name.md)
- [Avoid `init()`](init.md)
- [Exit in Main](exit-main.md)
- [Exit Once](exit-once.md)
- [Use field tags in marshaled structs](struct-tag.md)
- [Don't fire-and-forget goroutines](goroutine-forget.md)
- [Wait for goroutines to exit](goroutine-exit.md)
- [No goroutines in `init()`](goroutine-init.md)
- [Performance](performance.md)
- [Prefer strconv over fmt](strconv.md)
- [Avoid string-to-byte conversion](string-byte-slice.md)
- [Prefer Specifying Container Capacity](container-capacity.md)
- Style
- [Avoid overly long lines](line-length.md)
- [Be Consistent](consistency.md)
- [Group Similar Declarations](decl-group.md)
- [Import Group Ordering](import-group.md)
- [Package Names](package-name.md)
- [Function Names](function-name.md)
- [Import Aliasing](import-alias.md)
- [Function Grouping and Ordering](function-order.md)
- [Reduce Nesting](nest-less.md)
- [Unnecessary Else](else-unnecessary.md)
- [Top-level Variable Declarations](global-decl.md)
- [Prefix Unexported Globals with _](global-name.md)
- [Embedding in Structs](struct-embed.md)
- [Local Variable Declarations](var-decl.md)
- [nil is a valid slice](slice-nil.md)
- [Reduce Scope of Variables](var-scope.md)
- [Avoid Naked Parameters](param-naked.md)
- [Use Raw String Literals to Avoid Escaping](string-escape.md)
- Initializing Structs
- [Use Field Names to Initialize Structs](struct-field-key.md)
- [Omit Zero Value Fields in Structs](struct-field-zero.md)
- [Use `var` for Zero Value Structs](struct-zero.md)
- [Initializing Struct References](struct-pointer.md)
- [Initializing Maps](map-init.md)
- [Format Strings outside Printf](printf-const.md)
- [Naming Printf-style Functions](printf-name.md)
- Patterns
- [Test Tables](test-table.md)
- [Functional Options](functional-option.md)
- [Linting](lint.md)
57 changes: 57 additions & 0 deletions src/atomic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Use go.uber.org/atomic

Atomic operations with the [sync/atomic] package operate on the raw types
(`int32`, `int64`, etc.) so it is easy to forget to use the atomic operation to
read or modify the variables.

[go.uber.org/atomic] adds type safety to these operations by hiding the
underlying type. Additionally, it includes a convenient `atomic.Bool` type.

[go.uber.org/atomic]: https://godoc.org/go.uber.org/atomic
[sync/atomic]: https://golang.org/pkg/sync/atomic/

<table>
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
<tbody>
<tr><td>

```go
type foo struct {
running int32 // atomic
}

func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// already running…
return
}
// start the Foo
}

func (f *foo) isRunning() bool {
return f.running == 1 // race!
}
```

</td><td>

```go
type foo struct {
running atomic.Bool
}

func (f *foo) start() {
if f.running.Swap(true) {
// already running…
return
}
// start the Foo
}

func (f *foo) isRunning() bool {
return f.running.Load()
}
```

</td></tr>
</tbody></table>
93 changes: 93 additions & 0 deletions src/builtin-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Avoid Using Built-In Names

The Go [language specification] outlines several built-in,
[predeclared identifiers] that should not be used as names within Go programs.

Depending on context, reusing these identifiers as names will either shadow
the original within the current lexical scope (and any nested scopes) or make
affected code confusing. In the best case, the compiler will complain; in the
worst case, such code may introduce latent, hard-to-grep bugs.

[language specification]: https://golang.org/ref/spec
[predeclared identifiers]: https://golang.org/ref/spec#Predeclared_identifiers

<table>
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
<tbody>
<tr><td>

```go
var error string
// `error` shadows the builtin

// or

func handleErrorMessage(error string) {
// `error` shadows the builtin
}
```

</td><td>

```go
var errorMessage string
// `error` refers to the builtin

// or

func handleErrorMessage(msg string) {
// `error` refers to the builtin
}
```

</td></tr>
<tr><td>

```go
type Foo struct {
// While these fields technically don't
// constitute shadowing, grepping for
// `error` or `string` strings is now
// ambiguous.
error error
string string
}

func (f Foo) Error() error {
// `error` and `f.error` are
// visually similar
return f.error
}

func (f Foo) String() string {
// `string` and `f.string` are
// visually similar
return f.string
}
```

</td><td>

```go
type Foo struct {
// `error` and `string` strings are
// now unambiguous.
err error
str string
}

func (f Foo) Error() error {
return f.err
}

func (f Foo) String() string {
return f.str
}
```

</td></tr>
</tbody></table>

Note that the compiler will not generate errors when using predeclared
identifiers, but tools such as `go vet` should correctly point out these and
other cases of shadowing.
29 changes: 29 additions & 0 deletions src/channel-size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Channel Size is One or None

Channels should usually have a size of one or be unbuffered. By default,
channels are unbuffered and have a size of zero. Any other size
must be subject to a high level of scrutiny. Consider how the size is
determined, what prevents the channel from filling up under load and blocking
writers, and what happens when this occurs.

<table>
<thead><tr><th>Bad</th><th>Good</th></tr></thead>
<tbody>
<tr><td>

```go
// Ought to be enough for anybody!
c := make(chan int, 64)
```

</td><td>

```go
// Size of one
c := make(chan int, 1) // or
// Unbuffered channel, size of zero
c := make(chan int)
```

</td></tr>
</tbody></table>
Loading

0 comments on commit 9242f02

Please sign in to comment.