- 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.
7.7 KiB
7.7 KiB
Effective Go - Key Points Summary
Source: https://go.dev/doc/effective_go
Formatting
- Use
gofmtto 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:
// 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.Buffernotbytes.ByteBuffer)
Getters/Setters
- Getter:
Owner()notGetOwner() - Setter:
SetOwner()
Interface Names
- One-method interfaces use method name + -er suffix
- Examples:
Reader,Writer,Formatter,CloseNotifier
MixedCaps
- Use
MixedCapsormixedCapsrather than underscores
Semicolons
- Lexer automatically inserts semicolons
- Never put opening brace on its own line
Control Structures
If
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
Redeclaration
f, err := os.Open(name)
// err is declared here
d, err := f.Stat()
// err is redeclared here (same scope)
For
// 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)
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
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
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
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
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
p := new(int) // p is *int, points to zeroed int
Constructors and Composite Literals
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)
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
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).
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
value, ok := str.(string)
Type Switches
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
import _ "net/http/pprof" // Import for side effects
Interface Checks
var _ json.Marshaler = (*RawMessage)(nil)
Embedding
Composition, not Inheritance
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
gokeyword
Channels
- Allocate with
make - Unbuffered: synchronous
- Buffered: asynchronous up to buffer size
ci := make(chan int) // unbuffered
cj := make(chan int, 0) // unbuffered
cs := make(chan *os.File, 100) // buffered
Channels of Channels
type Request struct {
args []int
f func([]int) int
resultChan chan int
}
Parallelization
const numCPU = runtime.NumCPU()
runtime.GOMAXPROCS(numCPU)
Errors
Error Type
type error interface {
Error() string
}
Custom Errors
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
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
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))
}