Статьи

9 вещей, которые мне нравятся в Go

Вчера я нашел одно из моих первых приложений Go. В момент ретроспекции я понял, насколько я счастлив работать с языком Go. Я подумал, что на минутку документирую почему.

Здесь, в произвольном порядке, приведены 9 основных причин, по которым мне нравится работать с Go:

1. Набор инструментов

Программист переменного тока смотрел на один из моих проектов Go. Он был раздражен на меня: «Где твой Makefile?» Мне приходилось объяснять ему несколько раз, что мне это не нужно. goИнструмент обрабатывается все , что для меня. «Но как насчет зависимостей?» Обрабатывается. «Установка?» Обрабатывается. «Создание документации?» Обрабатывается.

Разработчики считают, что Go — это язык для разработчиков, и нигде это не доказывается так, как фантастическая цепочка инструментов Go. Простой и мощный, goфарфор делает его простым для создания, запуска, форматирования и отладки кода.

Мне нравятся очевидные инструменты ( go build, go test), но мне действительно нравится более глубокая мысль и работа, связанные с такими вещами, как обнаружение гонки, форматирование кода (к которому я еще вернусь), проверка и установка удаленных пакетов ( go get).

2. Чистый код

Немногие разработчики принимают этот важный урок наизусть: когда речь идет о написании хорошего кода, его чистота и читаемость так же важны, как и функциональность. Это хорошо для тех, кто будет читать и редактировать ваш код, и хорошо для будущего — вас, которые будут проклинать вас — когда код слишком сложный.

Один из моих коллег недавно сделал это замечание по поводу Go: «Кажется, не имеет значения, кто пишет код, все выглядит примерно так. Я могу его прочитать». На мой взгляд, это одна из самых высоких похвал, которую может получить язык программирования в целом. Да, иногда Go более многословен. Обработка ошибок даже намеренно. Но многословие ради ясности — благородный обмен, и многие краткие преступления были совершены во имя краткости.

Тот факт, что соглашения о кодировании были созданы с самого начала и аккуратно соблюдаются go fmtgolint), способствует чистоте кода Go. Но так же, как и простой синтаксис и отсутствие удобных, но часто вводящих в заблуждение неправильно используемых функций, таких как троичные операторы.

3. Перейти рутины

Недавно мы перенесли основной сервер приложений с Java на Go. И одна из причин в том, что писать хороший многопоточный код Java сложно. Это не элегантно, требует много концептуальных затрат и потребляет много ресурсов.

Фактически, большинство основных языков, с которыми я работал, превращают параллельные вычисления в рутинную работу. Раньше я слышал, что «нам нужно сделать его многопоточным», мой мозг автоматически говорил: «Добавьте две недели к графику разработки».

Но иди не так. Процедуры Go являются концептуально простыми, функциональными и легкими в использовании. (Однажды мы случайно сгенерировали несколько сотен тысяч из них на небольшом экземпляре Amazon!) На самом деле, работа с подпрограммами Go настолько проста, что теперь я использую их по умолчанию ! Мне даже не нужно думать о том, как я буду управлять ресурсами или убирать беспорядок … Я просто должен спросить, имеет ли смысл работать параллельно. Это оно!

Когда так легко сделать правильный выбор, что-то явно сделано правильно.

4. Каналы

По горячим следам любой дискуссии о гоу-рутинах мы должны говорить о каналах Короче говоря, каналы обеспечивают уровень связи между процедурами go. И они работают, как чувствительные к типу розетки.

У меня было около недели, чтобы заново внедрить (в небольшом масштабе) проприетарную систему очередей сообщений, предоставленную нам третьей стороной. Замещение, которое я написал, должно было быть совместимым со всеми их существующими библиотеками, а также должно быть достаточно надежным, чтобы обрабатывать очереди и ретрансляцию в реальном времени.

Каналы настолько естественным образом подходят для этой проблемы, что ядро ​​из нескольких сотен строк обрабатывает весь вход и выход, необходимые для основной очереди сообщений. С быстрым обходом API у меня был запас времени на сервер. И это было быстрее, чем оригинальное решение.

5. Метафизическая скупость

У меня есть значительный опыт в философии. Одна вещь, которая всегда очаровывает меня, это чтение различных подходов, которые философы использовали в области, известной как умозрительная метафизика . Грубо говоря, это упражнение в создании мира.

В 17-19 веках спекулятивная метафизика достигла своего рода вершины. Декарт, Спиноза, Лейбниц, Юм, Кант, Гегель и многие другие разработали системы для описания основных компонентов реальности. Тем не менее, если бы вы сидели и читали метафизику, одну книгу за другой, вы были бы шокированы тем, насколько взгляд каждого метафизика отличается от других. Чтобы читать, скажем, Гегеля по сравнению с Декартом, можно было бы описать две совершенно разные вселенные.

Programming languages are like that, too. In fact, a programming language is a metaphysical system. It describes what sorts of «realities» you can build. It describes how things interact in that «reality». In fact, it even defines what a «thing» is in that world.

The trouble is, many programming languages get so bogged down in metaphysical purity that it becomes a chore to build things.

Go’s approach to the metaphysics of programming is marked by a tendency toward the austere. But not austerity or for the sake of ideological purity. Rather, it feels as if Go was written as an answer to the question «what do we actually need in order to write well-ordered and elegant software?»

The result: Building Go applications is a practical exercise, not an ideological one.

Continue reading…

6. Standard Libraries for Today

Go is what I would call immanently useful. That means it has features that make it practical for writing code without needing a ton of supporting libraries.

I was a teenager when I started coding in the mid-1990’s. And Java drew my attention because it was «not my parents’ language.» Hot with Internet-oriented technologies, I could build things with the JDK that would have required dozens of supporting libraries in other languages.

Today I feel the same way about Go that I did about Java in the ’90’s. It’s a language that’s captured the Zeitgeist. Rich with network-focused and web-focused libraries, Go provides an out-of-the-box toolset that is conducive to today’s cloud-centric applications.

To wit, Go has a template engine, fantastic network and HTTP services, crypto support, a built-in AST parser, and a logging system.

My Java example should come as a warning, though: 20 years from now, Go may have that same dated feel that Java has today. But should that put us off from using it now? I don’t see why it should.

7. Core Data Structures

Go doesn’t have generics. Go probably won’t have built-in generics. Rob Pike has made this clear repeatedly. Understandably, many developers feel like one of Go’s big deficiencies is its lack of generics-backed collections.

But I would like to point out the contrary view. Go has the right number of built-in collections. It has first-class support for arrays and maps, along with built-in support for slices, an abstraction that feels like growable arrays.

These data structures are what we might call essentials. No moderately sized and well structured application can be built without (roughly) these sorts of collections.

«But it should have linked lists and binary trees and skiplists and stacks and queues… and these should either be built-in or allow generics.» I hear that complaint, and on any given day I might even grouch that way myself. But when I get really honest, I admit that I don’t really use most of those data structures (in their pure form) that often. And when I do, I need it for a specific purpose, and implementing it is not a big deal.

(Note: Go does have some built-in data structures like linked lists. I rarely use them.)

For a language clearly trying to reduce cognitive overhead, I think Go has made the admirable trade off: Deep and thorough support for a few fundamental data structures.

What about generics? I’m afraid that I just can’t get too excited one way or another about them. I’ve seen grave crimes committed with generics systems, and a big part of me is both happy and relieved that I won’t ever witness those in Go. On the reverse side, there are clearly cases where I’d rather have generics than worry about, say, someone inserting the wrong object into my []interface{}. (Easy solution: Don’t use []interface{})

8. Multiple Returns (and Error Handling)

Go functions can return multiple values. The first time I saw Go, I said to the person standing next to me: «Multiple returns? Are you kidding? That’s the dumbest thing I’ve ever seen!»

I’ll gladly eat those words.

First, multiple return values make good programming even easier. When we wrote Cookoo, one of the functions we wrote has a signature like this:

funcCookoo()(*cookoo.Registry,*cookoo.Router,cookoo.Context)

Yes, that’s right… it’s one function that takes no parameters and returns three things. The reason I’m proud of this function is that it has hidden a ton of boilerplate setup from the user. All three of these objects are related to each other, and we can encapsulate all the work of relating them without the library user having to do anything. By the time they get all three of these, they don’t even have to be aware of the relationship. Devs can just get to work.

But the prevalent usage of multiple returns is the error handling pattern pervasive throughout Go:

funcDoSomething()(Result,error)

By convention, Go functions return their errors as the last return value. And this return value is declared as an error type.

  • It is easy to detect when an error happened: The error will be non-nil.
  • Errors are handled in the main flow of the program, not in additional special structures like try/catch.
  • The caller still has the opportunity to return a meaningful value along with the error (a practice I’d love to see become universal). This makes it easier for a program to recover.

New Go developers occasionally complain that this method of error handling makes their code longer. Does it really? Surprisingly, no. Rather, it encourages developers to handle errors right away. It looks more verbose because it avoids the Java-esque (though by no means restricted to Java) anti-pattern of passing exceptions back up the execution chain until some default handler somewhere finally catches.

9. Interfaces

I am learning a lot about good architecture simply by writing Go programs. The implicit interfacing in Go is a great teacher.

Go uses interfaces to represent functional capabilities of disparate types. The typical example is Go’s io.Reader interface. The interface looks like this:

typeReaderinterface{Read(p[]byte)(nint,errerror)}

Simply stated, to be a Reader is to have a Read function (matching the signature above).

Since interfaces are implicit, an implementation of the interface needs no declaration. It simply needs to provide the Read function and it is, in virtue of that, an io.Reader.

This mechanism is profoundly powerful. First, it accomplishes the same outcome as explicit interfaces do in other languages (like Java). Second, it encourages careful thinking about the topology of our code. In other words, it helps us design systems with the notion of functional similarity in mind. It also eases testing and mocking.

But most importantly, it gives developers the ability to add interfaces post hoc. I can think immediately of several times where we took existing third-party libraries, built interfaces that described their API, and used those interfaces. Later, we could build our own wrappers, adapters, and even replacements using those interfaces… all without ever touching the underlying library.

Conclusion

I don’t want to play the part of an apologist for Go. My theory of adopting programming languages borders on the mundane: Choose languages that are useful for rapidly, efficiently, and maintainably building software. Go is one of the most practical programming languages I’ve ever used. And that’s the point I have tried to convey.

Of course it has its weak spots, and of course it is not going to please everyone (especially not purists in either the functional or OOP camps). But the absence of, say, tail recursion or strong inheritance does not make Go a bad language.

Nor do I want to sound like I am suggesting that Go is a solution for all problems. You probably don’t want to write GUI applications or embedded systems in Go. You probably shouldn’t write an OS kernel in it, either.

But at the end of the day, I find writing (reading, and using) Go code to be enjoyable. And I find that the software I produce in Go is higher quality.