Pull to refresh

Comments 47

опять же и опять же — на небольших приложениях любая архитектура себя отлично проявляет, но в крупных enterprise проектах ломается об многие процессы разработки…
Не совсем согласен, я больше года работал на проекте, который писали до меня больше 1,5 года 5 разных разных разработчиков и в разное время. 50+ экранов. Там не соблюдается даже элементарный MVC.

По-вашему, лучше так все и оставить? Или хотя бы пытаться перевести это на нормальную архитектуру, с разделением слоев? Я сам писал долгое время приложения, как попало. И они работают и приносят мне небольшой доход. Но сейчас понимаю, что их тяжело поддерживать из-за того, что криво написаны.
«Лишь ситхи все возводят в абсолют» (с)

Между VIPER и отсутствием архитектуры есть тот же MVC, или MVP, или MVVM. И на некоторых проектах они могут быть куда удобнее VIPER.
VIPER можно назвать MVPI: Model (или те же сервисы), View понятно, Presenter и Interactor. Убери отсюда Interactor, получится MVP. Сделай из ViewController-а контроллер, а не View, получится MVC. Лично мне понравился VIPER хоть для маленького, хоть для большого приложения. Если вам по душе другая архитектура проблем нет. Статья же про рабочий пример на VIPER, а не про то, что это лучшая архитектура на свете.

Почему Вы решили, что VIPER это не MVC? Визуальное представление как было, так и осталось. Контроллер как был, так и остался. В конце концов, без модели вы никак не обойдетесь попросту. Вам не кажется что проблема не в MVC изначально? Ведь VIPER и есть MVC, а точнее один из вариантов реализации MVC.

Как понять, что контроллер, как был, так и остался? В VIPER ViewController это не контроллер, а View. В MVC от эппл это контроллер. Визуальное представление в VIPER разделяется на презентер с логикой отображения и самим отображением во View/ViewController. В VIPER больше слоев и ролей. Я в статье привел ссылку, где подробней сравниваются разные архитектуры.

Что мешает выделить логику из «эпловского view controller-a» в отдельные сущности? Вы сильно привязались к самим названиям, а речь в первую очередь о роли класса. Чем презентер в VIPER не контроллер описанный в MVC? Он как раз является посредником между данными и отображением, вполне себе контроллер.

Ну так VIPER и выделяет слои из вьюконтроллера в презентер и интерактор. Названия тут не важны, я про это тоже указывал в статье.

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

Так ругают то, что расхлябанность в MVC приводит к гигантским вьюконтроллерам. Расскажите, что можно писать во вьюконтроллере, а что нет?

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

SRP и является причиной появления этих слоев в VIPER. Если у вас полностью статичный вьюконтроллер, можете удалить ненужные слои для этого модуля. Каждый модуль может иметь разное количество файлов и классов. Часто в сложных вьюконтроллерах их даже больше, чем перечисленные в VIPER.
но сложные VC можно так же поделить на множество маленьких простых VC (с помощью чайдов), сложные сервисы сложить и закрыть простыми фасадами, анимацию вынести в классы Animator, реализации протоколов в отдельные extension .swift файлы, за навигацию будет отвечать класс Координатор, и получится ViewController толщиной до 300 строк со всеми отступами по код стайл.

ИМХО, Вайпер интересно смотрелся в мире Objc с обязательным .h/.m разделением, но в Swift можно делать и проще и удобнее и лаконичнее.
Ну так я и не говорю, что разделение на слои и модули можно сделать только с помощью VIPER. Если у вас соблюдаются принципы SOLID, Clean Architecture и остального, то почему нет.

VIPER – один из способов решить проблему Massive VC и «лапшекода» и я попытался показать это на примере. Напишете свою реализацию архитектуры с примером. Я бы с удовольствием посмотрел.
Так не будет никакой проблемы «массивного вьюконтроллера» если нормально прописывать бизнес логику в моделях, а логику отображения во вьюхах. Вместо преждевременного усложнения кода слоями абстракций, нужно просто не забывать периодически гасить «технический долг».
Можете привести пример кода, как это реализовывается на практике. Модели – это логика модулей/вьюконтроллеров или это сервисы/хелперы всего приложения? Вьюконтроллер сам хранит в себе данные и, получается, тогда выполняет 2 роли: вьюшки и контроллера?

Пример с пылу-жару: функционал в чатах hold-to-talk. Можно написать в контроллере это и ругать за «массивность», можно сделать то же самое в размазав логику по презентеру и интерактору, а можно выделить модель которая будет описывать поведение и использовать ее, а то что касается UI части, как и писали выше, описать в отдельном controllere и сделать его чайлдом. Сразу и тестируемость, и распределние по отвественности, и отличная переиспользуемость кода.

По идее — у тебя ViewController будет выполнять функцию View слоя, и только он делает import UIKit.
Cервисы должны быть чистыми функциями первого порядка — не могут хранить никаких данных.
Для данных у тебя есть отдельный слой Model, модели могут быть пассивными — это старый вариант, и активными — этот путь как раз и описывается в MVC по гайдлайну Apple. Один из хороших примеров M слоя — это CoreData, просто закрой ее за сервис, возвращающий Plain Object.
А логика работы конкретного вьюконтроллера где описывается?
логику работы можно описать в слое Controller, который знает о фасадах с бизнес логикой, и возвращает новое состояние во View слой (в данном случае выступает XXXViewController), output protocol того, что будет рисовать View.

Мне нравится из концепции REDUX возвращать один метод func update(_ state: MyState) и через switch - case обновлять весь UI под новые данные.
Раскрою подробнее свою мысль.
Когда говорят про VIPER, то почти первым же предложением добавляют — тестируемость и модульность. Но странным образом, когда берутся за реализацию, то unit тесты сразу отпадают, их просто не хотят писать ни по TDD, ни по BDD техникам. Где то была обширная статейка про статистику количество багов на проект, и процент Crash Free от смены богомерского MVC на правильный VIPER не дал перфоманса, даже малейшего.

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

И чисто из технических подходов — это двунаправленная связь через протоколы легко ломает бизнес логику и приводит к взаимоисключающим состояниям, Presenter раздувается до невообразимых размеров (Massive View Controller перекочевал в слой Presenter ?), а Router вообще плохо ложится на UIKit.

Описывать проблемы использования VIPER в контексте IOS можно бесконечно…

Другими словами — VIPER это один из видов MV(X) архитектуры, но с более дробленном контроллером.
Лень писать тесты – это не проблема VIPER. У меня в кривой архитектуре с несоблюдением MVC даже Crash Free больше 99.5%. Проблема в поддержке такого кода.

На модули же разделяют не для того, чтобы потом сделать из них маленькие фреймворки и переиспользовать. А больше для того, чтобы легче было тестировать и отлаживать каждый слой в модуле. Проблема с UI? Значит ищем во View или презентере. И т.д. Знаем, в каком слое что искать.

По поводу «протоколы легко ломает бизнес логику и приводит к взаимоисключающим состояниям» не совсем понял. Довольно абстрактно без конкретного примера.

Проблему с роутером я решил в своей реализации. Она использует все родные механизмы. Но зато логику перехода смотрим в роутере. И не надо все это искать во вьюконтроллере.

По поводу дробления контроллера я абсолютно согласен. Про это и статья. Как разбить Massive View Controller.
По поводу «протоколы легко ломает бизнес логику и приводит к взаимоисключающим состояниям» элементарный пример — на экране показывается AlertView или любое модальное окно с какой-либо информацией как callback одного из сервисов, а другой сервис уже хочет дернуть навигацию для перехода в следующий экран. В любом однонаправленном принципе разработки — это будет просто новый State модуля, а двунаправленной архитектуре придется разруливать.

Буду очень признателен (и не я один), если продемонстрируете свой пример использования Роутера, так как удачный и функциональный способ будет очень полезен для сообщества.

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

Наиболее близкий вариант VIPER-а какой я использую у себя на проекте :)

Massive View Controller это давно устаревший анекдот, хватит уже.

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

В том-то и дело, что имхо не стоит заражать интернет этой болезнью. Неопытный разработчик увидит все эти навороты, решит, что раз непонятно, значит круто, и будет дальше распространять эпидемию. А через месяц такую же статью напишет, "потому что massive view controller". В итоге куча каких-то хрен пойми зачем слоёв, протоколы, и view.setInputValue(with:...) вместо view.inputValue = ...

view.setInputValue(with:...) потому что у inputValue нет геттера и вообще нет такого свойства. И это метод, который обновит UI компонент. А зачем эти хрен пойми какие слои в статье и рассказывается.

Или вот extension на UITextField. Там ни одна строчка кода не относится к UITextField, это просто манипуляции над строками. После такого ни на какие вайперы даже смотреть не хочется.

Так это в вайперу вообще отношения не имеет :-)
Ну конечно же имеет. Как можно верить чуваку, который рассказывает про какую-то навороченную штуку, которая якобы помогает как-то улучшить код и жизнь, если в деталях есть такие простые ошибки. Ну или может не ошибки, но я бы назвал это отсутствием вкуса.
view?.prop1 = val1
view?.prop2 = val2
// хороший программист уже распаковал бы view
view?.setInputValue(with: inputValue) // зачем with:?

func urlButtonClicked(with urlString: String?)
// вместо
func urlButtonClicked(with url: URL)
// и вместо какого-нибудь
func handle(url: URL)


В `getAllCurrencies` если не дай бог придёт не dictionary, то HUD никогда не скроется. и в файле `MainInteractor.swift` вообще сложно понять кто и когда показывает и скрывает HUD, там явно несбалансированные вызовы.

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

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

Чистота кода и чистота архитектуры – разные вещи. Я про архитектуру больше хотел сказать и заранее вот это написал в статье:
Я не понимаю барьера между «чистым кодом» и «чистой архитектурой». Что такое архитектура? Это как, я не знаю, ссать в подъезде, но строго в один и тот же угол. Но ни в коем случае не в другой.

Я не пытаюсь оскорблять, скорее хочу донести мысль о том, что обратив внимание на те «мелочи», которые по сравнению с АРХИТЕКТУРОЙ кажутся уже незначительными, можно сильно улучшить качество приложения. Как кода – так и самого приложения, как следствие. Обратить внимание на поток данных, сделать его простым и понятным. Использовать подходящие структуры данных. Я уже обращал внимание на `urlString?` вместо `URL`. Зачем N слоям приложения мучаться с optional строкой, когда там на самом деле URL, ещё и потенциально невалидный? Зачем добавлять код в extension UI элемента, если код только лишь преобразует одни данные в другие? Самое место сделать отдельную функцию, которая принимает String и возвращает String – это уже даст гораздо больше для SRP, тестируемости и прочего XYZ. Начнёшь с маленького, а там и большое не захочется портить.
Чистота архитектура – это то, где вы будете писать ту или иную часть логики. ГДЕ, а не как. Чистота код – это то, КАК конкретно будет писаться сам код. Это то, к чему вы начали придираться.

Это как, я не знаю, ссать в подъезде, но строго в один и тот же угол. Но ни в коем случае не в другой.

В данном примере по правильной и чистой архитектуре вообще нельзя ссать в подъезде, это надо делать в туалете. А как конкретно и чем вы ссыте – это правильные конструкции кода или чистый код.

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

Приведите свой пример небольшого приложения для сравнения. Поделитесь с другими, как писать идеальный негрязненький код без никакой архитектуры.
Как бы все сводилось к тому, что плохой дизайн архитектуры может иметь право на существование при хорошем дизайне кода, но скрывать плохой дизайн кода (читай размазывать не лучшие решения в разные слои) очень быстро приводит к деградации любой архитектуры.
Вот и промолчал бы лучше. Я уже сказал, почему использовал view?.setInputValue вместо свойства. with: — это просто частность, я же не учу именно так писать.

func urlButtonClicked(with urlString: String?) тут String потому что вьюконтроллер не должен заниматься преобразованием в URL, это делается в интеракторе, а View-компонент знает только строки.

func handle(url: URL) нельзя писать во вьюконтроллере. Это уже логика заточенная будет. Мы должны только сообщать презентору, что нажалась кнопка.

В getAllCurrencies идет проверка на dict внутри saveAllCurrencies есть completion(CurrencyError(description: «Currencies' data format is wrong»)). Есть, конечно, опасность, что ни error ни ответа не придет, но там дописать один else только. Но это рабочий пример, а не приложение для продакшна.

Презентер ничего не знает о UIKit, он не знает об UIButton, UILabel и никаких других визуальных компонентах. Это очень важно.

и далее
presenter.currencyPickerView = viewController.currencyPickerView
Вы, видимо, проглядели это:
weak var currencyPickerView: CurrencyPickerViewProtocol?

Презентер знает только это:
protocol CurrencyPickerViewProtocol: class {
    var arrayCurrencyNames: [String] { set get }
    var title: String { set get }
    var selectedCurrencyIndex: Int? { set get }
    func reload()
}
хм, я думал что ни сам презентер, ни протоколы которые он реализует не должны быть зависимыми от UIKit…
Они и не зависят. presenter.currencyPickerView и presenter.view в презенторе известны лишь как некие объекты, соответствующие протоколам, о которых знает презентор. Сами currencyPickerView и view уже зависят от UIKit. Но в тестах вам необязательно создавать будет реальные
CurrencyPickerView: UIView, UIPickerViewDataSource, UIPickerViewDelegate, CurrencyPickerViewProtocol и MainViewController: UIViewController, MainViewProtocol, UITextFieldDelegate

Их можно заменить моками, соответствующим протоколам.
Всё понял. Просто в файле MainPresenter.swift у вас есть import UIKit, что меня и смутило. Видимо в процессе разработки забыли убрать
Все мои попытки использовать VIPER закончились не так хорошо, как хотелось бы. Основная проблема — неопределённая область ответственности за выполнение переходов. Зачастую router не имеет нужных данных, чтобы принять решение.
Sign up to leave a comment.

Articles