Просто код
28 subscribers
9 photos
1 video
1 file
6 links
Сейчас пишу на языке Dart и последние несколько лет использую, в основном, Flutter. Походу дела я делаю пометки, когда изучаю новые подходы или получаю фидбек об успехе прошлых решений. Многие из них актуальны и для других языков.
Download Telegram
3 уровня безопасности моделей. Интерфейсы

*На уровне интерфейса убираем state errors и обращения к null*

Бывает, что сущность, которую представляет модель, имеет разные состояния.

У экрана со списком задач тоже несколько состояний. View model такого экрана будет иметь следующие состояния:
- "данные грузятся" — никаких данных больше не нужно,
- "ошибка загрузки данных" — текст ошибки, чтобы показать ее пользователю,
- "данные загружены" — список задач для отображения.

Если описать состояния одним классом, будет такое:


class TasksState {
final bool isLoading;
final String errorMessage;
final List<SomeClass> data;
}


В виджете придется делать проверки на текущее состояние и пользоваться тем или иным набором данных. Виджету придется понимать логику этой модели.

Например, на то, что данные загружены, указывают два факта: isLoading == false и data ≠ null, и наверное не пустое. Не очевидно, как надо проверять факт загруженности данных.

Еще мне не нравится, что если мы захотим изменить набор состояний, перераспределить поля между ними, придется менять как презентатор, так и виджет экрана.

Смысл этого уровня в том, чтобы разбить эти состояния на отдельные наборы данных, не объединенные общим интерфейсом.

Исправить такие проблемы можно в духе пакета freezed. Я стараюсь избегать любых дополнений в классах моделей и в бизнес-логике. Но вот эта очень помогает получить безопасный код и сохранить время работы.

Freezed помогают сделать для каждого состояния свой интерфейс. Условно говоря, в результате такого подхода на каждое состояние будут три калбека, каждый со своим набором полей:


final TasksState data = snapshot.data;

data.when(
loading: () {},
loaded: (data) {},
error: (errorMessage) {},
);


Таким образом, мы снова значительно улучшаем код:

- виджету не нужно знать логику состояний,
- виджет всегда сразу получает все необходимые данные,
- мы никогда не забудем обновить ui после изменения логики состояний.

Что делаем: используем freezed для view моделей.

Что получаем: простой и надежный код, а все знание о логике состояний модели сосредоточена на уровне презентаторов и app-логики.
3 уровня безопасности моделей. Итоги

Таким образом, мы самой организацией кода технически делаем невозможными большое количество ошибок.

Уменьшаем количество проверок во всех местах использования таких классов данных. Особенно это касается самой стремной, неудобной и часто меняющейся части кода: презентаторы и UI.

Прозрачный и понятный интерфейс предлагает более простую обработку данных и подбор подходящего отображения.

Не надо будет писать код на скорую руку, игнорируя внутреннего перфекциониста, который кричит: "а что если это null?! А если эти две штуки будут иметь неконсистентные данные?".
Как и когда использовать union типы

Юнион типы очч классные ребята, помогают избегать ошибок, делать код чище и прямее. Но и с ними можно легко напортачить и влезть в дебри говнокода, если не соблюдать вот эти простые правила.

Используй union типы только в двух местах:
1. тип возвращаемого значения функции или метода,
2. тип в коллекции.

Избегай использовать union для указания типа поля класса. Скорее всего, значение нужно заранее привести к какому-то более общему или конкретному типу.
МАПЫ ВМЕСТО МОДЕЛЕЙ — ПЛОХО
Но это не точно

Я делал дашборд, и у меня в нем были виджеты, с набором пропертей. Еще есть редактор этого дашборда, где есть панель пропертей виджета.

Спецификации, параметры я хранил как мапы <String, Smth>. И буквально все остальные модели были организованы через мапы.

Чем это плохо

Геморно менять имя поля. Да, можно выделить константу на имя поля, но не решает других проблем.

Нет валидации полноты модели. Мап — это мап, ему все равно, что ты там задал.

Много мест для изменений, которые легко пропустить. Плохо для добавления новых полей в виджеты.

Что делать

Я пока разбираюсь, как тут лучше. Пока есть идея все сделать иммутабельными классами с полями.

Вся идея с мапами идет от желания получить общий интерфейс при работе с моделями виджетов разных типов. Сейчас есть union-type (freezed). Значит, в нужных местах сделаем юнион.

Вопрос только в том, не надо ли будет создавать миллион похожих юнион типов, и не надо ли будет где еще какие проблемы третьим способом решать. Например, как получить список названий полей и их значений для отображения в панели пропертей?
Кстати, если кому-то не нравится кодо-генерация, вместо freezed можно попробовать использовать sealed_unions. Выглядит неплохо на первый взгляд.
Я, конечно, не новостной канал, но с новым flutter 3.3 и dart 2.18 появляются проблемы в их оптимизации async операции.

У меня в каком-нибудь презентере (типа блок) в конструкторе создается запрос на загрузку доп. данных с сервера (инфа о пользователе, например), а после этого я закидываю в поток первое состояние экрана с ожиданием загрузки.

В новом дарте может выйти так, что данные о пользователе уже загружены и закешированны, и я покажу сразу нормальный рабочий экран, а следующей строчкой я добавляю уже не актуальное состояние предзагрузки, и пользователь никогда не увидит рабочу форму.

Упс, не удобненько выйдет. Надо внимательно все переписывать и тестировать теперь после апдейта.
Антипаттерн “Класс с инициализацией”

Пишем класс типа:


class AntiClass {
final String uri;
late final String param1;
late final String param2;

AntiClass(this.uri);

Future<void> init() async {
final result = await LoadSomth();

param1 = result.param1;
param2 = result.param2;
}
}

/// somewhere:
final obj = AntiClass(uri);
await obj.init();


Вот так делать плохо.

У нас уже существует объект, поля которого не инициализированы. Если во время init была ошибка, то объект так и остается существовать в памяти, и его могут ошибочно использовать дальше. То есть, никто нам не скажет, правильное ли у объекта состояние.

Надо переделать код, вот к чему придем:

- Мы разделим создание объекта и загрузку данных, это хорошо.
- Если объект существует, значит он валидный. И это очень хорошо.
- Объекту не придется хранить доп. поля для инициализации (uri).
- Мы избежим очень косячных типов полей late final, что тоже хорошо.

Новый код будет выглядеть так:


Future<GoodClass> loadObj() async {
final result = await LoadSomth();
return GoodClass(
param1: result.param1,
param2: result.param2,
);
}

class GoodClass {
final String param1;
final String param2;

GoodClass({@required this.param1, @required this.param2});
}

/// somewhere:
final obj = await loadObj(uri);


Все стало проще, понятнее и независимее.

Заметка, опять же, по мотивам очень злого бага на проде.
Частичное переиспользование enum

Переиспользование кода часто приводит к проблемам в коде. Вот сегодняшний герой из этой лиги.

Представьте ситуацию: мы получаем с сервера данные о качестве воздуха, значения могут быть нескольких типов.

Для обозначения типа мы сделали enum ValueType с четырьмя значениями: влажность, температура, co2 и pm2.5. Где-то в приложении есть экран с двумя вкладками: co2, pm25.

Я часто наблюдаю ситуацию, когда для обозначения текущей вкладки используют тот же enum ValueType.

Получается, что где-то в виджете экрана есть место, где отбрасываются два ненужных значения этого enum, а ошибки игнорируются. И частенько таких мест больше одного!

Так, конечно, делать плохо. Будет сложно рефакторить, будет слишком много сцепок между совершенно независящими друг от друга слоями приложения.

Чего будет стоит добавление новой вкладки для показа температуры и влажности вместе! А если наоборот, в типы загружаемых данных добавят еще три новых значения?

В таких случаях надо обязательно иметь два разных набора значений. количество и назначение вкладок ну точно не должны зависеть от типов данных.

И, конечно, с самого начала при написании приложения или нового экрана такие “переиспользования” следует избегать.

#антипаттерн #переиспользование_кода
Человек пишет про хороший код на примере создания экрана инвенторя в игре на Unity. Первым делом он предлагает создать абстрактный базовый класс BaseItem для описания разных предметов.

У меня сразу ладонь летит в лоб. Думаю, парень, с чего ты взял, что тебе нужна древовидная иерархия предметов по одному полю тип? Чем этот тип такой волшебный? Ты уверен, что тебе надо создать 100500 классов для всех типов предметов вместо одного класса с полем itemType?

Наследование нужно применять там, где нужно иметь разные варианты поведения. Но предметы в интвентаре ведут себя одинаково! А все их различия можно описать полями в едином классе.

Это, наверное, пришло из учебников по ООП, где выстраивали цепочки "животное" - "собака" - "лайка". И почему-то наследование стало дефолтным способом разработки чего угодно.

К чему это я? К тому, что мне мой опыт говорит, чтоб я бежал как можно дальше от наследования. Так много от него проблем было.

Update. Извините, погорячился

https://habr.com/ru/post/700272/
👍3
Простая переделка Singleton

Увидел такое решение забавное, которое позволит заменить ненавистный синглтон на другую похожую конструкцию без прокидывания зависимостей.

Обычно выглядит так:


class MySingleton {
static final MySingleton instance = MySingleton._();
MySingleton._();
}

class SomeClass {
void someMethod() {
MySingleton.instance.doSmth();
}
}


А можно переделать это на такой вариант:


class MySingleton {
// some mistery here so far
}

void main() {
final singleton = MySingleton.create();
MySingleton.put('my_id', singleton);
//...
}

class SomeClass {
void someMethod() {
MySingleton.get('my_id').doSmth();
}
}


Стало ли лучше?

Да. Теперь мы можем вместо создания настоящего экземпляра MySingleton создать mockSingleton, использовать его для тестов, или для смены версии базы данных, и подобное.

Кроме того, нам почти не приходится переписывать много кода и реорганизовывать его по новому.

Стало ли хуже?

Немного да. Теперь надо во-первых, хранить как-то этот id. А во вторых, мы добавили теоретическую возможность того, что экземпляр класса еще не создан или не задан для переданного id.

Что в итоге

Думаю, так можно пробовать делать для распутывания немаленького приложения, уводя его от плохого паттерна Синглтон. Это решение напоминает service locator.

Если проект замучен синглтонами, это — хорошее движение в сторону лучшего кода.
👍1
Media is too big
VIEW IN TELEGRAM
Привет! Решил записать в формате видео, напишите ,как вам, дослушали или бросили, интересно было, или мне нужно лучше готовить текст. Удобно ли в таком формате?

Тема видео: показываю простые базовые элементы архитектуры, чтобы мы лучше понимали друг друга. Чтобы я какие-то более сложные мысли по архитектуре мог описать и был понят.

Это эксперимент. Сам удивился, что получилось так длинно. Да, пишите в комменты, чо как вам, понятно, доступно или совсем наоборот. Или даже мысли по теме ролика. 👍
👍2
вот так всегда и выходит:
Это прост… Ну… Я такой всегда 😢 👇
Кто работает с DDD? Я вот думаю про модели, entities.

Я думаю, что существует такой подход, соломенное чучело вот такое придумал себе. Ты думаешь: у меня на сервере есть вот такие-то сущности, с такими-то полями-тополями. А значит, мне надо создать на front-end точно такой же класс и назвать его SmthEntity. Так и жить, и везде этот тип как есть кидать. Потом там конечно кто-нибудь припихнет toJsom и fromJson, toString и прочие важные для сущности методы, но это не так важно.

А я думаю, что нафиг такое не надо! Я думаю, что надо проектировать те классы, которыми будешь пользоваться в реальных потоках данных. Например, у тебя данные идут от некоего DataSource в какой-то класс репозиторий, потом идут данные в бизнес-логику. Потом — в логику представления (презентер), и потом уже в само представление.

Вот представлению нужен класс Entity? Нет, ему нужна пачка полей типа String, ну и может числа и enum какие-то. Он не хочет ковыряться в этих ваших структурах данных и собирать по крупицам то, что ему надо нарисовать. Нет. Пусть ему класс логики представления (презентер то есть) все готовое скидывает в виде одного объекта. Вот так приятно.

А презентеру нужны эти Entities? Ну… не. Ему нужны куча самых разных данных, которые он собирает через сервисы и бизнес-логики, может берет напрямую у репозитория. То есть, он может работать с самыми разными типами данных, может что-то мутирвоанное, может это огрызок одной Entity, может это какая-то сборная солянка из нескольких Entity. То есть, ему прям 100% не нужны сами Entities, ему нужна просто инфа, в том виде, как получится собрать.

Или вот с другого конца пойдем. DataSource состоит как минимум из двух штук: одна штука в каком-то сыром виде считывает данные из удаленного источника, а вторая штука - парсит данные в какие-то классы, гарантируя их правильность и полноту, чтобы было все как обещалось по протоколу клиент-сервер.

И во-первых, DataSource сначала оперирует классами совсем рыхлыми, где условно говоря одни строки. А во-вторых, потом он предоставляет данные лишь в формате протокола клиент-сервер, и этот протокол совсем-совсем не обязательно похож на передачу entities как они есть. Вполне могут быть отличия. И вот этот слой DataSource тоже далеко не всегда будет и может оперировать этими entities.

Поднимаемся вверх, и тут у нас репозитории данных и какие-то более высокоуровневые функции загрузки данных с сервера, которые гоняют более примитивные и атомарные DataSource. Вот где-то на этом уровне и можно собрать Entities.

И тут два вопроса. Во-первых, далеко не всегда нужно и удобно хранить информацию именно в виде этих entities. А во вторых, получается, что эти Entities сами по себе никому не нужны. Чаще всего на стороне представления нужны какие-то то более широкие срезы информации, то более узкие, то вообще не похожие на entities.

По крайней мере у меня так в работе получается, что эти Entities налепил. А потом какой-нить маленький чих хочешь переделать-исправить, а тут уже все приложение, мильон классов от этого класса сущности зависит, и сума сойдешь, чтобы что-то изменить.

Вот, давайте, ругайте меня.
👍2
А и да, забыл совсем сказать. Я пропал, потому что у меня возникли здоровские проблемы с глазами, даже в больнице лежал. Это был ужас, 90-е! Старики в трениках, старушки со своими тарелками, все ободранное. Одна разница — у людей теперь смартфоны. Все остальное не изменилось за 30 лет.

В общем, я пропал по этой вот причине, да. Даже не работал долгое время. Надо бы продолжить постинг 🙂 Кстати, есть идеи о чем дальше написать? Я остановился на видосе, где хотел какую-то базу по архитектуре расписать, но без картинок конечно вышло так себе. Да и термины, что я употреблял, разные люди совершенно не так восприняли. Кажется, надо расписать все, игнорируюя любые термины, а то невозможно прост 🙂
Хочется как-то устаканить термины, устаканить общие знания, чтобы можно было обсуждать какие-то детали смело и без путаницы.

И для этого (связь не очевидна) я предлагаю вот что: напишите какие-то ваши типовые проекты, что это за программа, что она делает. Чтобы по описанию примерно было понятно, какие потоки данных.

Например

Приложение для изучения английского языка. В основном скачивает уроки-картинки-файлы, и показываетих на экране. Скачивает тесты и правильные ответы. На сервер отправляет статистику, и данные о завершении очередной задачи. И обновление анкеты пользователя.

Что у вас? Я свой вариант тоже напишу.
Не по теме архитектуры. Но вот случилось. Гитхаб закрыл мне доступ к аккаунту до тех пор, пока я не включу двухфакторку.

А я не хочу. У меня отличные рандомные пароли по 20-30 символов, и я отлично хендлю хранилище паролей. А вот свой телефон я не хендлю, я даже не знаю, какой номер лучше указать. Я хз в какой стране живу и когда тот или иной номер у меня отберут.

Устанавливать еще одно приложение в смартфон я тоже не хочу. Вот потеряю я его, или он сброситься, на новый перееду. Как мне переезжать на новый без доступа к старому?

Может быть я потерял смартфон или номер телефона и надо в двухфакторке подключать новые. А как, через почту же? То есть, выкидываем двухфакторку на этот период? Какой смысл изначально тогда в ней — я не понимаю.

Никаких плюсов, и только ухудшение безопасности. Новые программки — новые ошибки.

Сейчас я даже побраузить репозитории не могу, скачать что-нибудь опенсорсное. Мне надо куки очистить, чтобы гитхаб забыл меня и перестал заставлять двухфакторку подрубать.
Программировать за кем-то — полный отстой. Одно дело, когда один программер уволился и ты поступаешь вместо него. Это одноразовый случай, надо вникать в проекты, это абичний.

А бывает, что на проекте работают несколько программистов, и внезапно тебе передают новую фичу, который начал делать другой, может он заболел или его перевели на другую срочную задачу.

И никто задачу не передаст со всеми почестями. Просто на тебе в лицо маленькое изначальное описание задачи и сам ищи в коде, что к этой задаче относилось, где не дописаное, что сломано, что не сделано. Разгадывай тайный замысел, что чувак хотел чувак вообще сделать.

И мало кто тебе еще передаст доп инфу, которую изначальный исполнитель добыл в личных чатах с менеджерами, продакт-овнерами и прочими людьми. Считается, что раз тебе это назначили, инфа к тебе в мозг магически поступает через "коллективное бессознательное"