Don't panic!
Tags: go
Published: 2024-03-28 08:56:18

_This post comes from my daily email list: Boldly Go! Daily. [Sign up to learn a bit more about Go every day!](https://boldlygo.tech/daily)_

--

Don’t panic.

Don’t panic is such ubiquitous Go advice that it’s one of the famous [Go proverbs](https://go-proverbs.github.io/).

So why do we have a built-in `panic` function if we’re not supposed to use it?

Well, it’s not so much that it’s never appropriate to use, as much as it’s often heavily overused. Especially by programmers who are accustomed to using exceptions and try/catch blocks to manage control flow.

Don’t do that!

At the moment I’m helping one client with a massive refactor of their application to remove the problematic, non-idiomatic pattern of using `panic` rather than proper Go error handling.

I’m sure we’ll talk a lot more about this in the future, but probably the most important reason not to panic is that it’s very non-obvious. This problem exists in languages that uses exceptions for error handling, too (and is why Go’s design doesn’t encourage this pattern). Consider this code (taken from the above mentioned client’s code base, with names edited to protect the innocent):

```golang
func CreateRecordWithIDAndType(ctx context.Context, id model.ID, typ model.ResourceType) {
	userID := appctx.AuthenticatedUserIdOptional(ctx)
	if userID.Valid() {
		recordReq := &RecordRequest{
			ActionRequest: ActionRequest{
				Type: learnType,
				Id:   id,
			},
		}

		CreateRecordWithCDP(ctx, recordReq)
	}
}
```

Here’s the question: Can the call to `CreateRecordWithCDP` result in an error condition?

It’s impossible to be sure. Although we have one strong clue: It accepts a `context.Context` value, which is often, though not always, an indication that the function call may block, and be terminated early if the context is cancelled. This would suggest that an error state is possible. Although the context could just be used to read a value, in which case it may legitimately not be possible for `CreateRecordWithCDP` to result in an error condition. It also doesn’t return anything, so whatever it’s “creating”, presumably is stored somewhere–probably in a database. And database operations, as a rule, can err.

In any case, since we don’t know, we’re forced to either investigate `CreateRecordWithCDP` (and any functions it calls) to determine if it might panic, or just play it safe, and add a deferred `recover` before we call it.

Wouldn’t it be much easier if we had some obvious indication? Perhaps like this new version?

```golang
func CreateRecordWithIDAndType(ctx context.Context, id model.ID, typ model.ResourceType) error {
	userID := appctx.AuthenticatedUserIdOptional(ctx)
	if userID.Valid() {
		recordReq := &RecordRequest{
			ActionRequest: ActionRequest{
				Type: learnType,
				Id:   id,
			},
		}

		return CreateRecordWithCDP(ctx, recordReq)
	}
  return nil
}
```

This make the fact that calling `CreateRecordWithCDP` (and in turn `CreateRecordWithIDAndType`) can result in an error condition. And it makes programming to use these functions much simpler, and eliminates a lot of guesswork.

Of course, this does lead to the situation many complain about, of cluttering your code with `if err != nil { ... }`. But IMO, while this is a bit annoying to type, typing is a very small price to pay for the clarity it provides.