Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность.
Go все еще молод, поскольку языки программирования развиваются. Впервые он был выпущен 10 ноября 2009 года. Его создатель Роберт Гриземер Роб Пайк и Кен Томпсон работают в Google, где огромные проблемы масштабирования побудили их разработать Go как быстрое и эффективное решение для программирования для людей с большими кодовыми базами, управляемое несколькими разработчиками, со строгими требованиями к производительности и охватывающее несколько сетевых и вычислительных ядер. Основатели Go также воспользовались возможностью изучить сильные, слабые стороны и уязвимости других языков программирования при создании своего нового языка. В результате получается чистый, понятный и функциональный язык с относительно небольшим набором команд и функций.
В этой статье сегодняшняя статья познакомит вас с 9 функциями, которые отличают Go от других языков.
1. Go всегда включает бинарники в сборку
Среда выполнения Go предоставляет такие услуги, как выделение памяти, сборка мусора, поддержка параллелизма и работа в сети. Он компилируется в каждый двоичный файл Go. Это отличается от многих других языков, многие из которых используют виртуальные машины, на которые необходимо установить программы для правильной работы.
Включение среды выполнения непосредственно в двоичный файл позволяет очень легко распространять и запускать программы Go и избегать несовместимости между средой выполнения и программой. Виртуальные машины для таких языков, как Python, Ruby и JavaScript, также не оптимизированы для сборки мусора и выделения памяти, что объясняет превосходящую скорость Go относительно других подобных языков. Например, Go будет хранить как можно больше в стеке, где данные упорядочены для более быстрого доступа, чем в куче. Подробнее об этом позже.
И последнее, что касается статических двоичных файлов Go, это то, что они запускаются очень быстро, потому что не нужно запускать внешние зависимости. Это полезно, если вы используете такой сервис, как Google App Engine, платформу как услугу, работающую в Google Cloud, которая может масштабировать ваше приложение до нуля экземпляров, чтобы сэкономить на облачных затратах. App Engine может запустить экземпляр программы Go в мгновение ока при получении нового запроса. Тот же опыт в Python или Node обычно приводит к 3-5-секундному (или более) ожиданию, поскольку требуемая виртуальная среда также запускается с новым экземпляром.
2. В Go нет централизованного хостинга программных зависимостей.
Чтобы получить доступ к опубликованным программам Go, разработчики не полагаются на централизованно размещенные службы, такие как Maven Central для Java или NPM Registry для JavaScript. Вместо этого проекты распространяются через их репозиторий исходного кода (чаще всего Github). Командная строка go install позволяет загружать библиотеки таким образом. Почему мне нравится эта функция? Я всегда думал, что централизованно размещенные службы зависимостей, такие как Maven Central, PIP и NPM, представляют собой что-то вроде устрашающего черного ящика, возможно, отвлекающего от хлопот загрузки и установки зависимостей, но неизбежно вызывающего страх, когда зависимости идут не так, как надо. Сердцебиение прекращается.
Кроме того, сделать ваш модуль доступным для других так же просто, как поместить его в систему контроля версий, что является очень простым способом распространения вашей программы.
3. Go вызывается по значению
В Go, когда вы предоставляете примитивное значение (число, логическое значение или строку) или структуру (грубый эквивалент объекта класса) в качестве аргумента функции, Go всегда копирует значение переменной.
Во многих других языках, таких как Java, Python и JavaScript, примитивы передаются по значению, а объекты (экземпляры класса) передаются по ссылке, а это значит, что принимающая функция фактически получает указатель на исходный объект, а не его копию. . Любые изменения, внесенные в объект в принимающей функции, отражаются в исходном объекте.
В Go структуры и примитивы по умолчанию передаются по значению, опционально указатели, с помощью оператора звездочки:
// 按值传递
func MakeNewFoo(f Foo ) (Foo, error) {
f.Field1 = "New val"
f.Field2 = f.Field2 + 1
return f, nil
}
Приведенная выше функция берет копию Foo и возвращает новый объект Foo.
// 通过引用传递
func MutateFoo(f *Foo ) error {
f.Field1 = "New val"
f.Field2 = 2
return nil
}
Приведенная выше функция принимает указатель на Foo и изменяет исходный объект.
Это четкое различие между вызовом по значению и вызовом по ссылке делает ваше намерение очевидным и снижает вероятность того, что вызывающая функция непреднамеренно изменит переданный объект (когда этого не должно происходить (что многие начинающие разработчики испытывают с трудом). тяжело делать) немного) держать крепко).
Как заключил Массачусетский технологический институт: «Изменчивость затрудняет понимание того, что делает ваша программа, и затрудняет соблюдение контрактов».
Кроме того, вызов по значению значительно сокращает работу сборщика мусора, что означает более быстрое и эффективное использование памяти приложениями. В статье сделан вывод о том, что трассировка указателя (извлечение значений указателя из кучи) в 10–20 раз медленнее, чем извлечение значений из непрерывного стека. Следует запомнить хорошее эмпирическое правило: самый быстрый способ чтения из памяти — это последовательное чтение, что означает минимизацию количества указателей, которые случайным образом хранятся в ОЗУ.
4. Ключевое слово «отложить»
В NodeJS, прежде чем я начал использовать knex.js, я вручную управлял соединениями с базой данных в своем коде, создавая пул базы данных, а затем в каждой функции открывал новое соединение из пула, как только желаемая функциональность базы данных CRUD была завершена.
Это своего рода кошмар обслуживания, потому что, если я не освобождаю соединения в конце каждой функции, количество невыпущенных соединений с базой данных будет медленно расти до тех пор, пока в пуле больше не будет доступных соединений, а затем сломает приложение.
Реальность такова, что программам часто нужно освобождать, очищать и удалять ресурсы, файлы, соединения и т. д., поэтому Go ввел ключевое слово defer как эффективный способ управления ими.
Любой оператор, начинающийся с defer, откладывает свой вызов до тех пор, пока не завершится работа окружающей функции. Это означает, что вы можете поместить код очистки/разборки вверху функции (очевидно), зная, что это будет после того, как функция будет выполнена.
func main() {
if len(os.Args) < 2 {
log.Fatal("no file specified")
}
f, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
data := make([]byte, 2048)
for {
count, err := f.Read(data)
os.Stdout.Write(data[:count])
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
}
}
В приведенном выше примере метод закрытия файла отложен. Мне нравится эта схема объявления вашего служебного намерения в верхней части функции, а затем забвение об этом, зная, что как только функция выйдет, она выполнит свою работу.
5. Go использует лучшие черты функционального программирования
Функциональное программирование — эффективная и творческая парадигма, и, к счастью, Go использует лучшие черты функционального программирования. В Го:
- Функции — это значения, что означает, что их можно добавлять к картам как значения, передавать в качестве аргументов другим функциям, задавать как переменные и возвращать из функций (так называемые «функции более высокого порядка», декораторы часто используются в Go для создания шаблона промежуточного программного обеспечения). .
- Анонимные функции могут создаваться и вызываться автоматически.
- Функции, объявленные внутри других функций, допускают замыкания (функции, объявленные внутри функций, могут обращаться к переменным, объявленным во внешних функциях, и изменять их). В идиоматическом Go замыкания широко используются для ограничения области действия функции, а для установки состояния функция затем используется в своей логике.
func StartTimer (name string) func(){
t := time.Now()
log.Println(name, "started")
return func() {
d := time.Now().Sub(t)
log.Println(name, "took", d)
}
}
func RunTimer() {
stop := StartTimer("My timer")
defer stop()
time.Sleep(1 * time.Second)
}
Выше приведен пример закрытия. Функция «StartTimer» возвращает новую функцию, которая через замыкание имеет доступ к значению «t», установленному в области его рождения. Затем эта функция может сравнить текущее время со значением «t», создав полезный таймер. Спасибо Мэту Райеру за этот пример.
6. В Go есть неявные интерфейсы
Любой, кто читал литературу по SOLID-кодированию и шаблонам проектирования, вероятно, слышал мантру «предпочитайте композицию наследованию». Короче говоря, это говорит о том, что вам следует разложить свою бизнес-логику на разные интерфейсы вместо того, чтобы полагаться на иерархическое наследование свойств и логики от родительских классов.
Другим популярным подходом является «программа для интерфейсов, а не для реализации»: API должны публиковать контракты только для своего предполагаемого поведения (его сигнатуры методов), а не подробности о том, как реализовать это поведение.
Оба они показывают важность интерфейсов в современном программировании.
Поэтому неудивительно, что Go поддерживает интерфейсы. Фактически интерфейсы — единственные абстрактные типы в Go.
Однако, в отличие от других языков, интерфейсы в Go реализованы не явно, а неявно. Конкретный тип не объявляет, что он реализует интерфейс. И наоборот, если набор методов для этого конкретного типа содержит все наборы методов базового интерфейса, то Go рассматривает объект как реализующий интерфейс.
Эта неявная реализация интерфейса (формально известная как структурная типизация) позволяет Go обеспечивать безопасность типов и развязку, сохраняя большую часть гибкости, проявляемой в динамических языках.
Напротив, явный интерфейс привязан к клиенту и реализации, например, заменить зависимость в Java намного сложнее, чем в GO.
// 这是一个接口声明(称为Logic)
type Logic interface {
Process (data string) string
}
type LogicProvider struct {}
// 这是 LogicProvider 上名为“Process”的方法 struct
func (lp LogicProvider) Process (data string) string {
// 业务逻辑
}
// 这是具有 Logic 接口作为属性的客户端结构
type Client struct {
L Logic
}
func(c Client) Program() {
// 从某处获取数据
cLProcess(data)
}
func main() {
c := Client {
L: LogicProvider{},
}
c.Program()
}
В LogicProvider нет объявления о том, что он соответствует интерфейсу Logic. Это означает, что клиенты могут легко заменить своего поставщика логики в будущем, если этот поставщик логики включает в себя весь набор методов базового интерфейса ( Logic ).
7. Обработка ошибок
Обработка ошибок в Go сильно отличается от других языков. Короче говоря, Go обрабатывает ошибки, возвращая значение типа error в качестве последнего возвращаемого значения функции.
Параметр ошибки возвращает nil, если функция выполняется должным образом, и значение ошибки в противном случае. Вызовите функцию, а затем проверьте возвращаемое значение ошибки и обработайте ошибку или выдайте свою собственную ошибку.
// 函数返回一个整数和一个错误
func calculateRemainder(numerator int, denominator int) ( int, error ) {
//
if denominator == 0 {
return 9, errors.New("denominator is 0"
}
// 没有错误返回
return numerator / denominator, nil
}
Go ведет себя так не просто так: он заставляет программистов думать об исключениях и правильно их обрабатывать. Традиционные исключения try-catch также добавляют в код по крайней мере один новый путь к коду и создают отступ в коде, который трудно понять. Go предпочитает рассматривать «счастливый путь» как код без отступов, идентифицируя и возвращая любые ошибки до тех пор, пока «счастливый путь» не будет завершен.
8. Одновременно
Можно сказать, что самые известные свойства Go — это количество одновременно разрешенных для параллельной работы на обрабатывающей машине или на сервере доступных ядер. Когда взаимозависимость не является отдельным процессом (не нужно запускать последовательно) и критична временная производительность, параллелизм наиболее значим. Обычно это бывает при запросах ввода-вывода, которые читают или записывают диск или сеть быстрее, чем на порядки медленнее обрабатывают все, кроме самых сложных процессов в памяти. Ключевое слово «Go» запустит функцию одновременно с вызовом функции.
func process(val int) int {
// 用 val 做一些事情
}
// 对于 'in' 中的每个值,同时运行 process 函数,
// 并将 process 的结果读取到 'out'
func runConcurrently(in <-chan int, out chan<- int){
go func() {
for val := range in {
result := process(val)
out <- result
}
}
}
Параллелизм в Go — это глубокая и довольно продвинутая функция, но там, где это имеет смысл, она обеспечивает эффективный способ обеспечения оптимальной производительности программ.
9. Перейти к стандартной библиотеке
Go имеет философию «батарейка включена», и многие требования современных языков программирования включены в стандартную библиотеку, что облегчает жизнь программисту. Как уже упоминалось, Go — относительно молодой язык, а это означает, что многие проблемы/требования современных приложений могут быть решены в стандартной библиотеке.
С одной стороны, Go обеспечивает первоклассную поддержку сетей (особенно HTTP/2) и управления файлами. Он также обеспечивает собственное кодирование и декодирование JSON. Поэтому настроить сервер для обработки HTTP-запросов и возврата ответов (JSON или иным образом) очень просто, что объясняет популярность Go для разработки веб-сервисов HTTP на основе REST.
Как также отметил Мэт Райер, стандартная библиотека имеет открытый исходный код и является отличным способом изучения лучших практик Go.
🛬 Уху! Взлетай!
我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇 Go 有别于其他语言的九个特性教程。 Я люблю делиться технологиями и радостью через статьи. Вы можете посетить мой блог:Талант /user/204034…Чтобы получить больше информации. Надеюсь, вам это понравится! 😊
Если вы действительно узнали что-то новое из этой статьи, ставьте лайк, добавляйте в закладки и делитесь с друзьями. 🤗Наконец, не забудьте поддержать ❤ или 📑