If anything, this is a free translation of an article from the blog of Jeremy Mikkola.This article is about how some time later I tasted and still loved the language of Go (aki golang).
Complaints
A year ago, I had a lot of complaints about this or that aspect of Go. I basically complained that despite the fact that you cannot use Go as a “real system language” (in terms of writing-on-it-OS), you still have to think about what you usually think about in system languages: for example, use a pointer here or not. The type system lies in an unproductive gorge between sufficient rigor to become “automatic” (hello to Haskell adepts!) And sufficiently dynamic not to distract (as in Python). For a while I was stupid at such things as going through the Unicode string, not characters, but bytes, and all that. I blamed Go for not always following its principle of simplicity, since there are many different narrow things in it, like
make()
. And of course, I complained that when working with Go, I constantly have to repeat
if err != nil { return err }
blocks.
')
At first, I definitely could not call myself a Go fan.
Technically, I agree that most of my complaints are correct. I agree that the type system could be invented more abruptly, and it would not hurt to abstract a bit from the pointers. But as soon as I begin to understand that (not all, but many) problems that I complained about, in fact, it seems that they are not even unfit. In fact, it was like whining at a whole forest, due to a couple of ugly trees. In everyday use of the language, most of the things I was so worried about did not make themselves felt. I have never met a bug, one way or another connected with the passage along the line. In real code, you almost do not cast
interface{}
as often as you would like for those guys who are jerking on type systems. Oh yes, there is no trouble in calling
make()
in one place and
new()
in another. Most of all, I cursed those architectural solutions of the language that complicated the creation of abstractions. You can't just make a data structure that will become an alternative to embedded data structures (hi code generation!). Without generalizations, you cannot build large generalized abstractions. Most likely, this is done intentionally.
Series 217: New Hope (on Go)
Now I think of Go differently. It is not a system language, it is not a dynamic language, and it is not even necessarily a language for the web, it is a language that stands against abstractions. As soon as I think about Java, the first thing that comes to mind is a giant zamylennaya system, formed through extreme examples of compulsory generalization and love to create object hierarchies for the sake of object hierarchies. Yes, I know that abstractions are very, very convenient: they allow us to do many different things using a few lines of code. The main problem is that they are very, very complex!
When an interface is just an interface of some kind of abstraction, not even its implementation, too large to fit completely into your head, the abstraction makes the programmer suffer! The lines of code used to solve a specific task are a rather poor metric of how difficult it was to solve this problem. Writing in 2 or even 10 times more code with more straightforward abstractions (or without them), the solution may ultimately be more effective. At leisure, you can watch the
“Simple Made Easy” comrade named Rich Hickey, who famously outlined the difference between simplicity and ease.
What do you think is easier to understand for a programmer who just came to the project and does not know anything about those giant abstractions that you have built up here: 10 lines of code that only makes sense if you know abstractions or still 20-100 lines of code who speak for themselves? This is where Go goes to the sky! Saving your mortal soul from abstractions (at the cost of "a couple of lines of code"), the code on Go keeps the meaning and logic down to the smallest pieces of the program. The programmer has to pay for this ... by writing code (suddenly)! Considering the fact that you initially spent considerable efforts to understand this or that abstraction, when you will have to use a smaller abstraction, your productivity will drop dramatically, since you will have to implement even more logic.
In most cases, this initial cost of development is subsequently justified many times when other programmers (or you in six months) come and try to read, or even work with the code. Go rather optimizes the direct understanding of the code throughout the development cycle, rather than allowing you to quickly rivet large pieces.
Code reading
This thing is definitely worth optimizing. The code is never deleted immediately after writing, people read it. Let's remember Isaac Azimov and his “Three Laws of Robotics”:
- A robot cannot harm a person or by its inaction allow a person to be harmed.
- A robot must obey all orders that a person gives, except when these orders are contrary to the First Law.
- A robot must take care of its security to the extent that it does not contradict the First and Second Laws.
And I came up with the “Three laws of codification”:
- The program should not be difficult to understand for a person, to the same extent that a lack of clarity should allow for a lack of understanding to be established.
- The program should be quick and concise, except when it begins to violate the First Law.
- The program should be easily understood by the computer to the extent that it does not contradict the First and Second Laws.
Someone even
said that “programs need to be written in such a way that people can read them and only sometimes start computers.”
Usually these rules are applied only in reverse order. First, develop a language that makes it easy to convey the essence of the computer. Then, already working in this framework, the program is made as quick and short as possible. And finally, as a “bonus”, we finish the language a little in terms of its understanding. Go, for example, selects the correct sequence. Programs on Go, in the first place, are easy to read by people, then easily understood by the computer (Go takes care of reducing the compilation time) and only last is fast and short. Interesting fact: it is impossible to say that the programs on Go are not fast, Go is a very fast language. Go may be overly verbose, but for good reason.
As soon as we put the Second Law in the first place, we get “premature optimization”, or its not so popular brother - “premature generalization”. Both, in general, are the root of evil in programming. As soon as we put the Third Law in the first place, we get an assembler. Like? I'm not!
A year ago
When I initially wrote my impressions about Go, I did not yet know about another new system language - Rust.
I’m sure that if I saw Rust then, at least in the form in which we see Rust today, I would definitely prefer it to Go. In the Rust language there is everything: “human” type inference, generics, impurities, macros, various wonderful compile-time checks. You can even ask the compiler to check that return values ​​of certain types are not ignored anywhere! Rust answers almost all the questions that I had to go. So why am I still choosing Go, starting a new project?
In the Rust language, you work with powerful abstractions. You can do a lot of things with a small amount of code - sometimes not even code. This, of course, is paid for by having to learn abstractions. Since this is a new language, a very large share of this “tribute” has yet to be paid. Every time when I do something with Rust, I constantly find myself on the fact that I have fun, playing with different abstractions, but really do almost nothing in the direction of solving the problem. In Go, I'm starting to solve a problem right from the start of working on it.
Size matters
What to say, for certain projects, I still choose Rust. Especially for very large projects, where impressive confidence in abstractions will make more sense. The Rust language has a cool type system, which bears more fruit in huge (100.000+ lines of code) projects. But even with abstractions, the code for Rust will not necessarily be clearer than the code for Go, since the former exchanges a bit of brevity for such powerful things as generics.
Size does matter, but what matters is not the size of the project as such, but the size of the scope, which you must keep in mind to be aware of what is happening. Go famously reduces this area for small and medium projects. Yes, it is necessary to print, but this is not the most difficult thing in programming!