visit
Go has a built-in error
type, which allows developers to easily differentiate errors from “normal” strings and check to make sure functions exit without a problem in a more explicit way. The error
type is an that simply requires the type in question to define an Error()
function that prints itself as a string.
type error interface {
Error() string
}
Never use a normal string where an error is appropriate! When a string is returned from your function you imply to other developers that when the string isn’t empty it’s just “business as usual”. The error
type makes it clear that something is wrong when the error isn’t nil
.
func divide(a, b float64) (float64, string) {
if b == 0 {
return 0.0, "can't divide by zero"
}
return a / b, ""
}
This will work perfectly. In fact, anywhere an error type works a string could be used instead. However, if we’re interested in writing code that other developers can more quickly understand and make contributions to, we should use an error
type:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0.0, errors.New("can't divide by zero")
}
return a / b, nil
}
func formatTimeWithMessage(hours, minutes int) (string, error) {
formatted, err := formatTime(hours, minutes)
if err != nil {
return "", err
}
return "It is " + formatted + " o'clock", nil
}
The problem here is that the formatTime
function can be called many other places within our application or library. If all we do is pass along the raw error, when the error is eventually printed, it gets really hard to tell where exactly the error originated from. Instead, let’s do the following:
func formatTimeWithMessage(hours, minutes int) (string, error) {
formatted, err := formatTime(hours, minutes)
if err != nil {
return "", fmt.Errorf("formatTimeWithMessage: %v", err)
}
return "It is " + formatted + " o'clock", nil
}
Additionally, if you are working in Go 1.13 or later, then you can look into the more explicit method for error chains.
[fmt.Errorf()](//golang.org/pkg/fmt/#Errorf)
is similar to fmt.Printf()
, but returns an error
instead of a string
. You may have done this in the past:
err := errors.New("Bad thing happened! " + oldErr.Error())
This can be accomplished more succinctly using fmt.Errorf():
err := fmt.Errorf("Bad thing happened! %v", oldError)
The difference in readability becomes even more obvious when the formatting in question is more complicated and includes more variables.
func main() {
make := "Toyota"
myCar := Car{year:1996, make: &make}
fmt.Println(myCar)
}
Will print something like:
{1996 0x40c138}
We likely want to get the value in the pointer, and we probably want to see the keys of the struct. So we can implement a default String()
method on our struct. If we do so, then the Go compiler will use that method when printing.
func (c Car)String() string{
return fmt.Sprintf("{make:%s, year:%d}", *c.make, c.year)
}
func main() {
make := "Toyota"
myCar := Car{year:1996, make: &make}
fmt.Println(myCar)
}
Which will print something like:
{make:Toyota, year:1996}
fmt.Printf("%s beat %s in the game\n", playerOne, playerTwo)
Turns out, it is much easier to just use the fmt.Println()
function’s ability to add spacing:
fmt.Println(playerOne, "beat", playerTwo, "in the game")
It’s often tempting to roll your own logging package, but I would advise that in most cases, the is probably all you need. The standard library defines a type, , which you can use to customize your logging in an idiomatic way. If you don’t want that much power and responsibility, you can do what I usually do and use the standard and functions which just print to standard output along with a formatted date and time prefix.