Go Style Guide
Unless specified otherwise below, stick to golang’s CodeReviewComments.
Generally the code should be formatted with gofmt (checked by CI).
Lines must be at most 100 characters long (checked by CI via lll).
Naming
We use mixedCaps notation as recommended by Effective Go. Perhaps unintuitively, Go
treats ID as an initialism, and we treat If as a word. The following
rules apply (note that a significant part of the code base uses other
notations; these should be refactored, however):
- Use - sdor- SDto refer to the SCION Daemon, not- Sciondor- SCIOND.
- Use - IfIDor- ifIDfor SCION Interface Identifiers, not- IFIDnor- InterfaceIDnor- intfID.
- Use - IfIDSomethingor- ifIDSomethingwhen concatenating- ifIDwith- something.
- Use - Svcor- svcfor SCION Service Addresses, not- SVCor- Service.
- Use - TRCor- trcfor Trust Root Configurations, not- Trc.
Imports (checked by CI)
Imports are grouped (separated by empty line) in the following order:
- standard lib 
- third-party packages 
- our packages 
Within each group the imports are alphabetically sorted.
Function declaration over multiple lines
If a function declaration uses more than 1 line, each parameter should be declared on a separate line and the first line of the function body should be empty:
func usingMultipleLines(
    foo int,
    bar []string,
    qux bool,
) error {
    // start the code here
}
Abbreviations
For variable names common abbreviations should be preferred to full names, if they are clear from the context, or used across the codebase.
Examples:
- Seginstead of- Segment
- Msgrinstead of- Messenger
- Syncinstead of- Synchronization
Specialities
goroutines should always call defer log.HandlePanic() as the first statement (checked by CI).
Logging
- To use logging, import - "github.com/scionproto/scion/go/lib/log".
- The logging package supports three logging levels: - Debug: entries that are aimed only at developers, and include very low-level details. These should never be enabled on a production machine. Examples of such entries may include opening a socket, receiving a network message, or loading a file from the disk. 
- Info: entries that either contain high-level information about what the application is doing, or “errors” that are part of normal operation and have been handled by the code. Examples of such entries may include: issuing a new certificate for a client, having the authentication of an RPC call fail, or timing out when trying to connect to a server. 
- Error: entries about severe problems encountered by the application. The application might even need to terminate due to such an error. Example of such entries may include: the database is unreachable, the database schema is corrupted, or writing a file has failed due to insufficient disk space. 
 
- Do not use - log.Root().New(...), instead use New directly:- log.New(...).
- Keys should be snake case; use - log.Debug("msg", "some_key", "foo")instead of- log.Debug("msg", "someKey", "foo")or other variants.
- Try to not repeat key-value pairs in logging calls that are close-by; derive a new logging context instead (e.g., if multiple logging calls refer to a - "request"for- "Foo", create a sublogger with this context by calling- newLogger = parentLogger.New("request", "Foo")and then use- newLogger.Debug("x")).
- An empty - log.New()has no impact and should be omitted.
Here is an example of how logging could be added to a type:
type Server struct {
	// Logger is used by the server to log information about internal events. If nil, logging
	// is disabled.
	Logger Logger
}
// Use a func because this is a godoc example, but this would normally be something like
// s.Run().
Run := func(s *Server) {
	if s.Logger == nil {
		s.Logger = DiscardLogger{}
	}
	s.Logger.Debug("this message is discarded now")
}
Run(&Server{})
Metrics
Metrics definition and interactions should be consistent throughout the code base. A common pattern makes it easier for developers to implement and refactor metrics, and for operators to understand where metrics are coming from. As a bonus, we should leverage the type system to help us spot as many errors as possible.
To write code that both includes metrics, and is testable, we use the metric
interfaces defined in the pkg/metrics/v2 package.
A simple example with labels (note that Giant’s metrics can be unit tested by
mocking the counter):
type Giant struct {
	MagicBeansEaten metrics.Counter
	RedPillsEaten   metrics.Counter
	BluePillsEaten  metrics.Counter
}
counter := prometheus.NewCounter(prometheus.CounterOpts{
	Name: "magic_beans_eaten_total",
	Help: "Number of magic beans eaten.",
})
pillCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
	Name: "pills_eaten_total",
	Help: "Number of pills eaten.",
}, []string{"color"})
giant := Giant{
	MagicBeansEaten: counter,
	RedPillsEaten:   pillCounter.WithLabelValues("red"),
	BluePillsEaten: pillCounter.With(prometheus.Labels{
		"color": "blue",
	}),
}
giant.MagicBeansEaten.Add(4)
giant.RedPillsEaten.Add(2)
giant.BluePillsEaten.Add(1)
Calling code can later create Giant objects with Prometheus metric reporting
by plugging a prometheus counter as the Counter as shown in the example.
Note
Some packages have metrics packages that define labels and initialize
metrics (see the go/cs/beacon/metrics package for an example). While this
is also ok, the recommended way is to define labels in the package itself and
initialize metrics in main.
Best Practices
- Namespace should be one word. 
- Subsystem should be one word (if present). 
- Use values that can be searched with regex. E.g. prepend - err_for every error result.
- snake_caselabel names and values.
- Put shared label names and values into - go/lib/prom.
- Always initialize - CounterVecto avoid hidden metrics link.