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

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

НЛО прилетело и опубликовало эту надпись здесь
те языки и подходы, которые в итоге оказывались самыми практичными и популярными

Популярность — продукт рекламы и продвижения крупными компаниями. Качество при этом и рядом не валялось. Только финансовые интересы.

А с учетом такой популярности про практичность говорить бессмысленно.

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

Прикладное программирование — это когда школота лепит быдлокод? Для них и придуман был ООП.
Один мой знакомый в давние времена сказал такую умную вещь: объектное программирование должно быть в голове, а не в языке.

А большинству современных программистов до Дейкстры, как до Шанхая…
Прикладное программирование — это когда школота лепит быдлокод?

Ну, если школота прямо сейчас решит задачу, которая позволит уже завтра продать продукт, то почему нет? У меня есть один коллега, все ворчит, что код не тот. Но когда ему говоришь «ОК. У тебя появляются возможность 2 дня писать код без оплаты». Он вдруг задает вопрос, а почему это?

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

Хотим мы с вами или нет, но мы решаем задачи бизнеса. А пользователь на бизнес стороне, как правило, понятия не имее про ООП или BDD или синглтон. Ему наплевать что там в кишках! Он просто тыкает кнопку и если она не решает его задачу он не платит денег. И хоть вы там идеальную архитектуру наколбасите ему будет пофиг, лишь бы кнопка делала то что надо!
Проблема быдлокода лежит в другой плоскости. Ну набросал ты по быстрому и оно у тебя даже работает, продал… и тут клиент хочет ещё рюшечку добавить, ну так мелочь и с точки зрения клиента это решается просто «ну оно же тут вот… сюда сюда и всё» а вы смотрите в код и понимаете что нужны изменения которые будут стоить так же как исходный продукт, а то и дороже. И с каждым очередным внедрением этих рюшечек будет всё сложнее, дольше и дороже. В конечном итоге получается программа-мотылёк, поработала один день и дешевле написать новую чем модифицировать эту.
Собственно всё упирается в том, что мы не умеем делать хорошие программы. Не в том смысле, что технологий нет. Нет культуры. Как в 90е, когда «новые русские» ринулись строить свои безумные котеджи.

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

Автор же, совершенно справедливо, отмечает, что нормальные, удобные дома — так не строят. Но до этого индустрия разработки ПО — пока не доросла.

На самом деле методы написания хороших программ были придуманы и стандартизованы ещё во времена первых Space Shuttle. Просто до сих пор мало кто в это умеет: из-за холиварщиков типа Дейкстры программисты уже три десятка лет занимаются поиском самого "правильного" языка программирования и обсиранием "неправильных" языков: это как если бы электронщики каждые 5-10 лет придумывали новый набор обозначений на схемах для отображения всё тех же резисторов, транзисторов и конденсаторов.


А решение проблемы находится на более высоком уровне: на уровне организации, методик и подходов.

А решение проблемы находится на более высоком уровне: на уровне организации, методик и подходов.
… которые не позволяют даже NASA избежать ошибок…

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

Это уже скорее аналог добавления новых функций в библиотеку: никто не пересматривает обозначения резистора, например, или сложения в математике.

Есть 2 обозначения резисторов, «советский» прямоугольником и «западный» пилой. Диоды следовало бы пересмотреть — когда рисовали их значок, думали что ток течёт в другую сторону, а дальше «так исторически сложилось».
Навскидку.

Всё в порядке с диодами, просто не надо путать направление тока и направление движения электронов!


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

Есть 2 обозначения резисторов, «советский» прямоугольником и «западный» пилой.

Есть примеры и в математике: tg и tan, скажем. Или десятичная запятая/точка.


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

А вот тут никакой проблемы нет. Направление тока (туда же, куда электроны, или в противоположную сторону) вообще не имеет особого значения на практике для расчёта цепей. Надо просто один раз договориться. И договорились: от плюса к минусу.

На самом деле методы написания хороших программ были придуманы и стандартизованы ещё во времена первых Space Shuttle.
И сколько такие методы будут стоить компаниям? В том-то и проблема что хорошесть программ может определяться очень разными способами, не существует универсального параметра качества. И стоимость разработки решения практически всегда вносит определенный вклад в то, насколько программа хороша для ее заказчика.

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

Нужно исходить из ситуации. Какие могут вообще ситуации?
1. Пользователь решил проблему. Больше не пришел
2. Кроме одного пользователя эту сделанную фичу больше никто не использовал
3. Пользователь решил проблему. Пришел с багой\предложением

и еще 100500 вариантов развития будущего.

Вы уверены, что в написанном коде вам действительно придется ковыряться и что-то переделывать? Да, можно вспомнить закон Мерфи про то что худшее по-любому случится, если оно может случиться. Но на практике вариаций развития будущего масса! Как надо будет переписывать так и не надо, а иногда и оказалось не надо и лучше удалить код, нет кода — нет багов!

Мы же когда пишем код мы же обычно так поступаем:
Встретилась задача просто пишем код. Встретилась второй раз тупо копи-пастим. Встретилась в третий — выносим в отдельную функцию. Втретилась в четвертый — выносим в библиотеку.

Ну так и тут надо также! Сначала вы просто пишете код. Да, стараетесь написать по-лучше. Но слишком заморачиваться не стоит, т.к. «лучшее враг хорошего». И уж если потом потребуется тогда и сделаете как надо, т.е. тогда, когда это действительно будет оправдано.

Другими словами: Если значимость и ценность кода еще не доказана требованиями бизнеса, то тратить на него слишком много времени и усилий не следует. Исходить нужно из математической фразы «необходимо и достаточно»
Исходить нужно из математической фразы «необходимо и достаточно»

Я всё же предпочитаю делать небольшой "запас прочности", да хотя бы чтобы удалить больше ненужный код было просто без особых рисков сломать что-то.

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

Чтобы написать хороший код вы должно понимать (и желательно хорошо так понимать) — что за код вы пишите и как он долежн работать.

Чтобы написать плохой код… вам не нужно ничего. Тяп-ляп и в продакшн.

То есть написать какой-то плохой код — гораздо быстрее, написать отлаженный плохой код… на самом деле даже дольше, чем хороший… но кого это волнует?

Посмотрите вокруг: «потерявшиеся» кортинки, неработающие кнопки и так далее и тому подобное… это же норма! Никто не доводит код до безошибочного состояния.
>>>и тут клиент хочет ещё рюшечку добавить
Мы, программисты, всегда почему-то свято верим в то, что пользователь придет и по-любому попросит что-то поменять\добавить. А это не так! Да, приходят и просят, но не всегда! Очень часто решат свою задачу и забудут, то у вас в принципе есть такая хорошая программа.

Как я в прошлом году пошел в гугл, нашел youtube-видео-даулодер, поставил, скачал видео и все! Больше я НИ РАЗУ с прошлого года эту прогу не запускал. Мне абсолютно наплевать насколько там идеально хорошо продумана архитектура.

Вот вы напишите сразу «хорошо» потратите на 5 часов больше времени, а пользователь больше ни разу не обратиться по поводу вами разработанной фичи. А из какого кармана эти допольнительные 5 часов будут оплачиваться? Из вашего? Согласны, чтоб из вашего, а не работодателя?

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

Все так. Вот в чистом виде конфликт между экономикой для результата и экономикой для прибыли.

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

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

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

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

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

А разве таковым не был Dart который не взлетел?

Не знаю, не знаком. А вот то, что взлетело, это как птица в том анекдоте — летит и гадит.

Сейчас уже есть typescript который потихонечку взлетает и webassembly

Думаю, что Джон МакКарти (отец LISP, 50-е годы) и многие другие великие и умные люди сказали бы вам много причин, по которым статический контроль типов не нужен.
Не нужен — да, обходился же LISP без него. Полезен? Ну я думаю Джон МакКарти не стал бы заниматься LISP 2 с поддержкой статической типизации, если бы он считал эту идею бессмысленной.

Другое дело, что оказалось не так-то просто добавить статическую типизацию в LISP и не потерять его выразительности…

Думаю, что многие другие великие и умные люди вроде Пирса, Барендрегта, Мартина-Лёфа или Жирарда с ним бы с интересом побеседовали о нужности типов.

И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль. Очень возможно, что из Haskell/Rust/F# сообщества появится такая система типов. А до тех пор у нас будут void *, interface {} и пр. в языках, которые имеют статическую типизацию.

Самая лучшая апологетика динамической типизации (не с ура-пофигистских позиций типа «а нафига типы?»), которую я видел, здесь:
youtu.be/YR5WdGrpoug
youtu.be/2V1FtfBDsLU?t=1143 (можно даже отсюда начать: youtu.be/2V1FtfBDsLU?t=1639)
И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль. Очень возможно, что из Haskell/Rust/F# сообщества появится такая система типов.

Она, ну, именно что уже есть. Пишу на хаскеле, система типов очень помогает, ни на что её не променяю.


А до тех пор у нас будут void *, interface {} и пр. в языках, которые имеют статическую типизацию.

Ни разу не использовал void* даже в C++, ЧЯДНТ?


Самая лучшая апологетика динамической типизации (не с ура-пофигистских позиций типа «а нафига типы?»), которую я видел, здесь

Расшифровка есть?

Расшифровка есть?

Текст выступления? Или перевод на русский?
Насколько я знаю, ни того, ни другого нет.

Текст, лучше в оригинале.


Жалко, смотреть видео — ну такое.

Ни разу не использовал void* даже в C++, ЧЯДНТ?

Прекрасная позиция, ведь за Вас его используют авторы библиотек. )) Вот пусть они и отдуваются)))
А чем плоха позиция? Goto за нас ставит компилятор — и вроде как уже все смирились с с тем, что это его работа.

А void* — да, с ним авторы библиотек пусть возятся. Это их работа.
Ну да, Вы же весь в белом! Зачем Вам руки пачкать? Так?

Какие авторы?


Я, если что, писал свой any и свой variant. Даже там как-то удалось обойтись без void*.


Единственная библиотека, которую я могу вспомнить и которая использует void* достаточно активно — Qt, да и то потому, что они так реализуют сигналы/слоты для совместимости со старыми компиляторами.

Я, если что, писал свой any и свой variant. Даже там как-то удалось обойтись без void*.

Было бы занятно взглянуть. Это возможно?

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


Но, если в общем говорить, any делается через type erasure, variant — на самом деле даже проще, через вариадики.

Никогда толком не вникал, что такое type erasure, но интуитивно тот же void*, вид сбоку. А в Вашем variant на вариадики (шаблоны, я верно понял?) можно менять содержимое на другой тип после объявления?
Если Вы используете такой срез C++, где не нужно явно использовать просто машинный адрес и/или взаимодействовать с C — почему нет. Но я бы не стал этим гордится)))
Никогда толком не вникал, что такое type erasure, но интуитивно тот же void*, вид сбоку.

Не, ну если под определённым углом...


А так — указатель на базовый класс, от которого наследуется шаблонный производный класс. Всё. Никаких void*, всё очень типобезопасно.


А в Вашем variant на вариадики (шаблоны, я верно понял?) можно менять содержимое на другой тип после объявления?

Вы имеете в виду такое?


variant<int, std::string> v { 42 };
v = "42";

Если такое, то да, можно. Там внутри тоже в каком-то смысле type erasure вылезет, но за счёт того, что список типов известен статически, можно даже в RTTI не лазать.


Если Вы используете такой срез C++, где не нужно явно использовать просто машинный адрес и/или взаимодействовать с C — почему нет. Но я бы не стал этим гордится)))

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


А если вы пишете на настолько низком уровне, то потребность в void* обусловлена не какими-то недостатками статической типизации, а самой сутью предметной задачи. И смоллтолк с джаваскриптом тут бы вряд ли помогли.

А так — указатель на базовый класс, от которого наследуется шаблонный производный класс. Всё. Никаких void*, всё очень типобезопасно.

И указатель на этот самый базовый класс и оказывается нижней гранью выбранной «подсистемы» типов. Но C++ не ограничивается только этой «подсистемой», поэтому void* в нем необходим.
Я понял, что Вы имеете в виду. Согласен, для каждого конкретного случая можно обойтись без void*, зафиксировав нужные типы в параметрах шаблонов.
Но не согласен, что void* не является полноценным элементом системы типов. Как и просто void, в шаблонах он тоже вылазит и, по слухам, там без него не обойтись. То, что void* можно использовать для обхода типизации — следствие наличия в языке низкоуровневых средств и особенностей типа «указатель на» (преобразование T*->void*->T* безопасно). Именно это средство C++ делает возможным «бескомпромиссную совместимость» с миром C и с аппаратурой, например.
Так что гордится тем, что ни разу не использовал void* не стоит: просто повезло запереть себя в «чистой комнате». Как-то так)
Но не согласен, что void* не является полноценным элементом системы типов.

Является, конечно, обратного никто и не говорил. Наоборот, там выше было утверждение (на которое я и отвечал), что void* прям нужен в C++, иначе никуда.


То, что void можно использовать для обхода типизации — следствие наличия в языке низкоуровневых средств и особенностей типа «указатель на» (преобразование T->void->T безопасно). Именно это средство C++ делает возможным «бескомпромиссную совместимость» с миром C и с аппаратурой, например.

T* -> char* -> T* тоже безопасно, кстати. Или через std::byte теперь.


Впрочем, давайте с другой стороны. Почему при работе с железом нельзя обойтись без void*?

Впрочем, давайте с другой стороны. Почему при работе с железом нельзя обойтись без void*?

Возможно, это просто историсеское наследие, но думаю, что преобразование беззнаковых целых в указатели так или иначе включает «создание» void* из целого. Указатель ведь, по сути, структура с битовыми полями и кучей атрибутов, и на низком уровне на него накладываются ограничения, которые, ну, скажем, невыразимы в языке, по крайней мере в текущем виде. Нужен какой-то тип данных, который позволит все это делать. Я так думаю. Это может быть и не видно явно, для C и даже C++ такая спрятанная в компилятор неявность — дело обычное.
И да, указатель на байт может заменить указатель на void. Но что это даст в плане типобезопасности? Те же яйца, вид сбоку, в gnuc void* ведет себя, как byte*.
Вон сколько уже ISO/IEC TR 18037 все не примут, так и нужда пройдет.
Возможно, это просто историсеское наследие, но думаю, что преобразование беззнаковых целых в указатели так или иначе включает «создание» void* из целого.

Зачем? reinterpret_cast<Foo*>(0xd34df00d). Снова никаких void*.

Если честно, то не задумывался, что можно через reinterpret_cast, пребывал в заблуждении, что все-таки нужен указатель. Не сказал бы, что это чем-то лучше.
Остался в языке для совместимости с C, видимо.
Сколько безобразного кода люди готовы написать только для того, чтобы гордо заявлять «мне void* не нужен!»… )))

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


Да и как вы в C++ из void* получите более типизированный указатель, кроме как кастом? Это в C он сам ко всему приводился.

В молодости я тоже так думал.)))
А сейчас и (void*) кажется годным, да и пишу больше на C…
Хотя согласен, если уж плюсы, то лучше если все по взрослому, чтобы без слез на код не взглянуть. Более строгая типизация, чего уж там.

Ну это уж совсем субъективный вопрос. Я бы не сказал, что код на плюсах стрёмнее потому, что там reinterpret_cast вместо того, чтобы всё заметать под ковёр void*.

И Алан Кей и Рич Хики говорят, что ещё не видели системы типов, которая бы помогала, а не мешала, и не причиняла бы боль.

Ну вы не ровняйте всех программистов с Кеем и Хикки. Им, может, и не помогает, а программистам ниже уровня — помогает вполне.
Я вот дурачок, самому думать трудно, по-этому мне часто проще, когда за меня думает тайпчекер.


Самая лучшая апологетика динамической типизации

Рассуждать о динамике вс статике сейчас имеет смысл исключительно в контексте тайскрипта. С-но, значительную часть проблем тс решает, из оставшейся части значительную часть — не решает, но оно в принципе решаемо.

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

Только, кажется, отвечали вы на самом деле не мне, ну да ладно.

Да, сорри. Запутался в ветках :)
Это был ответ на пост evocatus, конечно же.

Ну, если школота прямо сейчас решит задачу, которая позволит уже завтра продать продукт, то почему нет?

Потому что предпочтительнее считается смотреть не только на один день вперед, но ещё и на два, а иногда и ещё больше, например?

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

А речи про крутое решение за куччу денег не было, можно и MVP накидать нормально, а можно сделать уже абсолютно нерасширяемую программку.

очевидно что второй вариант потратит заметно меньше ресурсов

martinfowler.com/bliki/DesignStaminaHypothesis.html

И даже в первом случае выкатить что-то на говнокоде и допиливать потом имеет смысл

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

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

А может совсем и не оказаться. Вопрос в том, происходит ли оценка рисков, или higest payed person/product owner считает что «так выгоднее», потому что ни капли не программист и далёк от критериев качества ПО.

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

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

Легко можно продать:


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

:)

Это на словах легко )
Может, как я и писал, можно, но я не слышал о таких success story, возможно потому что объяснять уже нужно будет не только бизнесу, а ещё и юзерам/инвесторам как-то сознаться что в продукте все не так радужно.
Возможно потому, что, опять же, дорого, конкурентов надо как-то обгонять.

Я понимаю, потому и смайлик поставил. На самом деле при разработке чего-то вроде MVP и его дальнейшем развитии я стараюсь искать баланс между скоростью и поддерживаемостью/расширяемостью/… Благо веб-разработка позволяет относительно малой кровью совмещать старый и новый код даже на разных фреймворках, языках, ОС и прочих платформах, часто всего лишь изменением конфига реверс-прокси.

За ссылку на Фаулера спасибо (хотя я подозреваю что конкретно это я уже читал), но речь не о том, чтобы меньше тратить на дизайн. Речь о том, что есть множество неизвестных. Можно попробовать долго и упорно уменьшать их количество на стадии дизайна — организовывать опросы, разбирать детально предметную область, может быть заказать парочку исследований даже. И только после этого закончить дизайн. А можно — разработать дизайн с кучей неизвестных, выкатить демо версию и посмотреть на реакцию. Я понимаю что аналогия будет выглядить не очень уместной, но мне кажется она подходит: это как с тестовым запуском ракеты — есть масса неизвестных, которые можно промоделировать за большие деньги и долго, а можно быстрее и дешевле (не всегда конечно) сделать прототип, запустить его и просто все что нужно измерить.
конкуренты будут делать качественный и стабильный продукт, выкатывая новые фичи в срок и не ломая старый фукционал.
Чтобы получилось как вы говорите нужно чтобы конкуренты умели сделать быстро и качественно то, что мы умеем либо быстро, либо качественно. В рельности же скорее всего конкуренты после нашего MVP будут все же на шаг позади. И если они начнут долго и упорно пилить идеальный продукт, то о них можно вообще не думать, к тому моменту как они закончат рынок уже будет поделен среди тех кто выкатывал быстро но не очень качественно.
Вопрос в том, происходит ли оценка рисков, или higest payed person/product owner считает что «так выгоднее», потому что ни капли не программист и далёк от критериев качества ПО.
А этот вопрос вообще за рамками текущего обсуждения. То же самое может произойти в любом случае.
Дело в том что проект и с кучей неизвестных можно сделать более менее качественным и расширяемым, и не иметь необходимости его переписывать. А можно наклепать и за несколько месяцев такого, что расширять не проще чем с нуля переписывать.

Ну и обычно MVP всё же часть будущего продукта, а не вещь которую скоро выкинут
Дело в том что проект и с кучей неизвестных можно сделать более менее качественным и расширяемым, и не иметь необходимости его переписывать.
Я в этом не уверен. В каких-то частных случаях — наверняка. Но мне не сложно представить случай когда расширяемость будет сделана не для того и не туда для чего и куда она будет нужна. Можно попробовать запланировать расширяемость во все возможные направления, но результат будет так себе выглядеть и работать будет, скорее всего, совсем не просто. И даже так для меня не очевидно что это в принципе всегда возможно.
Невозможность архитектуры приспособиться к каким-либо изменениям говорит об ошибках в проектировании, и чем менее качественная у программы архитектура, тем сложнее вносить в неё изменения, вот и всё.
Анализ потока изменений имеет место быть, но при грамотном проектировании не должно происходить ситуаций требующих переписывания значительной части приложения, а в «написанном школниками на коленке» MVP — вполне себе.

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

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

Предполагать будущее — так себе затея.
Если мы пока что не предполагаем, например, что наше приложение будет использовать другие устройства ввода/вывода, это вовсе не повод забыть про абстрагирование от них, и лепить прямую зависимость из всех модулей что с i/o работают.

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

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

Ну и время на создание MVP все же оптимизируется сокращением и упрощением фич, а не выкидыванием этапа проектирования/рефакторинга.
Предполагать будущее — так себе затея.
Конечно. Именно об этом я и говорю. Но не предполагая будущего вы не сможете построить расширяемую систему. Потому что вам необходимо знать куда она должна уметь расширяться.
не выкидыванием этапа проектирования/рефакторинга.
Никто не говорит про выкидывание. Но очевидно что на этот этап можно потратить разное количество времени. И достаточно очевидно что с какого-то момента дополнительное затраченное на него время будет приносить все меньше и меньше пользы, а потом даже начнет работать во вред. Вы про overengeneering когда-нибудь слышали? Термин не на пустом месте появился, его не маркетологи придумали.
Но не предполагая будущего вы не сможете построить расширяемую систему. Потому что вам необходимо знать куда она должна уметь расширяться.

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

Вы про overengeneering когда-нибудь слышали? Термин не на пустом месте появился, его не маркетологи придумали.

Да, но я не считаю что посадить на проект разработчиков, а не «школьников на коленке» — оверинжиниринг.
Так ведь суть проектирования в том чтобы при появлении новых / изменении требований менять как можно меньше кода, для этого ведь нужны всякие DI, OCP, ISP.
Я не очень понимаю — вы пытаетесь сказать что всегда можно спроектировать так чтобы потом не было проблем с расширением? Или с чем именно вы спорите?
> есть предположение что наше решение решит чью-то проблему.

Быстро сделав прототип «тяп-ляп», мы получаем:
— возможность рекламировать что-то относительно реальное, а не «вот через 10 лет будет вау»
— Инвесторы гораздо легче одобрят что-то прототипное прямо сейчас, чем графики-картинки и сказки как будет круто
— можно набирать пользователей, получать реальную обратную связь, действительно полезные замечания внедрять сразу
— В конце концов, это даст возможность оценивать реальную нужность идеи.

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

Много используете программ, написанных Дейкстрой? Я — ни одной.

почти как сказать «много ли вы используете компьютеров, сделанных Тьюрингом?»

Это примерно как если бы Тьюринг давал советы по пайке и разводке инженерам, делавшим ENIAC.

Учитывая, что Тьюринг занимался проектированием компьютера ACE, возможно, он смог бы что-то дельное посоветовать и разработчикам ENIAC.

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


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

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

А, например, COBOL вы в ту же категорию относите?

На COBOL крутится очень много бизнес-логики, на нём до сих пор пишут ПО. Раз его используют спецы в этой области — значит для своих задач он хорош. Мнение человека вроде Дейкстры, не имеющего опыта в энтерпрайзе, годится лишь для холиваров. Для меня вообще удивительно, что он допускал такие резкие высказывания — видимо в академической среде какая-то своя атмосфера: не шарящим студентам, лижущим тебе задницу ради оценки, всегда можно навешать на уши х*ету.

Раз его используют спецы в этой области — значит для своих задач он хорош.

Нет, не значит. Скорее это значит что так 'исторически сложилось'

Во всяком случае это решать спецам, а не холиварщикам со стороны.

Насчёт «исторически» возможно отчасти и так, я Кобол не знаю, но я знаю Фортран, ещё более древний язык. По большому счёту, это первый высокоуровневый язык программирования вообще. И я знаю, что в науке и технике он до сих пор широко применяется. Просто потому, что он на самом деле самый удобный и быстрый язык для численных расчётов. Причём даже в новых проектах с нуля, без наследия 60-х годов. И сейчас нет проблемы конвертировать Фортран в другие языки. Первые трансляторы, например на Си, появились как минимум 30 лет назад.
Просто потому, что он на самом деле самый удобный и быстрый язык для численных расчётов.

Как там с аналогом плюсовых expression templates?


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

Последние исследования по expression templates, которые видел, утверждают что они уступают вручную оптимизированному коду. Года 4-5 назад, но уже не помню подробностей.
Мне кажется, что ET все-таки скорее средство «быстрой разработки» численных расчетов, дающий вполне достойную, но не пиковую производительность. Может быть, требования к срокам и стоимости разработки стали жестче и теперь нельзя шлифовать код до идеала?

Там же не зря ещё «удобный» упоминается. А так-то понятно, что любой результат, полученный с использованием хоть плюсов, хоть фортрана, можно написать на ассемблере.

Аналогов в самом языке ещё нет. Кое-что делается оптимизирующим компилятором, также есть внешние инструменты типа Parametric Fortran и Fypp.
Я не зря написал «удобный и быстрый». Expression templates в C++ это просто кошмар какой то. Чтобы их использовать в реальных задачах нужен настоящий эксперт в С++, который потратил годы на обучение и изучение всех тонкостей языка. Это реально эзотерическое программирование. А отладка? Сообщения об ошибках в шаблонах способны заставить рыдать даже закалённого бородатого кодера. Нет, это не подходит для программирования научного кода, который обычно пишется учёными, а не профессиональными программистами. Конечно, можно нанять и их. Например у нас в организации на каждый десяток physical scientists есть один или пара computer scientists. Но оказалось, что их усилия гораздо эффективнее направить на другие методы оптимизации, например переписать под многопоточность или векторные операции, ручную оптимизацию кода наконец.
Expression templates в C++ это просто кошмар

А это вообще в реальности кто-то использует? В чем проблема использовать внешний кодогенератор?

Я использую.


Проблемы с кодогенераторами в том, что их интегрировать тяжело. Если у меня есть модуль класс для работы с SQL, то я хочу иметь возможность прям там написать что-то вроде (пример почти из жизни)


auto GetRecentRSSNews(FeedID feedId)
{
    return m_myOrm->Select(field<Feed::id> == feedId &&
            field<Channel::feedId> == field<Feed::Id> &&
            field<Item::channelId> == field<Channel::id> &&
            field<Item::date> >= yesterday());
}

без того, чтобы туда как-то ещё кодогенерацию вкорячивать.


А как-то построить дерево выражения, протайпчекать его и, возможно, соптимизировать позволяют именно expression templates.

А есть какие-то фундаментальные причины, по которым в язык не могут прикрутить нормальные макросы, с которыми это все было бы ez? Как я понимаю, востребованность есть, в чем затык? Просто ретроградство тех, кто пишет стандарт, или что-то более серьезное?

Это сложно сделать правильно сразу, а у тех, кто пишет стандарт, есть большое желание делать правильно сразу.


Впрочем, метаклассы отчасти помогают, компилтайм-рефлексия поможет, на CppNow вот какой-то чувак аналог гигиенических макросов предлагал.

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

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


Ну и да, я не считаю себя топовым спецом по C++, но в expression templates нет ничего сложного. И, спасибо clang, сообщения об ошибках уже давно весьма хороши.

И, спасибо clang, сообщения об ошибках уже давно весьма хороши.
Они, кстати, вполне неплохи и в последних версиях gcc… но это потому что их clang заставил…

Я ченджлог gcc 9 читал не так давно и поймал себя на том, что там как-то прям даже вкусно сделали.


Да, спасибо clang'у за то, что возродил конкуренцию среди компиляторов.

Да, я смотрел эти библиотеки, а именно Eigen и Blaze. Увы, они не быстрее давно известных библиотек, написанных на обычном Фортране типа Intel MKL.
Фортран дожил до наших дней только потому, что на нем на заре программирования было написано много расчетных программ, и никто сейчас не хочет их заново переписывать. По современным понятиям Фортран — такое же уродство, как и JavaScript. Но не надо в него кидать камни, он был первым языком высокого уровня. А вот зачем сейчас плодят уродов? Но это риторический вопрос.
НЛО прилетело и опубликовало эту надпись здесь
в 16 реакте мы наблюдаем феерический разворот от классов к функциональному стилю )
Вы хотели сказать «к процедурному»? Тот феерический ужас, который они реализовали с useState не имеет ничего общего с фп, а просто какая-то хрень от современных ооп-хейтеров, чтобы не писать гадкое слово class
Вы хотели сказать «к процедурному»? Тот феерический ужас, который они реализовали с useState не имеет ничего общего с фп, а просто какая-то хрень от современных ооп-хейтеров, чтобы не писать гадкое слово class

Самое смешно в данной ситуации то, что useState — это, по факту, бедная реализация методов классов на замыканиях из SICP :)
То есть, вместо того, чтобы использовать нативные классы, ребята просто сделали свою параллельную костыльную ООП-подсистему.

НЛО прилетело и опубликовало эту надпись здесь

Так вот сложилось, что функция от процедуры и правда не сильно отличается. А вот функциональное программирование и процедурное — два разных мира.

НЛО прилетело и опубликовало эту надпись здесь
но при этом позволяет использовать любую парадигму программирования

Как там с type-driven development в стиле Idris, например?

Не трогайте их. А то они сейчас компилятор Idris в js напишут и скажут, что его возможности — это возможности js.

Люди вечно путают тьюринг-полноту с возможностями языка. Так-то можно сказать что любой язык с eval поддерживает любую, мыслимую и немыслимую, парадигму — просто потому, что он полон по Тьюрингу.
А то они сейчас компилятор Idris в js напишут и скажут, что его возможности — это возможности js.

Поздняк.

Как уже написали, аргумент «раз на нем пишут, значит он хорош» очень спорный. Дейкстра может быть прав или нет по поводу COBOL, ваше право с ним не соглашаться, но для критики языка программирования не обязательно иметь какой-то особый «опыт в энтерпрайзе». Скорее, теоретические знания и опыт разработки других языков программирования более релевантны. И, критика, вообще, вещь полезная для индустрии.

В энтерпрайзе одни подходы, в академии — другие. А ещё вспоминается поговорка про плохого танцора.

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

На коболе просто написанно много плохого кода, который переписывать очень страшно и чревато большими простоями и рисками. Одновременно с этим на этом не лучшем языке люди не хотят писать, поэтому их заманивают большими деньгами. Ну примерно как кроссовер платит 50 баксов в час за проекты в стиле Delphi + .Net 1.1. Знаю из первых рук.


Вот так и складывается "стоимость специалистов необходимого уровня".

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

Он не хорош для энтерпрайза, просто выхода другого нет особо. По крайней мере для бизнеса, который "не парится", и не хочет инвестировать на сроки в 5-10 лет.

Я с этим и не спорю. Я только говорил что для действительно адекватной и предметной критики языка для энтерпрайза нужно все-таки понимать реалии энтерпрайза. Чисто академические знания здесь не подходят.
Много занимался корпоративным ПО на Java и все никак не могу понять о каком таком особом энтерпрайзе идет речь. Зато видел тонны плохо кода, подходов и инструментов, которые можно смело критиковать после одного взгляда ни них. К слову, иногда неадекватность какого-то решения легче заметить человеку «со стороны», чем уже привыкшему к нему. ИМХО, энтерпройзу нужен универсальный язык программирования и адеквантные инструменты, по большей части тоже довольно универсальные. То есть, определенные отличия в мире корпоративной разработки, конечно, есть — вспоминается статья Джоэла Спольски «Пять миров» — но все это не существенно для написанного выше. Если я не прав, то тогда что имеется ввиду под словом энтерпрайз и какие там особые задачи, которые не подчиняются общим принципам и для них нужен особый язык?
Много занимался корпоративным ПО на Java и все никак не могу понять о каком таком особом энтерпрайзе идет речь. Зато видел тонны плохо кода, подходов и инструментов, которые можно смело критиковать после одного взгляда ни них.
Суть в том, что бизнес не очень волнует качество кода само по себе. Его волнует стоимость получения результата и, иногда, стоимость последующей поддержки и развития. И если нужен сайт визитка, который будет таким и оставаться, а при необходимости чего-то более крутого просто перепишется, то совершенно неважно какой там отстойный код напишет нанятый джун. И нанятый джун на «плохом» JS, например, будет гораздо дешевле чем нанятый джун с плюсами. Хотя бы потому что первый получит результат куда быстрее. При этом его код может быть максимально отвратителен — но кого это волнует? То есть приоритеты другие, академичность и правильность by the book могут легко быть неприменимы в бизнесе. Это не значит что обязательно будут, просто нужно понимать когда и почему они все-таки применимы. А это очень сложно сделать без опыта работы на собственно бизнес.

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

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

Это да. Всегда когда речь идёт о красивости и идиоматичности кода вспоминаю про WordPress :)

бизнес не очень волнует качество кода само по себе. Его волнует стоимость получения результата и, иногда, стоимость последующей поддержки и развития.
Согласен.
То есть приоритеты другие, академичность и правильность by the book могут легко быть неприменимы в бизнесе.
В некотором смысле да, но в то же время, в бизнесе используются инструменты и подходы рожденные в академической среде. То есть, если кто-то для простой разовой задачи начнет использовать некий академический подход требующий избыточного количества времени, то это конечно ошибка. Но вроде бы подобной массовой проблемы и нет, когда академики ломают бизнес. Зато, например, тот же джун может с пользой для себя и своей продуктивности потратить время на изучение какой-то «академической» литературы.
В некотором смысле да, но в то же время, в бизнесе используются инструменты и подходы рожденные в академической среде. То есть, если кто-то для простой разовой задачи начнет использовать некий академический подход требующий избыточного количества времени, то это конечно ошибка.
Вот мой комментарий именно о том, что вот этот вот подход, на мой взгляд, и подразумевается когда говорится про отличие энтерпрайза от академических рассуждений и решений. При этом задача необязательно разовая или простая (хотя вероятнее всего именно так, но бывают разные ситуации все же). Просто иногда могут случаться всякие обстоятельства когда академический подход не сработает из-за недостатка ресурсов — времени и всего из него вытекающего (наывыка, количества рук) или денег. Например если в компании кризис и нужно срочно что-то выкатывать максимально быстро или закрываться. В таком случае выкатить что-то что внутри будет выглядеть отвратительно и будет очень тяжело поддерживать и расширять, но вот прямо сегодня, а не через месяц — вполне может оказаться лучше.
Теорию пишут практики. Если применение «академического подхода» приводит к неудачным результатам, скорее всего он был применен неправильно, и скорее всего из-за плохого понимания подхода тем, кто решил его использовать, например кто-то в очередной раз решил что нашел серебрянную пулю, и решил срочно внести в проект какой-то подход даже не проанализировав, какие проблемы он решает, и существуют ли они в проекте на текущий момент.
Ну, его крестовый поход против goto увенчался успехом…

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

Самое смешное, что goto так никуда и не делся, поскольку выходить из вложенного цикла без него ещё не научились.

Можно выйти из вложенного цикла на любой уровень и без goto.
Для этого перед break нужно присвоить переменную указывающую куда нужно выйти, а во внешних циклах ловить значение этой переменной, и делать break в зависимости от её значения.
А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))
А потом посчитать, насколько уродливее и тяжелее та кажущаяся «красота» на уровне human-readable абстракций…
)))
А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))

Борьба с goto велась всегда с оговоркой «не используйте его без надобности». Выход из вложенных циклов — это один из немногих осмысленных кейсов goto.

Не всегда. Видел я кандидата наук, которая считала break разновидностью goto и снижала за него баллы на ЕГЭ...


Да и тут на Хабре похожие кадры попадаются.

Видел я кандидата наук, которая считала break разновидностью goto и снижала за него баллы на ЕГЭ...

Это — клиника и не лечится. Я тоже таких к.т.н. видел.
Препод зачем-то вела уравнения мат. физики. При этом ни хрена в этом не соображала. Было забавно, когда студенты исправляли косяки в ее лекциях, потому что она не втыкала где у нее ошибка.
Это — клиника и не лечится.

Может, какие светила медицины попробуют? Готов стать подопытным.

Чем break не разновидность goto?
Думаю, фундаментом для этого вывода стало «снижала за него баллы на ЕГЭ».
Тем, что не continue? ;)
Чем break не разновидность goto?

Тем, что бездумное использование goto — резко снижает читаемость кода. Использование break — зачастую необходимость.
Но исходно речь шла про то, что занижать оценки за использование break — глупость. Хотя остается вариант, что это интерпретация студента, и оценка была занижена за что-то осмысленное, а не за break.

Оценка была занижена потому что использование break нарушает теорему Дейкстры. Это не интерпретация, это точная цитата с апелляции.

Оценка была занижена потому что использование break нарушает теорему Дейкстры. Это не интерпретация, это точная цитата с апелляции.

Ну так вы текст программы приведите — посмотрим. Судя по всему, break как таковой ни при чем.

Там был обычный цикл с выходом по-середине:


repeat
    {foo}
    if {some_condition} then break;
    {bar}
until false; 

Написать его по-другому без дублирования кода невозможно (а за дублирование кода тоже штрафуют).

Чтобы не быть голословным пример из моего солидити кода


for (uint64 i = uint64(dates.length - 1); ; i--) {
    if ((!latestStatusIndex.hasValue || dates[i] > dates[latestStatusIndex.value]) && isDistributionStatus(statusCodes[i])) {
        latestStatusIndex = Types.OptionU64(true, i);
    }
    if (i == 0) {
        break;
    }
}

переменная i — unsigned, поэтому проверять на i < 0 в условии цикла нельзя.

Можно так:
uint64 i = uint64(dates.length);
do
{
    --i;
    if ((!latestStatusIndex.hasValue || dates[i] > dates[latestStatusIndex.value]) && isDistributionStatus(statusCodes[i])) {
        latestStatusIndex = Types.OptionU64(true, i);
    }
}
while (i > 0);

Если что, я не против break (и не против goto, честно говоря).

Во-первых в солидити (как и многих других языках) нет do циклов.
Во-вторых этот код сломается если dates пустой массив.

Ваш вариант вроде тоже ломается, разве нет?

Вот так мы и нашли баг :)


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


Правда, про do момент остается.

Про do — OK, для меня просто слово «солидити» ничего не означало тогда — принял его за название проекта, а код за плюсовый. Так то согласен, без цикла с пост-уловием — или дублирование, или break.
Я что-то не догоняю, паскаль не помню, наверное. until false — это do {...} while(1); или do {...} while(0);?

until — это противоположность while. Так что until false — это while(1).

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

Заданием на ЕГЭ в части С всегда является написание работающего кода, без перерасхода памяти, времени и строчек программы.

А борьба с «перерасходом» строчек программы не может ли вести к перерасходу памяти и замедлению программы? Подсказка: цена абстракций)))

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


Условно, всем должно быть очевидно, что лучше добавить три строчки в программу, но выиграть в 100 раз по памяти. А если есть какая-то неоднозначность — существует апелляция.


А на самом деле на апелляции сидит "кандидат наук", сравнивающая текст программы посимвольно.

Понял Вас, ЕГЭ оно такое ЕГЭ… Что тут скажешь, сочувствую…
Вы так и не привели текст программы.
Вот это:
использование break нарушает теорему Дейкстры.

я могу трактовать как «преждевременный выход из цикла нарушает теорему Дейкстры». Т.е. претензия не к break'у, а к алгоритму. Чтобы это проверить — нужен текст программы, а вы его не привели.

Вы издеваетесь или как? Как я вам вспомню какую задачу я решал на экзамене 12 лет назад и какой код при этом написал?


И ладно бы это был самый сложный экзамен всей моей жизни, так ведь нет, это был очередной экзамен на внимательность при заполнении бланка...


я могу трактовать как «преждевременный выход из цикла нарушает теорему Дейкстры». Т.е. претензия не к break'у, а к алгоритму.

Во-первых, теорема Дейкстры на самом деле называется теоремой Бёма — Якопини.


Во-вторых, эта теорема говорит лишь о возможности приведения программы к некоторому виду, но никак не запрещает иной вид программы. Теорему невозможно "нарушить".


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

Вы издеваетесь или как? Как я вам вспомню какую задачу я решал на экзамене 12 лет назад и какой код при этом написал?

Почему издеваюсь?
1) Да, тупые преподы бывают.
2) Да, бывают студенты, которые считают, что их незаслуженно обидели.
3) При этом бывает, что обидели студента заслуженно.
4) Как я могу определить, что имело место быть на самом деле без текста программы?
5) Поскольку название алгоритма непонятное, непонятно и к чему придрался препод.
6) break используется повсеместно.
7) Наезда на языковую конструкцию в вашей цитате я не увидел.
8) Иногда преждевременный выход из цикла покрывает не все кейсы алгоритма и поэтому некорректен.
Как я могу определить, что имело место быть на самом деле без текста программы?

А слов разработчика с 8-летним стажем олимпиад и 10-летним стажем разработки вам недостаточно? Почему вы вообще взялись проводить какое-то расследование в комментариях на Хабре?


Поскольку название алгоритма непонятное, непонятно и к чему придрался препод.

А где вы вообще увидели название алгоритма?


Наезда на языковую конструкцию в вашей цитате я не увидел.

Для кого я писал комментарий выше? Эта фраза не может быть ничем кроме как наездом на языковую конструкцию.


Иногда преждевременный выход из цикла покрывает не все кейсы алгоритма и поэтому некорректен.

В таком случае мне бы сказали "алгоритм некорректно работает в таком-то случае", а не то что я цитировал выше.

А слов разработчика с 8-летним стажем олимпиад и 10-летним стажем разработки вам недостаточно?

Конечно, нет. Во первых, ошибаться / преднамеренно вводить в заблуждение может кто угодно. Во вторых, у вас на лице ваш стаж / квалификация не написаны. В третьих, на просьбу показать код последовало гениальное «begin… end», что в середине — не помню.
В четвертых, мне непонятно, чего вас колбасить начинает. Я высказал свое мнение. Не устраивает — пройдите дальше. Чего кипятиться-то?

Почему вы вообще взялись проводить какое-то расследование в комментариях на Хабре?

С того, что после моей фразы (безобидной вполне):
Хотя остается вариант, что это интерпретация студента, и оценка была занижена за что-то осмысленное, а не за break.

последовала какая-то бурная деятельность с вашей стороны.

А где вы вообще увидели название алгоритма?

«теорема Дейкстры». До букв докапываться не будем, ок? :)

Для кого я писал комментарий выше? Эта фраза не может быть ничем кроме как наездом на языковую конструкцию.

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

В таком случае мне бы сказали «алгоритм некорректно работает в таком-то случае», а не то что я цитировал выше.

Ну это ВАШЕ понимание, что нужно говорить. У других людей оно может несколько отличаться. И еще раз повторю: да, я видел тупых преподов. Море. Равно как видел нормальных преподов и тупых студентов, которые считают препода тупым.
«теорема Дейкстры». До букв докапываться не будем, ок? :)

Не ок. Четвертый раз вам говорю: это не название алгоритма!

Типичный пример полезного брейка:


let user_input: u32 = loop {
   if let Ok(user_input) = read_line().parse() {
      break user_input;
   }
   println!("Неверный ввод: ожидалось беззнаковое число");
}
Даже Дейкстра без break/return/goto не смог бы покинуть вечный цикл)

Эксепшены забыли. :) Ну и всякие штуки вроде setjmp, отправка сигнала самому себе, прерывания и т.д.

А еще можно у компьютера шнур питания выдернуть!

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

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

Вечный цикл покинуть нельзя. Если используется break это значит цикл не вечный, но условие выхода находится где-то унутре

Бездумное использование break снижает читаемость, goto (jmp) — зачастую необходимость. Но это никак не говорит о том, является ли что-то из этого разновидностью другого.

Goto — безусловный переход.
Break — безусловный переход к любому оператору после определённого цикла.
Бездумное использование break снижает читаемость

Бездумное программирование снижает читаемость.

goto (jmp) — зачастую необходимость.

Примеры, конечно можно привести. Но, «зачастую»… В своей практике, вообще ни разу не применял goto. Кейсов таких не возникало. А вот break — постоянно.

Break — безусловный переход к любому оператору после определённого цикла.

Что значит к любому? К следующему оператору за циклом. Что обычно и требуется.
Бездумное программирование снижает читаемость.

Именно!

Опять же, в своей практике я и в break не нуждался.

Что значит к любому? К следующему оператору за циклом.

Ну… К любому, всякому, какой бы ни оказался за этим циклом.

Таки безусловный переход.
А еще можно сравнить итоговый код в машинных командах и офигеть, насколько короче и быстрее тот некошерный goto, чем Ъ и обобренный брейк… )))
Даже более. В машинных командах в любом случае будет аналог JMP операнда, который тот же Goto.

По сути, дело в абстракциях и удобстве.
Нет, дело не в удобстве. Дело в последствиях непредвиденного выхода. Очень сложно контролировать все точки выхода из процедуры или какого-то участка кода и на одном из них забыть освободить память или закрыть файл. А так-то вещь весьма удобная — вжик и вышел. А тут оказывается что надо не просто выйти а ещё и ресурсы освободить, файлы закрыть и т.д. Хорошо когда это делается в одном месте, и даже если туда переходить по goto но тогда сам оператор перехода становится ненужным — достаточно уже существующих конструкций.
Break — он не просто вызывает выход из цикла, попутно под капотом он может ещё и переменную/коллекцию освобождать.
А так-то вещь весьма удобная — вжик и вышел. А тут оказывается что надо не просто выйти а ещё и ресурсы освободить, файлы закрыть и т.д.

RAII?


Break — он не просто вызывает выход из цикла, попутно под капотом он может ещё и переменную/коллекцию освобождать.

Как он это сделает и почему в той же ситуации goto этого сделать не сможет?

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

Странное какое-то определение, учитывая что компилятор C++ таки вставляет вызовы деструкторов перед goto...

Ага, прикрутили костыль. Очередной.
Без него и освобождение ресурсов в C затруднительно))

Да и эксепшены по-сути эмулируют goto.

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

Да запросто:


for(...) {
   for(...) {
      if (condition) return;
   }
}

Да-да, цикломатическая сложность >= 2 — повод выделить этот код в отдельную функцию

Во-первых воротить отдельную функцию чисто для подцикла не делает код чище и понятнее, чем с одним goto, во-вторых — может потребоваться перейти на 2 цикла вверх или что-то подобное. А если это какая-то операция с тензорами, то отдельные функции просто для прохода каждого измерения не выглядят сильно оправданными.

Как правило это именно что делает код чище и понятнее, потому что это двойное-тройное ж-ж-ж-ж-ж обычно неспроста, а является какой-то условно "неделимой" операцией (поиск первого элемента удовлетворяющего условию), и ей обычно можно дать вполне нормальное имя типа FindFirst(coll, condition).

Еще и Duff's device есть)
break и continue поддерживают параметры
В вашем примере достаточно написать break 2;
Редко нужно, но порой выручает. Бывает цикл+switch, тоже можно делать break 2;

1) Не во всех языках, 2) отдельная функция всё равно чище, 3) если вы скопипастите кусок с break 2 и он попадёт в цикл другой глубины, то будет ошибка.


Так что лучше не делать так.

С тем же успехом можно случайно скопипастить кусок с return по-середине...

Я бы в таком случае засунул вложенный цикл в функцию и вместо goto использовал return
Если язык популярный, это не всегда значит, что он — практичный, то есть оптимальный для решения практических задач. Как по мне так Дейкстра видел перспективу на десятилетия вперёд, а любители «практического программирования» иногда не видят и на десятилетия назад)

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


Методики и подходы написания надёжных программ были выработаны ещё в 80х при создании софта для SpaceShuttle и к данному моменту стандартизованы. И ключ совсем не в выборе самого лучшего языка программирования, а в организации процесса.

Я написал коммент про языки программирования. И про то, что оценивать уровень Дейкстры не каждому из нас под силу. Скажите, пожалуйста, при чём тут красивый и некрасивый код, шаттлы и надёжность?) У меня просто жестокий разрыв контекста сейчас.

Основная идея в том, что если Дейкстра или кто-либо другой профессионально не писал программы для энтерпрайза и сложных систем, то не ему критиковать подходы, практикуемые там. Вроде бы очевидно, нет? Дальновидность его в области промышленного ПО можно оценить по тому, что он критиковал как раз те подходы, которые до сих пор является широко используемыми (хоть и критикуемыми), и, думаю, здесь дело не в повальной умственной отсталости тех, кто их использует. Проблема же написания качественного софта, которую Дейкстра пытался решать критикой (уровня обычного обсирания) отдельных технических подходов и языков, лежит не в плоскости языка, а в плоскости организационных подходов и методик — например таких, какие были выработаны при разработке ПО для Space Shuttle, а надёжное ПО, используемое десятки лет, можно писать хоть на ассемблере — например тот же MVS.

Википедия про Дейкстру:
В 2002 году получил ежегодную премию, вручаемую Симпозиумом по принципам распределённых вычислений (англ. Symposium on Principles of Distributed Computing) Ассоциации вычислительной техники «за публикацию, оказавшую наибольшее влияние на область распределённых вычислений»; в знак признания заслуг учёного с 2003 года эта премия носит название премии Дейкстры.

Действительно, откуда ему что-то знать?)))


Какую, говорите, премию он там получил?:)

Много — не значит хорошо. Ещё есть аргументы?)

Премия за научную публикацию — не значит практический опыт программирования в отрасли. И вы же сами первый захотели заняться писькомерством.

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

Не сказал бы, что COBOL кто-то сильно рекламировал. Скорее наоборот: такие, как Дейкстра, вели против него собственную кампанию-холивар.

Иногда популярность — это просто следствие исторически сложившихся обстоятельств. Можно не рекламировать плохую технологию, и она всё равно захватит мир, как какой-нибудь jQuery. В любом случае, нужно не полагаться на принцип, что «миллион леммингов не могут ошибаться», а подумать своей головой.
Лично для меня критерий прост: чем меньше я трачу времени и усилий на создание решения, и чем больше зарабатываю на нём, тем лучше у меня подход и инструментарий. По этой причине в моей конторе ООП давно списан в утиль.
Можно не рекламировать плохую технологию, и она всё равно захватит мир, как какой-нибудь jQuery
А чем jQuery — плохая технология? Для своего времени он был крайне актуальным и ушел, когда стал не нужным.
Есть миллион причин почему jQuery плох во все времена. Одна из них — он нереально усложняет отладку кода. Вторая — в динамике разработки; сначала код пишется чуть быстрее, а потом гораздо-гораздо медленнее, чем на чистом JS. В основном это происходит из-за потери гибкости, внесения дополнительных и совершенно ненужных абстракций и правил.
На самом деле чистый JS весьма неплохо спроектирован — атомарно и консистентно, на нём можно быстро писать эффектные прототипы. А продакшн-разработку можно вести на TypeScript.
Вы застали веб-разработку в 2009-м?

Я перешёл на web в 2013-м. Я не никогда не занимался фронтендами для масс маркета. Мне достаточно, чтобы веб-приложение работало на одном браузере.
Но даже если думать о реализации кроссбраузерности через jQuery. Да, он даёт полезный функционал, но при этом также подсовывает очень опасную архитектуру, от которой проект сильно деградирует. Для примитивного лендинга-однодневки это нестрашно. Для сложного web-приложения это большая проблема.

но при этом также подсовывает очень опасную архитектуру
Не подсовывает он никакую архитектуру. Это библиотека, а не фреймворк. Он:
1. Делает прозрачную кроссбраузерность
2. Дает более короткий, чем в браузере API

Всё. Эти вещи никак не влияют на архитектуру. На архитектуру могли влиять некоторые плагины. Но всё зависит от программиста
То что у вас код со временем пишется гораздо-гораздо медленнее говорит лишь о плохом проектировании софта, тоже самое лего получить и с чистым JS. Сейчас то конечно да, jQuery уже не актуальный и вплоне хватает чистого ванильного JS, но в свое время он был просто жизненно необходим, чтобы нормально писать кроссбраузерные сайты, потому-что раньше браузеры не следовали стандартам и делали все по своему.

То, что Вы делаете выводы о качестве проектировании софта, который не видели, говорит лишь о плохом воспитании.

А что не так с отладкой кода на jQuery?

Я попробовал, мне не понравилось. Если Вам нравится — не смею мешать — наслаждайтесь процессом)

НЛО прилетело и опубликовало эту надпись здесь
Если я буду выбирать инструмент для получения премии — я поучусь у Дейкстры, если мне понадобиться инструмент для реализации обработки транзакций — я поучусь у тех, кто её реализует.

Боюсь спросить, а какие-нибудь Дядя Боб да Фаулер для вас тоже так, академики, далекие от кода?)

Не правда ли забавно, что разработка ПО воспринимается некоторыми не как инженерная дисциплина, а как некий культ со своими еретиками и гуру? :) При этом софт всё равно часто приходится писать с использованием «плохих» (но эффективных) подходов, но открыто в этом сознаваться не принято. В тоже время никто не хочет читать давно придуманные стандарты по организации процесса разработки и методике написания качественного кода, которые существуют ещё со времён первых Шаттлов, вместо это все бросаются резкими цитатами своих любимых гуру.

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

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

часто приходится писать с использованием «плохих» (но эффективных) подходов, но открыто в этом сознаваться не принято

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

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

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

Я не использую плохие подходы, и все ок. ЧЯДНТ?

Скажите, какие походы в используете, и я объясню вам, почему они плохи.

Из очевидного — Tdd/Tld с покрытием в зависимости от задач и SOLID. Начинайте.

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


TDD замедляет и усложняет RnD, требуя фиксации интерфейса до полного понимания какой он должен быть и реализуем ли вообще.


SRP приводит ко кратному увеличению числа объектов, что даёт излишнюю нагрузку на GC. А это критично, когда число объектов исчисляется сотнями тысяч.


OCP приводит к протуханию кодовой базы, переполнению её устаревшими реализациями, устаревших интерфейсов.


LSP говорит лишь про ковариантность типов, однако в ряде случаев типы должны быть контравариантны, а в ряде инвариантны.


ISP приводит к интерфейсам состоящим из одного метода, что нивелирует смысл интерфейсов и приводит к огромным объёмам бойлерплейта и необходимости дополнительно декларировать каждый используемый метод стороннего объекта.


DIP затрудняет навигацию по коду, усложняет API объекта, приводит к излишнему бойлерплейту.

TDD. Про TDD в оригинальном варианте максималиста — я соглашусь с вами. Однако гармоничный путь, описанный в исскустве автономного тестирования — это смесь написания тестов иногда перед, иногда после, но в основном — одновременно с написанием кода. «думай о тестах и коде как о едином целом (с) Тестиус». Потому я и указал TDrivenD/TLastD что бы не нарваться на формализм. Кстати отсутствие атомарных тестов зачастую приводит к кровавому легаси.

SRP. Проблема в понимании SRP как «дроби пока дробится». Но это не так. SRP — он же принцип единой изменчивости, единой ответственности и локализации фичей — говорит про границы декомпозиции, а не про ее размер. Нарушение Srp — это не только God-object, но и размазывание и дублирование по коду какой-то конкретной фичи. Дублирование кода, как вы понимаете отнють не ускоряет даже MVP. Ежели мы касаемся вопросов оптимизации — то «преждевременная оптимизация — зло (с)». Кстати неверная декомпозиция, приводит к костылям в коде и кровавому легаси.

OCP, при нормальном проектировании необходим для выполнения SRP и тестирования. А устаревшие интерфейсы можно просто удалить. Другое дело что вы не сможете их удалить если у вас нарушен OCP, что приведет вас к кровавому легаси.

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

ISP не приводит к интерфейсам из одного метода. Аналогично SRP, ISP говорит о том как нужно делать интерфейсы в спорной ситуации, и позволяет развязать жуткие клубки зависимостей. Это крайне необходимо для больших команд, сложной бизнесс логики и ограниченных контекстов. В противном случае — ну вы поняли ;)

DIP — соглашусь. Есть доказательства того, что слепое следование DIP приводит к рекурсивному генерированию кода. Таким образом DIP это не принцип, а ползунок, пренебрегать которым не стоит ни в одну, ни в другую сторону.

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

Я надеялся что это так
Потому я и указал TDrivenD/TLastD что бы не нарваться на формализм.

Я ж не знал, как вы TLD расшифровываете. Я подумал, что это TLeadD.


Кстати отсутствие атомарных тестов зачастую приводит к кровавому легаси.

Не знаю, что такое "атомарные" тесты, но написать код так, чтобы он проходил один тест, но не проходил остальные, обычно сложно и бессмысленно.


Ежели мы касаемся вопросов оптимизации — то «преждевременная оптимизация — зло (с)».

Дьявол в слове "преждевременная". Архитектурная оптимизация должна выполняется как можно раньше, так как потом менять её будет сложно или даже невозможно. Это, кстати, большое заблуждение, что можно не думать о скорости, пока петух не клюнет.


Другое дело что вы не сможете их удалить если у вас нарушен OCP, что приведет вас к кровавому легаси.

Ко кровавому легаси приводит отсутствие регулярного рефакторинга. А OCP явно запрещает рефакторинг. OCP полезен лишь при взаимодействии со внешними клиентами, когда нужно сохранять обратную совместимость.


Везде где есть нарушение LSP — начинается задница и борьба с иерархиями.

Не, вы не поняли суть проблемы. Смотрите, Cat является подтипом Animal, Cat[] является подтипом Animal[]. И если мы только читаем, то мы можем где угодно вместо Animal[] передать Cat[]. Но если мы где-то помещаем в массив, например, Dog, то, внезапно, мы уже не можем передавать Cat[] вместо Animal[]. Во времена Барбары об этой проблеме ещё не думали.

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

undefined behavior пускай и зафиксированное тестами

Это как вообще возможно?

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

Тем не менее де-факто это defined behavior.

Тем не менее де-факто это defined behavior.

defined где?

Тесты как спецификация, все дела.

Тесты как спецификация

Тесты это не спецификация, это специфицируемый код.

Тогда уж double defined behavior — описано и в коде, и в тестах :)

Позвольте вашей фантазии, добавлять к каждому абзацу фразу про «кровавое легаси»:

Не знаю, что такое «атомарные» тесты, но написать код так, чтобы он проходил один тест, но не проходил остальные, обычно сложно и бессмысленно.


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

Дьявол в слове «преждевременная». Архитектурная оптимизация должна выполняется как можно раньше, так как потом менять её будет сложно или даже невозможно. Это, кстати, большое заблуждение, что можно не думать о скорости, пока петух не клюнет.


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

Ко кровавому легаси приводит отсутствие регулярного рефакторинга. А OCP явно запрещает рефакторинг. OCP полезен лишь при взаимодействии со внешними клиентами, когда нужно сохранять обратную совместимость.


Ocp не запрещает и не поощряет рефакторинг. Он говорит что «изменение поведения кода нужно делать через… поведения-)». Иными словами — не надо хардкодить.

Не, вы не поняли суть проблемы. Смотрите, Cat является подтипом Animal, Cat[] является подтипом Animal[]. И если мы только читаем, то мы можем где угодно вместо Animal[] передать Cat[]. Но если мы где-то помещаем в массив, например, Dog, то, внезапно, мы уже не можем передавать Cat[] вместо Animal[]. Во времена Барбары об этой проблеме ещё не думали.


Либо я вас по прежнему не понял, либо вы путаете LSP и проблемы ковариантных/контравариантных преобразований и структурной типизации.
Кажется человечество еще не придумало как распутывать такие клубки.

Придумало. Компонентное тестирование называется.


SRP. И это не заблуждение, по крайней мере если в стеке есть нормальные инструменты профайлинга.

Ну вот профайлер вам показывает, что создание 100к объектов вместо 10к — это как-то сильно долго. Что вы там соптимизируете, не выпиливая SRP?


Он говорит что «изменение поведения кода нужно делать через… поведения-)». Иными словами — не надо хардкодить.

Он говорит "интерфейс закрыт для изменения, но открыт для расширения", что прямо противоречит рефакторингу.


вы путаете LSP и проблемы ковариантных/контравариантных преобразований

LSP фактически утверждает, что все типы должны быть ковариантны. Что во первых не так. А во вторых даже не является свойством типа.

Он говорит «интерфейс закрыт для изменения, но открыт для расширения», что прямо противоречит рефакторингу.
Нет, не противоречит. Он говорит о том, как код должен быть написан, а не о том, как код должен писаться.

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

Принципы SOLID вообще довольно спорные, а понимают их как имели ввиду авторы вообще единицы. Интересное чтиво на тему.


Про LSP тоже интересный вопрос: должен ли список который каждый Add дублирует элемент наследоваться от обычного списка (или даже IList) или нет?

Цитата по вашей ссылке: "в результате расширения поведения сущности, не должны вноситься изменения в код, который эти сущности использует".


Код самой сущности править можно, главное — сохранять все инварианты.

Вот назвали вы метод неудачно. Что теперь, создавать новый класс с новым названием этого метода?

Переименование метода не является расширением поведения, а потому OCP его не запрещает.

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

А кто определяет инварианты? В ФП допустим у вас все в типе возвращаемого значения зашито, а в ООП? Какие инварианты у метода ICollection.Add? Логично ли предположить, что если мы вызввали Add, то Count должен увеличиться ровно на единицу?

Вот программист и определяет. Кстати, какие инварианты определяет тип возвращаемого значения IO ()? :-)

Вот программист и определяет.

А что если один программист одно подумал, а другой-другое?


Кстати, какие инварианты определяет тип возвращаемого значения IO ()? :-)

Плохие, поэтому файнал таглес и MTL в помощь :)

А что если один программист одно подумал, а другой-другое?

Плохо будет, но это общая проблема программирования, а вовсе не проблема OCP.

Хорошо, тогда давайте по-другому: какие у нас формальные критерии соблюдения LSP? Ну вот чтобы мы могли бота написать, который на хук гитхаба срабатывает и говорит, что вот тут нарушили. Для каого-нибудь SRP можно считать количество пересечений графа ответственности и зависимостей, для open-closed насколько часто модифицируется исходный код каких-то классов. Короче, сложно, но можно.


А для LSP кроме как "я так вижу" не могу ничего придумать.

В гипотетическом формально верифицируемом ОО-языке достаточно проверить результат этой самой верификации.


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


А вот как вы будете формально описывать граф ответственностей — ума не приложу...

Примерно так же, как работают алгоритмы оптимального разделения единого графа на страницы. Считать пересечения между зонами ответственности (страницами).

Что вы вкладываете в утверждение "все типы должны быть ковариантны"?

Не совсем по теме, но бездумное применение LSP может привести к забавным вещам. Например Java с её ковариантным array.

Ковариантные массивы в Java как раз LSP нарушают.

Ага, но почему они там оказались?

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

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

Ковариантность — свойство, которым может обладать лишь конструктор типов (дженерик), но никак не одиночный тип.


А то, что назвали вы, называется "полиморфизм".

Да нет, вас обманули. Вот смотрите, у вас есть 2 функции:


printFirstAnimal( animals : Animal[] ) — сюда можно передать Cat[], но нельзя Object[].


addAnimal( animals : Animal[] ) — сюда нельзя передать Cat[], но можно Object[].


В зависимости от того как вы работаете с типом — у него разная вариантность.

Несовершенство конкретного компилятора — такой себе аргумент.

По правилам языка — нельзя, а по семантике функций должно быть можно, в этом и проблема.

Собственно, поэтому в C# и появились ключевые слова in и out, чтобы объяснять компилятору где какая нужна вариантность.

Собственно, поэтому в C# и появились ключевые слова in и out, чтобы объяснять компилятору где какая нужна вариантность.

Осталось понять, при чём тут LSP...

Не везде тип может быть заменён своим подтипом. Более того, в ряде случаев он может быть заменён надтипом. Даже не знаю как ещё вам это объяснить.

Вы путаете тип и объект этого типа. LSP говорит о замене объекта, а не о замене типа.

Не заставляйте меня повторять вам определение LSP…
Давайте завязывать с этим словоблудием. Ваше уточнение ничего не меняет.

Давайте вы сами его перечитаете? И увидите, что там ничего не говорилось про дженерики, только про функции.

Можно подумать дженерики — не типы.

Дженерик — конечно же тип. А не функция.

Функция. Из типа в тип (если это дженерик тип) или из типа в терм (если это дженерик функция).

Вот только LSP ничего не говорит про функции из типа в тип...

LSP говорит про объекты x типа T. Дословная интерпретация этого как T : * вместо полиморфизма по сорту T выглядит притянутой за уши, тем более, что есть языки с bounded quantification, и там размышления в терминах LSP для дженериков оказываются полезными.

Именно так: LSP говорит про объекты x типа T, а вовсе не про обобщенные типы, принимающие сам тип Т как аргумент.


Рассуждения в терминах LSP для дженериков возможны, но только если мы пытаемся установить отношения подтипа для дженериков (в таком случае мы применяем LSP к самому дженерику, а не к его аргументу). Если мы не пытаемся так сделать — то и LSP тут неприменим.


Иными словами, существование инвариантного дженерика не нарушает LSP.

а вовсе не про обобщенные типы, принимающие сам тип Т как аргумент.

Когда обобщенный тип применяется к аргументу, он становится конкретным типом. И LSP тут становится напрямую применим.

Так я именно про это и написал же...

А кто из этих конкретных типов относится друг к другу как подтип?
(эта может быть та же самая ситуация, как с мутабельными кваратом и прямоугольником)

А кто из этих конкретных типов относится друг к другу как подтип?

Ну если у вас А подтип В, то List[A] подтип List[B]. примерно так.

Это применимо только для некоторых дженериков. Хотя вы и сами это знаете. Фактически по самому факту, что T подтип T1 нельзя сказать что X является подтипом для X и конкретно для List это не так.

Some kids go trick-or-treating for Halloween. They all have Bags of Candy. It's safe for them to eat anything that gets put in their Bag of Candy, because the only thing inside, by definition, is Candy.

But when they get to the end of the block, a grumpy old man who hates children begrudgingly opens the door. He looks down and sees the children all have Bags of Candy. But a Bag of Candy, he reasons, is also a Bag of Things. And the evil old man grabs a handful of RazorBlades — which are also Things — and puts them in the Bag of Things the children are holding.
Это применимо только для некоторых дженериков.

Для ковариантных. Для контравариантных — применимо наоборот. Для инвариантных неприменимо. И что, с-но?


конкретно для List это не так.

Для стандартного мутабельного списка это конечно так быть не должно, мутабельные списки должны быть инвариантными. А для иммутабельных — все окей.

Для ковариантных. Для контравариантных — применимо наоборот. Для инвариантных неприменимо. И что, с-но?

Вот именно, ничего. Всё, что вы перечислили, никоим образом не нарушает LSP.

Не везде тип может быть заменён своим подтипом. Более того, в ряде случаев он может быть заменён надтипом. Даже не знаю как ещё вам это объяснить.

Очень легко объяснить. В ковариантных позициях (тех, по которым генерик ковариантен) тип может быть заменен подтипом, в контравариантных позициях (тех, по который генерик контравариантен) тип может быть заменен надтипом, а в инвариантных — ничем не может быть заменен. Но вы сами это плохо понимаете.


Обе функции имеют одинаковые сигнатуры, но разную вариантность

У функций вариантности нет, вариантность есть у функционального типа (генерик Func<A, B>), и этот генерик ковариантен по первого аругменту и контравариантен по второму — всегда.


И не зацикливайтесь на генериках. Это проблема любых составных типов.

Нет, это проблема именно генериков и без генериков ее вообще не существует. Тип F: -> (генерик) называется ковариантным, если A <: B => F(A) <: F(B) и контравариантен, если A <: B => F(B) <: F(A)


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

И эти ключевые слова указывают именно вариантность генериков (типов с кондом * -> ...). У функций и аргументов вариантность указывать нельзя нигде и никак. Боле того — ни в каком языке нельзя написать ф-ю принимающую аргумент типа Т но не принимающую подтип Т (офк говорим о type safe случаях).


Компилятор мог бы вывести вариантность сам

Не мог бы, это алгоритмически неразрешимая задача.


// а сюда нельзя

В обе ф-и можно передавать Boy.

ничем не может быть заменен

Почему ничем? Record type может быть заменён другим типом с теми же полями в другом порядке в инвариантной позиции, например.

Record type может быть заменён другим типом с теми же полями в другом порядке в инвариантной позиции, например.

Так это тот же тип, в рекорд-тайпе порядка у полей нет, это не кортеж.

Это в каком языке так?


Если взять гипотетический нормальный язык с инвариантными массивами, то в обоих случаях в такие функции можно будет передавать только Animal[], но не Cat[] и не Object[].


А чтобы всё было так как вы расписали, функции должны выглядеть как-то так:


printFirstAnimal( animals : <? extends Animal>[] ) — сюда можно передать Cat[], но нельзя Object[]


addAnimal( animals : <? super Animal>[] ) — сюда можно передать Object[], но нельзя Cat[]


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

addAnimal( animals: Animal[] ) — сюда нельзя передать Cat[], но можно Object[].

Почему?

Потому что нельзя положить произвольное животное в массив кошек, очевидно же.

Дошло. Я ожидал либо


x.addTo(animals) либо static AddAnimal(animals, animalToAdd) просто по названию подумал, что функция метод добавляет животных в this

Потому что функции различаются только названием, если вы не заметили. Компилятор должен по названию что-то определять? И да, положить животное в массив кошек нельзя.

Все ф-и коварианты по типу аргумента, в вашем случае дело в вариантности генерика [].

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


И не зацикливайтесь на генериках. Это проблема любых составных типов.


class Man { pet : Animal }
class Boy extends Man { pet : Cat }

// сюда можно передавать Boy
declare function feedPet( man : Man ) : void

// а сюда нельзя
declare function giveDog( man : Man ) : void

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

И что же некорректного в этом наследовании?

Нарушение LSP.

Если нигде нет функций типа giveDog, то и никакого нарушения LSP нет.


Ну а вообще странный аргумент.


— LSP — чушь, вот, смотрите пример корректного кода, который не соответствует LSP.
— У вас тут некорректный код, потому что нарушает LSP.


Что-то мне это напоминает.

LSP — это требование к иерархии типов, а не к функциям. А потому LSP нарушает не функция giveDog, а потенциальная возможность её существования.

Если у меня есть понятие рантайм-типа и возможность в языке его узнать, то такая функция существует всегда.

Ну вот профайлер вам показывает, что создание 100к объектов вместо 10к — это как-то сильно долго. Что вы там соптимизируете, не выпиливая SRP?

Прежде чем «грязнить» код — есть много других возможностей:
1) Большинство проблем происходят из O-сложности. Если O — побеждена — переходим на уровень малой крови.
2) Делаем синглтоны или кэши для часто используемых объектов, меняем ref на value типы (если позволяет инфраструктура) — итд.
3) Если и это не помогло — то часто можно упростить или реорганизовать архитектуру во благо скорости и без ущерба смыслу (на этом этапе уже понятно как).
4) Для высоконагруженных — не забываем про горизонтальное масштабирование.

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

Что самое главное — предыдущие 4 пункта сложно выполнить, если у вас там все SOLID-ы порушены вкривь и вкось.

Он говорит «интерфейс закрыт для изменения, но открыт для расширения», что прямо противоречит рефакторингу.

Он говорит про изменение поведения, а не про то, что код нельзя трогать. Например — смена конекшн стринги из конфиг файла.

Или более сложный случай — вы хотите сменить одну SQL базу на другую — значит подготовьте бек-код так, что бы можно было менять провайдера DB, а не удалять старого и вместо него писать нового. Так же это говорит о том, что предпочитайте if-чикам — стратегии и композицию

LSP фактически утверждает, что все типы должны быть ковариантны. Что во первых не так. А во вторых даже не является свойством типа.

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

Есть ощущение, что вы принципиально пытаетесь переспорить изначальный тезис, что спортивно и прикольно.
Большинство проблем происходят из O-сложности.

Это ваша личная статистика. У меня другая.


Делаем синглтоны или кэши для часто используемых объектов

Как кеши помогут вам создать 100к объектов необходимых для старта приложения?


часто можно упростить или реорганизовать архитектуру во благо скорости и без ущерба смыслу

Ага, например не лепить по 10 объектов на каждую сущность.


не забываем про горизонтальное масштабирование

Это вообще не оптимизация, а брутфорс.


И только потом, когда все вышеперечисленное не помогло — начинаем крошить код во благо скорости.

А можно изначально не создавать себе проблему во благо сомнительных аббревиатур.


Lsp утверждает, что наследование и интерфейсы должно быть обоснованно по смыслу, а не для экономии кода.

Сможете процитировать, где оно что-то такое утверждает?


Есть ощущение, что вы принципиально пытаетесь переспорить изначальный тезис

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

А откуда у вас возьмется 100к разных ответственностей при старте приложения?

А кто вам сказал, что это будут 100к «разных ответственностей»?

Мы как-то расследование проводили — и обнаружили, что в Java один-единственный вызов String.format("%0.2g", 0.1); порождает несколько сотен объектов. Добавьте сюда пачку обсуждающихся акронимов — и вы получите 100к объектов вместо нужных вам 10к легко несмотря на все кеши и прочее. Рождённых как раз всеми вот этими ускоряторами.
что в Java один-единственный вызов String.format("%0.2g", 0.1); порождает несколько сотен объектов


Это вопрос уже не про SRP. А по поводу кол-ва объектов — повторюсь — SRP не про то что нужно все измельчать, а про то, как делить.
что в Java один-единственный вызов String.format("%0.2g", 0.1); порождает несколько сотен объектов
Это вопрос уже не про SRP.
Это-таки именно о нём. Если вы посмотрите на C версию printf (вот тут, например), то вы обнаружите страшное нарушение SRP, которое любой правоверный ООПшник заменит на десятки, а то и (как видим на примере Java) сотни маленьких объектиков. В результате — то, что в C версии аллоцирует пару объектов (связанных с локалью) при первом обращении (и ничего при последующих) теперь создают 200 с лишним объектов при каждом обращении (и хорошо за 1000 при первом).

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

Но это же scanf по ссылке. Хотя да, от GNU coding style глаза сразу в кучку, различить сложно :-)


Впрочем, это не имеет отношения к SRP. Задача функции printf — сформировать в соответствии с переданным шаблоном и аргументами итоговую строку, она именно это и делает.


Вот если мы в printf засунем возможность читать данные из mysql, — это будет нарушением SRP.

Но это же scanf по ссылке.
Не туда ткнул. Printf тут… но не сказал бы, что он сильно проще.

Задача функции printf — сформировать в соответствии с переданным шаблоном и аргументами итоговую строку, она именно это и делает.
Ну вот разработчики Java решили что нет… и они, в некотором смысле, правы: там действительно можно выделить много подзадач. Конвертировать аргументы, переставить их по разному (вы ведь в курсе, что «Hello, World!» можно напечатать так: printf("%3$s%2$*1$s\n", 6, "word!", "Hello"); ), обработать коллбаки (вы же в курсе, что можно дополнительные буквы зарегистрировать и дополнительный обработчик навесить) и так далее.

Нельзя сказать, что все эти сотни создаваемых объектов совсем ничего не делают… но даже после долгой обработки JIT'ом там происходит куда больше работы, чем в C printf… а уж пока JIT не прогреется — там вообще караул с производительностью…

И это, ещё раз повторяю — библиотека, которой пользуются миллиарды и, вроде как, неглупыми людьми написанная. Если они не умеют в SOLID, то кто тогда умеет?

С моей точки зрения вы правы, SOLID имеет свою цену. Так же как и любое другое повышение уровня абстракции. Переход на языки высокого уровня, СУБД, библиотеки тоже имеют свою цену.


Если бы вы использовали вместо sprintf на C специально написанный для вашего случая код на ассемблере, вероятно, это заняло бы еще меньше ресурсов.


Кстати, думаю, что если на каких-то задачах sprintf является узким местом, там бы код оптимизировали возможно уйдя от SOLID.

Если бы вы использовали вместо sprintf на C специально написанный для вашего случая код на ассемблере, вероятно, это заняло бы еще меньше ресурсов.
Конечно. Но вот вопрос: насколько меньше? Ответ: не так сильно меньше, чем вам кажется. Чтобы вывести 3-4 цифры в десятичной записи «наивно» — вам потребуется поделить число на 10. А это, опаньки, от 10 до 40 тактов (в зависимости от процессора). А вот printf — этого избегает.

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

А вот Java printf — с этих позиций, похоже, вообще не оценивали. Даже со всеми кешами и прочими чудесами (а их там хватает, пресловутые 4 шага разработчики Java освоили неплохо) — оно всё равно работает заметно медленнее, чем пара циклов и StringBuffer.

А вот это уже, извиняюсь — никуда не годится.

Кстати, думаю, что если на каких-то задачах sprintf является узким местом, там бы код оптимизировали возможно уйдя от SOLID.
Невозможно написать быстрый код «оптимизируя узкое место уйдя от SOLID». Это так не работает. То есть да, можно легко перейти от скорости в 0.1% от оптимальной к 1% от оптимальной. А вот уже даже 30%-40% получить, если у вас код в стиле фабрик-фабрик-фабрик сделан — не так-то просто.

Почему? Да потому что с 1982го года ничего принципиально не изменилось. И ваш настольный компьютер и ваш телефон — это, по прежнему, вот такой вот вот зверь, если заглянуть в микроскоп:


В нём по-прежнему разнесчастные 32KB-64KB памяти, а всё остальное — медленные свистелки и перделки.

Вылететь за 32KB-64KB памяти при использовании SOLIDного проектирования — нефиг делать. А это — сразу замедление на порядк. А если вы уже за пару мег вылетели — так там ещё один порядок будет…
А вот Java printf — с этих позиций, похоже, вообще не оценивали.

Если не оценивали за столько лет, может быть нет задач, где это является узким местом?


Невозможно написать быстрый код «оптимизируя узкое место уйдя от SOLID». Это так не работает. То есть да, можно легко перейти от скорости в 0.1% от оптимальной к 1% от оптимальной. А вот уже даже 30%-40% получить, если у вас код в стиле фабрик-фабрик-фабрик сделан — не так-то просто.

А по моему как раз наоборот, если есть SOLID то изменить начинку куска кода не меняя интерфейс проще.

SRP говорит о том, когда декомпозиция строго необходима. Достаточность же на усмотрение разработчика.


Разработчики стандартной библиотеки Java решили, что гибкость дизайна важнее производительности, вот и все.


У меня с точки зрения SRP нет претензий ни к тому, ни к другому. (Ну, к printf есть, но несущественные — можно было бы работу с буфером вынести отдельно).

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

Я пока слышал от вас аргументацию уровня джуниора, и «особое» понимание принципов SOLID.

Из ваших слов получается:

SRP вы воспринимаете как необходимость дробить до безумия,
OCP — запрет на редактирование кода
LSP — это о преобразование сложных типов
ISP — требует интерфейсов из одного метода

Честно я ждал от вас вдумчивых аргументов, тоскуя вечерами. А получается что вы просто не понимаете, не умеете и логично что не любите SOLID и всячески защищаете свою безграмотность.
SRP вы воспринимаете как необходимость дробить до безумия,
Это не он так его понимает — это его так «правоверные ООПшники, писавшие стандартную библиотеку Java» так понимают.

Можно, конечно, сказать, что они — ничего не понимают в ООП, но… если люди, пришущие стандартную библиотеку не умеют в SRP, то… кто тогда умеет?

OCP — запрет на редактирование кода
Да, часть из этих сотен объектов рождена из OCP…

А получается что вы просто не понимаете, не умеете и логично что не любите SOLID и всячески защищаете свою безграмотность.
Я ещё не видел людей, «понимающих и любящих SOLID» — и при этом способных сделать что-нибудь хоть сколько-нибудь сравнимое по эффективности решение с тем, которое могут предложить люди, «не умещие в SOLID».

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

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

Условно говоря: SOLID, это… ну не знаю — примерно способ выпекать гамбургеры в тысячах Макдональдсов по всему миру, но ни в коем случае не способ приготовления еды, которая понравится человеку, который хотя бы раз в жизни ел вкусную еду.
Не не не, это вы неправы! ;))

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


Для 5% задач, типа той же sprintf, которая используется миллионы раз, да, надо взять, написать эффективно и отлить в камне.


(Ну это, конечно, смотря какая область, если вы пишете ядро ОС или стандартную библиотеку, то там соотношение будет обратное).


Что касается стандартной библиотеки Java, скажу мягко, она во многом не пример для подражания. Надо делать скидку на середину 90-х, когда еще были иллюзии, что JIT-ом получится все оптимизировать, и даже о специализированных CPU для Джавы всерьез думали.

Для 95% задач важнее чистая и гибкая архитектура, позволяющая эффективно решать возникающие задачи и быстро реагировать на меняющиеся требования как в краткосрочной, так и в долгосрочной перспективе.
Вот только фикция это: не позволяет эта «гибкая архитектура» ничего менять. Вот проводить 100500 рефакторингов — позволяет. А делать вещи, которые не сводились бы к «добавлению красненького» — нет. Вот в чём парадокс-то.

Что касается стандартной библиотеки Java, скажу мягко, она во многом не пример для подражания.
Если она — «не пример для подражания», то почему ей все подражают? Ведь об этом вся статья, в принципе. А не о том, что нужно про ООП забыть и всё на чистый Pascal ISO/IEC 7185:1990 переписать…

Надо делать скидку на середину 90-х, когда еще были иллюзии, что JIT-ом получится все оптимизировать, и даже о специализированных CPU для Джавы всерьез думали.
Ну тогда много о чём думали… и что компилятор вместо суперскаляра работать сможет (не смог) и что JIT сможет 100500 уровей индирекции «схлопнуть» (не умет) и что GC смогут сделать таким, что он вместо ручного распределения памяти сможет работать (не смогли: предсказуемости всё равно нет, хотя в некоторых ограниченных масштабах «цирковые трюки» и работают).

На самом-то деле самое главное, что происходило «в середине 90х» — это резкое, просто реактивное, ускорение процессоров: 486й (1й процессор, где большинство инструкций исполнялись за 1 такт) на 25MHz — это 1989й, на 50MHz — это 1991й, 1993й — это P5 (1.5 команды за такт плюс-минус и 60MHz), P54CS на 200Mhz — это 1995й, а Pentium II на 300MHz (плюс спекулятивное исполнение) — 1997й, через пару лет Katmai на 600Mhz и ещё через два года Tualatin на 1GHz с гаком.

Производительность удваивалась каждую пару лет — и под это всё и затачивалось.

А потом… оп-па: шайтан-машина издохла, славный путь к 10-20GHz (а такие планы всерьёз в конце прошлого века озвучивались!) обломался… но разработка-то ведётся в предположении, что вот-вот уже, совсем скоро, мёртвая лошадь встанет и побежит! А когда покупатели этого… добра, резонно спрашивают — почему оно всё тормозит и жрёт память «как не в себя»… то им отвечают: ну тут гибкость такая, необычайная… вы главное больше в эту гибкость верьте! То, что в конце прошлого века за пару часов разработчик мог без всяких CI и прочих мудрых слов добавить в формочку и отчёт новые данные — это всё фигня. Это вы просто «не такую» гибкость ищите. Неправильную. Там зато «дизайна» не было правильного…

P.S. Есть только одна вещь, в которую я не верил 10 лет назад, но которая сейчас реально работает: удаление «мёртвого кода». И Clang/GCC/MSVC и Java/C# и даже JavaScript движки — отлично с этим справляются (JavaScript — сносно)… при одном условии: если у вас нет вот этого вот всего SRP, OCP, DIP, ISP и прочего. Когда данные, которые используются совместно — и лежат в памяти близко. А вот как раз все эти SOLID'ные подходы устраивают в данных полный кавардак: вещи, которые используются совместно оказываются в разных местах памяти, а вещи, которые совместно почти никогда не используются — собираются в один объект.

Да, чёрт возьми, сама основа современных языков такова, что данные расположить в памяти «плотно», вообще говоря, невозможно! Вообще! Никак! Ну… если, вдруг, вас не устраивает ситуация, когда ваш огнедыщаший дракон с потреблением как доменная печь, почему-то работает медленнее, чем калькулятор, то вот вам, с барского плеча, костылик… радуйтесь.
А вот как раз все эти SOLID'ные подходы устраивают в данных полный кавардак: вещи, которые используются совместно оказываются в разных местах памяти, а вещи, которые совместно почти никогда не используются — собираются в один объект.

Выглядит как нарушение SRP как раз