И да, вы не ответили на мой вопрос — а писали ли вы большую систему с самостоятельным выделением абстракций (абстракции — это не про ООП, в ФП тоже полно абстракций)? Видимо, это значит, что нет.
Ну и напоследок про ФП — фронтенд опустим, я в нём всё-таки не специалист, уже лет 15 в эту сферу носа не совал. А вот про ядро Линукс — ну, я бы не сказал, что это ФП. Некоторые вещи есть, которые напоминают, но не более того. Некоторые вещи — это некоторые функции, не меняющие состояние и зависят только от аргументов. Ну, указатели на функции везде — но так это просто старый добрый C-стиль для всего, включая ООП. Callback'и? Так это вообще не исключительное свойство ФП. Ну и язык тоже как бы не очень позволяет уж особо развернуться с ФП.
Насчёт «не читал», впору спросить, а помните ли вы её сами — вот этот код приведён в вашей статье после слов про истинный полиморфизм:
type Circle = { type: "circle"; radius: number }
type Rectangle = { type: "rectangle"; width: number; height: number }
type Shape = Circle | Rectangle
const getArea = (shape: Shape): number => {
// Убедитесь, что правило ESLint @typescript-eslint/switch-exhaustiveness-check, требующее исчерпывающие блоки switch, включено.
// Данный код полностью типизирован и проверяется компилятором.
switch (shape.type) {
case "circle":
return Math.PI * shape.radius * shape.radius
case "rectangle":
return shape.width * shape.height
}
}
Но это — подход, в котором каждая операция ЗНАЕТ про все возможные типы. И если видов объектов (то есть форм в вашем примере) два, а операция одна (getArea), то это не тот случай, в котором ООП вообще нужно, это не нормальный проект, а пример из детских задач по программированию, который всё равно, как реализовывать, всяко будет неплохо.
Когда операций будет штук хотя бы пять (ещё лучше — десять), а новые виды объектов будут со временем добавляться (и их тоже будет хотя бы пять), то осознайте, что вам придётся сделать, чтобы длюавить такой новый вид. Пусть операций N, а видов объектов M. Всего-то идете в каждую из N операций, добавляете case в switch. При этом, поскольку в каждой из операций уже есть M case'ов, они будут достаточно объёмистыми. И либо вы фактически получите единую простыню кода в одном файле, либо растащите этом по N файлам, идля добавления нового типа надо не забыть отредактировать каждый из них.
В случае с погодными параметрами, который я приводил в пример, общих для всех типов операций (которые были определены на уровне абстрактного интерфейса, бывшего базой для всего) было немало — взять скалярное значение, бинарно сериализовать (две операции — туда и обратно), преобразовать в строку текста, сложить с другим, вычесть другое, домножить на вещественное число, и, конечно, узнать, какого типа значение. Это восемь операций, выходит, N=8.
Число типов объектов было тоже 8, сейчас всего не упомню (ветер, давление, куча каких-то данных о волне и зыби — в половину я не вникал, меня интересовала чисто математическая сторона вопроса). То есть M=8. Но начинал я, естественно, с M=1 и постепенно добавлял новые параметры. Вот примерная иерархия, я сейчас все восемь параметров не вспомнил, но нарисовал шесть:
Примерная иерархия классов
Итак, я реализовал сначала один параметр — давление воздуха, там поначалу даже типа «скаляр» не было, я его позже выделил, когда появились другие типы с идентичной математикой. На нём всё было отлажено, потом пошёл добавлять новые. Естественно, у меня был enum «тип значения», и ровно в одном месте была короткая функция, которая создавала значение и возвращала интерфейс. При добавлении я писал новый класс (если наследоваться не напрямую от интерфейса, а от одного из абстрактных классов на среднем уровне), мне часто надо было определить всего два метода — «выдай тип» и «преобразуйся в текст», для некоторых — «дай скаляр». И вот это как раз классическое правильное использование наследования — не выгребную яму от сортира, а более частный случай от более общего.
Обратите внимание, что мне надо было сделать для добавления типа — добавить новое значение в enum, добавить новый файл (заголовочный и с реализацией), добавить в функцию-фабрику две строчки кода для создания. Реально вокруг этой иерархии накручно было много (немало было одних плясок вокруг того, что в конкретной системе нельзя было использовать new, и у меня существовала самописная библиотека с своими аллокаторами, кучами и кучей подобного).
А теперь осмыслим, что надо сделать, чтобы добавить новый тип в вашем чудесном полиморфизме на свитчах внутри операций. Структуру данных, определить — раз. Два — пройтись по коду каждой операции и добавить новый case в switch. В N местах с риском где-то забыть. А уж как изолирована реализация одного типа от другого — вообще фантастика, всё стащено вместе.
А между тем я с этим несчастным ООП спокойно разделил эти реализации — и они вообще нигде не пересекались, и лишь один тривиальный кусок кода (фабрика) должен был знать о том, как типы бывают. И если я где-то операцию не реализовал (в вашем чудо-полиморфизме «не вставил в одном месте сase») — это ошибка компиляции, которая находится сразу: «Не могу инстанцировать абстрактный класс».
Сравните вероятность ошибки при работе с большой системой (эта, кстати, небольшая — библиотечка на 100К исходников, по масштабам всей системы, в которой она использовалась — наверное, 1% кода).
Ну и на десерт — какова вычислительная сложность switch'а на M вариантов? Я вам скажу, O(M). И если свитч сделан в операциях, которые вызываются миллионами, это вполне может сказаться. Конечно, можно написать функции для операции для одного типа объекта, положить в массив и вытаскивать по индексу. Но это вы уже будете виртуальные методы руками делать, и гораздо менее эффективно, что это делает компилятор C++. Так что такой полиморфизм ещё и для написания производительного кода довольно сомнителен.
В вашем примере полиморфизма сердцем кода является свитч. Но и эрланговское pattern matching или его аналоги — это во многих случаях очень плохая замена полиморфизму в ООП, которая немногим лучше, чем switch (в том числе, кстати, по производительности, хотя я в основном не о ней, но ведь для эффективного полиморфизма надо не варианты перебирать). Я привёл вам очень понятный пример (про погодные данные) того, где подобный подход не сработает, поскольку он не соберёт всё, связанное с одним из видов данных, вместе, а размажет по большому количеству мест. И вот мне нужно модифицировать один из видов значений, я что, полезу по всем местам, где есть выбор действия в заточить от типа? Нет, в нормальном полиморфизме я создам новый класс и, если добавил тип, поменяю фабрику, которая объекты со значениями создаёт, чтобы умела создавать новый тип значений. Это ровно два точечных изменения — создать тип и разрешить с ним работать. Впрочем, возможно, есть языки для ФП, где проблема решена, это, в общем, не фунт изюма.
Ваши доводы (начиная уже с картинки перед статьёй про наследование сортира) лишь наводят на мысль, что вы знакомились с ООП по каким-то довольно уродливым фреймворкам (которых, кстати, хватает) и никогда не проектировали действительно больших систем. Ну, либо это троллинг, но пока я думаю, что вы добросовестны. Поймите, ООП ведь даже не с ОО-языками возникло, это просто результат осмысления того, как люди структурировали сложное ещё в эпоху процедурного программирования, когда о классах никто не думал.
Просить показывать код, который собеседник писал в течение жизни (а он не покажет, поскольку всю жизнь писал проприетарщину и на гитхабе этого нет; но порассказать мне есть что, и с деталями, которых придумавший опыт не сочинит) — немного сомнительный приём, нет? Есть вопрос, который я бы вам задал, если не возражаете. Насколько большие системы приходилось вам разрабатывать и проектировать лично? И не на уровне «фреймворк такой-то задал мне структуру, я живу в ней», а именно на уровне «сам проанализировал сущности и спроектировал всё». По вашей статье есть ощущение, что ничего действительно большого и сложного (и при этом оригинального, начиная с уровня структуры абстракций) вы пока просто не разрабатывали. Это не обвинение (и не означает, что я считаю вас некомпетентным), это всего лишь предположение, что именно тем, для чего ООП предназначено, вы не занимались, из-за чего даже проблематику не осознаёте. Мне вот, например, поиспользовать функциональщину именно в больших промышленных системах не довелось (а хотелось), но я ведь и не утверждаю, что она для этого не годится. А вы именно утверждаете, что ООП не годится.
К ФП и я отношусь с симпатией, и тот же Эрланг мне скорее нравится. Но вопрос не в том, хорошо ли ФП, вопрос в том, ужасно ли ООП.
И тут сразу, когда как образец работы с множеством форм подаётся switch как замена полиморфизму, мне всё-таки смешно. Нет, когда у вас три формы и знание о них собрано воедино, разумеется, написать switch проще. Но для этого и не надо тащить в код ООП. Вообще, если говорить о реальных проблемах ООП, их всего три:
Его не стоит тащить в простые случаи. Для кучи вещей на свете функция с параметрами — прекрасная абстракция, и непонятно, зачем заворачивать простое действие в объект, создавать класс, инстанцировать и т. п. И люди стреляют из пушки по воробьям, а потом ругаются насчёт обилия бойлерплейтов. А не надо было, ООП совсем не для того создано, оно создано как средство структурировать сложные системы абстракций, и для организации этих абстракций может применятьс довольно успешно. Кстати, есть довольно здоровая традиция в Python, там не принято начинать клепать иерархии классов до тех пор, пока эта иерархия тебе действительно не становится нужна.
Вместо реального ООП часто используют лишь языковые механизмы его поддержки, но для проектирования, котрое объектно-ориентированным не является. Иногда это оправданно, иногда — нет. Скажем, в C++ контейнеры в STL и большая часть Boost — не вполне ООП. Да, они, может, и предназначены для использования в программах, построенных с применением этой парадигмы, но внутри их классы и наследование используются часто как кодировочные трюки. Но в STL хоть всё по уму, а иногда это доходит до маразма. Классика — унаследовать самолёт от крыльев и фюзеляжа (в ООП это значило бы: «Самолёт является частным случаем крыльев и фюзеляжа»; в понимании некоторых это значит «Я так переиспользую код классов»). И многие люди успешно делают это раз за разом, удивляясь, что ничего не работает. В общем, механизмы поддержки ООП — это ещё не ООП. Более того, можно реализовывать ООП на старом добром C без намёка на два плюса на конце, как и непонятно что на C++ (и любом другом языке со встроенными механизмами ООП). Между прочим, шутка про наследование сортира от выгребной ямы — это никакого отношения к ООП не имеет. Применение наследования не равно ООП, это азы. А что до того, что код так пишут — так, перефразируя товарища Сталина, «Других програмыстов у мэня дла вас нэт!» (В оригинале было про писателей, конечно!)
Эволюция языков, вынужденных обеспечивать обратную совместимость десятилетиями, болезненна. C++ никогда не был маленьким и простым языком, но сейчас он уродливо сложен. Даже довольно хорошие программисты теперь протаптывают в нём свои системы тропок и многого не знают за их пределами. Для того, чтобы представлять этот кошмар в целом, нужно, наверное, быть не прикладником, а разработчиком компиляторов и позаниматься этим лет 20. Современный C++ чудовищен. Просто для понимания — я писал на этом языке с начала 90-х до 10-х (на C начал писать вообще в середине 80-х), когда-то вполне спокойно осилил шаблоны Алекандреску и списки типов, даже написал довольно затейливую библиотеку для работы с микроконтроллерами AVR с оптимизацией вывода на разбросанные по разным ногам GPIO логические шины, где эта оптимизация делалась на этапе компиляции. Так что я не очень боялся сложности языка до примерно конца 10-х. Потом он стал мне активно не нравиться — я не люблю пользоваться тем, что не могу понять до конца. Думаю, с другими ООП-языками, пусть и в меньшей степени, происходит то же.
А что до так не нравящихся вам полиморфизмов и наследований — могу рассказать про самый, наверное большой ООП-проект, который я проектировал (реализовывали мы его уже большой командой, но все основные иерархии спроектировал лично я). Это была картографическая библиотека, работавшая с морскими навигацонными картами (довольно многих форматов), преимущественно векторными. Лестница абстракций было довольно велика — от составных элементов одного объекта карты до карт, форматов карт (различных чуть менее, чем полностью), проекций, картографических панелей, многопоточных «рисовалок» этих панелей, картографических коллекций, системы подбора карт под конкретную область и масштаб. Сама библиотека занимала несколько мегабайт кода ещё тогда, когда ей занимался я, потом ещё подросла. Это было примерно начало нулевых, C++.
И честно сказать, я поныне считаю, что для организации абстракций подобного монстра ООП подходит прекрасно, и сейчас бы его использовал ничтоже сумняшеся. Причём даже если бы языком был C без встроенных механизмов ООП, я бы всё равно применял ООП, хотя это было бы немного более трудоёмко.
Сейчас я больше пишу на Питоне (ну и вообще лично пишу не так много, даже проектирую на уровне кода не так часто). И пока передо мной простая и частная задача, я нередко пишу её в достаточно процедурном стиле. Но когда иерархия абстракций усложняется, её становится нужно организовывать — и тут приходит на помощь именно ООП. Хотя пуристский ООП-подход я бы всё-таки применять не стал (имхо, он обычно является глупостью).
И ведь заметьте, когда у вас есть структуры данных, и для каждой структуры вы начинаете писать (ну, допустим, в том же модуле) функции, которые с ней работают, это тоже ООП, те же классы, только вручную.
Ну и вернёмся напоследок к примеру со switch. Видите ли, когда у вас одна операция над объектом (допустим, нарисовать), полиморфизм ведь и правда не так нужен, можно даже switch сделать, никто не умрёт. Но теперь представьте пример, который я сейчас приведу — реальный пример из конца 90-х, когда мы занимались накладыванием на карты погодной информации и я проектировал подсистему работы с чуть ли не десятком различных параметров. Все они хранились единообразно, для всех из них единообразно производилась интерполяция (они были заданы в узлах сетки широт и долгот на карте, для получения значения в произвольной точке приходилось интерполировать), по ним строились изолинии (причём некоторые параметры были векторными, но для построения изолиний превращались в скаляры, скажем, от вектора ветра брался его модуль). В общем, над каждым типом данных было довольно много операций, которые очень отличались по внутреннему наполнению, но вызывались из десятка разных алгоритмов, каждый из которых не хотел разбираться, с чем имеет дело, а просто говорил — вот эти сложи, на это раздели, тут выдай скаляр и т. п.
Что тут может быть лучше полиморфизма? Это ведь не игрушечный пример с треугольничками, квадратиками и кружочками, где поставил один switch — и прекрасно. А тут — есть чуть меньше десятка типов значений (что-то вектор — например, ветер, что-то скаляр) и набор операций (тоже чуть меньше десятка), которые можно делать с каждым типом значений. И куча алгоритмов, которые работают со значениями, не разбираясь с тем, с чем работают.
Нет, ну можно, конечно, собрать по операциям свитчи воедино (ну или не свитчи, чтобы не перебирать все возможные типы, а выбор указателя на функцию по идентификатору типа — массив функций завести). Но не находите ли вы, что это я велосипед для поддержки того же полиморфизма изобретаю, а имя этому велосипеду — механизм виртуальных функций. А если его не изобретать — эти ужасные свитчи будут ровным слоем размазаны по коду и знание о том, какие типы могут там быть, будет необходимо в каждом. Нет, если это — прогресс, то я лучше останусь в своём каменном веке с динозаврами и полиморфизмом.
В общем, я далёк от мнения, что ООП — это инструмент для всего подряд. Но для упорядочения реализации систем сложных абстракций оно определённо хорошо, и для этого я его использовал, использую и буду использовать, пока ничего лучшего не придумано.
Имхо, любой фреймворк типа SAFe, если он при внедрении не превратился в кастомный через какое-то время — маразм. Сами принципы Agile не предполагают жёсткости. Но нет, мы получили весь этот идиотизм сертификациями, "без бумажки ты букашка" и прочий формализованный идиотизм под видом Agile.
А ваш кто писал? Общая грамотность и наличие сложной пунктуации – это не признак работы "нейронки".
Тогда это не было странно (но тогда –в конце 86-го – я был не инженер, а школьник, 9-й класс, и с подобной техникой дела не имел). Теперешним людям, которые Ленина не помнят, было бы странно. С середины девяностых-то уже были ксероксы в свободном доступе, а первые – и раньше, примерно с распадом СССР.
Да какая теперь разница, насколько уступали или не уступали советские жанре ПК аналогам? Просто для тех, кто помнит это время, это история, которую интересно иногда вспомнить.
Я вот начинал программировать на СМ-1800 с клоном Intel 8080 и 64 килобайтами памяти. Это никак не домашний ПК, здоровенная хреновина в форм-факторе большого стола, огромная тумба справа содержала собственно компьютер и дисководы (два восьмидюймовых). На столе стоял монитор ВТА-2000, а на мониторе — пульт процессора, позволявший смотреть состояние, получать доступ к памяти и портам ввода вывода, и даже выполнять код пошагово. Для 1986 года — уже сильно устаревшая машина. Сколько времени работал компилятор C в ассемблер даже для небольших программ -- страшно вспомнить. Но чего только мы там не делали. Писали игры на C, программировали на ассемблере. Где-то достав документ, я долго разбирался с системой прерываний и научился в итоге обрабатывать прерывания от клавиатуры (вообще там в BIOS и в CP/M всё было без прерываний — да ведь там даже чтение данных с диска было совершено синхронным, никаких тебе DMA, всё вытаскивали по байтику из портов ввода, а необходимая задержка реализовывалась циклами). Эх, молодость-молодость. Как раз между 14 и 16 годами. А как я ассемблер учил по какой-то странной копии документации из доксероксных времён!
К сожалению, советская микроэлектроника действительно сильно проигрывала западной и в ассортименте, и в надёжности, да и в объемах производства тоже. Мой отец в те времена как раз занимался вопросами производства микросхем на одном крупном предприятии -- анализом отказов, выявлением причин брака и т.п. Он много тогда порассказал.
Но сорок лет прошло. Теперь всё это только история.
Он такого бреда не предлагал. Он говорил о Teal organizations, которые ни по спиральной динамике, ни по AQAL, ну просто ни фига не бирюзовые. Не говоря о том, что то, что он описывал, даже к teal не имеет отношения, там банальный продвинутый зелёный.
Феноменально! Оказывается, если написать спецификацию, вероятность успеха драматически возрастает! Срочно в номер! Ни за что бы не подумал! Agile теперь, оказывается -- работа даже без попытки заранее понять, что мы делаем.
Если спецификацию написать можно -- её надо писать, это очевидно. Если нельзя, надо хоть что-то всё-таки написать, чтобы ну хоть какие-то вещи были продуманы заранее.
Понятно, что Agile -- это группа методологий, ориентированных на работу в изменчивом окружении. Писать полную и детальную спецификацию до работ часто нет смысла. Но если горе-практики поняли это как "тогда мы вообще не будем думать заранее, всё сделаем по месту", то шансов на провал у проекта очень много. Именно из-за таких практиков и формируется подобная прекрасная статистика.
Никогда Agile не запрещал писать спецификации (да, статус спецификации другой -- это не закон, а что-то, что легко можно поменять, в то время как в более старых подходах это бумага, вокруг которой будет идти торг — сделано/не сделано). Agile -- это не хаотическая разработка. И если кто-то подумал, что это способ не продумывать ничего заранее, то так ему и надо. Чем лучше поставлена задача, тем больше шансов на успех, и это не зависит от методологии, это общее правило.
Можно не хотеть расписывать какие-то детали до того, как пощупаешь первый прототип. Но одно дело -- не расписывать "какие-то детали", для которых пока нет видения, а другое -- "ничего продумывать не будем, там разберёмся".
И если хоть каких-то вменяемых описаний желаемого нет (ну, кроме разве что проектов по багфиксингу, там есть баги, есть представления об их срочности и прикидочной трудоемкости -- иди и чини примерно в нужном порядке), это не Agile. Это бардак. Правда, нынче модно называть любой бардак Agile'ом, за что и расплачиваемся.
Паттерн Строитель нужен в основном тогда, когда вы постепенно создаёте объект с действительно сложной внутренней структурой. Например, в зависимости от того, что именно передадут, инициализируете по-разному сложные структуры данных, взаимосвязи между частями и т.п., а состав объекта может получиться разным Разумеется, применять данный шаблон для того, чтобы задать поле color, которое ни на что больше не влияет -- это настолько unpythonic, насколько возможно.
Что же до нужды в синглетонах, то поведение, связанное с инициализацией, просто совершенно не нужно вшивать в класс через __new__(). А вот статический метод get_instance() вполне может иметь смысл. Правда, и это чаще всего не нужно, как вы и написали. Кстати, самый смешной и нелепый подход к реализации синглетона в Python -- это Borg. Это правда очень странно
Вообще мне кажется, что половина проблем возникает даже не из самого паттерна, а из-за неудачной попытки вшить поведение в код неуместным в большинстве случаев языковым механизмом (new, метаклассы и подобное) вместо написания чего-то элементарного. Именно языковой механизм даёт интересные побочки типа странного поведения при наследовании.
Все эти почасовые отчёты -- обычно признак серьёзных проблем в процессе/коллективе. Например, я видел ситуацию, когда такие отчёты используются для понимания затрат по проектам. Тот факт, что точность таких данных -- плюс минус сто процентов, никого не волнует. Особенно забавно, когда такое происходит в среде, позиционирующей себя как Agile.
Интересно, что после этого очень трудно объяснить кому-то полезность ведения, скажем, burndown. И хотя в burndown интересно только то, сколько команде осталось в спринте, а не то, как ты работал по часам, доверие людей, подорванное микроменеджментом, вернуть трудно.
Что реально формируют почасовые отчёты -- так это закрытую защитную культуру, где каждого человека можно пытать, но всё равно не понять, какие реально есть проблемы и нужно ли принять какие-то меры (ну, скажем, помочь, обсудить выбрасывание части малозначимых задач из спринта и т. п. -- я не про "дисциплинарное воздействие", а про реальную работу с нормальными текущими проблемами).
Нигде, кроме фриланса, эти идиотские почасовые отчёты почти неприменимы, толку дают очень мало, а вред наносят. В том же Скраме, если так уж нужно делить расходы по проектам, точность, уверен, не упадёт, если работать с оценками в Стори Поинтах, потому что она и так очень низкая. Зато никаких дополнительных отчётов уже не потребуется. Скажем, если для хранения задач и оценок используется какой-то трекер типа Джиры, всю статистику можно собрать автоматически, не мороча людям головы бесполезными отчётами.
Каждый бессмысленный отчёт -- это подрыв нормальной атмосферы в командах.
А я там выше как раз привёл пример. Элемент поведения -- это не одно действие, а определенный аспект того, что класс делает. Это может быть далеко не одно действие. Более того, в стратегии и объекты внутри могут быть (тот же объект синхронизации в приведенном ранее пример подсчёта ссылок). Для того, чтобы считать что-то стратегией важно то, что оно делает, а не то, как реализовано. Это уже детали конкретной стратегии.
Функция, естественно, может описывать поведение. Но, скажем, упомянутое выше "как синхронизировать доступ к счетчику ссылок" -- это вполне себе аспект поведения объекта. Хотя там -- минимум два метода, стратегия описывается классом, а в экземпляре класса ещё и объект синхронизации (под виндой, скажем, критическая секция) запрятан.
Вообще суть понятия стратегия -- точно не в том, как именно она реализована. Класс (именно класс без объекта -- в программировании на шаблонах частое решение), объект, функция -- всё это может быть стратегией. Тут важна цель -- для чего, что стратегия делает. Скажем, работает объект с БД. Надо ли закрыть транзакцию в конце? Иногда надо, а иногда это просадит производительность, да ещё и ошибки может вызвать. И вот параметризация стратегией позволяет это настроить. А как что стратегия реализована -- вопрос десятый.
В немного более общем виде, стратегия — это элемент поведения, который передаётся в объект или функцию для кастомизации какого-то аспекта поведения. И далеко не всегда это одна функция, часто целое семейство. Когда-то давно в C++, скажем, базовый класс для объекта с подсчётом ссылок параметризовали классом-стратегией, определявшим правила многопоточной синхронизации для доступа к счётчику. А там минимум пара функций. Стратегией это, конечно, быть не переставало.
Имхо, сама история термина "бирюзовая компания" на русском совершенно позорна.
Во-первых, они ни капли не бирюзовые. Система кодирования цветов заимствована из модели AQAL Уилбера. И там в русском языке есть сложившаяся и устоявшаяся традиция переводить Teal как "изумрудный", а как бирюзовый переводить следующий, Turquoise. Так что для начала Teal organizations -- это изумрудные организации, но с лёгкой руки бывшего не в контексте первого переводчика мы уже получили то, что получили, традицию перевода термина, конфликтующую с традицией перевода литературы по сопряжённым областям.
Во-вторых, если вчитаться в описания организаций у Лалу (видно, что там устраняются иерархии) и в то, что собой представляют уровни AQAL, то и к изумрудному уровню эти структуры отношения не имеют, это всего лишь дальнейшее воплощение "зелёных" идей. Следующий же уровень, изумрудный (обозванный по ошибке бирюзовым), этой идеей не одержим, он достаточно спокойно выстраивает иерархии и достаточно гибко их меняет. "Зелёному" уровню вообще свойственна несколько нездоровая фиксация на идеях всеобщего равенства.
Я абсолютно уверен, что никаких "бирюзовых" или "изумрудных" организаций в бизнесе на сегодня нет и быть не может, ни в России, ни где-либо ещё. Более того, свойства организаций данного уровня пока не очень понятны. Но они точно не будут деиерархизированы. Гибкими -- вероятно. Находящими место людям с разным развитием -- да. Но помешанными на равенстве -- нет.
А вся эта традиция считать организации то ли бирюзовыми, то ли изумрудными -- результат хайпа, не имеющего под собой вообще никаких реальных оснований.
Вообще степень токсичного влияния зелёных идей трудно даже описать. Именно отсюда растут ноги бесчисленных заигрываний с персоналом, совершенно искаженных игр в псевдодемократию даже там, где она совершенно неуместна, и многое, многое другое. Половина Agile заражена этими идеями. Отсюда берутся команды, которые не в состоянии обеспечить хоть какую-то прозрачность и контролируемость работы, но при этом агрессивно претендуют на полную самоорганизацию и их право решать любые вопросы в пределах своей досягаемости. Отсюда берётся нарождающаяся традиция руководства, заигрывающего с подобными подчинёнными, и много чего ещё.
Диктатура плоха, спору нет. Но лучше ли ЭТО? Я сомневаюсь.
Да, тоже забавно читать рассуждения про бессмысленность ООП. Вполне рабочая парадигма до сих пор. Не единственная, и я бы не заигрывался в пуризм. Но стопроцентно живая. Просто нет смысла играть в неё в некоторых (обычно простых) случаях. На свете есть очень много вещей, которые просто реализуются функцией, и не надо натягивать их на ООП. Меня в своё время поразил раздолбайский подход, принятый в Python. Разумеется, ООП там поддерживается, и часто активно используется, но на нижнем уровне часто очень много сделано просто функциями без какой-либо потуги на ООП. К этому стилю я после многих лет C++ привыкал, но потом оценил. Впрочем, иерархии классов я там всё равно использую, просто только там, где это действительно проще и понятнее. Даже многие паттерны проектирования там принято реализовывать совсем иначе. Но и там ООП много. Некоторые абстракции всё-таки хорошо реализуется именно средствами ООП. Но не все.
Критики же ООП не понимают обычно, что если они использовали, скажем, наследование -- это не обязательно ООП. Если единственный мотив для этого наследования -- переиспользовать код, то речь о применении языковых механизмов поддержки ООП для проектирования, не являющегося объектно-ориентированным. В классическом ООП наследование всегда выражает отношения генерализации (общее-частное). Словом, нет с ООП никакой проблемы, кроме двух случаев -- кривого использования и стрельбы из пушки по воробьям (т.е. искусственного впихивания в парадигму простых вещей, совершенно в этой парадигме не нуждающихся).
Современное программирование, строго говоря, мультипарадигменное, как и большинство современных языков. Оно совершенно не ограничено ООП, что, впрочем, ООП не отменяет. Некоторые языки больше провоцируют применения ООП, некоторые меньше, но чуть не все поддерживают.
ООП на уровне языка со своими тремя китами (инкапсуляция, наследование, полиморфизм) очень удобно для описания НЕКОТОРЫХ абстракций. И существенно менее удобно для других. Собственно, в этом и ответ на вопрос, когда и кому нужно ООП.
И если для чего-то наиболее подходящей абстракцией будет функция, а не класс с методами, то ничего плохого в этом нет совершенно. Хотя многие базовые абстракции очень удобно оформлять как классы. ООП -- прекрасная штука, просто не стоит натягивать сову на глобус.
К слову, значительная часть даже стандартной библиотеки C++ -- это не вполне ООП, иногда даже вообще не ООП.
Если ООП понимать как обязательное к исполнению везде и во всём -- это кандалы. Зачем себя ограничивать раз и навсегда? Если как инструмент, очень неплохо решающий свои задачи удобного описания НЕКОТОРЫХ абстракций -- это совсем другое дело.
Если же говорить о паттерне Strategy, то весь этот "ужас" кажется излишними тяжестями до тех пор, пока вы не оказываетесь перед необходимостью кастомизировать какое-то действительно сложное поведение. Впрочем, и тут все зависит от ситуации и, кстати, языка. Если желаемое поведение состоит из одного действия, а не развесистого интерфейса, вполне может хватит, скажем, передачи простой функции. А где-то стратегию удобно делать параметром шаблона. Общая идея одна, а реализации совершенно разные.
И наконец, есть ООП как идея и есть поддержка механизмов ООП на уровне языка. Интересно, что эти языковые механизмы ООП можно использовать для программирования в стиле, который ООП вообще не является. Допустим, то же наследование для реализации частного случая, расширяющего базовый -- это именно ООП. А наследование для переиспользования функциональности класса -- это не ООП (но нередко хороший кодировочный трюк). Ну и наоборот, ООП на чистом C -- совершенно возможная (даже нередкая) вещь, просто приходится обходиться без языковых механизмов, которые в C++ есть.
Я видел в европейской компании это. И слово "нажать" можно употреблять по-разному. Я вообще не о диктаторстве. Я о псевдоаджайл-трансформациях с выпиливанием инструментов вертикального управления. Вообще никто не может от команды ничего потребовать. Есть PO, есть бэклог. PO не заинтересован, никакую общетехнологическую мелочь с уровня компании не пропихнуть. Или приходит аджайл-коуч, говорит -- трудности вижу такие-то, надо пробовать то-то (и нет, он их не на голове стоять просит). И они что, пробуют? Нет, пусть трудности очевидны, они говорят: нет, не хотим. Творят реально чёрт знает что, но нажать (не раздавить, а просто сказать: мы нашли вам коуча, поделайте некоторое время то, что он предложит), некому. У нас демократия.
И очень часто аджайл понимают именно так. "Всё решает команда, всё равны, никто нам не указ". Хотя реально идея была всего лишь "не дергай команду без нужды, дай им делать своё дело, справляются -- не трогай". И когда команда справляется и изначально может сорганизоваться, это отлично. А если нет? Некоторые команды надо реально учить самоорганизовываться.
Самое глупое, что в конце концов при долгой неэффективной работе всё равно всех разгонят по причине финансовой невыгодности.
Очень всё это напоминает рассуждения о дисфункциональную зелёном из книги Уилбера "Трамп и эпоха постправды".
И да, вы не ответили на мой вопрос — а писали ли вы большую систему с самостоятельным выделением абстракций (абстракции — это не про ООП, в ФП тоже полно абстракций)? Видимо, это значит, что нет.
Ну и напоследок про ФП — фронтенд опустим, я в нём всё-таки не специалист, уже лет 15 в эту сферу носа не совал. А вот про ядро Линукс — ну, я бы не сказал, что это ФП. Некоторые вещи есть, которые напоминают, но не более того. Некоторые вещи — это некоторые функции, не меняющие состояние и зависят только от аргументов. Ну, указатели на функции везде — но так это просто старый добрый C-стиль для всего, включая ООП. Callback'и? Так это вообще не исключительное свойство ФП. Ну и язык тоже как бы не очень позволяет уж особо развернуться с ФП.
Насчёт «не читал», впору спросить, а помните ли вы её сами — вот этот код приведён в вашей статье после слов про истинный полиморфизм:
Но это — подход, в котором каждая операция ЗНАЕТ про все возможные типы. И если видов объектов (то есть форм в вашем примере) два, а операция одна (getArea), то это не тот случай, в котором ООП вообще нужно, это не нормальный проект, а пример из детских задач по программированию, который всё равно, как реализовывать, всяко будет неплохо.
Когда операций будет штук хотя бы пять (ещё лучше — десять), а новые виды объектов будут со временем добавляться (и их тоже будет хотя бы пять), то осознайте, что вам придётся сделать, чтобы длюавить такой новый вид. Пусть операций N, а видов объектов M. Всего-то идете в каждую из N операций, добавляете case в switch. При этом, поскольку в каждой из операций уже есть M case'ов, они будут достаточно объёмистыми. И либо вы фактически получите единую простыню кода в одном файле, либо растащите этом по N файлам, идля добавления нового типа надо не забыть отредактировать каждый из них.
В случае с погодными параметрами, который я приводил в пример, общих для всех типов операций (которые были определены на уровне абстрактного интерфейса, бывшего базой для всего) было немало — взять скалярное значение, бинарно сериализовать (две операции — туда и обратно), преобразовать в строку текста, сложить с другим, вычесть другое, домножить на вещественное число, и, конечно, узнать, какого типа значение. Это восемь операций, выходит, N=8.
Число типов объектов было тоже 8, сейчас всего не упомню (ветер, давление, куча каких-то данных о волне и зыби — в половину я не вникал, меня интересовала чисто математическая сторона вопроса). То есть M=8. Но начинал я, естественно, с M=1 и постепенно добавлял новые параметры. Вот примерная иерархия, я сейчас все восемь параметров не вспомнил, но нарисовал шесть:
Итак, я реализовал сначала один параметр — давление воздуха, там поначалу даже типа «скаляр» не было, я его позже выделил, когда появились другие типы с идентичной математикой. На нём всё было отлажено, потом пошёл добавлять новые. Естественно, у меня был enum «тип значения», и ровно в одном месте была короткая функция, которая создавала значение и возвращала интерфейс. При добавлении я писал новый класс (если наследоваться не напрямую от интерфейса, а от одного из абстрактных классов на среднем уровне), мне часто надо было определить всего два метода — «выдай тип» и «преобразуйся в текст», для некоторых — «дай скаляр». И вот это как раз классическое правильное использование наследования — не выгребную яму от сортира, а более частный случай от более общего.
Обратите внимание, что мне надо было сделать для добавления типа — добавить новое значение в enum, добавить новый файл (заголовочный и с реализацией), добавить в функцию-фабрику две строчки кода для создания. Реально вокруг этой иерархии накручно было много (немало было одних плясок вокруг того, что в конкретной системе нельзя было использовать new, и у меня существовала самописная библиотека с своими аллокаторами, кучами и кучей подобного).
А теперь осмыслим, что надо сделать, чтобы добавить новый тип в вашем чудесном полиморфизме на свитчах внутри операций. Структуру данных, определить — раз. Два — пройтись по коду каждой операции и добавить новый case в switch. В N местах с риском где-то забыть. А уж как изолирована реализация одного типа от другого — вообще фантастика, всё стащено вместе.
А между тем я с этим несчастным ООП спокойно разделил эти реализации — и они вообще нигде не пересекались, и лишь один тривиальный кусок кода (фабрика) должен был знать о том, как типы бывают. И если я где-то операцию не реализовал (в вашем чудо-полиморфизме «не вставил в одном месте сase») — это ошибка компиляции, которая находится сразу: «Не могу инстанцировать абстрактный класс».
Сравните вероятность ошибки при работе с большой системой (эта, кстати, небольшая — библиотечка на 100К исходников, по масштабам всей системы, в которой она использовалась — наверное, 1% кода).
Ну и на десерт — какова вычислительная сложность switch'а на M вариантов? Я вам скажу, O(M). И если свитч сделан в операциях, которые вызываются миллионами, это вполне может сказаться. Конечно, можно написать функции для операции для одного типа объекта, положить в массив и вытаскивать по индексу. Но это вы уже будете виртуальные методы руками делать, и гораздо менее эффективно, что это делает компилятор C++. Так что такой полиморфизм ещё и для написания производительного кода довольно сомнителен.
Нет, вы реально ООП не понимаете.
В вашем примере полиморфизма сердцем кода является свитч. Но и эрланговское pattern matching или его аналоги — это во многих случаях очень плохая замена полиморфизму в ООП, которая немногим лучше, чем switch (в том числе, кстати, по производительности, хотя я в основном не о ней, но ведь для эффективного полиморфизма надо не варианты перебирать). Я привёл вам очень понятный пример (про погодные данные) того, где подобный подход не сработает, поскольку он не соберёт всё, связанное с одним из видов данных, вместе, а размажет по большому количеству мест. И вот мне нужно модифицировать один из видов значений, я что, полезу по всем местам, где есть выбор действия в заточить от типа? Нет, в нормальном полиморфизме я создам новый класс и, если добавил тип, поменяю фабрику, которая объекты со значениями создаёт, чтобы умела создавать новый тип значений. Это ровно два точечных изменения — создать тип и разрешить с ним работать. Впрочем, возможно, есть языки для ФП, где проблема решена, это, в общем, не фунт изюма.
Ваши доводы (начиная уже с картинки перед статьёй про наследование сортира) лишь наводят на мысль, что вы знакомились с ООП по каким-то довольно уродливым фреймворкам (которых, кстати, хватает) и никогда не проектировали действительно больших систем. Ну, либо это троллинг, но пока я думаю, что вы добросовестны. Поймите, ООП ведь даже не с ОО-языками возникло, это просто результат осмысления того, как люди структурировали сложное ещё в эпоху процедурного программирования, когда о классах никто не думал.
Просить показывать код, который собеседник писал в течение жизни (а он не покажет, поскольку всю жизнь писал проприетарщину и на гитхабе этого нет; но порассказать мне есть что, и с деталями, которых придумавший опыт не сочинит) — немного сомнительный приём, нет? Есть вопрос, который я бы вам задал, если не возражаете. Насколько большие системы приходилось вам разрабатывать и проектировать лично? И не на уровне «фреймворк такой-то задал мне структуру, я живу в ней», а именно на уровне «сам проанализировал сущности и спроектировал всё». По вашей статье есть ощущение, что ничего действительно большого и сложного (и при этом оригинального, начиная с уровня структуры абстракций) вы пока просто не разрабатывали. Это не обвинение (и не означает, что я считаю вас некомпетентным), это всего лишь предположение, что именно тем, для чего ООП предназначено, вы не занимались, из-за чего даже проблематику не осознаёте. Мне вот, например, поиспользовать функциональщину именно в больших промышленных системах не довелось (а хотелось), но я ведь и не утверждаю, что она для этого не годится. А вы именно утверждаете, что ООП не годится.
В общем, завершая мысль. Я думаю, будущее за мультипарадигменными языками, и одной из парадигм по-прежнему должно быть именно ООП. Но только одной из.
<Случайно попал в комментарии вместо ответов, а удалить не дают>
К ФП и я отношусь с симпатией, и тот же Эрланг мне скорее нравится. Но вопрос не в том, хорошо ли ФП, вопрос в том, ужасно ли ООП.
И тут сразу, когда как образец работы с множеством форм подаётся switch как замена полиморфизму, мне всё-таки смешно. Нет, когда у вас три формы и знание о них собрано воедино, разумеется, написать switch проще. Но для этого и не надо тащить в код ООП. Вообще, если говорить о реальных проблемах ООП, их всего три:
Его не стоит тащить в простые случаи. Для кучи вещей на свете функция с параметрами — прекрасная абстракция, и непонятно, зачем заворачивать простое действие в объект, создавать класс, инстанцировать и т. п. И люди стреляют из пушки по воробьям, а потом ругаются насчёт обилия бойлерплейтов. А не надо было, ООП совсем не для того создано, оно создано как средство структурировать сложные системы абстракций, и для организации этих абстракций может применятьс довольно успешно. Кстати, есть довольно здоровая традиция в Python, там не принято начинать клепать иерархии классов до тех пор, пока эта иерархия тебе действительно не становится нужна.
Вместо реального ООП часто используют лишь языковые механизмы его поддержки, но для проектирования, котрое объектно-ориентированным не является. Иногда это оправданно, иногда — нет. Скажем, в C++ контейнеры в STL и большая часть Boost — не вполне ООП. Да, они, может, и предназначены для использования в программах, построенных с применением этой парадигмы, но внутри их классы и наследование используются часто как кодировочные трюки. Но в STL хоть всё по уму, а иногда это доходит до маразма. Классика — унаследовать самолёт от крыльев и фюзеляжа (в ООП это значило бы: «Самолёт является частным случаем крыльев и фюзеляжа»; в понимании некоторых это значит «Я так переиспользую код классов»). И многие люди успешно делают это раз за разом, удивляясь, что ничего не работает. В общем, механизмы поддержки ООП — это ещё не ООП. Более того, можно реализовывать ООП на старом добром C без намёка на два плюса на конце, как и непонятно что на C++ (и любом другом языке со встроенными механизмами ООП). Между прочим, шутка про наследование сортира от выгребной ямы — это никакого отношения к ООП не имеет. Применение наследования не равно ООП, это азы. А что до того, что код так пишут — так, перефразируя товарища Сталина, «Других програмыстов у мэня дла вас нэт!» (В оригинале было про писателей, конечно!)
Эволюция языков, вынужденных обеспечивать обратную совместимость десятилетиями, болезненна. C++ никогда не был маленьким и простым языком, но сейчас он уродливо сложен. Даже довольно хорошие программисты теперь протаптывают в нём свои системы тропок и многого не знают за их пределами. Для того, чтобы представлять этот кошмар в целом, нужно, наверное, быть не прикладником, а разработчиком компиляторов и позаниматься этим лет 20. Современный C++ чудовищен. Просто для понимания — я писал на этом языке с начала 90-х до 10-х (на C начал писать вообще в середине 80-х), когда-то вполне спокойно осилил шаблоны Алекандреску и списки типов, даже написал довольно затейливую библиотеку для работы с микроконтроллерами AVR с оптимизацией вывода на разбросанные по разным ногам GPIO логические шины, где эта оптимизация делалась на этапе компиляции. Так что я не очень боялся сложности языка до примерно конца 10-х. Потом он стал мне активно не нравиться — я не люблю пользоваться тем, что не могу понять до конца. Думаю, с другими ООП-языками, пусть и в меньшей степени, происходит то же.
А что до так не нравящихся вам полиморфизмов и наследований — могу рассказать про самый, наверное большой ООП-проект, который я проектировал (реализовывали мы его уже большой командой, но все основные иерархии спроектировал лично я). Это была картографическая библиотека, работавшая с морскими навигацонными картами (довольно многих форматов), преимущественно векторными. Лестница абстракций было довольно велика — от составных элементов одного объекта карты до карт, форматов карт (различных чуть менее, чем полностью), проекций, картографических панелей, многопоточных «рисовалок» этих панелей, картографических коллекций, системы подбора карт под конкретную область и масштаб. Сама библиотека занимала несколько мегабайт кода ещё тогда, когда ей занимался я, потом ещё подросла. Это было примерно начало нулевых, C++.
И честно сказать, я поныне считаю, что для организации абстракций подобного монстра ООП подходит прекрасно, и сейчас бы его использовал ничтоже сумняшеся. Причём даже если бы языком был C без встроенных механизмов ООП, я бы всё равно применял ООП, хотя это было бы немного более трудоёмко.
Сейчас я больше пишу на Питоне (ну и вообще лично пишу не так много, даже проектирую на уровне кода не так часто). И пока передо мной простая и частная задача, я нередко пишу её в достаточно процедурном стиле. Но когда иерархия абстракций усложняется, её становится нужно организовывать — и тут приходит на помощь именно ООП. Хотя пуристский ООП-подход я бы всё-таки применять не стал (имхо, он обычно является глупостью).
И ведь заметьте, когда у вас есть структуры данных, и для каждой структуры вы начинаете писать (ну, допустим, в том же модуле) функции, которые с ней работают, это тоже ООП, те же классы, только вручную.
Ну и вернёмся напоследок к примеру со switch. Видите ли, когда у вас одна операция над объектом (допустим, нарисовать), полиморфизм ведь и правда не так нужен, можно даже switch сделать, никто не умрёт. Но теперь представьте пример, который я сейчас приведу — реальный пример из конца 90-х, когда мы занимались накладыванием на карты погодной информации и я проектировал подсистему работы с чуть ли не десятком различных параметров. Все они хранились единообразно, для всех из них единообразно производилась интерполяция (они были заданы в узлах сетки широт и долгот на карте, для получения значения в произвольной точке приходилось интерполировать), по ним строились изолинии (причём некоторые параметры были векторными, но для построения изолиний превращались в скаляры, скажем, от вектора ветра брался его модуль). В общем, над каждым типом данных было довольно много операций, которые очень отличались по внутреннему наполнению, но вызывались из десятка разных алгоритмов, каждый из которых не хотел разбираться, с чем имеет дело, а просто говорил — вот эти сложи, на это раздели, тут выдай скаляр и т. п.
Что тут может быть лучше полиморфизма? Это ведь не игрушечный пример с треугольничками, квадратиками и кружочками, где поставил один switch — и прекрасно. А тут — есть чуть меньше десятка типов значений (что-то вектор — например, ветер, что-то скаляр) и набор операций (тоже чуть меньше десятка), которые можно делать с каждым типом значений. И куча алгоритмов, которые работают со значениями, не разбираясь с тем, с чем работают.
Нет, ну можно, конечно, собрать по операциям свитчи воедино (ну или не свитчи, чтобы не перебирать все возможные типы, а выбор указателя на функцию по идентификатору типа — массив функций завести). Но не находите ли вы, что это я велосипед для поддержки того же полиморфизма изобретаю, а имя этому велосипеду — механизм виртуальных функций. А если его не изобретать — эти ужасные свитчи будут ровным слоем размазаны по коду и знание о том, какие типы могут там быть, будет необходимо в каждом. Нет, если это — прогресс, то я лучше останусь в своём каменном веке с динозаврами и полиморфизмом.
В общем, я далёк от мнения, что ООП — это инструмент для всего подряд. Но для упорядочения реализации систем сложных абстракций оно определённо хорошо, и для этого я его использовал, использую и буду использовать, пока ничего лучшего не придумано.
Имхо, любой фреймворк типа SAFe, если он при внедрении не превратился в кастомный через какое-то время — маразм. Сами принципы Agile не предполагают жёсткости. Но нет, мы получили весь этот идиотизм сертификациями, "без бумажки ты букашка" и прочий формализованный идиотизм под видом Agile.
А ваш кто писал? Общая грамотность и наличие сложной пунктуации – это не признак работы "нейронки".
Тогда это не было странно (но тогда –в конце 86-го – я был не инженер, а школьник, 9-й класс, и с подобной техникой дела не имел). Теперешним людям, которые Ленина не помнят, было бы странно. С середины девяностых-то уже были ксероксы в свободном доступе, а первые – и раньше, примерно с распадом СССР.
Да какая теперь разница, насколько уступали или не уступали советские жанре ПК аналогам? Просто для тех, кто помнит это время, это история, которую интересно иногда вспомнить.
Я вот начинал программировать на СМ-1800 с клоном Intel 8080 и 64 килобайтами памяти. Это никак не домашний ПК, здоровенная хреновина в форм-факторе большого стола, огромная тумба справа содержала собственно компьютер и дисководы (два восьмидюймовых). На столе стоял монитор ВТА-2000, а на мониторе — пульт процессора, позволявший смотреть состояние, получать доступ к памяти и портам ввода вывода, и даже выполнять код пошагово. Для 1986 года — уже сильно устаревшая машина. Сколько времени работал компилятор C в ассемблер даже для небольших программ -- страшно вспомнить. Но чего только мы там не делали. Писали игры на C, программировали на ассемблере. Где-то достав документ, я долго разбирался с системой прерываний и научился в итоге обрабатывать прерывания от клавиатуры (вообще там в BIOS и в CP/M всё было без прерываний — да ведь там даже чтение данных с диска было совершено синхронным, никаких тебе DMA, всё вытаскивали по байтику из портов ввода, а необходимая задержка реализовывалась циклами). Эх, молодость-молодость. Как раз между 14 и 16 годами. А как я ассемблер учил по какой-то странной копии документации из доксероксных времён!
К сожалению, советская микроэлектроника действительно сильно проигрывала западной и в ассортименте, и в надёжности, да и в объемах производства тоже. Мой отец в те времена как раз занимался вопросами производства микросхем на одном крупном предприятии -- анализом отказов, выявлением причин брака и т.п. Он много тогда порассказал.
Но сорок лет прошло. Теперь всё это только история.
Он такого бреда не предлагал. Он говорил о Teal organizations, которые ни по спиральной динамике, ни по AQAL, ну просто ни фига не бирюзовые. Не говоря о том, что то, что он описывал, даже к teal не имеет отношения, там банальный продвинутый зелёный.
Феноменально! Оказывается, если написать спецификацию, вероятность успеха драматически возрастает! Срочно в номер! Ни за что бы не подумал! Agile теперь, оказывается -- работа даже без попытки заранее понять, что мы делаем.
Если спецификацию написать можно -- её надо писать, это очевидно. Если нельзя, надо хоть что-то всё-таки написать, чтобы ну хоть какие-то вещи были продуманы заранее.
Понятно, что Agile -- это группа методологий, ориентированных на работу в изменчивом окружении. Писать полную и детальную спецификацию до работ часто нет смысла. Но если горе-практики поняли это как "тогда мы вообще не будем думать заранее, всё сделаем по месту", то шансов на провал у проекта очень много. Именно из-за таких практиков и формируется подобная прекрасная статистика.
Никогда Agile не запрещал писать спецификации (да, статус спецификации другой -- это не закон, а что-то, что легко можно поменять, в то время как в более старых подходах это бумага, вокруг которой будет идти торг — сделано/не сделано). Agile -- это не хаотическая разработка. И если кто-то подумал, что это способ не продумывать ничего заранее, то так ему и надо. Чем лучше поставлена задача, тем больше шансов на успех, и это не зависит от методологии, это общее правило.
Можно не хотеть расписывать какие-то детали до того, как пощупаешь первый прототип. Но одно дело -- не расписывать "какие-то детали", для которых пока нет видения, а другое -- "ничего продумывать не будем, там разберёмся".
И если хоть каких-то вменяемых описаний желаемого нет (ну, кроме разве что проектов по багфиксингу, там есть баги, есть представления об их срочности и прикидочной трудоемкости -- иди и чини примерно в нужном порядке), это не Agile. Это бардак. Правда, нынче модно называть любой бардак Agile'ом, за что и расплачиваемся.
Паттерн Строитель нужен в основном тогда, когда вы постепенно создаёте объект с действительно сложной внутренней структурой. Например, в зависимости от того, что именно передадут, инициализируете по-разному сложные структуры данных, взаимосвязи между частями и т.п., а состав объекта может получиться разным Разумеется, применять данный шаблон для того, чтобы задать поле color, которое ни на что больше не влияет -- это настолько unpythonic, насколько возможно.
Что же до нужды в синглетонах, то поведение, связанное с инициализацией, просто совершенно не нужно вшивать в класс через __new__(). А вот статический метод get_instance() вполне может иметь смысл. Правда, и это чаще всего не нужно, как вы и написали. Кстати, самый смешной и нелепый подход к реализации синглетона в Python -- это Borg. Это правда очень странно
Вообще мне кажется, что половина проблем возникает даже не из самого паттерна, а из-за неудачной попытки вшить поведение в код неуместным в большинстве случаев языковым механизмом (new, метаклассы и подобное) вместо написания чего-то элементарного. Именно языковой механизм даёт интересные побочки типа странного поведения при наследовании.
Все эти почасовые отчёты -- обычно признак серьёзных проблем в процессе/коллективе. Например, я видел ситуацию, когда такие отчёты используются для понимания затрат по проектам. Тот факт, что точность таких данных -- плюс минус сто процентов, никого не волнует. Особенно забавно, когда такое происходит в среде, позиционирующей себя как Agile.
Интересно, что после этого очень трудно объяснить кому-то полезность ведения, скажем, burndown. И хотя в burndown интересно только то, сколько команде осталось в спринте, а не то, как ты работал по часам, доверие людей, подорванное микроменеджментом, вернуть трудно.
Что реально формируют почасовые отчёты -- так это закрытую защитную культуру, где каждого человека можно пытать, но всё равно не понять, какие реально есть проблемы и нужно ли принять какие-то меры (ну, скажем, помочь, обсудить выбрасывание части малозначимых задач из спринта и т. п. -- я не про "дисциплинарное воздействие", а про реальную работу с нормальными текущими проблемами).
Нигде, кроме фриланса, эти идиотские почасовые отчёты почти неприменимы, толку дают очень мало, а вред наносят. В том же Скраме, если так уж нужно делить расходы по проектам, точность, уверен, не упадёт, если работать с оценками в Стори Поинтах, потому что она и так очень низкая. Зато никаких дополнительных отчётов уже не потребуется. Скажем, если для хранения задач и оценок используется какой-то трекер типа Джиры, всю статистику можно собрать автоматически, не мороча людям головы бесполезными отчётами.
Каждый бессмысленный отчёт -- это подрыв нормальной атмосферы в командах.
А я там выше как раз привёл пример. Элемент поведения -- это не одно действие, а определенный аспект того, что класс делает. Это может быть далеко не одно действие. Более того, в стратегии и объекты внутри могут быть (тот же объект синхронизации в приведенном ранее пример подсчёта ссылок). Для того, чтобы считать что-то стратегией важно то, что оно делает, а не то, как реализовано. Это уже детали конкретной стратегии.
Функция, естественно, может описывать поведение. Но, скажем, упомянутое выше "как синхронизировать доступ к счетчику ссылок" -- это вполне себе аспект поведения объекта. Хотя там -- минимум два метода, стратегия описывается классом, а в экземпляре класса ещё и объект синхронизации (под виндой, скажем, критическая секция) запрятан.
Вообще суть понятия стратегия -- точно не в том, как именно она реализована. Класс (именно класс без объекта -- в программировании на шаблонах частое решение), объект, функция -- всё это может быть стратегией. Тут важна цель -- для чего, что стратегия делает. Скажем, работает объект с БД. Надо ли закрыть транзакцию в конце? Иногда надо, а иногда это просадит производительность, да ещё и ошибки может вызвать. И вот параметризация стратегией позволяет это настроить. А как что стратегия реализована -- вопрос десятый.
В немного более общем виде, стратегия — это элемент поведения, который передаётся в объект или функцию для кастомизации какого-то аспекта поведения. И далеко не всегда это одна функция, часто целое семейство. Когда-то давно в C++, скажем, базовый класс для объекта с подсчётом ссылок параметризовали классом-стратегией, определявшим правила многопоточной синхронизации для доступа к счётчику. А там минимум пара функций. Стратегией это, конечно, быть не переставало.
Имхо, сама история термина "бирюзовая компания" на русском совершенно позорна.
Во-первых, они ни капли не бирюзовые. Система кодирования цветов заимствована из модели AQAL Уилбера. И там в русском языке есть сложившаяся и устоявшаяся традиция переводить Teal как "изумрудный", а как бирюзовый переводить следующий, Turquoise. Так что для начала Teal organizations -- это изумрудные организации, но с лёгкой руки бывшего не в контексте первого переводчика мы уже получили то, что получили, традицию перевода термина, конфликтующую с традицией перевода литературы по сопряжённым областям.
Во-вторых, если вчитаться в описания организаций у Лалу (видно, что там устраняются иерархии) и в то, что собой представляют уровни AQAL, то и к изумрудному уровню эти структуры отношения не имеют, это всего лишь дальнейшее воплощение "зелёных" идей. Следующий же уровень, изумрудный (обозванный по ошибке бирюзовым), этой идеей не одержим, он достаточно спокойно выстраивает иерархии и достаточно гибко их меняет. "Зелёному" уровню вообще свойственна несколько нездоровая фиксация на идеях всеобщего равенства.
Я абсолютно уверен, что никаких "бирюзовых" или "изумрудных" организаций в бизнесе на сегодня нет и быть не может, ни в России, ни где-либо ещё. Более того, свойства организаций данного уровня пока не очень понятны. Но они точно не будут деиерархизированы. Гибкими -- вероятно. Находящими место людям с разным развитием -- да. Но помешанными на равенстве -- нет.
А вся эта традиция считать организации то ли бирюзовыми, то ли изумрудными -- результат хайпа, не имеющего под собой вообще никаких реальных оснований.
Вообще степень токсичного влияния зелёных идей трудно даже описать. Именно отсюда растут ноги бесчисленных заигрываний с персоналом, совершенно искаженных игр в псевдодемократию даже там, где она совершенно неуместна, и многое, многое другое. Половина Agile заражена этими идеями. Отсюда берутся команды, которые не в состоянии обеспечить хоть какую-то прозрачность и контролируемость работы, но при этом агрессивно претендуют на полную самоорганизацию и их право решать любые вопросы в пределах своей досягаемости. Отсюда берётся нарождающаяся традиция руководства, заигрывающего с подобными подчинёнными, и много чего ещё.
Диктатура плоха, спору нет. Но лучше ли ЭТО? Я сомневаюсь.
Да, тоже забавно читать рассуждения про бессмысленность ООП. Вполне рабочая парадигма до сих пор. Не единственная, и я бы не заигрывался в пуризм. Но стопроцентно живая. Просто нет смысла играть в неё в некоторых (обычно простых) случаях. На свете есть очень много вещей, которые просто реализуются функцией, и не надо натягивать их на ООП. Меня в своё время поразил раздолбайский подход, принятый в Python. Разумеется, ООП там поддерживается, и часто активно используется, но на нижнем уровне часто очень много сделано просто функциями без какой-либо потуги на ООП. К этому стилю я после многих лет C++ привыкал, но потом оценил. Впрочем, иерархии классов я там всё равно использую, просто только там, где это действительно проще и понятнее. Даже многие паттерны проектирования там принято реализовывать совсем иначе. Но и там ООП много. Некоторые абстракции всё-таки хорошо реализуется именно средствами ООП. Но не все.
Критики же ООП не понимают обычно, что если они использовали, скажем, наследование -- это не обязательно ООП. Если единственный мотив для этого наследования -- переиспользовать код, то речь о применении языковых механизмов поддержки ООП для проектирования, не являющегося объектно-ориентированным. В классическом ООП наследование всегда выражает отношения генерализации (общее-частное). Словом, нет с ООП никакой проблемы, кроме двух случаев -- кривого использования и стрельбы из пушки по воробьям (т.е. искусственного впихивания в парадигму простых вещей, совершенно в этой парадигме не нуждающихся).
Современное программирование, строго говоря, мультипарадигменное, как и большинство современных языков. Оно совершенно не ограничено ООП, что, впрочем, ООП не отменяет. Некоторые языки больше провоцируют применения ООП, некоторые меньше, но чуть не все поддерживают.
ООП на уровне языка со своими тремя китами (инкапсуляция, наследование, полиморфизм) очень удобно для описания НЕКОТОРЫХ абстракций. И существенно менее удобно для других. Собственно, в этом и ответ на вопрос, когда и кому нужно ООП.
И если для чего-то наиболее подходящей абстракцией будет функция, а не класс с методами, то ничего плохого в этом нет совершенно. Хотя многие базовые абстракции очень удобно оформлять как классы. ООП -- прекрасная штука, просто не стоит натягивать сову на глобус.
К слову, значительная часть даже стандартной библиотеки C++ -- это не вполне ООП, иногда даже вообще не ООП.
Если ООП понимать как обязательное к исполнению везде и во всём -- это кандалы. Зачем себя ограничивать раз и навсегда? Если как инструмент, очень неплохо решающий свои задачи удобного описания НЕКОТОРЫХ абстракций -- это совсем другое дело.
Если же говорить о паттерне Strategy, то весь этот "ужас" кажется излишними тяжестями до тех пор, пока вы не оказываетесь перед необходимостью кастомизировать какое-то действительно сложное поведение. Впрочем, и тут все зависит от ситуации и, кстати, языка. Если желаемое поведение состоит из одного действия, а не развесистого интерфейса, вполне может хватит, скажем, передачи простой функции. А где-то стратегию удобно делать параметром шаблона. Общая идея одна, а реализации совершенно разные.
И наконец, есть ООП как идея и есть поддержка механизмов ООП на уровне языка. Интересно, что эти языковые механизмы ООП можно использовать для программирования в стиле, который ООП вообще не является. Допустим, то же наследование для реализации частного случая, расширяющего базовый -- это именно ООП. А наследование для переиспользования функциональности класса -- это не ООП (но нередко хороший кодировочный трюк). Ну и наоборот, ООП на чистом C -- совершенно возможная (даже нередкая) вещь, просто приходится обходиться без языковых механизмов, которые в C++ есть.
Я видел в европейской компании это. И слово "нажать" можно употреблять по-разному. Я вообще не о диктаторстве. Я о псевдоаджайл-трансформациях с выпиливанием инструментов вертикального управления. Вообще никто не может от команды ничего потребовать. Есть PO, есть бэклог. PO не заинтересован, никакую общетехнологическую мелочь с уровня компании не пропихнуть. Или приходит аджайл-коуч, говорит -- трудности вижу такие-то, надо пробовать то-то (и нет, он их не на голове стоять просит). И они что, пробуют? Нет, пусть трудности очевидны, они говорят: нет, не хотим. Творят реально чёрт знает что, но нажать (не раздавить, а просто сказать: мы нашли вам коуча, поделайте некоторое время то, что он предложит), некому. У нас демократия.
И очень часто аджайл понимают именно так. "Всё решает команда, всё равны, никто нам не указ". Хотя реально идея была всего лишь "не дергай команду без нужды, дай им делать своё дело, справляются -- не трогай". И когда команда справляется и изначально может сорганизоваться, это отлично. А если нет? Некоторые команды надо реально учить самоорганизовываться.
Самое глупое, что в конце концов при долгой неэффективной работе всё равно всех разгонят по причине финансовой невыгодности.
Очень всё это напоминает рассуждения о дисфункциональную зелёном из книги Уилбера "Трамп и эпоха постправды".