Как стать автором
Обновить

Комментарии 80

Мы в похожем творческом стиле пробовали сделать учебные материалы по шаблонам. Плюс задачки с шутками-прибаутками. Если интересно — могу выложить на какой-нибудь файлообменник и скинуть сюда ссылку. Скажите только куда принято выкладывать файлы на хабре (habratorage только для картинок, как я понимаю).
github?
Понял. Буду знать. Вот ссылочка. Там вордовский файлик и exe-файл, собранный из флеша (интерактивный пример). Делал это ещё на четвёртом курсе, перечитал сейчас — конечно, глупости есть. Можете глянуть если любопытно — просто чтобы стиль был ясен.
О, здорово, посмотрю.
НЛО прилетело и опубликовало эту надпись здесь
Пожалуйста, не говорите за всех. Лучше объясните, зачем уходить обратно.
Все уже отходят назад от классов и интерфейсов в купе с зависимостями обратно к объектам и массивам

Можете пример привести? Просто интересно стало, кто эти «все»?
Я думаю, имеются в виду всякие модные штуки, например, из Go и Rust — примеси и собирание классов из кусочков. Могу ошибаться, но, как по мне, это никак не отменяет возможность конструирования архитектуры на основе старых добрых шаблонов… Больше того — сами по себе примеси могут быть сконструированы в рамках объектного программирования, о чём написано на википедии — там вообще пример примеси на основе шаблонов С++.
Наверное. Хотя, очень странно было услышать, что «все отходят» — означает именно появление Rust и Go, языков, каждый из которых уже занял определенную нишу.
Все уже отходят назад от классов и интерфейсов в купе с зависимостями обратно к объектам и массивам, а вы этому школьников учите.

Кто «все»?

Так делать нездорово, а даже вредно.

Аргументируйте.
НЛО прилетело и опубликовало эту надпись здесь
Все — это просветленные.

Конкретные примеры?

Те, кто понимают разницу между массивом, объектом, контентом, контейнером и контекстом.

Эту разницу понимает любой образованный программист, ничего просветленного в ней нет.

А классы — это зло и тупик!

Аргументировать как-то можете?
НЛО прилетело и опубликовало эту надпись здесь
Я так понимаю, вы никаких аргументов в пользу своего громкого заявления высказать не можете (как, кстати, и конкретных примеров «просветленных»).
О, это более чем возможно. Переверните её обратно, в таком случае — я готов вас выслушать.
Зависимость PrimeNumberCalculator от Console кажется притянутой за уши. При решении реальных задач — это прямое нарушение SRP
Прошу прощения, но вы неправы. Если перед нами стоит задача выводить числа в режиме их получения, мы должны её реализовать. Можно рассматривать более сложные варианты — скажем, вызывать событие, когда число найдено. Или возвращать IEnuerable с отложенным вычислением в стиле Хаскеля.
SRP мы нарушаем, когда смешиваем разную логику в одном классе. Здесь же мы — фактически — пришли к тому, что делаем шаг алгоритма, а потом передаём управление кому-то, чтобы там что-то сделали с нашим найденным числом. Логика содержится в этом ком-то. Так что нет, не нарушается SRP.

Но, повторю, согласен с тем, что лучше бы тут вызывать событие.
Нарушаем. этот класс «Считает И делегирует» а не только считает и только делегирует. На мой взгляд, корректно этот вопрос решает FRP — добавить третий метод в интерфейс класса — который будет возвращать стрим простых чисел — на который на другом уровне мы можем уже навесить печать — ибо разные задачи.
А необходимость передавать управления внутри шага алгоритма вызвана лишь наложенными самостоятельно (не необходимыми) ограничениями о том, что мы должны вернуть массив И напечатать.
Делегация в данном случае, по-моему, есть лишь способ возвращения результата. Считает и возвращает результат. Не самым обычным способом, но возвращает.
У функции должен быть один вход и один выход. В этом случае, он возвращает его не только в консоль, но и обычным return.

Да и то, что метод GetPrimeNumbers принимает обязательный аргумент IWriter — это как? Если бы я был blackbox тестировщиком или сторонним пользователем — я бы, во-первых, удивился, а во-вторых, послал бы туда null, если мне нужен просто набор простых чисел, без вывода, например, для дальнейшей обработки. И это привело бы к исключению.
Более того, мы вызываем GetPrimeNumber… он возвращает одно число, но в консоль выводит все (!) простые числа вплоть до этого. Это не годится.
Всегда было трудно читать примеры, в которых сразу видится простое альтернативное решение.
Например, простая функция NextPrimeNumber(n);
prime2 = NextPrimeNumber(1);
prime3 = NextPrimeNumber(prime2);
prime4 = NextPrimeNumber(prime3);
...

С такой функцией можно что угодно делать — перебирать по порядку простые числа сколько нужно, писать в файл, складывать в массив.
только тут есть скрытое состояние, что не есть хорошо
Скрытое состояние тут — внутренняя оптимизация, сокрытая от клиента.
Да, согласен — уже многие отметили, что условный yield return подошёл бы лучше.
Я думаю сделать этот ход следующим шагом.
Раз пошла такая пьянка, книга от SergeyT. Причины использования паттернов, применение языковых средств при реализациях, плюсы-минусы реализаций в наличии. Хотя для «Введения» будет сложновато.
Поддерживаю, книга отличная. И актуальная.
Ребят, а можно ли объяснить (в принципе) необходимость или потребнсть в хорошем дизайне?
Меня иногда просто де-мотивируют вопросы коллег-программистов из серии «А зачем так усложнять?».
Опускаются руки.
Я тоже фанатею от крутой архитектуры, за что тоже часто страдаю…
На самом деле, всё сильно зависит от масштаба и перспектив роста системы. Если пишешь калькулятор валют, например, или крестики-нолики — то не имеет смысла много времени на проектирование тратить. Если игру-стратегию, либо редактор разнотипных объектов — то стоит подумать наперёд о том, как будет меняться всё со временем.
Трудовые будни в сообществе любителей ооп:

Ребят, я люблю возводить объектно-ориентированные горы. Коллеги спрашивают «Зачем?», но я не знаю что ответить. Объясните, какими доводами можно подкрепить это желание?
+2

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

Может есть варианты кроме ооп?
-2
там нужно проектировать иерархии объектов более тщательно и обильно, делая решение сложной задачи ещё сложнее.


Ммм… Вообще-то, в потенциально сложных системах это нужно как раз потому, что эти системы более сложные и потому, соответственно, имеют более сложное отображение в объектное представление.

Может есть варианты кроме ооп?


Громадную статью на английском по ссылке не осилил — к сожалению, у меня не такой уж беглый английский… Можете нам, дремучим работникам ООП, рассказать её смысл в двух словах?
более сложное отображение в объектное представление.


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

Громадную статью на английском по ссылке не осилил — к сожалению, у меня не такой уж беглый английский… Можете нам, дремучим работникам ООП, рассказать её смысл в двух словах?


Статья, проницательно копающая в суть проблемы. Одна из мыслей в следующем: есть вещи (nouns) и действия (verbs).

ООП-языки (Java) занимаются тем, что возводят вещи (объекты) на первое место, а действия — на второе (методы). Методы / преобразования должны принадлежать объектам, на вторых ролях. Объекты же образуют иерархии, наследуются друг от друга, инициируя и отвечая за все действия.

На другой стороне ортодоксально-функциональные языки, типо Haskell'а. Там действия это чистые функции, и они играют главную роль — многое сделано для того, чтобы их комбинировать, передавать как аргументы в другие функции, разными способами применять. Для данных (вещей) же есть неизменяемость, система типов, полиморифзм типов и т.д.

Есть же языки мультипарадигменные — JS (прототипное наследование / элементы функциональной парадигмы), Clojure (куча отличных идей из разных парадигм), Scala.

Возведение вещей в абсолютный приоритет губительно тем, что при этом приходится мыслить в терминах вещей. В терминах DistributedQueryAnalyzer'ов, NotificationSchemaManager'ов, PriorityMessageDispatcher'ов, проектировать их иерархию, принося в задачу дополнительную сложность, которая с самой задачей никак не связана — одним словом, возводить горы. После реализации они выглядят настолько внушительно и недвижимо, что заставляют чувствовать наслаждение (сам испытывал) и продолжать делать новые. Ещё более громадные и величественные.

Суть в том, что ооп — это всего лишь один из способов мышления над решениями задач. Концептуально это хорошая идея, но после ознакомления с другими подходами 90% времени вы не будете по ним скучать.
Ну, сейчас, наверное, все мэйнстрим языки уже можно считать мультипарадигменными. Пускай без сахара, а иногда и очень коряво, но сочетают подходы, как минимум, ООП и ФП. Как часто и насколько уместно используют в одной программе тот или иной подход конкретные программисты и их команды — отдельный разговор, о возможность сочетать подходы у них есть
Да, в последнее время так и происходит, и это хорошее направление) Разговор, конечно, и о том что инженерный подход — выбирать инструмент в зависимости от специфики задачи, а не подминать решение под любимую парадигму / инструмент.
Чтобы выбирать инструмент под задачу, увы, нужно знать хотя бы два. Сейчас же зачастую неплохие вроде программисты, смотрят как баран на новые ворота, когда говоришь им сделать свертку/редьюс массива. Посчитать какую-то характеристику этого массива в цикле, рекурсии, или даже параллельно, они могут, но что делают не понимают.
Может, им просто термин незнаком? Сейчас загуглил, оказалось, я эту свертку много раз делал.
Не, там другое — нет понимания, что это операция над массивом как единым целым, даже если выделили операцию в отдельный метод, где массив — единственный параметр, просто такой способ передать набор значений
Спасибо за ответ. Возможно, я снова не до конца понял, но я не согласен с тем, что в объектном программировании существует возведение вещей в абсолютный приоритет. При использовании существующего ООП-кода всегда изучается только интерфейс класса: то есть его поведенческое наполнение. Лезть в реализацию считается моветоном. Это и есть инкапсуляция.

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


Не могу представить себе какая парадигма может быть сильнее объектной, либо околообъектной (прототипной, компонентной, и.т.д) в подавляющем большинстве задач. Сугубое ИМХО, соглашусь сразу что тут вопрос субъективного удобства описания модели реального мира в рамках выбранной парадигмы (где-то неделю назад вёл подобное обсуждение по поводу функциональных языков) — но, как по мне, именно взгляд на предмет как на нечто, обладающее набором свойств, с которыми можно взаимодействовать через набор определённого множества действий — именно такой взгляд естественен для человеческого восприятия. Нас с детства так учат: «Вот машинка, она может гудеть и ездить: ж-ж-ж… А вот печка. Её не трогай, она горячая». То есть есть объект — как кусок реальности — взаимодействуя с которым определённым образом можно получить результат. При этом в подобном восприятии, на мой взгляд, объект выступает самодостаточным элементом, а не каким-то аргументом, который подставляется в функцию воздействия на объект. То есть всё-таки именно вещи оказываются на первом месте.
Удобно когда вы работаете с печками и машинками, но в программировании есть вещи, которые объектами тяжело описать. Например парсер. Конечно его можно написать чисто ООП, но смешать это с ФП может сэкономить много строк и головной боли.
Так кто спорит-то? Иногда другие подходы хороши — в рамках какого-то спектра задач. Но в комментарии Naissur была заявка про то, что без ООП можно обойтись в 90% случаев.
Позвольте, влезу в обсуждение. Я согласен с вами, что часто ФП может в несколько раз сократить код, сделать его и куда как более читаемым (при намётанном глазе), и исключить многие ошибки. Но отбросить ООП как таковое — слишком решительный шаг. Господствуют на рынке именно ООП-языки, и даже на продвинутом C# сложновато писать функционально. Есть ещё, конечно, Scala, но так ли много людей на ней пишут?
Так вот, статья посвящена была введению именно в ООП. Не вижу, как рассказывать его одновременно с ФП.
Неверно считать функциональное программирование прямым конкурентом объектно-ориентированного подхода. Это, скорее, разные «мощности» организации проекта. Функциональная композиция в качестве способа минимизации описательной сложности проекта вовсе не отменяет полезных свойств ООП, а надстраивается именно над объектной моделью, выражающую предметную область решаемой задачи. Из академических кругов функциональное программирование уже уверенно пролезло в энтерпрайз именно как ответ на постоянное усложнение разрабатываемых информационных систем. «Любая достаточно сложная программа на C или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp».
(«Любая достаточно сложная программа на C или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp» — это становится особенно очевидным по ООП-коду, если его автор уже обладает опытом вынесения бизнес-логики в отдельный архитектурный слой.)
а надстраивается именно над объектной моделью, выражающую предметную область решаемой задачи


Вот! Золотые слова же! У каждой задачи своя предметная область, у каждой предметной области своя наиболее естественная парадигма представления модели.

Любая достаточно сложная программа на C или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp


Вот эту фразу слышал… Но каким боком она касается ООД?
> Но каким боком она касается ООД?
Не очень понял вопроса (причём тут ООД?). Попробую пояснить смысл цитаты «вообще», без вопроса.

Если достаточно опытному разработчику рассказать о каких-то паттернах проектирования, то он их сможет отыскать и в своих проектах, которые он разрабатывал до того, как ему рассказали об этих паттернах. Просто потому, что паттерны проектирования — это не какая-то сакральная тайна, а лишь обобщение и так очевидных (опытному разработчику) решений. И вот если такому опытному программисту, никогда не прикасавшемуся к FP, но поучаствовавшему в крупных проектах, объяснить логику LISP-машины, то он наверняка узнает в ней архитектурный слой своего проекта, ответственный за описание и исполнение бизнес-логики.
Я так понимаю, вы используете функциональное программирования для рабочих задач… Можете рассказать в чём главные достоинства функциональной парадигмы программирования? Да, я могу загуглить — но интересно мнение человека, имеющего опыт коммерческой разработки.

То, что я успел схватить из чтения случайных постов:
1. Возможность простой организации фильтрации данных.
2. Возможности передавать функции как аргументы других функций, задавая таким образом не только контекст данных для выполнения действия, но и контекст поведения.
3. Определённая простота оптимизации и параллелизации кода за счёт уменьшения связности по данным (то, что передаётся как аргумент может обрабатываться только в рамках функции, в которую этот аргумент передаётся).

П.С.: По поводу одиозной фразы про lisp-машину — это фраза ни о чём. Так же можно сказать, что любую программу, написанную на функциональном языке, можно представить в рамках плохо оптимизированной и медленной объектно-ориентированной программы. Конечно, можно… Но зачем?
> Можете рассказать в чём главные достоинства функциональной парадигмы программирования?

Нет никаких преимуществ, продолжайте пользоваться тем, чем вам удобно решать свои задачи. Когда не сможете решить (или решение покажется слишком сложным), только тогда и начинайте искать дополнительные способы справиться с этой сложностью (предварительно попытавшись сформулировать свои затруднения). «Заранее» не получится понять целесообразность чуждых идей (велик риск обучиться догмату, а не принципу упрощения).

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

Нет, нельзя. Либо вы до сих пор не поняли смысла цитаты. Функциональная декомпозиция происходит, как правило, на более высоких уровнях абстракции, чем объектная.
Ну как это нет преимуществ? Ведь почему-то включают функциональные парадигмы в современные языки и почему-то выбирают их в коммерческой разработке некоторых проектов (например, вашего проекта).

А за советы спасибо. Возможно, пригодятся.
Свойства вещей — это да, нормальное, восприятие окружающего мира. Есть какие-то сущности, обладающие каким-то набором свойств, которые могут изменяться (когда понятно в результате чего, когда — нет), о состоянии которых мы можем узнать активно (потрогать горячее или нет) или пассивно (услышали гудок). Но вот процессы взаимодействия они обычно снаружи вещи, а не внутри, это именно взаимодействия каких-то субъектов или воздействие субъекта на объект. В ООП же традиционно в методы объекта вкладываются его действия над состоянием самого себя, а не его действия с другими объектами, что в целом для человеческого восприятия не характерно: не говорим мы печке «нагрейся», мы переводим переключатель в нужное состояние и ожидаем, что она начнёт нагреваться, периодически контролируя этот процесс. Не stove.heat(), а turnOnStove(conctreteStov), не file.close() должно быть, а fileSystem.close(file) — так характерно для человеческого восприятия, «сезам, откройся» и «горшочек, не вари» даже в сказках не всегда работает, хотя предполагается, что сезам и горшочек — объекты, а не субъекты.
С самого начала, семантика «метода» в ООП обозначала отправку сообщения объекту, а не действие над объектом. Т.е. объект здесь является получателем сообщения, а не исполнителем. Субъект, который отправляет сообщение — это собственно сама программа. Поэтому запись file.close() читается как [я отправляю]file[сообщение]close().

Понимание метода как действия, приводит обычно к скатыванию в «процедурщину»: модулям, библиотекам функций и т.п. (это не в смысле «хорошо» или «плохо», а в смысле «не-ООП»). Это, кстати, хорошо видно в вашем примере, где появляется дополнительная инструментина для выполнения действий над файлами «fileSystem.close()», а сам file автоматически превращается тупо в данные.
В догонку. Тут можно рассмотреть еще и языковые особенности. Так, в английском языке порядок слов в предложении фиксирован: подлежащее, сказуемое и т.п. При этом сказуемое не может быть без подлежащего. В процедурных языках роль подлежащего выполняют слова «модуль» или «программа», которые содержат действия.
program Example;
begin
  Write('Hello, ');
  WriteLn('World!');
end.

Здесь оба глагола Write() и WriteLn() связаны с подлежащим program Example (читаются как program Example Write('Hello '), program Example WriteLn('World!')), а не существуют сами по себе.

В отличие от английского, в русском языке, можно опускать разные части предложения (программу можно читать в безличной форме как «написать(»Hello "); написать(«World! „)). Это создает новые семантические контексты в понимании “естественности». Не думаю, что это хорошо, скорее просто выносит мозг, поскольку приводит к неправильным выводам.
Тут можно рассмотреть еще и языковые особенности. Так, в английском языке порядок слов в предложении фиксирован: подлежащее, сказуемое и т.п. При этом сказуемое не может быть без подлежащего.

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

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

А это просто натяжка.

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

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

Это создает новые семантические контексты в понимании “естественности».

Не, не создает. Смотрите:

программу можно читать в безличной форме как «написать(»Hello "); написать(«World! „)

… это не безличное предложение, а повелительное наклонение. Строго аналогично можно читать и в английском: write line, write line, write line — это не неполные предложения (сказуемые без подлежащего), это императивы.
не говорим мы печке «нагрейся», мы переводим переключатель в нужное состояние и ожидаем, что она начнёт нагреваться, периодически контролируя этот процесс


Конечно, не говорим. Мы используем метод — читайте, переключатель (точнее, действие поворота переключателя) — который выступает поведенческим интерфейсом между нами и печкой. turnOnStove(conctreteStov) — согласен с funca — это процедурный подход, очень неудобный в проектировании крупных проектов. Представьте себе проект больше чем из двух десятков единиц взаимодействия — поддерживать его будет весьма затруднительно.

Согласен с funca и по поводу подлежащего и сказуемого. Объектный подход, помимо всего прочего, ещё относительно хорошо передаёт семантику естественных языков (которые, в свою очередь, также отображают мышление человека). В описанном вами варианте нет понятия объекта — есть только понятие действия. Объект выступает просто набором данных — контекстом — в котором выполняется действие. Что даже в приведённом примере не отвечает сути. Да, есть абстрактное понятие нагревания чего угодно, но когда мы работаем с печкой, мы мыслим принципами управления печкой — и тут, на мой взгляд, спуск на более абстрактный уровень нагревания чего угодно только усложняет ситуацию.

В объектном подходе чётко выражается объект (то, что идёт «до точки») и действие, которое этот объект должен выполнить (то, что идёт «после точки»). stove.heat() — именно так. Пример алгоритма из жизни: "-Mom, how can I cook a dinner? -Just heat the stove and put the pan on it, honney". Мама могла бы сказать: «Just heat something and put the pan on it» — и это было бы действительно более абстрактно, но при этом последовал бы вопрос как именно можно нагреть это самое «что-то». И всё равно пришлось бы объяснять интерфейс печки с методами взаимодействия с печкой (было бы интересно глянуть реализацию функции в вашем примере, кстати).

Stove theStove;
Pan thePan;

// heat a pan - читается в обратную сторону, но это издержки синтаксиса
// языка программирования.
theStove.heat();

// put the pan on the stove - тут пришлось переконструировать предложение - 
// потому, что печка тут выступает "главным" объектом с точки зрения ООД.
// Это уже вопрос архитектуры, который должен решаться в зависимости от
// взгляда на вещи.
theStove.setObjectOn(thePan);


Попробуйте описать подобный алгоритм более лаконично и естественно в другом, не базирующимся на ООП подходе?

П.С.: Я прекрасно понимаю что мы имеем дело с вопросом взгляда на вещи. Это так же трудно (и, наверно, невозможно) объяснить другому, как, например, убедить современного астроном в черепахе, на которой стоит Земля… Или как убедить металлиста слушать Ранеток. Но, может, получится донеси мысль друг другу… Было бы круто, во всяком случае.
Я не собираюсь вас убеждать в том, что в объектно-ориентированном программировании речь идёт прежде всего об объектах, содержащих методы — это слишком неочевидное утверждение.

Как и в том, что «в подавляющем большинстве задач» кроме ооп есть более простые решения — имея мысли только в пределах объектов, понять это невозможно, потому что и топор можно мысленно приспособить для прорубания шахты — стоит только вообразить взятие топоров в достаточном количестве, трату чуть большего количества времени, и убеждение остальных в том, что это единственная правильная картина мира.

Эти мысли об универсальной «серебрянной пуле под названием X» уходят только после внутреннего вопроса «А может здесь что-то не так?» и дальнейшей работе (не просто прочтения статей, а именно непосредственной работе) с другими подходами, либо не уходят никогда.
Как правило, хороший дизайн — это простой дизайн, поэтому прежде чем что-либо усложнять надо хорошо подумать.

Учебные примеры по применению паттернов часто грешат высасыванием абстракций из пальца (чтобы продемонстрировать принцип), и нередко можно увидеть как простой и понятный код в 10 строк превращается в нагромождение методов, классов и интерфейсов. В реальной жизни, если после рефакторинга код стал сложнее, чем был до него — что-то сделано не так.
Да, но как иначе? Это именно учебный пример, потом приходится принять условности — мы явно будем делать больше необходимого. Невозможно на лекции развернуть и показать сложный проект, в котором рефакторинг и в самом деле применим. Или возможно — но я не знаю как.
Может взять примеры из личного опыта, или из общеизвестных опенсорсных проектов? Проекты целиком показывать необязательно, только необходимые фрагменты кода, где применяются или не применяются паттерны. Заодно можно обсудить целесообразность применения этих паттернов и альтернативные варианты.
НЛО прилетело и опубликовало эту надпись здесь
И вроде бы Success, но… фраза «А если завтра сменится поставщик БД»? Так почему бы завтра и не решить этот вопрос?
Гораздо неудобнее будет, если он никогда не сменится, а время потрачено.
НЛО прилетело и опубликовало эту надпись здесь
Ну вот, выходит что такой подход работает только если вы пророк… Эх, мне бы так.
Пророчество — суть планирование на основе выводов из уже пережитого опыта. Нет никаких формальных алгоритмов или правил (как минимум, пока не изобрели strong AI) для проектирования информационной системы без сознательного участия проектировщика и независимых от его опыта/сообразительности. Впрочем, всегда найдутся всевозможные тренеры и «продавцы серебряной пули», которые постараются опровергнуть данный тезис (за ваши деньги). Паттерны проектирования — это, скорее, удобный способ описать (коллегам и себе) то, что ты сделал УЖЕ, а не способ постичь то, что ты ещё никогда не делал.
Ну вот да… На основании опыта. Видимо, как раз его не очень достаточно у меня пока. Часто хочется учесть все задачи — но оказывается, что этого вообще не нужно было, и я получаю по голове от шефа (либо получаю лишнюю трату своего времени — если пишу pet-project).
Хороший дизайн способствует снижению издержек на модификацию и поддержку кода.

Если на пальцах. Абстрактный проект в вакууме живет 3-5 лет. Время на разработку составляет 3-5 месяцев (т.е. всего лишь 10% срока жизни, Карл!). Остальные 90 приходятся на этапы внедрения и эксплуатации. 3 года в современном мире это большой строк, за который бизнес-ситуация может поменяться несколько раз. Соответственно, команде разработки неизбежно приходится сталкиваться с задачами модификации кода. В определенном смысле, код переходит в состояние эксплуатации сразу как только закрывается задача в трекере на его разработку. Необходимость в модификациях может выявится уже на самых ранних этапах — тестирования, внедрения, и т.д. Модификации влекут за собой риски возникновения дополнительных ошибок и регрессий в ранее отлаженном приложении и соответствующих издержек на их устранение (включая упущенную прибыль, финансовые риски по взаимоотношениям с клиентами, следующими из SLA, и т.п.). Учитывая масштабы, решение вопросов минимизации регрессий и снижения затрат на эксплуатацию становится, как минимум, интересным.

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

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

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

Шаблоны проектирования описывают типовые приемы декомпозиции в типовых случаях. Система описания шаблонов достаточно универсальна, она не связана с ООД, хотя основная масса литературы выпускается именно про них.

Проблемы выразительной подачи материала здесь, как мне кажется, появляются именно из-за того, что при знакомстве с паттернами, студент неявно ставится в роль кодера на этапе разразработки, а модульность сама по себе привносит в разработку сложность. Отсюда и вопрос «А зачем так усложнять?». Но профит от модульности становится очевиден лишь на этапе эксплуатации, когда возникает задача модификации. Причем, чем больше контекст, тем ярче ощущения от резолюции «да тут надо лишь одну строчку поменять» и сильнее инсайт от понимания «и при этом я уверен, что больше ни где ни чего не сломается».
Большое спасибо за столь развернутый ответ.
> Меня иногда просто де-мотивируют вопросы коллег-программистов из серии «А зачем так усложнять?».
> Опускаются руки.

Если честно, то правильно опускаются. Дизайн должен быть простым, и любое усложнение должно быть обосновано. Вопрос разумный, так как усложнять ради усложнения — тупик. Если вы не можете обосновать причину изменений — значит изменение не нужно.
В последнем примере массив _writers не объявлен.
Токо хотел написать, а тут уже написали об этом…
Спасибо, поправил
Enumerator бы подошел гораздо лучше (который через yield). А пользователь пусть сам делает с результатами что угодно.
Да, согласен. Народ уже советовал yield return. Думаю, это вполне можно сделать следующим шагом преобразований.
НЛО прилетело и опубликовало эту надпись здесь
Из текста на 10 строк сделали говно из 100500 классов, в которых без бутылки не разберешься.
Ну, а что делать? Да, это учебный пример, и да, это явный overdesign. На лекции не развернуть реально сложный пример, где рефакторинг ведёт к заметному упрощению.
Я бы советовал взять книжку «refactoring to patterns». Там как раз есть примеры как приводить более-менее правдоподобный код к паттернам и есть выгода в этом.

Не надо пытаться показать более двух паттернов на одном примере из 10 строк. Паттерны — типичные решения типичных проблем проектирования. Очень сомневаюсь что в маленьком коде есть столько проблем.
Проблема такого подхода к обучению паттернам в его неправдоподобии и ущербности в описанном случае. Из Вашего синтетического примера студент не поймет значимости и «крутизны» паттернов. У каждого паттерна есть свой конкретный случай удачного применения в реальной жизни: Строитель для создания sql или http запросов; Фабрика для создания Адаптеров к API операторов доставки или платежным системам; Фасад для ORM ActiveRecord и т.п. Сделайте примеры каждого паттерна на основе реальной задачи, где преимущества подхода очевидны, причем не следует объединять несколько паттернов в одном примере, тогда проблем с пониманием у студентов не будет.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории