📜 ⬆️ ⬇️

Postulates Go

Go has very simple rules in the game, but to become a master in it is not easy, you need to learn how to think with patterns, territories and other strategic concepts. The Go language is not entirely randomly named like the name of this game. In a speech at a recent San Francisco mitap, Rob Pike mentioned the book “Postulates of the Go” (Go Proverbs), which describe complex strategies with simple poetic phrases. These postulates have a deeper meaning for those who are familiar with the game of Go, and may seem empty phrases to those who are not familiar. And in this non-technical report, he proposed to invent “postulates” for the Go language in a similar way.

Here are some of them:


This is somewhat similar to Zen of Python . You may disagree with them, but these postulates reflect the idiomatic nature and approach in Go. Let's take a closer look at each of them based on the report (video at the end of the post).


“Do not communicate by sharing memory. Share memory through communication. " (Don't communicate by sharing memory, share memory by communicating)
Everybody knows this postulate; it sounded more than once in reports and posts on competitive Go programming patterns. This postulate is intended to give an understanding of the essence of the mechanism of the channels and gorutin - your functions do not share the same memory, they communicate safely by transferring data through the channels.
')
“Competition is not parallelism” (Concurrency is not parallelism)
I'm not sure that the word concurrency translates into Russian any well, but the point is that parallelism is just parallel execution of the same code, and software that takes full advantage of multi-core functionality — competitive software is competitiveness, that is, a way of structuring logic programs. Beginners very often confuse these two concepts. There is an excellent performance Pike with the same name , be sure to see who has not yet.

Channels are orchestrated, mutexes are serialized (Channels orchestrate; mutexes serialize)
A good postulate to the topic of the previous article ( “Dancing with mutexes in Go” ), about when to use mutexes, and when channels. Mutexes simply serialize the execution of access to something, but the gorutines and channels give you the tool to create programs with a complex architecture and structure for exchanging data between parts of the program.

The larger the interface, the weaker the abstraction (The bigger the interface, the weaker the abstraction)
Beginners in Go, especially those who came with Java, often consider that interfaces must be large and contain many methods. Also often they are confused by the implicit satisfaction of interfaces. But the most important thing in interfaces is not this, but the culture around them, which is reflected in this postulate. The smaller the interface, the more useful it is. Pike jokes that the three most useful interfaces he wrote - io.Reader, io.Writer and interface {} - for three, on average, have 0.666 methods.

Make the zero value useful.
The point is that it is better to make the zero value of types useful without any constructors. Of course, sometimes constructor functions are necessary, but if there is an opportunity to make a variable of your type working “right out of the box”, this always benefits. An example is the type bytes.Buffer or sync.Mytex from the standard library. An empty value is a buffer that is ready to use, although there are constructors like bytes.NewBuffer and bytes.NewBufferString ().

An empty interface does not say anything (interface {} says nothing)
This postulate suggests that interfaces - “behavioral types” - must mean something. If you are creating an interface, it means something and serves a specific purpose. The empty interface (interface {}) means nothing and does not mean anything. There are situations when it needs to be used, but they are more often an exception - do not use interface {} for no reason. Beginners often reuse empty interfaces, and a lot of questions on Stack Overflow are about them.

Gofmt style is not someone's preference, but gofmt everyone prefers (Gofmt's style is not one's favorite, yet gofmt is everyone's favorite)
Many novices, struggling with habits, complain that the code in Go is not formatted the way they like it. Gofmt is a single style, it is not someone’s personal preference. Even Grismieyer himself, the author of the go fmt utility, himself would have preferred a different formatting style. But from the moment the format was approved and gofmt is used everywhere and always - just take it for granted. Experienced programmers consider go fmt salvation, as it greatly simplifies working with someone else's code - hell with different formatting styles in other languages ​​is a thing of the past.
It is noteworthy that new languages ​​adopt this approach (for example, rustfmt ) - this is what Dave Cheney said in his speech, “Heritage Go” - new languages ​​will no longer be considered complete without a uniform formatting style and utility that provides it.

Slight copying is better than small dependence (A little copying is better than a little dependency)
This already applies to programming as a whole, but in Go it can be seen both in the standard library and in the culture as a whole. In part, this echoes the well-known installation "Duplication is cheaper than a bad abstraction." Pike tells how in the early days of Google he was told that they were most concerned about code reuse. If you can avoid writing one repetitive line, making the import - you need to do so. According to Pike, Google still raids problems with the code because of this approach. Very often, the functionality can be similar (before the first refactoring), or only a part of the dependency functional is necessary, and it is often more efficient to write 5 lines than to import 150kb dependencies. As an example, the IsPrint function from the strconv package - it duplicates the unicode.IsPrint () functionality, while the tests verify that the functions work in the same way. Summarizing - do not be afraid to copy the code, where it is justified.

Code with syscall calls must contain build tags (Syscall must always be guarded by build tags)
In the subject of the recently translated article "How to write Go code that is easily portable . " Some complain about the syscall package in Go - you need to change it, it is not ported. But syscalls are by definition platform-specific calls, so if you want to make high-quality cross-platform code, always add build tags to files that use syscall.

Cgo must also contain build tags (Cgo must always be guarded by build tags)
For the exact same reason - always add build tags when using cgo. If you call C - no one knows what is actually happening there, it is almost certainly not portable.

Cgo is not Go (Cgo is not go)
Many people rejoice in how easy it is to use C code in Go. Sometimes, of course, it is necessary, but Pike admits that he never used Cgo himself, and in the Go world there is a clear understanding of memory, stability, security, garbage collection ... and with Cgo it all goes to the firebox. In 90% of cases on Google, when someone says “my program crashed, help me figure it out” - it turns out to be in Cgo.

With unsafe package there are no guarantees (With the unsafe package there are no guarantees)
It emphasizes that the code written using the unsafe package is also not portable, and guarantees of backward compatibility within Go 1 are not covered (this is written in the very first line of the package documentation). So far, nothing has changed in it, but if you are going to use unsafe for some reason - be prepared that in future versions you will have to adjust to the changes.

Clear is better than clever (Clear is better than clever)
The postulate that in Go the clarity and readability of the code comes first. And you should also strive to write clear and understandable code, instead of abstruse code. Sooner or later your code will be read, improved and refactored by other programmers, and the clearer it will be, the easier and faster the result will be. Go will be all the ways and forces you to help and facilitate this, the entire design of the language is imbued with this goal.

Reflection is never clear (Reflection is never clear)
Reflection (the ability of the program to change its own structure) in Go is implemented by the reflect package, and people often try to use it out of place. Reflection is a very, very powerful thing, but very difficult to understand, and in fact is needed by a very small number of people and tasks. Pike says that he probably wrote the most code with reflection in Go, and still hates doing it. And he believes that it is necessary to encourage people not to use reflection, since it, as a rule, is not needed as it seems to them.

Errors are values (Errors are values)
Another important postulate, which has been repeatedly covered in Habré, and to which, in particular, an article in the official blog Go is devoted . Newbies often do not understand this and ask, “why should I write these if err! = Nil everywhere”. Yes, because you do not program, you rivet the code. Many people think that if err! = Nil is such an alternative to try..catch ... is a special construction of the language for working with errors. But it is not, you cannot program with try ... catch ... - this is a control construct. With errors in Go, you can and should do what the program logic requires.

Don't just check for errors, handle them nicely (Dont 'just check errors, handle them gracefully)
It is very important not only to check the errors, but also to think about what to do with them so that the erroneous situation was handled correctly. Perhaps you need to supplement the error with some information, perhaps remember to process later, something else. A huge and very important part of programming is how you handle errors. Do not just check and forward to the top, but think about how to properly handle this error. This is true for all languages, but since in Go the errors are just values, it is easier to do and easier to implement beautiful and correct error handling in the code.

Think over the architecture, name the components, document the details (Design the architecture, name the components, document the details)
Here we are talking about the importance of proper naming of your code components, methods, functions and variables. These names will then be in the code of users of your package. And if the names are successful and clear, the design of the program will be more clear and simple to understand. But, of course, there are always details that need to be explained - and now they need to be placed in the documentation.

Documentation - for users (Documentation is for users)
The fact that the documentation for the function should describe not what this function does, but for what it is needed. This is a very important distinction that is often overlooked. When writing documentation, look at it through the eyes of the user of your package.

Well, and if you can come up with some more postulates that meet the criteria for brevity, poetry and conceptuality - feel free to offer.

go-proverbs.imtqy.com

Slides (not original, true): speakerdeck.com/ajstarks/go-proverbs

Video:

Source: https://habr.com/ru/post/272383/


All Articles