Comments 8
Только начал читать пример с UserID, сразу хотел указать на эту особенность
Возможно создать пустую структуру вне пакета, даже если все поля неэкспортируемые
Вот это было одной из головных болей, когда я писал на го
Каждый раз думаешь, ну напишешь ты свой тип с конструктором для валидации возможных значений, но ведь ничего не помешает другому разработчику инстанциировать этот тип без конструктора, или заполнить его поля произвольно, избежав все проверки
Ну а проверять в каждой функции по типу IzZero - ну уж нет, извините, в пхп насмотрелся на if ($a != null && is_array($a)) и т.д., больше не хочу
Именно из за людей с таким мышлением как в статье код на го в некотрых проектах превратился в нереальную мешанину из папок, файлов, слоев, доменов, hex'ов и прочего мусора который в большинстве случаев является препятствием а не решением реальных проблем.
Не нужно строить город там где достаточно
func (x *X) X(xrequest *XRequest) (xresponse *XResponse) {
x.db.QueryRow("select * from x where x = $1", xrequest).Scan(xresponse)
}
Можете уточнить свой пример? Сейчас выглядит так, будто это какая-то идеальная апишка, которой для ответа не нужно ни рассчитывать дополнительных параметров запроса в БД, ни предобрабатывать переданные, а отдавать можно поля без постобработки. Кажется, апишка в таком виде просуществует примерно нисколько.
Будь всё всегда так просто, статья бы не появилась, мне кажется 😅
у вас в примерах с тегами ` протерялись
Для себя выработал простое правило в разработке - "Не выё..ся". Чем проще и понятнее твой код, тем лучше. Если можно обойтись без сложной иерархии - обходись без нее, если не нужны дженерики - к черту дженерики, если можно обойтись без кодогенерации, то к черту кодогенерацию (тут у меня особые вьетнамские флешбеки, после работы на одном проекте где из-за самописной кодогенерации проект на мощном железе по пол дня пытался собраться и не всегда у него получалось).
Всякие сложные и замудренные штуки добавлять только тогда, когда без них никак не обойтись, или без них код станет еще сложнее чем с ними.
Когда вы хотите обновить что-либо в структуре, вы понятия не имеете, что еще может измениться.
Вот эта фраза для меня стала красным флагом чтобы остановится и не читать дальше. К моему большому сожалению когда презентовали Golang всем сказали буквально следующее:
"Удобно и производительно, быстрое создание микросервисов"
Точнее, это так услышали. Реальность она совсем другая. Если кто переходил в Golang с других языков типа Python/Nodejs и в задаче было ускорение существующей схемы и смог переступить парадигму мышления которую накладывают другие языки скорее всего пришел к многопоточности и побочным проблемам которые она приносит (например Race Condition, работа с Mutex и тд). И эти проблемы заставляют мыслить совсем по другому, делать рефакторинг того что уже сделано...а самое главное, ты начинаешь заглядывать под капот пакетов которые используешь и смотришь их реализацию
Именно поэтому я зацепился за конкретное предложение автора. Если мы создаем условную универсальную модель которую хотим использовать в разных проектах/пакетах (там был пример про Users), то для того чтобы её использовать ты пишешь методы буквально на все. Пример:
type Users struct { ... }
// Можно пойти вот так...добавив еще какую-то валидацию
func (u *Users) SetName(n string){
u.Name = n
// Можно еще какую-то логику валидации сделать и обработку ошибок
}
// А еще вот так...для кейсов где требуется инкрементное потокозащищенное изменение
func (u *Users) SetName(n string){
ChUsers <- Update{Action: "Name", Param: n}
}
// Тут должен быть пример еще с mutex...без него никуда
И тоже самое касается чтения. И для того чтобы это понять надо один раз наступить на многопоточность. И сразу начинаешь понимать ЧТО РЕАЛЬНО ИЗМЕНЯЕТСЯ и как, иначе Golang вам принесет пару неприятных сюрпризов...и никакого прямого доступа к значению по вызову Users.Name ты не делаешь т.к это не безопасно с точки зрения многопоточности. Только методы а-ля func (u *Users) GetName string {...} и тп
Вот такое:
...
CreatedAt null.Time `boil:"created_at" json:"created_at,omitempty" toml:"created_at" yaml:"created_at,omitempty"`
...
Это содомия чистой воды. Давайте рассмотрим ситуацию где к примеру надо перевести на json какую-то общую модель...как делается в больших многопоточных проектах и личное мое мнение – правильный вариант:
Мы создаем отдельный пакет, где инициализируется метод/функция с временной структурой где уже проставляется: json:"created_at,omitempty"
А далее в зависимости от ваших предпочтений заполняется к примеру подобными методами как в примере выше а-ля: "(u *Users) GetName"
Почему так? Потому что универсальная модель которую ты грузишь в различные приложения должна быть без лишнего мусора. Зачем модель с параметрами json в приложении где нет вообще преобразований этих. Зачем этот мусор туда тащить? Golang - это про скорость
В заключение.
Я дочитал статью до конца и не в обиду автору, но все же эти высокие размышления сами по себе отваливаются как только Golang начинает использоваться так, как его задумывали изначально т.е в многопоточном исполнении. И структура "как правильно" нарисуется сама
Это хороший комментарий, но, поправьте меня если я ошибаюсь, автор как раз говорит что единая модель есть результат привратников понятого принципа DRY и лучше отказаться от этой идеи так как вам прийдется впадать в содомию, на которую вы верно указали, а захотев изменить что-то в этой мегасущности вы столкнетесь с проблемами. Автор как раз указывает что User это антипатерн.
Анти-паттерны в Go Web Applications