Why Does Go Not Have Try-Catch?
Go intentionally does not support try-catch and only supports the panic-recover syntax. This has drawn criticism from numerous developers accustomed to error handling using try-catch. So, what is the reason for not including try-catch? It is because try-catch possesses several inherent problems.
try-catch-finally
The try-catch construct is designed to handle error conditions and exceptional situations that may occur during program execution (runtime). Furthermore, the finally block contains code that must be executed irrespective of whether an exception occurred.
Error Handling and Responsibility
In the 1980s and 1990s, error handling was exceedingly simplistic. Error messages were limited to phrases such as 'Floppy disk full,' 'No floppy disk in drive,' or 'Floppy disk write protected.' During this era, developers would uniformly process errors by throwing them to a centralized error handling point. In such circumstances, the try-catch construct operated efficiently.
However, over time, the situation changed. Business logic became more complex, databases and transactions emerged, and it became necessary to interpret numerous request messages by calling countless APIs over networks. Moreover, with the advent of concurrency programming, errors often had to be handled in threads other than the main thread.
Errors became too complex to be handled in a single location, and no single entity could assume responsibility for all errors. This is where a serious problem with try-catch arises.
The Delegation of Responsibility in try-catch
In essence, try-catch is a mechanism by which the entity that caused an error delegates the responsibility for its aftermath to another party. This delegated party could be the catch block, its parent method, or even a distant ancestor method. In other words, in a world where error handling is increasingly prevalent and intricate, the approach adopted by try-catch is precisely, "Someone else will handle it." Consider the following code:
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 the ambiguity regarding the entity responsible for processing printStackTrace, and the inability to ascertain which part of the code generated the error. This problem becomes even more severe as the logic within the try statement expands. Paradoxically, as development grew more complex, developers became addicted to the responsibility-delegating try-catch construct, neglecting considerations for error handling, diminishing their sense of responsibility, and ultimately forgetting the essence of error and exception handling. So, how did Golang address this issue?
panic, recover
One of Go's advantages is its provision of various systems designed to guide developers toward good practices rather than leading them astray. Panic-recover serves as an exemplary instance of this.
At first glance, panic and recover might appear indistinguishable from try-catch; however, they differ in that they do not externalize responsibility when a problem arises. In Go, when a panic occurs, the resolution should ideally take place at the location where the panic originated, rather than externalizing the value. This approach assigns responsibility for error handling to the developer, prompting more profound consideration of where, by whom, and how the error should be managed. It grants autonomy to the user while simultaneously providing room for thoughtful consideration.
Furthermore, the selection of the word "panic" is highly commendable. Unlike try-recover, panic-recover intrinsically pressures developers to use it exclusively in unambiguous error scenarios, rather than mere exceptional situations. Consequently, developers naturally refrain from overusing recover and employ it only where necessary. This significantly contributes to writing concise code in Go.