Comments 24
За статью плюс, но про null и undefined супротив zero value принципиально не согласен. Это очень грамотная фишка js
Это просто следствие использования вариантного типа. Иначе бы не вышло.
null - это zero value для object, а undefined - zero value для variant.
Поддержу. Go не знаю, и мне вот вообще не понятно, как объявить к примеру юзера, возраст которого опционален - то есть, по заветам JS, number или undefined. Go в данном случае всё равно установит возраст 0 по умолчанию? Как тогда отличить пользователя, который возраст никогда не указывал, от того, возраст которого действительно(и внезапно) 0?
type User struct {
Age *int
}Плохая идея
Это заставляет писать проверку if =!nil каждый раз при работе с объемом
Но это то, что нужно. Если пользователь не указал возраст, то его возраст неизвестен. И нужно всегда проверять, известен ли нам возраст пользователя
Проверка на nil ещё ладно. Тут проблема, в другом - теперь мы не отличаем указать на возраст и опциональный возраст. И как следствие - не можем сделать not nil указатель.
Всегда инициализировать это поле со значением -1?
Предложу как один из вариантов создать свой тип Age с методом Value() (uint, bool). Если Value вернет true, значит, значение в типе верное (инициализированное). При возврате false будет означать undefined.
Просто в struct указать что поле age будет *int (ссылкой на место в памяти где может будет int), а такой тип может быть nil
У вас сломанный пример, возраст не может быть 0, setter не должен позволять делать такого, и как указали ниже можно делать как в Сях -1, во взрослых языках value типы могут иметь nullable значения, если уж сильно хочется.
Это актуальная проблема. Как вы смотрите на создание ValueObject?
type UserAge struct {
age int
filled bool
}Не удобно писать код с телефона, но, надеюсь, мысль передал. Поля приватные, экземпляр создается через конструктор. Там же может быть валидация. Публичный геттер может возвращать два значения для проверки и обработки. Zerovalue структуры будет иметь filled false, что так же корректно обрабатывается
Справедливости ради в TS/JS наследование это всего лишь удобство синтаксиса.
На деле мы можем как и раньше менять прототипы и использовать defineObject.
Также и с методами их можно отдельно писать как и делали раньше.
Просто это достало ;)
Именно так. class в JS сахар над прототипами, появившийся в ES6 чтобы не писать Object.create() и Object.defineProperty() руками.
Go в этом смысле честнее: нет притворяющегося классом синтаксиса. Структура это данные. Методы отдельно. Композиция через встраивание явная. Никто не ожидает поведения "настоящего" ООП и не удивляется, когда его нет.
В JS классы создали иллюзию привычного ООП для людей из Java/C#. Иллюзия работает, пока не полезешь в _proto_ или не попробуешь сделать множественное наследование. Тогда вспоминаешь, что под капотом всё те же прототипы.
Отказ от undefined не спасет от криворуких кодеров. Ну не тут, так в другом месте напишут такое, что будет падать. И сделают они это со свойственной им тупизной. ТС, конечно, накидывает на вентилятор - кто сейчас в js проверяет опциональные свойства когда для этого есть специальный оператор?
100%... Падает со страшным грохотом так же легко как в js
Про криворуких кодеров согласен, от них не спасёт никакой язык. Можно и на Rust написать что-то, что будет падать, если очень постараться. Но речь не о защите от злого умысла, речь о том, какой код получается по умолчанию.
Про ?. (optional chaining) справедливое замечание. Действительно, с ES2020 ситуация стала лучше:
const name = user?.profile?.name ?? "Anonymous";Но тут два нюанса:
?. это костыль поверх проблемы, а не её решение. Вы всё равно должны помнить, где нужен ?., а где нет. В Go нулевое значение это дефолт, а не исключение. Разница в когнитивной нагрузке.?. появился в 2020. Sentry-ошибки из статьи это реальный опыт 2017-2019 годов. Легаси-код без optional chaining живёт и здравствует.
Но вы правы, в современном JS эта проблема смягчена. Можно было упомянуть ?. как контрпример. Статья фокусируется на различиях в философии языков, а не на современных workaround'ах, но замечание учту, спасибо.
Все
.goфайлы в одной директории должны объявлять один и тот жеpackage. Они видят друг друга полностью, как если бы были одним файлом.
В такой форме это путает новичка. Если все файлы должны иметь одну и ту же строку, то зачем она вообще нужна? Ну, может быть если файлы вдруг перескочат из одного каталога в другой...
Если бы написать чуть полнее, скажем про go.work, go.mod и то, когда имена пакета и директории могут не совпадать - будет понятно, что обязательный package не 100% исторически сложившееся уродство, а в нём есть и некоторая логика и некий потенциал на мало ли что в будущем. Мне кажется, что в статье изложено меньше чем квант.
Про "видят друг друга полностью" - неверно. Файлы друг друга не видят вообще, их видит команда go. Поэтому в одном каталоге может быть несколько .go файлов в каждом из которых есть и "package main" и "func main" и команды типа "go run m_v3.go p1.go p2.go" и "go run m_v1.go p1.go p2.go" будут работать, а комманда "go run ." - нет.
Такие мелочи важны как раз для начинающих, у них и main может быть в разных вариантах, и раскладка по модулям плавать.
Объявляет и инициализирует переменную одновременно
Тут я бы непременно вставил слово "новую", как минимум. Иначе это тоже запутывает новичка. А про "новую" он, даже читая по верхам, точно вспомнит как только слева от := окажется больше одной переменной.
В JavaScript два пустых значения:
undefinedиnull. Тони Хоар назвалnull"ошибкой на миллиард долларов". JS решил эту проблему... удвоив её.
Он не удвоил, он именно решил... для случая когда возможны ровно три состояния - известно, неизвестно, не спрашивали. Так нередко и бывает, поэтому в комментариях некоторые и пишут что получилось хорошо. Как только состояний больше - JS требует задуматься, а весь огород вокруг undefined и nil только мешает. В Go такого нет, да.
То же самое относится и к обработке ошибок. Пока состояний два - получилось и упс, try да ? работают идеально. Как только задача не столь тривиальна - метод Go удобнее чем баян который можно нагородить вокруг типов исключений и отметки мест откуда они.
Спасибо за развёрнутый разбор.
Про package и организацию модулей. Да, я сознательно упростил. Статья для JS-разработчиков, которые только смотрят в сторону Go. Полноценное объяснение go.mod, go.work, рабочих пространств и случаев несовпадения имени пакета и директории это отдельная статья. Здесь ментальная модель "пакет = папка" работает в 95% случаев для входа в язык.
Но согласен, что фраза "видят друг друга полностью" технически неаккуратна. Файлы видит go, а не друг друга. И ваш пример с go run m_v3.go p1.go p2.go vs go run . отличная иллюстрация того, как упрощение может сбить с толку. Исправлю формулировку.
Про := и "новую" согласен на 100%. Там действительно нужно слово "новую", иначе при x, err := …; y, err := … начинающий запутается. Спасибо, поправлю.
Про undefined/null и "удвоение проблемы". Тут я остаюсь при своём. Три состояния "известно / неизвестно / не спрашивали" это нюанс, который появляется в конкретных доменных задачах. Для 80% кода это просто два способа получить TypeError. Про "решил" - вопрос вкуса. Моё мнение: если типичный вопрос на собеседовании "в чём разница между null и undefined" это признак неудачного дизайна, а не решения проблемы.
Про обработку ошибок согласен частично. Для сложных сценариев Go-подход действительно удобнее благодаря явности. Но try/catch тоже не просто так существуют 30+ лет. Оба подхода имеют своё место. Статья намеренно показывает Go в положительном свете. Это материал для тех, кто рассматривает переход.
Zero Values и никакого undefined: Чему Go научит JS-разработчика (Часть 2)