GoSuda

Why Go Chose Explicit Error Returns and panic-recover Over try-catch Exception Throwing

By Rabbit Princess views ...

Go intentionally does not support try-catch and supports only the panic-recover syntax. This has drawn complaints from many developers who are accustomed to error handling with try-catch. Then why was try-catch left out? It is because try-catch has several problems.

try-catch-finally

The try-catch statement is a construct for handling error situations and exceptional situations that may occur during program execution (at runtime). In addition, the finally clause contains code that must be executed unconditionally, regardless of whether an exception has occurred.

Error Handling and Responsibility

In the 1980s and 1990s, error handling was very simple. The error messages were limited to "floppy disk full," "no floppy disk in drive," and "no write permission for floppy disk." During this period, when an error occurred, developers would throw the error up to a central error handling point and handle it in a unified manner. In this situation, the try-catch statement worked efficiently.

However, as time passed, circumstances changed. Business logic became complex, databases and transactions emerged, programs had to call numerous APIs over networks and interpret countless request messages, and with the rise of concurrent programming, errors sometimes had to be handled in threads other than the main thread.

Errors became too complex to be handled in one place, and no single location could take responsibility for all errors. At this point, a serious problem arises with try-catch.

Delegation of Responsibility in try-catch

Put simply, try-catch is a method by which the entity that raises an error delegates the responsibility for handling it (cleanup) to someone else. The party to whom it is delegated may be the catch statement, its parent method, its parent's parent's parent's parent's... and so on. In other words, in a world where error handling grows more numerous and complex, the approach try-catch adopts is precisely "someone will take care of it." Consider the code below.

1try {
2    data = readFile("hello.txt");
3    structuredData = parseData(data);
4    insertDBStatus(structuredData[1]);
5    startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7    e.printStackTrace();
8}

The problem with the code above is that it is unclear which entity handles printStackTrace, and it becomes impossible to tell in which line the error occurred. Moreover, the more logic is added inside the try statement, the more terrible the problem becomes. Ironically, however, as development became more complex, developers became addicted to try-catch constructs that shift responsibility, stopped thinking about error handling, their sense of accountability faded, and eventually they forgot the essence of error and exception handling. So how did Golang solve this problem?

panic, recover

One of Go's strengths is that it has various systems designed to make developers good developers rather than leading them down bad paths, and panic-recover is one example of this.

Panic and recover may appear at first glance to be no different from try-catch, but they differ in that they do not push responsibility outward when a problem occurs. In Go, when a panic occurs, it must be resolved at the location where the panic happened rather than passing the value outward. This gives the developer responsibility for error handling and encourages them to think more deeply about where, by whom, and how the error should be handled. It guarantees autonomy to the user while leaving room for reflection.

Also, the choice of the word "panic" is quite outstanding. Unlike try-recover, the word panic-recover itself pressures developers to use it only for clear error situations rather than exceptional ones. Naturally, developers do not overuse recover and use it only where necessary. This greatly helps in writing concise code in Go.