Comments 8
👏 крутая работа! Любо-дорого смотреть на такие красивые решения. Хотя последние пару лет на Swift не пишу (но скучаю), кайфую от его выразительности.
1)Вы сказали, что эти данные приходят с сервера. Почему вы тогда используете и обычный инициализатор, и инициализатор от codable? Вы используете один и тот же объект для разных целей? Но это логически неправильно - отсюда и проблемы.
2)вы проверки валидности данных закинули внутрь самого объекта. Почему не вынести их в отдельный валидатор, который в зависимости от конкретной задачи будет или в init передавать уже корректные данные, или после декодинга дополнительно проверять уже заполненные данные?
3)
typealiasTag = NonEmpty<Lowercased<Truncated<32,Collapsed<Trimmed<Stripped<String>>>>>>
эта страшная запись - тоже один из недостатков. Почему не сделать то же самое, например, через property wrappers?
Спасибо за вопросы, возможно я привел мало примеров / не до конца раскрыл тему.
Сервер не всегда единственный источник данных. Если приложение туду-лист, то тудушка может быть создана локально, а потом отправлена в сеть, или прийти в ходе синхронизации. Так что в offline-first приложениях оба инита используются равнозначно — данные появляются и из UI, и из БД, и из тестов/моков.
У меня раньше так и было — глобальные хелперы с кучей параметров для гибкости. Но проблема в том, что они работают только в момент создания модели, а дальше начинается свистопляска: какой-нибудь метод из другого субмодуля принимает сырое
String, а неUser.Name. И вот снова проверять на пустоту, снова обрезать пробелы, чистить символы, плюс те же самые тесты. Сейчас сам тип гарантирует корректность и избавляет код от бойлерплейта и человеческого фактора (забыть что-то проверить в одном из методов, например)А мне наоборот нравится такая декларативная запись — считаю это лаконичным решением (если только не смотреть с телефона, где строка не влезает полностью и разбивается на три). Про
@propertyWrapper— сначала так и хотел, но там возникает куча нерешаемых проблем. Представим модельstruct Pollс полемlet options: [String]. Поле должно быть непустым, но через@NonEmptyэтого не сделать — оно станет опциональным. Далее, как наложить@Truncated(256) @Trimmed @Collapsedна внутреннюю строку массива? Никак, придется создавать вложенную структуруOption. А ее значение тоже должно быть непустым, значит для обеих структур нужны ручные проверки при инициализации:guard !options.isEmpty else { return nil }и такое же для опции. А если еще моделька должна бытьCodable, то опять нужноinit(from decoder:)прописывать вручную, дважды. В общем, на мой взгляд@propertyWrapperудобен для простых случаев внутриView, но для моделей с вложенными коллекциями и сложными правилами — не подходит. Но возможно, я что-то упускаю с@propertyWrapper, может можно как-то хитро извернуться и добиться такого же мощного эффекта. Если у вас есть опыт подобного использования, буду только рад, если поделитесь
Я конечно не поклонник SOLID и игнорирую в разработке часть его принципов для большей скорости разработки, но отказываться от "single responsibility" выходит себе дороже. Предпочитаю держать в приложении несколько наборов данных (каждый соответствует представлению в своем модуле) и переводить из одного набора в другой по мере необходимости.
Для вашего конкретного примера у меня бы был UserResponse (ответ от сервера), UserObject (данные в бд) + набор view model (микс различных данных, соответствующих визуальному содержимому экрана). Я знаю, что сервер может мне прислать полную ерунду, поэтому я запрашиваю данные как UserResponse (который еще и Codable) и тут же перевожу их в UserObject с проверками. Больше модулей, из которых получаете данные? Ок, еще какой-нибудь UserTransferResponse, который по похожей схеме все равно потом превратится в UserObject.
В такой схеме получается, что UserObject никак не зависит от особенностей сторонних модулей и их ошибок - даже если у сервера api изменится, то у меня изменится только UserResponse и способ перевода в UserObject, а остальная логика останется нетронутой.
Да, я с вами во многом согласен. Отдельные модели для работы с БД (для миграций) и отдельные модели и для сетевого слоя (если нужно). Это решает проблему и с Codable, и защищает от изменений в API. Но рутинные проверки внутри “главных” моделей/методов при этом все равно остаются.
У меня на практике было так: есть UserAccount (полные данные текущего аккаунта), UserProfile (публичные данные) и UserPreview (только основные данные для списков и тп). Это три независимые сущности для разных задач, и их инициализаторы никак не связаны друг с другом, но логика проверки/корректировки для общих полей была буквально одинаковая. Решается через создание отдельных моделей для каждого поля, но все равно отнимает время и силы.
Все по полочкам, оч полезная статья, автор молодец
Как я избавился от тысяч строк валидации в Swift