
The main advantage of Go
If you ask the average static Go developer what advantages Go has, you will most likely hear a familiar list of goodies. A lot has already been written about them, but very often another thing is bypassed, a much more interesting one is the long-term effect of various solutions of the language design. I want to open this topic a little wider, which is actually relevant not only for Go. But in this article, I will take two aspects as an example - the way errors are handled in Go and the testing system, and try to show how the design of the language forces people to write better code.

You probably know that in imperative languages there are two main mechanisms for reporting an error - throw an exception or return a code / value explicitly. One could say that there are even two camps here - supporters of exceptions and supporters of an explicit return, but everything is somewhat worse. There are actually two camps, but they are different - those who understand the importance of error handling in code, and those who for the most part ignore this aspect of programming. Moreover, the second camp is, so far, the undisputed leader.
It is logical to assume that this is exactly what distinguishes "good" programmers from "bad" programmers, and there is undoubtedly some truth to this. But there is one thing. Toolkit - in this case it is a "programming language" - also solves. If your language allows you to do the “wrong” thing much easier than doing the “right thing” - be sure that no number of articles and books “How to write on [LANG]” will help - people will continue to do the wrong thing. Just because it's easier.
It would seem that every schoolchild already knows that “global variables” are evil. How many articles on this topic - everything seems to be clear to everyone. Nevertheless, even now, in 2015, you will find tons of code using global variables. Why?
And because to create a global variable - “do it wrong” takes exactly one line in almost any programming language. At the same time, in order to create any of the “right options”, any minimal wrapper - you need to spend more time and effort. Even if there is 1 character more - but it decides.
This is very important to realize - the toolkit decides. The toolkit forms our choice.
But back to error handling, we’ll try to understand why the Go authors considered the exceptions “the wrong way”, decided not to implement them in Go, and what is the difference between “returning multiple values” in Go and similar ones in other languages.
Take for example a simple thing - opening a file.
Here is the C ++ code
This is fully working code, and it would be “correct” to handle the error either by checking the failbit flag, or by turning on ifstream.exeptions () and wrapping everything in a try {} catch {} block. But “wrong” is much easier to do - just one line, and “error handling can be added later.”
The same Python code:
The same thing - it is much easier to just call open (), and then handle the error handling later. At the same time, “error handling” most often means “wrap in try-catch so that it doesn't fall”.
(I’ll make a reservation right away — this example does not try to say that programmers in C ++ or Python do not check for errors when opening a file — just this, the example from the tutorial, is most likely checked most often. But in the less “standard” code, the message of this example becomes more obvious.)
And here is a similar example on Go:
And here it becomes interesting - we cannot just get the file handler, “forgetting” about a possible error. A variable of type error is returned explicitly, and it cannot just be ignored - unused variables are an error at the stage of compilation in Go:
It must either be silenced, replaced by _, or somehow check the error and react, for example:
"Muffling" errors in Go - is considered bad form. Even when it seems that “there can be no mistake” - for example, in functions like strconv.Atoi () - there is still a feeling of discomfort - and suddenly an error occurs, and then I take and consciously cut off the opportunity to find out about it - it it's not just that, after all. It's easier to handle this error somehow.
And this simplicity and the apparent inability to ignore the error creates an incentive. And the incentive becomes a habit - always return and check for errors. It has become too easy to ignore and avoid.
Now hardly anyone needs to prove that code covered by tests is the “right” way, and code without tests is evil. Even more than global variables. "Good" programmers - cover ~ 100% of the code with tests, "bad" - they score. But then again - this is not the fault of programmers, it is a toolkit that makes writing tests difficult.
Whoever is completely unfamiliar with the state of affairs in Go in terms of testing is a short story: to write a test for your function, you do not need any additional libraries or frameworks. All you need to do is create the mycode_test.go file and add a function starting with Test:
in which you write your conditions and checks. There is practically no additional syntax there - checks are carried out by standard if-conditions. It is commonplace and primitive, but it's terrible to just do and it works like a clock.
All you need now is to run
and you get a full test run. With additional parameters like -cover, you have the opportunity to see the coverage of code tests.
So this is a game-changer. Programmers do not like to write tests, not because they are “bad programmers,” but because the time and effort required to “write tests” is always high. There will be much more profit if you spend the same time writing new code. Go quietly and imperceptibly changes the rules of the game.
Something that always required the installation of an additional framework, dances with a tambourine in order to make the right version work on the right system, deal with often not very clear documentation, remember all these dozens of lines, patterns and commands that are necessary just to be able to write one simple test is hard. It’s easier not to write a test, but to dump all the testing on the QA department. Nothing takes away the incentive to do “right”, as the simplicity and seduction of the “wrong” way.
For example, I did not immediately begin to realize the importance of testing code, and when I started it was difficult and inconvenient, and at the first opportunity not to test - I did not test. Writing tests with Go was not so easy - it was embarrassing to “not write”. Without even realizing it, I began to use TDD - simply because it became damn easy and the time spent writing tests became minimal.
Many language design decisions are based on exactly this - to stimulate the “right” approaches in writing programs, and to make the “wrong” ones uncomfortable. Go simply forces programmers to accept the KISS principle as an axiom and reduces “unnecessary complexity” ( according to Brooks ) as much as possible. In the end, Go makes programmers better.
And in this, in my deep conviction, one of the main advantages of Go.
Why Go gets exceptions right
It's It's 2015. Why do we still write insecure software?

Error processing
You probably know that in imperative languages there are two main mechanisms for reporting an error - throw an exception or return a code / value explicitly. One could say that there are even two camps here - supporters of exceptions and supporters of an explicit return, but everything is somewhat worse. There are actually two camps, but they are different - those who understand the importance of error handling in code, and those who for the most part ignore this aspect of programming. Moreover, the second camp is, so far, the undisputed leader.
It is logical to assume that this is exactly what distinguishes "good" programmers from "bad" programmers, and there is undoubtedly some truth to this. But there is one thing. Toolkit - in this case it is a "programming language" - also solves. If your language allows you to do the “wrong” thing much easier than doing the “right thing” - be sure that no number of articles and books “How to write on [LANG]” will help - people will continue to do the wrong thing. Just because it's easier.
It would seem that every schoolchild already knows that “global variables” are evil. How many articles on this topic - everything seems to be clear to everyone. Nevertheless, even now, in 2015, you will find tons of code using global variables. Why?
And because to create a global variable - “do it wrong” takes exactly one line in almost any programming language. At the same time, in order to create any of the “right options”, any minimal wrapper - you need to spend more time and effort. Even if there is 1 character more - but it decides.
This is very important to realize - the toolkit decides. The toolkit forms our choice.
But back to error handling, we’ll try to understand why the Go authors considered the exceptions “the wrong way”, decided not to implement them in Go, and what is the difference between “returning multiple values” in Go and similar ones in other languages.
Take for example a simple thing - opening a file.
Here is the C ++ code
ifstream file;
file.open ("test.txt");
This is fully working code, and it would be “correct” to handle the error either by checking the failbit flag, or by turning on ifstream.exeptions () and wrapping everything in a try {} catch {} block. But “wrong” is much easier to do - just one line, and “error handling can be added later.”
The same Python code:
file = open('test.txt', 'r')
The same thing - it is much easier to just call open (), and then handle the error handling later. At the same time, “error handling” most often means “wrap in try-catch so that it doesn't fall”.
(I’ll make a reservation right away — this example does not try to say that programmers in C ++ or Python do not check for errors when opening a file — just this, the example from the tutorial, is most likely checked most often. But in the less “standard” code, the message of this example becomes more obvious.)
And here is a similar example on Go:
file, err := os.Open("test.txt")
And here it becomes interesting - we cannot just get the file handler, “forgetting” about a possible error. A variable of type error is returned explicitly, and it cannot just be ignored - unused variables are an error at the stage of compilation in Go:
./main.go:8: err declared and not used
It must either be silenced, replaced by _, or somehow check the error and react, for example:
if err != nil {
log.Fatal("Aborting: ", err)
}
"Muffling" errors in Go - is considered bad form. Even when it seems that “there can be no mistake” - for example, in functions like strconv.Atoi () - there is still a feeling of discomfort - and suddenly an error occurs, and then I take and consciously cut off the opportunity to find out about it - it it's not just that, after all. It's easier to handle this error somehow.
And this simplicity and the apparent inability to ignore the error creates an incentive. And the incentive becomes a habit - always return and check for errors. It has become too easy to ignore and avoid.
Testing
Now hardly anyone needs to prove that code covered by tests is the “right” way, and code without tests is evil. Even more than global variables. "Good" programmers - cover ~ 100% of the code with tests, "bad" - they score. But then again - this is not the fault of programmers, it is a toolkit that makes writing tests difficult.
Whoever is completely unfamiliar with the state of affairs in Go in terms of testing is a short story: to write a test for your function, you do not need any additional libraries or frameworks. All you need to do is create the mycode_test.go file and add a function starting with Test:
import "testing"
func TestMycode(t *testing.T) {
}
in which you write your conditions and checks. There is practically no additional syntax there - checks are carried out by standard if-conditions. It is commonplace and primitive, but it's terrible to just do and it works like a clock.
All you need now is to run
go test
and you get a full test run. With additional parameters like -cover, you have the opportunity to see the coverage of code tests.
So this is a game-changer. Programmers do not like to write tests, not because they are “bad programmers,” but because the time and effort required to “write tests” is always high. There will be much more profit if you spend the same time writing new code. Go quietly and imperceptibly changes the rules of the game.
Something that always required the installation of an additional framework, dances with a tambourine in order to make the right version work on the right system, deal with often not very clear documentation, remember all these dozens of lines, patterns and commands that are necessary just to be able to write one simple test is hard. It’s easier not to write a test, but to dump all the testing on the QA department. Nothing takes away the incentive to do “right”, as the simplicity and seduction of the “wrong” way.
For example, I did not immediately begin to realize the importance of testing code, and when I started it was difficult and inconvenient, and at the first opportunity not to test - I did not test. Writing tests with Go was not so easy - it was embarrassing to “not write”. Without even realizing it, I began to use TDD - simply because it became damn easy and the time spent writing tests became minimal.
Conclusion
Many language design decisions are based on exactly this - to stimulate the “right” approaches in writing programs, and to make the “wrong” ones uncomfortable. Go simply forces programmers to accept the KISS principle as an axiom and reduces “unnecessary complexity” ( according to Brooks ) as much as possible. In the end, Go makes programmers better.
And in this, in my deep conviction, one of the main advantages of Go.
Related Articles
Why Go gets exceptions right
It's It's 2015. Why do we still write insecure software?