Как стать автором
Обновить
17
0
Кирилл @ws233

Руководитель мобильной разработки

Отправить сообщение

Пример того, как любую идею можно довести до абсурда.

По моему скромному мнению, для того, чтобы следовать OCP, не нужно быть провидцем. Достаточно понимать, как вносить изменения, не ломая старого. Постфактум, без предвидения. Для этого ведь есть инструменты. Расширения во многих языках прям в стандарте заложены же. С определенными правилами. Надо просто брать их в чистом виде, принимать и использовать, как есть, не пытаясь применять не по назначению и искать различные хаки на их основе.

Я понял, у вас неприязнь к OCP и SOLID. Ок, специально для Вас: замените OCP в моей предыдущей реплике на следующую цитату из приведенной видюхи, смысл останется неизменным и справедливым:

"Не ломай публичный контракт без необходимости!"

Ну, или скажу другими словами. Вместо рефакторинга тестов в первых трех пунктах у@akaDualityнадо было просто не ломать публичные интерфейсы.

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

Желаете что-то возразить или поправить меня?

А за ссылку спасибо, полистаю, что там у Вас еще имеется.

В первых трех случаях вы нарушили Open/Close принцип в вашем коде. Вы изменили поведение, а должны были расширить его. Например, достаточно было завести вторую функцию, которая бы вызывала первую, но оборачивала бы ее в async/await. В таком случае Вам не нужно переписывать ни одного теста (и ни одной строчки кода!), достаточно написать пару новых тестов на новую функцию, которые будут очень примитивны: один проверит вызов функции, второй – возврат колбека. Тот самый SOLID, от которого все плюются, на практике :)

Четвертый случай еще как-то оправдывает рефакторинг тестов. Да, я такой случай не учел. Но даже в этом случае имеет смысл работать только с теми тестами, которые работают с кодом, который вы активно меняете. Остальные тесты можно тупо не запускать (по импакт-анализу) и получить тем самым даже больший выигрыш в скорости, чем от рефакторинга тестов.

Могу ли я по приведенной Вами выборке сделать в первом приближении вывод, что более 75% работы Вы делаете зря? При этом из-за Ваших же ошибок в коде?

И да, я тоже откачусь назад и слегка подправлю свой изначальный тезис: "Если Вам приходится рефакторить тесты, то Вы пишите неправильные тесты И неправильный код" :)

В общем, завязываю мешать Вам рефакторить тесты, пойду смотреть видюху! Спасибо еще раз Вам за нее.

Нет, Вы, кажется, не поняли. Рефакторите Вы код, но не тесты. Вы верно пишите, что поведение не меняется. Значит, не должны меняться и тесты. Значит, рефакториться они не должны :) Ну, смысла в этом тупо нет. Они есть, они проверяют поведение. Точка. Зачем их рефакторить? Ну, это понятно для кода: сделать его быстрее (в 99% случаев тоже сомнительно), сделать его читабельнее, масштабируемее, переиспользуемее... А тесты зачем рефакторить?

За видео спасибо. А не подскажете, таймфреймы по теме для экономии времени?

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

Все верно говорите. Но есть нюанс. Если Вам приходится рефакторить тесты, то Вы пишите неправильные тесты :)

Попробуйте подумать, что же не так с Вашими тестами.

Несколько подсказок.

  1. Тесты – это ТЗ, переведенное в код. Если ТЗ не менялось, тест тоже меняться не должен. Если Вы вынуждены менять тест при рефакторинге кода, то Ваш тест связан с кодом. Это ошибка. Связность в коде стараются избегать, а про то, что тест тоже не должен зависеть от кода, обычно забывают (от публичных интерфейсов тесты могут и должны зависеть, от деталей реализации – не должны)

  2. Вторая самая распространенная ошибка – это тесты, проверяющие все. Этакие God-тесты, когда в одном тесте проверяется сразу 100500 условий и поведений. В идеале тут тоже должен выполняться принцип единой ответственности – каждый тест должен падать строго от одной и только одной ошибки. Если у Вас при ошибке падает несколько тестов, Вы снова пишите их неверно. Снова связность. Тут, кажется, очень хорошо помогают мутационные тесты: вы сразу видите сколько тестов у вас сломалось от каждой мутации, и вынуждены думать, как же так их поменять, чтобы они конкретную мутацию проверили, но не зависели от остального кода.

  3. Принципы FIRST. Много полезного. Особенно буковка I - которая снова про изоляцию и связность.

Итог. Тесты не должны обновляться и рефакториться, если не менялось ТЗ.

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

А эта статья была? Что-то не нашел в Ваших статьях. С радостью бы почитал.

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

А есть где-то описание (желательно текстовое) как Вы этого добились? Просто я ни разу не видел, чтобы UI-тесты запускались быстрее, чем единицы или десятки секунд.

Плюс мне непонятно, у вас на видео открыт симулятор, при этом в нем нигде нет того компонента, скриншоты которого Вы проверяете. Это как это так работает?

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

Это как? Без запуска симулятора? 0_о

Можете чуть подробнее раскрыть первый пункт? Это как? Вы запускаете тяжелый скриншот-тестинг (который все равно запускает приложение) под все платформы вместо того, чтобы запускать приложение в Xcode? А в чем профит?

Как часто скриншот-тесты у вас показывают ошибки? Какие это ошибки?

А за что минусовать? За факт? Или за отсутствие связи представленного факта с объемом работ? :)

С фактом не спорю. Качество разработчиков бывает разное. Особенно, когда их надо в моменте много. приходится брать всех, кто есть на рынке. Это ж не секрет :)

В маленьких конторках не делают и 10% того, что делают в банках. Отсюда и бюрократия. Хочешь или нет, но если работа увеличивается в 10 раз, то ее раз в 100 сложнее координировать. Вот и появляются регламенты, документация и прочая бюрократия.

Можно, конечно, спросить, а зачем делать эти 90% работы, если маленькие конторки получают тот же результат, выполняя 10%? Вот только результаты все же разные. Маленький финтех стартап и Сбер получают совершенно разные результаты. Отличающиеся на несколько порядков. Да, даже Тинькофф, работающий по модели CapitalOne и Сбер – это две большие разницы по результату. Кто не верит, может погуглить цифры. Еще недавно они были открытые :)

А мне понравилось следующее.

Сначала написано, что есть 2 оценки: на глазок (считай, неверная) и на основе исторического опыта (эмпирическая).

Затем утверждается, что брать эмпирическую нельзя, т.к.ситуация будет новая.

А потом много-много буков объяснений о том, как много проблем в банке и что минимальная оценка все равно превратиться в эмперичекую – и в итоге добавление банера все равно займет 2 недели :)

Как написали выше, над теорией оценки нужно еще поработать. Но как статья для понимания масштаба катастрофы в Альфе, очень даже здорово. Поэтому лайк ^.^

Первоначально данная статья задумывалась как сравнение двух реализаций (VIPER и MM) одного приложения.

Не хватило понимания, чем ММ лучше, а чем хуже.

Посмотрите, как работает реактивный двигатель. Его основа - закон сохранения импульса. Реактивный двигатель работает в космосе. Только взрывается там обычное топливо, а не ядерное. С ядерным топливом принцип аналогичный. Сколько вещества помноженного на скорость после взрыва улетело в одну сторону, столько же вещества помноженного на скорость разгонится в другую сторону. В одну сторону отлетают продукты взрыва, в другую – корабль.

https://ru.wikipedia.org/wiki/Реактивная_тяга

А закончить надо было таким утверждением:

Мрак не в клозетах моделях, мрак – в головах

А теперь серьезно. Где сравнение-то? Какова цель архитектуры? Тема мрака не раскрыта.

Спасибо за содержательную дискуссию. Еще один плюсик к моей теории. Действительно же. Независимый UI не есть переиспользуемый. А так хочется переиспользуемого UI и мне тоже! :)

А! и дайте знать в личку, что получится, пожалуйста. Мне это важно.

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

Кстати, хорошая метафора. Адаптер – он и есть адаптер. С одной стороны "мама", с другой - "папа". Их мы все видим в хозяйстве и как использовать понимаем. А Медиатор - это шнур с двумя "папами". Такое у нас встречается все же реже. Но оно в обиходе требует меньше проблем. Представьте, если бы вы соединяли монитор с компом через адаптер, а не одним шнуром, да? :)
Забрал метафору себе в коллекцию. Буду объяснять теперь на примере.

Что касается вопроса, нужно ли так делать всегда, то есть ощущение, что нужно. Apple пишет так:

View objects and model objects should be the most reusable objects in an application. View objects represent the "look and feel" of an operating system and the applications that system supports; consistency in appearance and behavior is essential, and that requires highly reusable objects. Model objects by definition encapsulate the data associated with a problem domain and perform operations on that data. Design-wise, it's best to keep model and view objects separate from each other, because that enhances their reusability.

A goal of a well-designed MVC application should be to use as many objects as possible that are (theoretically, at least) reusable. In particular, view objects and model objects should be highly reusable. (The ready-made mediating controller objects, of course, are reusable.) Application-specific behavior is frequently concentrated as much as possible in controller objects.

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

Если вы переиспользуете вьюхи, то результат вы получите быстрее, чем если не переиспользуете их и работаете с адаптерами.

Это цель MVC. И MVP, я полагаю, тоже. А какая цель у clean?

3. Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.

Вроде та же. Тоже сделать UI переиспользуемым. Но делает ли Clean переиспользуемым UI? Мой пример про сервер или дизайн показывают, что вряд ли. Я вынужден дорабатывать UI, чтобы он подключился к имеющемуся у меня промежуточному слою. А на кой черт тогда этот промежуточный слой, если он все равно заставляет меня дорабатывать одну из точек взаимодействия с внешним миром? А вот если промежуточный слой сделать по шаблону "Медиатор", то я его создаю сразу под обе стороны, которые я не контролирую, но зато обе эти стороны я использую, как есть. Взял код домена на КММ и впихнул его в веб :)

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

Кажется, таким и должно быть программирование? Все специфичное для конкретного приложения в самом верху, все остальное – внизу и переиспользуется или взаимозаменяется? У Мартина Фаулера это называется app specific logic.

A screen and components with all application specific behavior extracted into a controller so that the widgets have their state controlled entirely by controller.

А цитату Элла по этому поводу я привел выше в последнем предложении ^.^

Рискну крамольную вещь сказать. Роберт Мартин слегка ошибся со своим средним слоем. Он должен быть верхним. И вот в такой постановке архитектуры, все работает, как часы. :)

Да, Вы правы.
Я понял свою ошибку по отношению к чистой архитектуре. Я перевернул зависимости с одной из сторон от среднего слоя. В чистой архитектуре они не перевернуты, все направлены в одну сторону.

Но разрешите все же донести свою мысль. И причину ошибки.

Смотрите, в чистой архитектуре средний слой – это адаптер. Его главная цель, соединить между собой 2 других слоя, которые должны быть независимы и переиспользуемы (вью переиспользуется между приложениями, а доменный слой даже между платформами). Но у этого шаблона проектирования есть небольшой недостаток, он делает зависимым то, что снаружи (сверху) от того, что внутри. Это приводит к проблемам переиспользования: доменную модель мы можем переиспользовать даже между платформами, а вот вью между приложениями уже не можем, т.к. даже имеющиеся вьюшки мы должны адаптировать под протоколы более низких слоев. В итоге, становится тяжело взять, например, текстфилд и перенести его в другое приложение, потому что для него все равно приходится писать адаптер.

Как это должно исправляться? Есть еще один шаблон. Он следует той же цели, но с одним существенным плюсом: слабая связность и отсутствие явных связей соединяемых объектов друг на друга. В таком случае мы не должны менять вью и переиспользовать ее становится гораздо проще. Вспомните типичную мобильную разработку (хоть на иос, хоть на андроид): Вы всегда вынуждены создавать экран... Вы его не можете переиспользовать без дополнительных танцев с бубном в случае с адаптером. А в случае с медиатором можете. И вот медиатор нас возвращает к MVx, в котором средний слой выше!, чем два соседних.

Опять же, если рассматривать

Мне настолько уже удобно использовать медиатор, что я и в чистую архитектуру его засунул. Хотя его там нет. А зря. Мне кажется, что если заменить адаптер в среднем слое clean на медиатор, то всем сразу заживется на порядок проще. И помирит, кстати, MVx, clean и кажется, все другие архитектуры :)

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

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

Все верно пишите в части того, что слои можно объединять и декомпозировать по необходимости.

Но вот то, как это делают – обычно неверно. Глядите, в чистой архитектуре общение с сервером или базой данных (БД) – это слой View. Внезапно. Он синенький. Это интерфейс общения с внешней системой. Это значит, что общение с сервером или БД должно зависеть от того, что ниже. А ниже у вас юз кейсы. А как делают обычно? Зависимость следующая V -> x -> M. А должно быть V -> x <- M. Видите? Зависимости обратные. Именно поэтому MVP (на андроид) и MVC (на iOS) рисуют с презентером (контроллером) вверху, а вью и моделью внизу. Картинка 7.2 отсюда.

Если это понять, то все встает на свои места. MVx – это не какие-то там "архитектурные паттерны презентационного слоя", это полноценная клин архитектура, в которой буковка М - это композиция Controller и Web (для случая работы с сервером) или Gateway и DB (при работе с локальным хранилищем). Термины из этой картинки. Да, контроллер и презентер тут вступают в конфликт терминологии с паттернами MVx. Но если различать их значения, то проблем восприятия не будет. Мне понравились картиночки вот отсюда. (Там, где написано про слои и линейность.)

Теперь про:

Что делать с юзкейсом, содержимое которого — одна строчка? Вызов репозитория, и всё. Может, в этом случае вызывать репозиторий напрямую?

По идее интерфейс общения с БД и сервером (View к внешним системам) у вас должны быть зависимы от внешней системы и независимы от ваших внутренностей (инверсия зависимости – раз. два – вы делаете визуальный интерфейс под удобство пользователя, а интерфейс сетевого слоя – под запросы сервера). Это значит, что эта самая View к внешним системам имеет свой собственный внутренний формат данных, отличный от вашего внутреннего формата приложения. Поэтому, юз кейс как минимум должен содержать преобразование данных от внешнего формата к внутреннему (к доменной области). Именно так. Видите же выше направления зависимостей? Применяем к ним инверсию зависимостей и получаем то, что нарисовано в чистой архитектуре в правом нижнем углу. Поэтому юзкейс не должен быть пустым. Он как минимум содержит преобразования данных от интерфейса внешних систем (сервера и БД), к интерфейсу с пользователем, через промежуточные внутренние Entity используемые в доменной области.

Конкретный пример. Есть такой инструмент, как Swagger - автоматический генератор сетевого слоя по контракту с сервером. Он генерирует запросы/ответы в своем собственном формате, который вообще не обязан совпадать с тем, что вы используете в доменной области или покажете пользователю на экране. Конечно, есть вероятность того, что форматы совпадут. Тогда юзкейс действительно будет просто проектированием данных. Но это скорее исключение, чем правило. Если же это у Вас правило, то скорее всего Вы неправильно определяете направление зависимостей между интерфейсами внешних систем и внутренними юзкейсами (V -> x -> M).

Что думаете?

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность