| What's in a name? |
| October 2014 |
| |
| Andrew Gerrand |
| Google Inc. |
| adg@golang.org |
| @enneff |
| https://go.dev/ |
| |
| |
| * Names matter |
| |
| Readability is the defining quality of good code. |
| |
| Good names are critical to readability. |
| |
| This talk is about naming in Go. |
| |
| |
| * Good names |
| |
| A good name is: |
| |
| - Consistent (easy to guess), |
| - Short (easy to type), |
| - Accurate (easy to understand). |
| |
| |
| * A rule of thumb |
| |
| The greater the distance between a name's declaration and its uses, |
| the longer the name should be. |
| |
| |
| * Use MixedCase |
| |
| Names in Go should use `MixedCase`. |
| |
| (Don't use `names_with_underscores`.) |
| |
| Acronyms should be all capitals, as in `ServeHTTP` and `IDProcessor`. |
| |
| |
| * Local variables |
| |
| Keep them short; long names obscure what the code _does_. |
| |
| Common variable/type combinations may use really short names: |
| |
| Prefer `i` to `index`. |
| Prefer `r` to `reader`. |
| Prefer `b` to `buffer`. |
| |
| Avoid redundant names, given their context: |
| |
| Prefer `count` to `runeCount` inside a function named `RuneCount`. |
| Prefer `ok` to `keyInMap` in the statement |
| |
| v, ok := m[k] |
| |
| Longer names may help in long functions, or functions with many local variables. |
| (But often this just means you should refactor.) |
| |
| |
| * Bad |
| |
| func RuneCount(buffer []byte) int { |
| runeCount := 0 |
| for index := 0; index < len(buffer); { |
| if buffer[index] < RuneSelf { |
| index++ |
| } else { |
| _, size := DecodeRune(buffer[index:]) |
| index += size |
| } |
| runeCount++ |
| } |
| return runeCount |
| } |
| |
| |
| * Good |
| |
| func RuneCount(b []byte) int { |
| count := 0 |
| for i := 0; i < len(b); { |
| if b[i] < RuneSelf { |
| i++ |
| } else { |
| _, n := DecodeRune(b[i:]) |
| i += n |
| } |
| count++ |
| } |
| return count |
| } |
| |
| |
| * Parameters |
| |
| Function parameters are like local variables, |
| but they also serve as documentation. |
| |
| Where the types are descriptive, they should be short: |
| |
| func AfterFunc(d Duration, f func()) *Timer |
| |
| func Escape(w io.Writer, s []byte) |
| |
| Where the types are more ambiguous, the names may provide documentation: |
| |
| func Unix(sec, nsec int64) Time |
| |
| func HasPrefix(s, prefix []byte) bool |
| |
| |
| * Return values |
| |
| Return values on exported functions should only be named for documentation purposes. |
| |
| These are good examples of named return values: |
| |
| func Copy(dst Writer, src Reader) (written int64, err error) |
| |
| func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error) |
| |
| |
| * Receivers |
| |
| Receivers are a special kind of argument. |
| |
| By convention, they are one or two characters that reflect the receiver type, |
| because they typically appear on almost every line: |
| |
| func (b *Buffer) Read(p []byte) (n int, err error) |
| |
| func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) |
| |
| func (r Rectangle) Size() Point |
| |
| Receiver names should be consistent across a type's methods. |
| (Don't use `r` in one method and `rdr` in another.) |
| |
| |
| * Exported package-level names |
| |
| Exported names are qualified by their package names. |
| |
| Remember this when naming exported variables, constants, functions, and types. |
| |
| That's why we have `bytes.Buffer` and `strings.Reader`, |
| not `bytes.ByteBuffer` and `strings.StringReader`. |
| |
| |
| * Interface Types |
| |
| Interfaces that specify just one method are usually just that function name with 'er' appended to it. |
| |
| type Reader interface { |
| Read(p []byte) (n int, err error) |
| } |
| |
| Sometimes the result isn't correct English, but we do it anyway: |
| |
| type Execer interface { |
| Exec(query string, args []Value) (Result, error) |
| } |
| |
| Sometimes we use English to make it nicer: |
| |
| type ByteReader interface { |
| ReadByte() (c byte, err error) |
| } |
| |
| When an interface includes multiple methods, choose a name that accurately describes its purpose (examples: `net.Conn`, `http.ResponseWriter`, `io.ReadWriter`). |
| |
| |
| * Errors |
| |
| Error types should be of the form `FooError`: |
| |
| type ExitError struct { |
| ... |
| } |
| |
| Error values should be of the form `ErrFoo`: |
| |
| var ErrFormat = errors.New("image: unknown format") |
| |
| |
| * Packages |
| |
| Choose package names that lend meaning to the names they export. |
| |
| Steer clear of `util`, `common`, and the like. |
| |
| |
| * Import paths |
| |
| The last component of a package path should be the same as the package name. |
| |
| "compress/gzip" // package gzip |
| |
| Avoid stutter in repository and package paths: |
| |
| "code.google.com/p/goauth2/oauth2" // bad; my fault |
| |
| For libraries, it often works to put the package code in the repo root: |
| |
| "github.com/golang/oauth2" // package oauth2 |
| |
| Also avoid upper case letters (not all file systems are case sensitive). |
| |
| * The standard library |
| |
| Many examples in this talk are from the standard library. |
| |
| The standard library is a great place to find good Go code. |
| Look to it for inspiration. |
| |
| But be warned: |
| |
| When the standard library was written, we were still learning. |
| Most of it we got right, but we made some mistakes. |
| |
| |
| * Conclusion |
| |
| Use short names. |
| |
| Think about context. |
| |
| Use your judgment. |
| |
| |