Как стать автором
Обновить

Комментарии 13

Может кто-нибудь пояснить возможные проблемы кода из раздела про состояние на уровне объектов:

package counter

type Counter struct {
        count int
}

func (c *Counter) Increment(n int) int {
        c.count += n
        return c.count
}
В статье речь была о проблеме с глобальной переменной count, а в примере с объектом всё хорошо, пока вы не вызываете метод Increment в разных горутинах.
Но и это починить не сильно сложно, просто добавь воды мьютекс или для счётчика можно использовать atomic (пришлось заменить int на int64, так как atomic умеет работать только с int32/uint32 и int64/uint64 указанными явно):
package counter

import (
	"sync"
	"sync/atomic"
)

type Counter struct {
	count int64
	mx    sync.Mutex
}

func (c *Counter) Increment(n int64) int64 {
	c.mx.Lock()
	defer c.mx.Unlock()

	c.count += n
	return c.count
}
func (c *Counter) IncrementAtomic(n int64) int64 {
	atomic.AddInt64(&c.count, n) 
	return atomic.LoadInt64(&c.count)
}

Спасибо, но там после этого кода идёт ещё два абзаца текста с описанием возможных проблем. С мьютексами всё понятно, не понятно зачем после этого примера идёт текст про бизнес-логику и параллельное тестирование. Я предположил, что даже такая конструкция всё равно плохая как и пример перед этим.
Чейни — мужик своеобразный, не всегда просто понять что он имеет в виду.
Как я понимаю, это что-то типо итога раздела, там ссылаются на ту же мысль, что с глобальной переменной было плохо, а без неё мы на вопросы
Вы можете изолированно тестировать её? Можете тестировать параллельно? Можете использовать одновременно несколько экземпляров?

можем ответить «Да», просто такое вполне может быть и в реальном проекте.

Например, далеко ходить не нужно, возьмём стандартную библиотеку net/http, и стандартный роутер для http сервера http.ServeMux.
Сделаем простой сервер (обработку ошибок опустим)
Код
package main

import (
	"net/http"
)

func main() {
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	go http.ListenAndServe(":3000", nil)
	go http.ListenAndServe(":3001", nil)

	select {}
}


В примере выше, зайдя на http://localhost:3000/hello и на http://localhost:3001/hello мы увидим одно и то же, так как http.HandleFunc по факту вызывает метод у глобальной переменной, а http.ListenAndServe при передаче вторым параметром nil её использует.
Хорошо ли это? Для прототипов, возможно, но это неявная «магия» и на вопросы выше уже не получится так однозначно ответить.
Но если мы сделаем тот же сервер с созданием нового роутера и передадим его явно, проблемы тут же исчезают:
Код
package main

import (
	"net/http"
)

func main() {
	r1 := http.NewServeMux()
	r1.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world 3000"))
	})
	
	r2:= http.NewServeMux()
	r2.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world 3001"))
	})
	
	go http.ListenAndServe(":3000", r1)
	go http.ListenAndServe(":3001", r2)

	select {}
}


И такое не редко встречается, но данный пример не так опасен, так как в данном случае мы смогли создать новый экземпляр который не повлияет на остальные, но ведь существуют библиотеки, которые хочется выполнять в разных горутинах или параллельно, но не получается, так как у неё объявлена важная глобальная переменная, которая влияет на все остальные вызовы этой библиотеки.
Такие библиотеки не хочется выполнять даже однократно. Они плохие, и всё тут.
func (c *Counter) IncrementAtomic(n int64) int64 {
	return atomic.AddInt64(&c.count, n) 
}

atomic.AddInt64
AddInt64 atomically adds delta to *addr and returns the new value.
Простое не значит лёгкое, это мы знаем..

А мне кажется, что в статье понятия простоты и лёгкости как раз перепутаны.

Go — лёгкий. Лёгкий потому что у него низкий порог входа, мало фич, и программы на нём писать «легко». Легко написанная программа на Go — простой от этого не становится.

Время освоения инструмента, это как константа в big-o нотации — значение имеет только на очень малом количестве входных данных.
При написании сложного проекта — важно сдерживать нелинейное возрастание сложности при росте проекта, а не пытаться уменьшать константу, и «сложные»(complex) инструменты могут быть проще в использовании, когда умешь ими пользоваться, для того они, собственно, и нужны.

Также хочется упомянуть тут этот прекрасный топик — habr.com/ru/post/469441, который показывает как решение в которое чуть сложнее вникнуть будет сильно проще.
И прекрасный доклад от Рича Хикки — Simplicity Matters.

Многие сравнивают эту лёгкость с примитивностью. С одной стороны это такой засахаренный си с малым количеством абстракций, но с другой стороны си далеко не примитивный, а очень даже мощный в том что позволяет делать. А го и не туда, и не сюда. Вроде и на питон чем то похож, но не такой гибкий и однородный, и вроде на си похож, но не такой могущественный. По сути его киллер-фича в компилируемости, и всё.

Разберем по частям.


засахаренный си

От си часть и убрали (всякие define, к указателю число нельзя прибавить), то что много боли несло как раз. Потом чуть добавили для нормальной структуры (модули, встраивание для ооп, конечно — GC, который очень хорош, и легковесные потоки, на которых можно делать асинхронный код) — это все полезные вещи, и их присутствие понравилось людям, язык развивается, проекты, фреймворки.


на питон чем то похож

с СИ, на мой взгляд, более корректное сравнение, потому что golang=си-трудныеМоменты+фичи. А с пайтоном он только си-подобным синтаксисом похож, и что многие задачи можно сделать на обоих языках


киллер-фича в компилируемости

киллер-фича в простоте и горутинах, вокруг этого выросла эко система, которая позволяет решать задачи, связанные с высокой нагрузкой.

И да, кто-то знает, почему блог м-ра Чейни (dave.cheney.net) уже какое-то время не доступен? По крайней мере у меня в России.

К сожалению, качество перевода сильно хромает. Многие вещи явно не стоило переводить вообще, потому что приходится переводить в уме обратно просто чтобы понять, о чём речь. Тот случай, когда сильно пожалел, что просто не пошёл сразу читать в оригинале. :(

На всякий случай приведу нормальный перевод The Zen of Python:


Красивое лучше, чем уродливое.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложное лучше, чем запутанное.
Плоское лучше, чем вложенное.
Разреженное лучше, чем плотное.
Читаемость имеет значение.
Особые случаи не настолько особые, чтобы нарушать правила.
При этом практичность важнее безупречности.
Ошибки никогда не должны замалчиваться.
Если не замалчиваются явно.
Встретив двусмысленность, отбрось искушение угадать.
Должен существовать один — и, желательно, только один — очевидный способ сделать это.
Хотя он поначалу может быть и не очевиден, если вы не голландец.
Сейчас лучше, чем никогда.
Хотя никогда зачастую лучше, чем прямо сейчас.
Если реализацию сложно объяснить — идея плоха.
Если реализацию легко объяснить — идея, возможно, хороша.
Пространства имён — отличная вещь! Давайте будем делать их больше!
Зарегистрируйтесь на Хабре, чтобы оставить комментарий