Логика на INotifyProperyChanged

Сложность 200-300

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

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

Я уже не раз писал и говорил насколько мне не нравится явная реализация INPC при реализации MVVM для WPF, но недавнее открытие в проекте коллег не смогло меня оставить равнодушным. Честно сказать, я не представлял, что таким образом можно построить логику приложения, но что есть, то есть. Итак, не будем более затягивать вступление и обратим взор на код.

Начнем с того, что INPC реализован самым примитивным методом

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

Уже много раз говорилось о том, что реализация свойств таким образом создает огромное количество проблем. Самое очевидное – разрастание когда и сложность рассмотрения его, 7 строк даже в «египетской» записи вместо одной. Далее идут более существенные проблемы с рефакторингом. Конечно R# может искать и строки для рефакторинга, но это может создавать больше проблем, чем решать. Далее перечислять не буду, можете поискать сами, но это только верхушка айсберга.

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

Далее идет такой вот код

И где-то еще в коде есть такие моменты:

и еще

При этом, как вы наверно можете догадаться, отписка нигде не происходит.

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

Разберем метод OnFilePropertyChanged(). Он должен выполнятся только если изменилось свойство Checked и что-то еще не равно null. Этот обработчик можно приписать куда угодно и соответственно, чем больше у нас свойств в модели, тем больше вызовов будет этого метода и каждый раз будет происходить проверка, то ли это свойство. Представили себе масштаб бедствия уже на этом этапе? Можете представить себе, что есть 5-7 моделей, у которых 6-10 свойств. По мере того как будут создаваться модели и будет осуществляться подписка, будет все больше вызовов, может быть 40-50 вызовов методов и только один выполнится по результатам проверки всех условий. В зависимости от того, как создаются модели может быть все совсем плохо или же просто плохо.

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

Следующая причина по которой так делать нельзя, это сам способ проверки события – это СТРОКА! Ранее я уже упоминал, чем плохо использовать строки, тут ровно та же история. При рефакторинге очень легко все сломать. Очень тяжело искать использования свойства, так как при стандартных средствах ищутся использования объектов по объектной модели приложения, а в таком виде упоминание свойства не будет найдено. Понадобится полнотекстовый поиск и надо будет фильтровать все другие значения, которые могут совпадать или частично входить в искомое название свойства. Для этого примера можно придумать множество развитий ситуации. Может быть у меня еще будут свойства в духе IsChecked (и не одно!), OrderChecked, CheckedAccounts, etc. И всё это надо фильтровать при случае вручную. Лютый ад.

Еще одной не менее важной причиной, чем все предыдущие, является невозможность быстрой навигации по коду. Даже больше, невозможность понять быстро к чему это вообще относится, у какой модели проверяется свойство. В методе OnFilePropertyChanged() еще можно догадаться, что свойство относиться к модели MassLoadingFileNameViewModel по следующим строкам в методе. Но если обратить взор на метод OnStoragePropertyChanged(), то совершенно непонятно откуда идет свойство Checked. При разборе и чтении кода это невыразимо раздражает, когда надо приложить значительные усилия просто чтобы найти откуда растут ноги у метода.

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

 

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

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

 

 

Hard’n’Heavy!

 

 

Оставить комментарий