… a common pattern in software engineering research is the development of system-building techniques, such as object-oriented design, which are strongly advocated in the absence of evidence - K.N. Whitley, “Visual Programming Languages and the Empirical Evidence For and Against,” J. Visual Languages and Computing, vol. 8, pp. 109-142, 1997.

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

Сразу оговоримся, что проблематика паттернов связана с ООП и в большей степени с такими языками как Java и C++. Большинство исследований, о которых пойдет речь дальше, рассматривает вопросы применения паттернов именно в этих языках. Однако это не означает, что, например, в JS паттерны будут работать как-то по другому. Вторая оговорка касается предмета нашего интереса. Несмотря на то, что существуют сотни вариаций паттернов проектирования, наибольшей интерес прикован к GoF. Существуют также и ограничения исследований, о которых мы поговорим в самом конце.

Линейка для паттернов

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

Но как нам оценить качество кода? Одним из самых популярных показателей является его поддерживаемость. Осторожно скажем, что если паттерны проектирования и приносят пользу, то только тогда, когда код развивается. В общем виде поддерживаемость кода представляет собой некое качество, тесно связанное с объемом усилий, предпринимаемых для его модификации. Абстрактно, не правда ли? Хотя именно так написано в стандарте ISO-9126. 

Давайте посмотрим на то, как конкретизируют это понятие в исследованиях. Wedyan и Abufakher [2020] в своем обзоре весьма детально разложили поддерживаемость кода на ряд составляющих.

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

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

Научные исследования

Теперь, когда мы примерно представляем себе то, как оценить качество кода, можно попытаться понять, как именно GoF паттерны влияют на поддерживаемость кода. Одна из первых попыток была предпринята в 2012 году Zhang и Budgen. В своей работе авторы отмечали, что несмотря на популярность темы паттернов в разработке, существовало достаточно небольшое количество эмпирических исследований, многие из которых лишь косвенно изучали применение паттернов в реальном коде. После анализа 219 публикаций ученые пришли к выводу о том, существуют лишь косвенные подтверждения того, что использование паттернов положительно влияет на поддерживаемость кода. Наряду с этим невозможно аргументировано сказать, когда именно необходимо использовать тот или иной паттерн в коде. 

Год спустя появилось обзорное исследование [Ampatzoglou, Charalampidou, Stamelos, 2013] 120 публикаций, посвященных GoF паттернам. Оно и по сей день является наиболее полноценным и цитируемым по данной проблематике. И в нем проявились весьма интересные и неоднозначные результаты.

Так, большинство GoF паттернов положительно влияют на некоторые аспекты поддерживаемости кода. Особняком стоит стабильность кода. На нее подавляющее большинство паттернов влияет негативно. Однако тут стоит разделять стабильность всего кода и стабильность паттернов. Нам известно, что использование GoF паттернов на примере 65 000 лежащих в открытом доступе Java классов положительно влияет на их стабильность [Ampatzoglou et. al, 2015]. Получается, что паттерны сами по себе стабильны, но за эту стабильность расплачивается окружение.

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

Вместо заключения

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

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

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

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

В-третьих, исследования паттернов в основном проводятся на основе опросов профессионалов. Использование объективных метрик весьма незначительно, что добавляет долю субъективности в конечные результаты. 

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

Что почитать

C. Zhang and D. Budgen, "What Do We Know about the Effectiveness of Software Design Patterns?," in IEEE Transactions on Software Engineering, vol. 38, no. 5, pp. 1213-1231, Sept.-Oct. 2012, doi: 10.1109/TSE.2011.79.

Apostolos Ampatzoglou, Sofia Charalampidou, and Ioannis Stamelos. 2013. Research state of the art on GoF design patterns: A mapping study. J. Syst. Softw. 86, 7 (July, 2013), 1945–1964. https://doi.org/10.1016/j.jss.2013.03.063

A. Ampatzoglou, A. Chatzigeorgiou, S. Charalampidou and P. Avgeriou, "The Effect of GoF Design Patterns on Stability: A Case Study," in IEEE Transactions on Software Engineering, vol. 41, no. 8, pp. 781-802, 1 Aug. 2015, doi: 10.1109/TSE.2015.2414917.

Wedyan, Fadi and Somia Abufakher. “Impact of design patterns on software quality: a systematic literature review.” IET Softw. 14 (2020): 1-17.

Комментарии (19)


  1. onyxmaster
    00.00.0000 00:00
    +18

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


    1. Gorthauer87
      00.00.0000 00:00
      +4

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


  1. Portnov
    00.00.0000 00:00
    +13

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

    Но чего-то большего от них ожидать, по-моему, не надо.


  1. gandjustas
    00.00.0000 00:00
    +5

    К сожалению когда в программировании возникает какой-то тренд, то все начинают эту трендовую концепцию пихать по делу и без.

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

    Я думаю к каждому применению паттрена надо задавать вопрос (возможно себе) "какую проблему он решает?", без внятного ответа паттерн скорее всего не нужен.

    Кстати о правильном применении паттренов есть книга Дж.Криевски "Refactoring to Patterns". Без нее изучение GoF может нанести вред.


    1. domix32
      00.00.0000 00:00

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


  1. NightShad0w
    00.00.0000 00:00
    +1

    Паттерны документировались на основе имеющейся кодовой базы. На тот момент наиболее популярны были комплексные программные решения с годами разработки и поддержки, оставаясь в рамках выбранного функционала. Процессы разработки таких систем требовали и требуют как квалификации разработчиков, так и эффективности общения. И эти задачи паттерны прекрасно продолжают выполнять и поныне.
    НО! Современная разработка это по большей части собрать в красивый веб-интерфейс 100500 пакетов и библиотек, набрать аудиторию, продать компанию большому игроку на рынке и уйти в закат на Мальдивы.
    В контексте таких продуктов ни знание, ни использование паттернов и прочих практик не имеет смысла для бизнеса.
    Искать применение и целесообразность паттернам надо в фундаментальных местах, а не в CRUD бэкэнде. Банально, Command ранее реализовался паттерном из книги, и работал локально, и позволял добавлять новые команды в продукты масштаба MS Office. В Веб-ориентированных продуктах - давайте просто в базу положим запись и все. А если надо откатить действие, положим новую запись отката и фронтенд просто отобразит последнее состояние из базы. Так быстрее и дешевле на короткой дистанции.


  1. khaa
    00.00.0000 00:00

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


  1. RA_ZeroTech
    00.00.0000 00:00
    +3

    Есть ли польза от GoF-паттернов?

    Да, есть.


    1. kickstarter
      00.00.0000 00:00
      +1

      Неправда.


  1. panzerfaust
    00.00.0000 00:00

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

    "Оказалось"... Чтобы сделать такой вывод, исследование не нужно. Топор тоже гораздо проще бензопилы в устройстве и обслуживании. А полуторка АМО проще, чем современный дизельный тягач. А отвар из ивовой коры проще, чем синтез и производство аспирина.

    Давайте что ли напишем статью "есть ли польза от бензопил на лесоповале"? Не, таких статей мы почему-то не пишем. Всем как-то интуитивно понятно, что чем больше плюшек приносит технология, тем она замороченнее. Тем более парадоксально, что когда программистам предлагают писать более замороченный, но более надежный код (кстати, писать по готовым рецептам), то сразу поднимается дискуссия "а надо ли", "а не слишком ли сложно", "мой брат писал по паттернам и заболел".


    1. UnknownUser
      00.00.0000 00:00

      "Мы провели серьёзное научное исследование, раздав топоры и бензопилы разным группам обезьян (предварительно научив их включать бензопилу). Выяснилось, что уровень травматизма при использовании бензопил очень высок, поэтому рекомендуем везде и всегда использовать топоры".


  1. dim2r
    00.00.0000 00:00

    По мне только запутывает технарей. Начиная с самого названия, которое ничего не обещает
    "бандa четырёх". И кончая отсутвием деталей, например бинарного интерфейса, который например предусмотрен в более проработанной модели COM.


    1. domix32
      00.00.0000 00:00

      Только не говорите, что синглтон c абстрактной фабрикой путаете из-за GoF? Да и COM это маленько не про паттерны.


  1. funca
    00.00.0000 00:00
    +2

    GoF в свое время предложили классную идею - документировать типовые решения типовых проблем в дизайне ПО. В качестве иллюстраций они взяли примеры из своего опыта разработки текстового редактора.

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

    К счастью, у GoF появились последователи, применившие подход к своим доменам в книжках: enterprise integration patterns, reactive design patterns, service design patterns, и т.п. - подальше от программирования и ближе к design или архитектуре, где они и должны быть.

    В программировании же их место постепенно отгрызает математика (FP, Category theory, ADT и т.п.), дающая более строгии и надёжные гарантии в плане композиции и валидации решений.


    1. panzerfaust
      00.00.0000 00:00
      +1

      спрашивать на собеседованиях и преподавать в ВУЗах

      Мне так никто и не может объяснить, что в этом плохого.

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


      1. gandjustas
        00.00.0000 00:00
        +1

        А если вместо pub\sub скажет что это Observer? Или скажет что надо использовать список коллбеков или events?


        1. panzerfaust
          00.00.0000 00:00

          Ну пусть говорит. У нас не ЕГЭ и не тестирование. Вопрос открытый, все ответы принимаются.


      1. funca
        00.00.0000 00:00
        +1

        Вопрос: что криминального, если я хочу на собесе услышать суть данного паттерна и его применение в реальных условиях?

        GoF оставили за скобками как минимум две важные темы: нефункциональные требования и композицию паттернов между собой.

        NFR принципиально влияют на способ реализации. В зависимости от требований к надёжности, гарантиям доставки, задержкам, throughput, и т.п. ваш pub-sub может быть, как вы заметили и шматок лапшекода внутри одного метода, и DSL с кодогенератором, и ООП с классами как у GoF, и брокер сообщений на кафках в нескольких датацентрах по всему миру. Т.е. без дополнительных вводных делать можно как угодно. И какой тогда правильный ответ?

        Что касается композиции, то те же https://typelevel.org/cats/typeclasses.html дают кусочки решения и конкретные правила что, с чем и как соединять, имея в бэкграунде вполне конкретную математику (у вас нет скалы, перепишите на джаваскрипте, будет плюс-минус то же самое), с возможностью статически проверить решение. GoF в этом смысле как художественная литература, дающая лирические советы как делать правильно, но без каких-либо намеков почему именно так правильно и как это на практике проверять.


  1. 1CUnlimited
    00.00.0000 00:00

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

    Какой паттерн вам нужно применить? Facade? Если читать литературу как минимум несколько подойдут. При этом Pattern не содержит знаний о BestPractice реализации такого решения. Вот найти бы хороший источник где собираются BestPractice по архитектуре это было бы ценно. Чтобы не наступать на одни и теже грабли