А самое прекрасное – что чек-лист теперь уже можно и пытаться автоматизировать, шаг за шагом. Автоматизация "с нуля" всего процесса была бы неподъемной задачей, просто потому, что процесс не был формализован и описан. Теперь он формализован, описан, закреплен "на бумаге" – чего бы не взяться за автоматизацию с уже созданным на 80% по пунктам планом? :) Молодцы! Действительно полезная вещь!
Подскажите, пожалуйста, а зачем в iOS реализовывать свои запросы с помощью curl? В инструкции по КриптоПРО нет ничего про это. Такое ощущение, что библиотека КриптоПРО подменяет системный уровень, аналогично тому, как это устроено на Android.
И ни слова про обратные стороны сего забавного когнитивного искажения. А они в АйТи на каждом шагу. Проявляются примерно так: "Смотрите я тут гениальный роутинг для АйОС собрал! Вот вам инструкция на 145 листах, первые 48 листов описывают инструменты, которые вам нужно подготовить, чтобы этот роутинг у себя собрать. Все просто! И естественно, гениально!"
Вот бы такую же подробную и полезную статью, а что делать-то, если эффект Икеи проявляется в обратную сторону: товарищ сочинил, а теперь из него этот табурет велосипедом не выколотить, или наоборот.
По теме статьи. У Apple в UIKit есть реализация шаблона "Команда": селекторы, таргеты, responder chain, first responder. Цель шаблона в том, чтобы доставлять команды (в том числе на навигацию) от источника до исполнителя, т.е.от условной кнопки для пользователя до условного контейнер-контроллера (например, UINavigationController), который уже и осуществит навигацию (в данном примере - push).
По сути, это системная реализация вашего Навигатора.
Ну, а коли он у вас все равно работает с UIKit, то не пробовали ли вы заменить Вашу реализацию Навигатора на системную через responder chain, first responder, таргеты и селекторы?
5 лет прошло. Интересно, как выглядит в итоге Ваша навигация. Хотим и уже попробовали навигацию через UIKit (Свифт у нас старенький по объективным в РФ событиям, использовать последние версии не можем), видим в ней потенциал, но не хотим реализовывать Навигатор, т.к.кажется, что в системе это уже все есть. Хочется скорректировать наше направление, во избежание...
и в узком смысле (стабильность интерфейсов), чтобы не провоцировать лишнюю работу в разработке (изменение клиентов под изменившийся интерфейс)
и в широком (стабильность собранных ранее артефактов), чтобы не провоцировать лишнюю работу по тестированию, дебагингу, документированию старого.
Еще раз спасибо за тонкий, но очень важный момент. Мы пока в более узком смысле OCP рассматривали, но потребность не провоцировать и лишнюю работу по тестированию у нас очень актуальна. Поглядим, как теперь мы могли бы изменить процессы, чтобы и в широком смысле OCP удовлетворять и сколько это будет стоить конкретно в наших условиях. Плюсанул Вам кармочку. Редко такие ценные комментарии на Хабре дают.
Попробовал загуглить, не удалось найти Вашу интерпретацию. Но в Википедии интерпретация другая, как раз на интерфейсы (я их назвал контрактами в сообщении выше). Ссылается википедия на первоисточник – на книгу Object-Oriented Software Construction и её автора Б.Мейера. К сожалению, оригинал не читал, но что-то мне подсказывает, что Википедия сильно не исказила смысл.
И почему вы говорите, что избегаете дублирования кода?
В принципе я уже ответил на этот вопрос. Сам код в единственном экземпляре. Есть лишь 2 интерфейса (2 контракта, 2 метода), которые этот код вызывают. Это чистое следование принципу сегрегации интерфейсов, который: а) самим эпплом активно используется (пруф). б) очень помогает принципу OCP, и в связке с ним дает вот такие существенные экономические эффекты.
с этими правками вы в любом случае нарушаете OCP.
Согласен. Если опереться на приведенное из Википедии определение, то по хорошему мы должны были оставить старый класс нетронутым полностью для старых клиентов и завести новый для новых. Новые клиенты должны были пользоваться новым классом.
Тут, видимо, мы машинально применили принципы рефакторинга К.Бека и объединили 2 класса, не нарушив интерфейсы и не спровоцировав ненужные изменения в клиентах. В итоге у нас не 2 класса (один из которых просто добавляет convinience initializer, реализуя тем самым новый интерфейс для удобства новых клиентов), а 1 - объединивший и старый и новый интерфейсы. Тут мы по сути тоже устранили дублирование машинально. Вы правы, стоит этот момент явно осветить в статье. Спасибо за эту тонкую деталь.
Давайте разбираться. Почему Вы считаете, что принцип нарушен?
Добавление нового свойства и инициализатора не меняет старый контракт – он закрыт для изменения. Но при этом мы в него добавили что-то новое, на что клиенты не обязаны завязываться – мы его расширили. Старых клиентов это не затронуло, новые могут пользоваться расширением. Мы выполнили оба условия принципа. Почему Вы считаете, что не выполнили?
Deprecated как раз поэтому и не используем. Это сломает старый контракт, мы запретим использовать старый метод (closed), которым пользовалась куча клиентов - это спровоцирует их на ненужные изменения и сильно удорожит разработку. Главная цель OCP как раз в том, чтобы минимизировать такие бесполезные изменения. Зачем обязывать клиентов использовать новый контракт, если их и старый устраивает и они получают необходимое им и без нового параметра? Таким образом, мы позволяем старым клиентам работать со старым контрактом, для новых (которым нужен новый параметр и расширенный функционал) мы завели новый. При этом во внутренней реализации наш старый метод будет просто вызывать новый, но с дефолтным значением вновь добавленного параметра. Этим мы избегаем дублирования и повышения стоимости поддержки нашего кода.
Разница в отсутствии типизации обусловлена лишь нюансами реализации. NSNotificationCenter реализован на языке без строгой типизации. Но это абсолютно не значит, что используя этот класс в Swift, Вы не можете получить строгую типизацию в шине данных.
Шаблон изучен на троечку. Применение строгой статической типизации к нестрого типизированным данным тоже. Пересдавать будете?ч
5 лет назад код на C++ без танцев с бубном собирался просто включением соответствующих файлов на C++ в ObjC-проект с изменением их расширения на .mm. Или даже и переименовывать не надо было, не помню уже -- старый стал. Полагаю, что и сейчас этот способ работает.
Этот же совет можно использовать и для работы со Swift ниже версии 5.9.
Ох, уж эти зуммеры, раз и навсегда похоронившие ObjC ^.^
... то надо хорошо подумать. Вьюмодель не должна ничего знать о вьюшках, в том числе и о том, как, какие и в каком порядке менять. Иначе получаете обратную зависимость и невозможность переносить вашу логику, например, на часы или телевизор. Или даже на макбук, Windows-компьютер или Android. Роутинг зависит от вьюшек, поэтому он платформенно зависим и должен быть инкапсулирован на уровне представления, а не опущен ниже.
Добрый день. У нас есть автоматическое формирование списка изменений, да. И даже автоматическое определение новой версии по анализу изменений в интерфейсе (Semver). Надеюсь, мы сможем обо всем этом рассказать в ближайшее время либо на Хабре, либо на Мобиусе.
Совет действенный. Подтверждаю. Как и то, что он часто используется молодыми техлидами. Но больно уж он дорогой для заказчика. Статья все же дешевле. :)
Забавное следствие из OCP, о котором Вы не упомянули.
Если сохраняется стабильность протокола, то не повышается мажорная версия (расширение протокола повышает лишь минорную версию, определение Symver). Отсутствие изменения мажорной версии позволяет в многомодульных системах не менять зависимые модули. Таким образом мы добиваемся исполнения принципа инверсии зависимости за счет исполнения принципа OCP. Стоит нарушить принцип OCP в многомодульных системах (да и не только, просто в многомодульных это нагляднее, чем в связном монолите, но и в последнем эффект тот же) и вы теряете DIP и приобретаете зависимость верхних модулей от нижних. Что в свою очередь ведет к тому, что при изменении нижнего модуля вы вынуждены переписать полприложения.
Таким образом, следование OCP упрощает следование DIP. В статье вы указали на обратное утверждение: "Следование DIP упрощает следование LSP и OCP". Оно тоже верное. А отсюда следует, что начиная правильно использовать любой из этих принципов, вы, желая того или нет, вынуждены будете следовать всем трем. И на самом деле всем 5, ибо они все аналогично упрощают работу друг с другом. В итоге Вы получите синергию всех 5 методов, значительно ускоряющих вашу работу. Эти 5 методов взаимно не позволят вам нарушить ни один из них.
Поэтому не нужно защиты в общий доступ выкладывать. Одно дело про них прочитать человеческим языком, другое – сидеть и ковырять защищенный в несколько слоев бинарь.
А самое прекрасное – что чек-лист теперь уже можно и пытаться автоматизировать, шаг за шагом. Автоматизация "с нуля" всего процесса была бы неподъемной задачей, просто потому, что процесс не был формализован и описан. Теперь он формализован, описан, закреплен "на бумаге" – чего бы не взяться за автоматизацию с уже созданным на 80% по пунктам планом? :)
Молодцы! Действительно полезная вещь!
Подскажите, пожалуйста, а зачем в iOS реализовывать свои запросы с помощью curl? В инструкции по КриптоПРО нет ничего про это. Такое ощущение, что библиотека КриптоПРО подменяет системный уровень, аналогично тому, как это устроено на Android.
И ни слова про обратные стороны сего забавного когнитивного искажения. А они в АйТи на каждом шагу. Проявляются примерно так: "Смотрите я тут гениальный роутинг для АйОС собрал! Вот вам инструкция на 145 листах, первые 48 листов описывают инструменты, которые вам нужно подготовить, чтобы этот роутинг у себя собрать. Все просто! И естественно, гениально!"
Вот бы такую же подробную и полезную статью, а что делать-то, если эффект Икеи проявляется в обратную сторону: товарищ сочинил, а теперь из него этот табурет велосипедом не выколотить, или наоборот.
Ознакомился, спасибо. Полезно.
Подскажите, а подобного обзора для современной навигации на SUI случайно нет?
По теме статьи. У Apple в UIKit есть реализация шаблона "Команда": селекторы, таргеты, responder chain, first responder. Цель шаблона в том, чтобы доставлять команды (в том числе на навигацию) от источника до исполнителя, т.е.от условной кнопки для пользователя до условного контейнер-контроллера (например, UINavigationController), который уже и осуществит навигацию (в данном примере - push).
По сути, это системная реализация вашего Навигатора.
Ну, а коли он у вас все равно работает с UIKit, то не пробовали ли вы заменить Вашу реализацию Навигатора на системную через responder chain, first responder, таргеты и селекторы?
5 лет прошло. Интересно, как выглядит в итоге Ваша навигация. Хотим и уже попробовали навигацию через UIKit (Свифт у нас старенький по объективным в РФ событиям, использовать последние версии не можем), видим в ней потенциал, но не хотим реализовывать Навигатор, т.к.кажется, что в системе это уже все есть. Хочется скорректировать наше направление, во избежание...
Добрый день. Устранили? Или это все же оказалась "фича"?
Ну, т.е. OCP можно использовать:
и в узком смысле (стабильность интерфейсов), чтобы не провоцировать лишнюю работу в разработке (изменение клиентов под изменившийся интерфейс)
и в широком (стабильность собранных ранее артефактов), чтобы не провоцировать лишнюю работу по тестированию, дебагингу, документированию старого.
Еще раз спасибо за тонкий, но очень важный момент. Мы пока в более узком смысле OCP рассматривали, но потребность не провоцировать и лишнюю работу по тестированию у нас очень актуальна. Поглядим, как теперь мы могли бы изменить процессы, чтобы и в широком смысле OCP удовлетворять и сколько это будет стоить конкретно в наших условиях. Плюсанул Вам кармочку. Редко такие ценные комментарии на Хабре дают.
Попробовал загуглить, не удалось найти Вашу интерпретацию. Но в Википедии интерпретация другая, как раз на интерфейсы (я их назвал контрактами в сообщении выше). Ссылается википедия на первоисточник – на книгу Object-Oriented Software Construction и её автора Б.Мейера. К сожалению, оригинал не читал, но что-то мне подсказывает, что Википедия сильно не исказила смысл.
В принципе я уже ответил на этот вопрос. Сам код в единственном экземпляре. Есть лишь 2 интерфейса (2 контракта, 2 метода), которые этот код вызывают. Это чистое следование принципу сегрегации интерфейсов, который: а) самим эпплом активно используется (пруф). б) очень помогает принципу OCP, и в связке с ним дает вот такие существенные экономические эффекты.
Согласен. Если опереться на приведенное из Википедии определение, то по хорошему мы должны были оставить старый класс нетронутым полностью для старых клиентов и завести новый для новых. Новые клиенты должны были пользоваться новым классом.
Тут, видимо, мы машинально применили принципы рефакторинга К.Бека и объединили 2 класса, не нарушив интерфейсы и не спровоцировав ненужные изменения в клиентах. В итоге у нас не 2 класса (один из которых просто добавляет convinience initializer, реализуя тем самым новый интерфейс для удобства новых клиентов), а 1 - объединивший и старый и новый интерфейсы. Тут мы по сути тоже устранили дублирование машинально. Вы правы, стоит этот момент явно осветить в статье. Спасибо за эту тонкую деталь.
Давайте разбираться. Почему Вы считаете, что принцип нарушен?
Добавление нового свойства и инициализатора не меняет старый контракт – он закрыт для изменения. Но при этом мы в него добавили что-то новое, на что клиенты не обязаны завязываться – мы его расширили. Старых клиентов это не затронуло, новые могут пользоваться расширением. Мы выполнили оба условия принципа. Почему Вы считаете, что не выполнили?
Deprecated как раз поэтому и не используем. Это сломает старый контракт, мы запретим использовать старый метод (closed), которым пользовалась куча клиентов - это спровоцирует их на ненужные изменения и сильно удорожит разработку. Главная цель OCP как раз в том, чтобы минимизировать такие бесполезные изменения. Зачем обязывать клиентов использовать новый контракт, если их и старый устраивает и они получают необходимое им и без нового параметра? Таким образом, мы позволяем старым клиентам работать со старым контрактом, для новых (которым нужен новый параметр и расширенный функционал) мы завели новый. При этом во внутренней реализации наш старый метод будет просто вызывать новый, но с дефолтным значением вновь добавленного параметра. Этим мы избегаем дублирования и повышения стоимости поддержки нашего кода.
Можно ли доработки для кастомизации пути снэпшотов предложить автору и сделать пул-реквест с ними в оригинальный репозиторий?
Как и для чего используется версионирование скриншотов? Что происходит со старыми версиями? Какие могут быть сценарии использования?
Но ведь
NSNotificationCenter
– это ничто иное, как шаблон "Шина данных" ("Event Bus").Разница в отсутствии типизации обусловлена лишь нюансами реализации.
NSNotificationCenter
реализован на языке без строгой типизации. Но это абсолютно не значит, что используя этот класс в Swift, Вы не можете получить строгую типизацию в шине данных.Шаблон изучен на троечку. Применение строгой статической типизации к нестрого типизированным данным тоже. Пересдавать будете?ч
5 лет назад код на C++ без танцев с бубном собирался просто включением соответствующих файлов на C++ в ObjC-проект с изменением их расширения на
.mm
. Или даже и переименовывать не надо было, не помню уже -- старый стал. Полагаю, что и сейчас этот способ работает.Этот же совет можно использовать и для работы со Swift ниже версии 5.9.
Ох, уж эти зуммеры, раз и навсегда похоронившие ObjC ^.^
И почему модульность нельзя реализовать исключительно на SPM?
... то надо хорошо подумать. Вьюмодель не должна ничего знать о вьюшках, в том числе и о том, как, какие и в каком порядке менять. Иначе получаете обратную зависимость и невозможность переносить вашу логику, например, на часы или телевизор. Или даже на макбук, Windows-компьютер или Android. Роутинг зависит от вьюшек, поэтому он платформенно зависим и должен быть инкапсулирован на уровне представления, а не опущен ниже.
Добрый день. У нас есть автоматическое формирование списка изменений, да. И даже автоматическое определение новой версии по анализу изменений в интерфейсе (Semver). Надеюсь, мы сможем обо всем этом рассказать в ближайшее время либо на Хабре, либо на Мобиусе.
Совет действенный. Подтверждаю. Как и то, что он часто используется молодыми техлидами. Но больно уж он дорогой для заказчика. Статья все же дешевле. :)
модульные тесты = юнит-тесты
Забавное следствие из OCP, о котором Вы не упомянули.
Если сохраняется стабильность протокола, то не повышается мажорная версия (расширение протокола повышает лишь минорную версию, определение Symver). Отсутствие изменения мажорной версии позволяет в многомодульных системах не менять зависимые модули. Таким образом мы добиваемся исполнения принципа инверсии зависимости за счет исполнения принципа OCP. Стоит нарушить принцип OCP в многомодульных системах (да и не только, просто в многомодульных это нагляднее, чем в связном монолите, но и в последнем эффект тот же) и вы теряете DIP и приобретаете зависимость верхних модулей от нижних. Что в свою очередь ведет к тому, что при изменении нижнего модуля вы вынуждены переписать полприложения.
Таким образом, следование OCP упрощает следование DIP. В статье вы указали на обратное утверждение: "Следование DIP упрощает следование LSP и OCP". Оно тоже верное. А отсюда следует, что начиная правильно использовать любой из этих принципов, вы, желая того или нет, вынуждены будете следовать всем трем. И на самом деле всем 5, ибо они все аналогично упрощают работу друг с другом. В итоге Вы получите синергию всех 5 методов, значительно ускоряющих вашу работу. Эти 5 методов взаимно не позволят вам нарушить ни один из них.
Такие забавные пирожки у тёти Клавы :)
За статью – респект. Примеры хорошие.
Поэтому не нужно защиты в общий доступ выкладывать. Одно дело про них прочитать человеческим языком, другое – сидеть и ковырять защищенный в несколько слоев бинарь.
Что будет с тестом, если функция
loader.loadMovies()
подвиснет?