These arent strict rules, just a description of how the code is generally written so you know what to expect when reading through it or contributing.
Theres a loose system of prefixes that tell you what kind of type youre looking at:
| Prefix | Kind | Examples |
|---|---|---|
S |
Struct | SClient, SSocket, SRequest |
M |
Map | MFileState, MLogLevel |
T |
Defined type / alias | TLogLevel, TMethod |
E |
Enum / bitmask | EFileEvent, ELogLevel |
Fn |
Function type | FnValidator, FnExecution |
That said, not every type follows this. Some packages just dont use prefixes at all.
Pointer receiver if the method mutates anything or if the type holds a mutex or a channel. Value receiver otherwise:
func (this *MFileState) Push(path string) error // pointer: mutates
func (this SClog) Clone(override SClog) SClog // value: returns a copy
func (this TList) String() string // value: read-onlyAvoid creating single character variables and instead make the variable name descriptive for its use.
The exception to this is of course some universal conventions like i, j, k ... in for loops.
Use var over :=.
It is generally harder to spot a single : to find the point of decleration.
The keyword var is usually also colored differently which makes spotting it even easier.
var str strings.Builder
var info, err = os.Stat(path):= only really shows up in for range loops and if-scoped declarations:
for i, arg := range args { }
if idx := strings.LastIndex(partial, "/"); idx >= 0 { }Always use this.
There is no reason (currently known to me) not to use this as the receiver.
func (this *SClient) Active() bool { return this.Connection != nil }The benefits:
- You dont have to think of another good and descriptive variable name.
- You never have to question what the receiver is for this function.
Check the error right after the call, no fmt.Errorf wrapping, no defer-error patterns. Just the straightforward if err != nil and log it.
Every package that logs makes its own logger:
var clog = clogger.Default.Clone(clogger.SClog{Name: "packagename"})Each level has a regular and a formatted variant, so you get both Error(msg) and Errorf(format, args...) and the same goes for the rest.
strings.Builder for anything more than a one-liner:
var builder strings.Builder
builder.WriteString(alias)
builder.WriteRune('\n')
return builder.String()str += should be avoided.
The entry file of a package is named after it - config/config.go, watcher/watcher.go, that kind of thing.
Sub-packages that implement CLI commands register themselves through init() by calling commands.List.Push(&def).
The init.go file at the project root imports them with a blank identifier to trigger all of that.
// NOTE:- Workarounds and non-obvious choices.// TODO:- Incomplete features.// BUG:- Known issues.
- Mutex:
sync.MutexwithLock()/defer Unlock()on types that get accessed from different goroutines. - Generics: A
call[TResult any]()helper inwebsocket/socket.gocuts down on boilerplate for the typed socket methods:
func call[TResult any](this *SSocket, method TMethod, params any, callback func(*TResult)) {
var message = this.send(method, params)
var response = AwaitResponse[TResult](message)
if callback != nil {
callback(response)
}
}