Go EP4: Skip the ‘Get’ Prefix For Getters
From enhancing code readability to optimizing inter-goroutine communication, these tips aim to refine your coding approach.
Welcome back to our weekly Go insights.
Here’s a recap of last week’s focus:
Pass Values, Not Pointers
Pointer Receivers and Value Receivers Nuances
Prefer Using a Pointer Receiver When Defining Methods
Now, we’re focusing on a few selected best practices and insights for Go programmers. From enhancing code readability to optimizing inter-goroutine communication, these tips aim to refine your coding approach:
Simplify Function Signatures with Structs or Variadic Options
Skip the ‘Get’ prefix for getters
Prefer ‘chan struct{}’ over ‘chan bool’ for signaling between goroutines.
Avoid Repetition in Naming
Take 1: Simplify Function Signatures with Structs or Variadic Options
When designing functions in Go, we might encounter the need to pass numerous parameters.
This can cloud the function’s purpose and make maintaining code a chore, especially when parameters of the same type are involved.
To keep things tidy, consider two strategies:
Option structs
Variadic options.
1. Option structs
Bundle your parameters into a struct, this not only enhances readability but also simplifies argument passing.
But, when to use it?
Your function has a lengthy list of parameters.
You aim for self-documenting code, as struct fields inherently describe their purpose (by name).
You wish to set default values easily or modify options flexibly.
When using this pattern, the context.Context should remain as a separate parameter and not be included within the options struct.
This is due to the context’s unique role in controlling request-scoped values, deadlines, and cancellation signals.
But here’re some minor tips when using optional structs:
The struct should be backward-compatible so that when adding a new field, we don’t break anything previously.
We can always validate the struct before it is processed.
Consider hiding your option struct (making it unexported), then expose a NewXXX() function and set your default values there.
2. Variadic Options
This method utilizes Go’s functional capabilities, allowing you to pass a flexible number of options in a cleaner manner.
It’s ideal when:
The function needs to be highly configurable.
Most options are optional or seldom used.
You prefer a concise call site.
It’s easier to set a default value than with an optional struct, you don’t need to hide it, just put the default value directly in ConnectToDatabase.
Take 2: Skip the ‘Get’ prefix for getters
When we’re coding, to name a function we start with a verb, right? get, set, fetch, update, calculate,…
But getters are one of the exceptions in Go.
“Why I even need getters and setters?”
In Go, encapsulation is applied through method visibility and naming conventions, subtly supporting encapsulation without strictly enforcing getter/setter methods.
Yet, if additional logic is needed or if we want to access a computed field, there’s nothing wrong with manually defining getters and setters.
“What is the idiomatic way to define a getter name?”
The idiomatic way is to simply use the field name with the first letter capitalized for getters (to export it):
Another illustrative example involves methods that provide computed properties or configurations that aren’t directly stored as fields within a struct.
Take 3: Prefer ‘chan struct{}’ Over ‘chan bool’ for Signaling Between Goroutines.
chan struct{}: Is used purely for signaling because the struct{} type occupies zero bytes of memory.
chan bool: Can also signal events, but it sends a boolean value (true or false), which might or might not carry some meaning. —
“Why prefer ‘chan struct{}’?”
Consider an example with ‘chan bool’:
Usage might be confusing: Do we send true to stop? False?
Preferring chan struct{} is a way to say: “I’m only interested in the fact that something happened, not in any data being passed.”
So, there are 2 (main) advantages:
Since struct{} is of zero size, sending a value over a ‘chan struct{}’ does not move any data across the channel, just the signal (a subtle memory optimization).
When a dev sees ‘chan struct{}’ in code, it’s immediately clear that the channel is used for signaling, thus reducing ambiguity.
The disadvantage might be the somewhat awkward “struct{}{}” syntax. This solution prevents the channel’s misuse for data transmission when its intended purpose is signaling.
Take 4: Avoid Repetition in Naming
1. Package vs. Exported Symbol Name
When you’re naming something that’s exported (visible outside its package), avoid repeating the package name.
Otherwise, names become longer and more repetitive, as the package name is already visible when these symbols are used outside the package:
This “better” version removes the repetition.
When you use it, it reads naturally: chocolate.NewBar(), clearly creating a new chocolate bar without the redundancy.
2. Variable Name vs. Type
We often don’t need to repeat the variable’s type in its name.
It’s usually clear from the context or how we use it.
Yet exceptions exist and should be considered.
If you have both []Car and map[string]Car (maybe for fast lookup), then do it for clarity.
“But how to name it? carList and carMap?”
CarList and carMap are good solutions. But we can make it clearer by indicating the form or stage of the data: []Car cars and map[string]Car carLookup.
Here is another example:
In the second solution, it’s clear it’s a string and an input.
3. Avoiding Repetition Boils Down to ‘Context’
Everything we’ve discussed so far boils down to context:
Package names
Method names
Type names
Filename
These should guide you in choosing names that are simple yet informative and avoid unnecessary repetition. Let’s discuss some other cases related to “context”.
Method with type name:
Function and its arguments:
Inside functions, especially when dealing with parameters or data closely related to the function’s purpose, for a bad example:
Now, we rename both the function name and the local variable name: