- Introduced a new skill for Golang, providing comprehensive guidance on writing, debugging, and best practices for Go programming. - Added reference materials including effective Go guidelines, common patterns, and a quick reference cheat sheet to support users in Go development. - Created a skill creator guide to assist in developing new skills with structured templates and resource management. - Implemented scripts for skill initialization and packaging to streamline the skill creation process.
424 lines
7.7 KiB
Markdown
424 lines
7.7 KiB
Markdown
# Effective Go - Key Points Summary
|
|
|
|
Source: https://go.dev/doc/effective_go
|
|
|
|
## Formatting
|
|
|
|
- Use `gofmt` to automatically format your code
|
|
- Indentation: use tabs
|
|
- Line length: no strict limit, but keep reasonable
|
|
- Parentheses: Go uses fewer parentheses than C/Java
|
|
|
|
## Commentary
|
|
|
|
- Every package should have a package comment
|
|
- Every exported name should have a doc comment
|
|
- Comments should be complete sentences
|
|
- Start comments with the name of the element being described
|
|
|
|
Example:
|
|
```go
|
|
// Package regexp implements regular expression search.
|
|
package regexp
|
|
|
|
// Compile parses a regular expression and returns, if successful,
|
|
// a Regexp object that can be used to match against text.
|
|
func Compile(str string) (*Regexp, error) {
|
|
```
|
|
|
|
## Names
|
|
|
|
### Package Names
|
|
- Short, concise, evocative
|
|
- Lowercase, single-word
|
|
- No underscores or mixedCaps
|
|
- Avoid stuttering (e.g., `bytes.Buffer` not `bytes.ByteBuffer`)
|
|
|
|
### Getters/Setters
|
|
- Getter: `Owner()` not `GetOwner()`
|
|
- Setter: `SetOwner()`
|
|
|
|
### Interface Names
|
|
- One-method interfaces use method name + -er suffix
|
|
- Examples: `Reader`, `Writer`, `Formatter`, `CloseNotifier`
|
|
|
|
### MixedCaps
|
|
- Use `MixedCaps` or `mixedCaps` rather than underscores
|
|
|
|
## Semicolons
|
|
|
|
- Lexer automatically inserts semicolons
|
|
- Never put opening brace on its own line
|
|
|
|
## Control Structures
|
|
|
|
### If
|
|
```go
|
|
if err := file.Chmod(0664); err != nil {
|
|
log.Print(err)
|
|
return err
|
|
}
|
|
```
|
|
|
|
### Redeclaration
|
|
```go
|
|
f, err := os.Open(name)
|
|
// err is declared here
|
|
|
|
d, err := f.Stat()
|
|
// err is redeclared here (same scope)
|
|
```
|
|
|
|
### For
|
|
```go
|
|
// Like a C for
|
|
for init; condition; post { }
|
|
|
|
// Like a C while
|
|
for condition { }
|
|
|
|
// Like a C for(;;)
|
|
for { }
|
|
|
|
// Range over array/slice/map/channel
|
|
for key, value := range oldMap {
|
|
newMap[key] = value
|
|
}
|
|
|
|
// If you only need the key
|
|
for key := range m {
|
|
// ...
|
|
}
|
|
|
|
// If you only need the value
|
|
for _, value := range array {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Switch
|
|
- No automatic fall through
|
|
- Cases can be expressions
|
|
- Can switch on no value (acts like if-else chain)
|
|
|
|
```go
|
|
switch {
|
|
case '0' <= c && c <= '9':
|
|
return c - '0'
|
|
case 'a' <= c && c <= 'f':
|
|
return c - 'a' + 10
|
|
case 'A' <= c && c <= 'F':
|
|
return c - 'A' + 10
|
|
}
|
|
```
|
|
|
|
### Type Switch
|
|
```go
|
|
switch t := value.(type) {
|
|
case int:
|
|
fmt.Printf("int: %d\n", t)
|
|
case string:
|
|
fmt.Printf("string: %s\n", t)
|
|
default:
|
|
fmt.Printf("unexpected type %T\n", t)
|
|
}
|
|
```
|
|
|
|
## Functions
|
|
|
|
### Multiple Return Values
|
|
```go
|
|
func (file *File) Write(b []byte) (n int, err error) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Named Result Parameters
|
|
- Named results are initialized to zero values
|
|
- Can be used for documentation
|
|
- Enable naked returns
|
|
|
|
```go
|
|
func ReadFull(r Reader, buf []byte) (n int, err error) {
|
|
for len(buf) > 0 && err == nil {
|
|
var nr int
|
|
nr, err = r.Read(buf)
|
|
n += nr
|
|
buf = buf[nr:]
|
|
}
|
|
return
|
|
}
|
|
```
|
|
|
|
### Defer
|
|
- Schedules function call to run after surrounding function returns
|
|
- LIFO order
|
|
- Arguments evaluated when defer executes
|
|
|
|
```go
|
|
func trace(s string) string {
|
|
fmt.Println("entering:", s)
|
|
return s
|
|
}
|
|
|
|
func un(s string) {
|
|
fmt.Println("leaving:", s)
|
|
}
|
|
|
|
func a() {
|
|
defer un(trace("a"))
|
|
fmt.Println("in a")
|
|
}
|
|
```
|
|
|
|
## Data
|
|
|
|
### Allocation with new
|
|
- `new(T)` allocates zeroed storage for new item of type T
|
|
- Returns `*T`
|
|
- Returns memory address of newly allocated zero value
|
|
|
|
```go
|
|
p := new(int) // p is *int, points to zeroed int
|
|
```
|
|
|
|
### Constructors and Composite Literals
|
|
```go
|
|
func NewFile(fd int, name string) *File {
|
|
if fd < 0 {
|
|
return nil
|
|
}
|
|
return &File{fd: fd, name: name}
|
|
}
|
|
```
|
|
|
|
### Allocation with make
|
|
- `make(T, args)` creates slices, maps, and channels only
|
|
- Returns initialized (not zeroed) value of type T (not *T)
|
|
|
|
```go
|
|
make([]int, 10, 100) // slice: len=10, cap=100
|
|
make(map[string]int) // map
|
|
make(chan int, 10) // buffered channel
|
|
```
|
|
|
|
### Arrays
|
|
- Arrays are values, not pointers
|
|
- Passing array to function copies the entire array
|
|
- Array size is part of its type
|
|
|
|
### Slices
|
|
- Hold references to underlying array
|
|
- Can grow dynamically with `append`
|
|
- Passing slice passes reference
|
|
|
|
### Maps
|
|
- Hold references to underlying data structure
|
|
- Passing map passes reference
|
|
- Zero value is `nil`
|
|
|
|
### Printing
|
|
- `%v` - default format
|
|
- `%+v` - struct with field names
|
|
- `%#v` - Go syntax representation
|
|
- `%T` - type
|
|
- `%q` - quoted string
|
|
|
|
## Initialization
|
|
|
|
### Constants
|
|
- Created at compile time
|
|
- Can only be numbers, characters, strings, or booleans
|
|
|
|
### init Function
|
|
- Each source file can have `init()` function
|
|
- Called after package-level variables initialized
|
|
- Used for setup that can't be expressed as declarations
|
|
|
|
```go
|
|
func init() {
|
|
// initialization code
|
|
}
|
|
```
|
|
|
|
## Methods
|
|
|
|
### Pointers vs. Values
|
|
- Value methods can be invoked on pointers and values
|
|
- Pointer methods can only be invoked on pointers
|
|
|
|
Rule: Value methods can be called on both values and pointers, but pointer methods should only be called on pointers (though Go allows calling on addressable values).
|
|
|
|
```go
|
|
type ByteSlice []byte
|
|
|
|
func (slice ByteSlice) Append(data []byte) []byte {
|
|
// ...
|
|
}
|
|
|
|
func (p *ByteSlice) Append(data []byte) {
|
|
slice := *p
|
|
// ...
|
|
*p = slice
|
|
}
|
|
```
|
|
|
|
## Interfaces and Other Types
|
|
|
|
### Interfaces
|
|
- A type implements an interface by implementing its methods
|
|
- No explicit declaration of intent
|
|
|
|
### Type Assertions
|
|
```go
|
|
value, ok := str.(string)
|
|
```
|
|
|
|
### Type Switches
|
|
```go
|
|
switch v := value.(type) {
|
|
case string:
|
|
// v is string
|
|
case int:
|
|
// v is int
|
|
}
|
|
```
|
|
|
|
### Generality
|
|
- If a type exists only to implement an interface and will never have exported methods beyond that interface, there's no need to export the type itself
|
|
|
|
## The Blank Identifier
|
|
|
|
### Unused Imports and Variables
|
|
```go
|
|
import _ "net/http/pprof" // Import for side effects
|
|
```
|
|
|
|
### Interface Checks
|
|
```go
|
|
var _ json.Marshaler = (*RawMessage)(nil)
|
|
```
|
|
|
|
## Embedding
|
|
|
|
### Composition, not Inheritance
|
|
```go
|
|
type ReadWriter struct {
|
|
*Reader // *bufio.Reader
|
|
*Writer // *bufio.Writer
|
|
}
|
|
```
|
|
|
|
## Concurrency
|
|
|
|
### Share by Communicating
|
|
- Don't communicate by sharing memory; share memory by communicating
|
|
- Use channels to pass ownership
|
|
|
|
### Goroutines
|
|
- Cheap: small initial stack
|
|
- Multiplexed onto OS threads
|
|
- Prefix function call with `go` keyword
|
|
|
|
### Channels
|
|
- Allocate with `make`
|
|
- Unbuffered: synchronous
|
|
- Buffered: asynchronous up to buffer size
|
|
|
|
```go
|
|
ci := make(chan int) // unbuffered
|
|
cj := make(chan int, 0) // unbuffered
|
|
cs := make(chan *os.File, 100) // buffered
|
|
```
|
|
|
|
### Channels of Channels
|
|
```go
|
|
type Request struct {
|
|
args []int
|
|
f func([]int) int
|
|
resultChan chan int
|
|
}
|
|
```
|
|
|
|
### Parallelization
|
|
```go
|
|
const numCPU = runtime.NumCPU()
|
|
runtime.GOMAXPROCS(numCPU)
|
|
```
|
|
|
|
## Errors
|
|
|
|
### Error Type
|
|
```go
|
|
type error interface {
|
|
Error() string
|
|
}
|
|
```
|
|
|
|
### Custom Errors
|
|
```go
|
|
type PathError struct {
|
|
Op string
|
|
Path string
|
|
Err error
|
|
}
|
|
|
|
func (e *PathError) Error() string {
|
|
return e.Op + " " + e.Path + ": " + e.Err.Error()
|
|
}
|
|
```
|
|
|
|
### Panic
|
|
- Use for unrecoverable errors
|
|
- Generally avoid in library code
|
|
|
|
### Recover
|
|
- Called inside deferred function
|
|
- Stops panic sequence
|
|
- Returns value passed to panic
|
|
|
|
```go
|
|
func server(workChan <-chan *Work) {
|
|
for work := range workChan {
|
|
go safelyDo(work)
|
|
}
|
|
}
|
|
|
|
func safelyDo(work *Work) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
log.Println("work failed:", err)
|
|
}
|
|
}()
|
|
do(work)
|
|
}
|
|
```
|
|
|
|
## A Web Server Example
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
)
|
|
|
|
type Counter struct {
|
|
n int
|
|
}
|
|
|
|
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
ctr.n++
|
|
fmt.Fprintf(w, "counter = %d\n", ctr.n)
|
|
}
|
|
|
|
func main() {
|
|
ctr := new(Counter)
|
|
http.Handle("/counter", ctr)
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
}
|
|
```
|
|
|