Pull to refresh

Comments 8

👏 крутая работа! Любо-дорого смотреть на такие красивые решения. Хотя последние пару лет на Swift не пишу (но скучаю), кайфую от его выразительности.

Спасибо, мне очень приятно! Честно, это мой первый публичный проект и первая статья. И вот такой вот первый комментарий) За такую красоту мы и любим Swift, не оставляет равнодушным никого

1)Вы сказали, что эти данные приходят с сервера. Почему вы тогда используете и обычный инициализатор, и инициализатор от codable? Вы используете один и тот же объект для разных целей? Но это логически неправильно - отсюда и проблемы.
2)вы проверки валидности данных закинули внутрь самого объекта. Почему не вынести их в отдельный валидатор, который в зависимости от конкретной задачи будет или в init передавать уже корректные данные, или после декодинга дополнительно проверять уже заполненные данные?

3)

typealias Tag = NonEmpty<Lowercased<Truncated<32,Collapsed<Trimmed<Stripped<String>>>>>>

эта страшная запись - тоже один из недостатков. Почему не сделать то же самое, например, через property wrappers?

Спасибо за вопросы, возможно я привел мало примеров / не до конца раскрыл тему.

  1. Сервер не всегда единственный источник данных. Если приложение туду-лист, то тудушка может быть создана локально, а потом отправлена в сеть, или прийти в ходе синхронизации. Так что в offline-first приложениях оба инита используются равнозначно — данные появляются и из UI, и из БД, и из тестов/моков.

  2. У меня раньше так и было — глобальные хелперы с кучей параметров для гибкости. Но проблема в том, что они работают только в момент создания модели, а дальше начинается свистопляска: какой-нибудь метод из другого субмодуля принимает сырое String, а не User.Name. И вот снова проверять на пустоту, снова обрезать пробелы, чистить символы, плюс те же самые тесты. Сейчас сам тип гарантирует корректность и избавляет код от бойлерплейта и человеческого фактора (забыть что-то проверить в одном из методов, например)

  3. А мне наоборот нравится такая декларативная запись — считаю это лаконичным решением (если только не смотреть с телефона, где строка не влезает полностью и разбивается на три). Про @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 (только основные данные для списков и тп). Это три независимые сущности для разных задач, и их инициализаторы никак не связаны друг с другом, но логика проверки/корректировки для общих полей была буквально одинаковая. Решается через создание отдельных моделей для каждого поля, но все равно отнимает время и силы.

Все по полочкам, оч полезная статья, автор молодец

Спасибо за комментарий! Рад, что мой подход с compile-time валидацией вам понравился. Сейчас думаю добавить секцию с дополнительными примерами до/после для большей наглядности

Sign up to leave a comment.

Articles