Комментарии 24
mariusschulz.com/blog/read-only-array-and-tuple-types-in-typescript
class UserService
Хорошо — давать нормальные имена классам, которые будут давать примерное представление что в этих классах находится.
А «Service» это что?
Если класс называется UserService в него всё-равно в итоге напихают всего, что хоть как-то связано с пользователем.
Документация по Angular, например, говорит что в сервисе происходит работа с данными. То есть созданный файл через angular-cli
будет как раз называться user.service.ts
, а класс в нем — UserService
. Отсюда, мне кажется, что лучше не какие-то жесткие правила, а договоренности в определенной среде.
В Angular придет новый разработчик и будет знать, что в UserService
происходит работа с данными юзера и при необходимости нужно смотреть туда. В других фронтенд-фреймворках, насколько я знаю, таких договоренностей нет, поэтому там может быть и UserManager
, и UserController
, и UserStore
, да и вообще что угодно.
Но их частенько нет, а UserService, UserManager и прочие абсолютно неиформативные названия есть, обычно из-за человеческой лени и нежелания думать над неймингом, а это важно.
Иначе в один момент проект просто превращается в гору *Service и бесконтрольных связей.
Спасибо за статью! Хотел только отметить, что бОльшая часть правил из двух статей относится скорее не конкретно к TypeScript, а вообще к любому языку. Принципы разделения ответственности, иммутабельность и прочее — это просто Best Practices. Что касается именно TypeScript, то, на мой взгляд, следовало бы немного добавить про особенности фреймворков. Например, в Angular не рекомендуется ставить get
на получение данных, используемых в шаблонах, так как это может привести к бесконечному Change Detection. В случае с Vue — get
в TypeScript вполне успешно заменяет computed
. И много других вариантов.
Разумеется, я понимаю, что на TypeScript пишут не только frontend и не только на фреймворках.
Мне ваше предложение по поводу рассмотреть какие-то особенности TypeScript и применяемость его в различных фреймворках, нравится. Подумаю о том чтобы выделить это отдельно.
Как и в первой есть спорные моменты.
Замечания к первой части:
- Enum должны быть строковыми, числовые, особенно с неявными значениями (без знака =) — абсолютное зло, т.к. дебажить их нереально.
- Гетеры и сетеры — зло (об этом уже писали). Не нужно повышать уровень магии в коде. А валидация должна быть внешней, либо в специальном методе.
Замечания ко второй части:
- Интерфейсы предпочтительнее типов, используйте типы только, если нельзя выразить интерфейсом. Пруф. type — это просто алис для типов (или комбинации типов), а interface полноценная сущность.
- Если и использовать fluent подход (цепочки), то обязательно иммутабельные (т.е. каждое звено должно генерировать клон). Но лучше обойтись, и писать явно.
К примеру:
const query = q('xxxx').where('y'); const query2 = query.limit(2); // если цепочка будет мутабельной, то мы неявно поменяем и query, что часто хотелось бы избежать
Используйте иммутабельность
Как связаны иммутабельность и readonly интерфейсы? Можно преспокойно заимплементить readonly интерфейс, состояние имплементации которого будет мутировать на ура.
Классы должны быть маленькими
Высокая сплоченность низкая связь
Предпочитайте композицию наследованию
Используйте цепочки вызовов
А причем тут Typescript?
Хотел бы добавить про иммутабельность. Вообще, readonly
действительно работает только с примитивами, то есть, когда речь идет о присваивании. Но, как уже было указано выше, существуют дополнительные типы вроде Readonly<YourInterface>
или ReadonlyArray<SomeOtherInterface>
, которые будут кидать ошибку при попытке изменить значение любым способом.
Единственный минус данного подхода, да и в целом TypeScript — это отсутствие интерфейсов в рантайме, что, в теории, дает возможность мутировать значение. Но и для этого есть свой метод — старый добрый Object.freeze. Правда, если возникает такая необходимость, то появляются вопросы к организации процесса разработки и тестирования.
Я это к тому, что если где-то в коде в Typescript вы получили readonly объект, не нужно ожидать, что он будет иммутабельным. Более того, это опасно ожидать, что он будет иммутабельным.
Может, есть какой-то более конкретный пример? Потому что пока это все выглядит как способ выстрелить себе в ногу. Да, если в классе стоит readonly someClass = new SomeClass()
— это не означает, что он будет иммутабельный сам по себе. Это означает, что ему нельзя присвоить новое значение.
Если же это будет простой объект с данными или массив, то есть сущности, которые ничего не делают сами по себе, то тогда readonly
или Readonly<ISomeInterface>
— это еще какой выход.
Может, есть какой-то более конкретный пример?
Какой-нибудь прокси класс или фасад, который может просто перепрокидывать данные из другого класса через readonly property, причем он может даже создавать их на лету при каждом вызове.
Надеятся на то, что в каждый момент времени через то же самое проперти одного и того же инстанса объекта будут приходить одни и те же данные не стоит. И вот это уже НЕ иммутабельность. Вопрос не в том, что readonly interface это плохо, я этого не говорил. Я лишь к тому, что readonly interface != immutability.
Readonly это круто, используйте это. Просто если вам нужна иммутабельность как она есть, то может стоит посмотреть в сторону каких-то решений, которые имеют отношение к иммутабельности. К примеру, github.com/immutable-js/immutable-js
Я так понимаю статья расчитана в большинстве своем для начинающих, поэтому предлагаю не вводить их в заблуждение о терминах.
Immutable data cannot be changed once created
А еще вопрос, какой смысл от такого массива?
const array: ReadonlyArray = [ 1, 3, 5 ];
что тут можно понять, есть ридонли массив с какими-то значениями. что это за значения? Как они применяются? Какая верятность, что пушить в массив никто не будет?
Какая верятность, что пушить в массив никто не будет?
Высокая. Всё-таки TypeScript является статически типизированным, и конструкция array.push(5)
в нём просто не скомпилируется.
Боюсь, что скомпилируется: https://habr.com/ru/post/485068/#comment_21181668
Чистый код для TypeScript — Часть 2