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

Колян, 40 лет.

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

У автора как раз, вроде, неплохая самоирония, а вот у вас с этим уже слабовато, прям как MzMz написал.
А с чего автора на самоиронию пробило? Скис потому что.
> луркмуар

Луркмоар
прям как MzMz написал: «прогрессирующее занудство»
я старался :)
Обнаружил, что шутки с луркмоар молодые сотрудники не понимают
Ну так на то и они и молодые) У них шутки про «патимейкер-шейкер-шейкер». Нам с вами не понять.

Луркнуар :)

Зависит от того, доволен ли человек своими результатами в жизни да процессом жизни в целом. С возрастом все основные черты личности проступают и усиливаются. Если был молодым занудой вырастешь той еще брюзгой )
если вы 40+ (а я 50+) могу предложить гантели (они дома, и с пробуждения ставят вопрос ребром сразу — да или нет? ;)) + ежедневный душ самой холодной водой какая доступна…

… и осознание — только скорость восприятия держит вас в реальности

это только ваш личный выбор — да, я «википедия», пусть меня читают… или еще нет ;)))
Что-то зачастили с такими статьями на хабре, настолько, что мне аж в 30 уже страшно стало.
Профессия программистов в массы не так уж давно пошла, особенно в россии.
Нытье 40 летнего программиста — 2017-40=1977 года рождения
+ 18 лет = 1995 год начала работы по профессии — по сути первая массовая волна как раз тех, кто занимался этим «с рождения».
2017-1995 ~= 20 лет, аккурат тот срок когда можно начинать ныть о «старых добрых временах» (при дельте в 10-15 лет как-то это еще не так звучит).
Чем дальше — тем больше будет таких статей и тем больше будет упоминаемый возраст.
В 2037 вангуем статью «Куда деваются программисты после 60».
При чем занудство сие постигло не только программистов, достаточно посмотреть аналогичные посты в контактике типа «лайкни если ты помнишь» и дальше какая-нибудь жевачка, которую почти никто не жевал, но вкладыши от которой были местной валютой:)

А вообще программист как таковой это типичная профессия со стеклянным потолком. Проблема стеклянного потолка умноженная на устаревание технологий не только в том что рано или поздно некуда больше расти, а еще в и в том, что опыт больше 5 лет по сути не имеет значения в большинстве случаев, поэтому эффект «не успел разогнаться тебя уже догнали» очень силен. А смена профы на менеджера или управленца это именно смена профы.
Добавлю к последнему абзацу то, что некие «сакральные» знания, якобы доступные исключительно людям с 20-летним опытом часто не нужны по причине того, что для 95% IT-проектов в России реально достаточно just good enough качество (особенно это касается коротких проектов и мелких бюджетов). То есть нужно просто знать технологию X и молча без зауми херачить проект Y.

Смена профессии на менеджера тоже не серебрянная пуля — в связи с более низкой ликвидностью среднего сферического менеджера относительно разработчика такой же геометрии.
Зачастую нужны сакральные знания по какому-то продукту, но проблема в том, что этот продукт или уникален или используется в единицах компаний и, уходя из одной такой, нет смысла менять шило на мыло. Как итог — сакральные знания есть, но толку… и чем дольше в этом вариться, тем больше этих знаний и тем больше полезного места они занимают и бОльшим якорем становятся.
Есть люди, которым и надо сидеть 20 лет на месте Z, зная только технологию X и молча без зауми пилить проект Y. А кому не надо, идут в ту же сервисную аутсорсинговую компанию и пилят сухие микросервисы на солидной архитектуре через TDD. Мода что-то пошла всех под одну гребенку грести. Программист, пожалуй — одна из самых толерантных к возрасту профессий. Это у токаря-фрезеровщика если единственный в городе завод закрылся, то дороги две — в алкаши да в дворники. А здесь кофе налил, пошел на какой-нибудь Апворк и сиди себе никуда не спеша поддерживай легаси проекты хоть до 80-ти. С нынешним курсом пенсиям даже до индусских рейтов, как до Луны.
Достаточно условно толернатная так-то. Если слесарь в 40 будет цениться практически однозначно выше 20летнего, и только из-за возраста проблем у него не будет точно, то про компании, в которых программистов отсеивают из-за того что «слишком старый» я периодически слышу. Это, конечно, не значит что работы сразу нет, но ее однозначно меньше чем 5-10-15 лет раньше.
У компаний тоже есть некий психологический возраст. Молодые разработчики всегда находятся чуть ниже этого возраста, поэтому подходят для практически любой фирмы, но чем более компетентен (заметьте, не просто более опытен, а именно более компетентен) становится разработчик, тем больше компаний он «перерастает». От того и складывается впечатление, что рабочих мест становится меньше. В абсолютных значениях — да, но в остальном всё на своих местах. Как в крупную корпорацию не нужен мальчик на должность системного архитектора, так и начинающим сервисникам, клепающим одностраничные визитки на шаблонах с themeforest, не нужны дядьки, рассуждающие про метод подстановки Лисков.
+ 18 лет = 1995 год начала работы по профессии — по сути первая массовая волна как раз тех, кто занимался этим «с рождения».

Гы. И кудаж мне теперь деваться? Я начал программировать в 1975, а деньги зарабатывать в 1978. В это время все студенты у нас к диплому изучали пару языков, если было на то желание, Алгол-60 давали на первом курсе, а Фортран приходилось учить самостоятельно, так как появилась потребность. И это не программисты, а инженеры-механики, между прочим.

Это я насчет «массы», если что.

А с остальным совершенно согласен — причем опыт больше 5 лет не просто не имеет значения — его еще и практически невозможно подтвердить, и если угодно, монетизировать — потому что управленцы, в большинстве своем, не в состоянии оценить этот самый уровень.
Вам надо было аналогичную статью тиснуть 20 лет назад:)

А по поводу «массы» все же. Массовой профессией программист стал когда на рынок массово пришли персональные компьютеры, это все же никак не 1975, даже на западе это ближе к 1985 году, а в ссср/россии ближе к 1995. До этих лет профессия программисты была не массовой, скорее областью для энтузиастов любителей и узких специалистов
Это все равно как сейчас назвать массовой профессию физика-ядерщика, то что их много — неоспоримо, но массовой профессией это станет не раньше выхода 120 айфона на ядерных батарейках, который будет жить аж 3 дня без дозарядки и на каждом углу будет киоск с починкой оных.
Ну, я целом наверное согласен, массовость тоже бывает разная. Просто в 1980-х один чисто инженерный факультет уже выпускал в год примерно 600 человек, каждый их которых знал как минимум Алгол-60. И это только один факультет, прошу заметить, а ведь были и другие, включая тех, у кого программист специальность. Разумеется, далеко не все они реально программировали — но факт обучения никуда не девался.
Да у меня после таких статей в 24 уже коленки начинают дрожать))) Соберитесь Мужики!!!
мне 52 и я кодирую… не ссцы!
спасибо всем кто плусанул )))

на самом деле вопрос кодирования и возраста имеет еще измерение — уровень достижений и желание самого процесса

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

в частности з/п как раз одно из измерений социального статуса ))

кратко, примеры с/с — наемный сотрудник, предприниматель, пенсионер, наследник о;;%?;«го состояния (то есть все на халяву), высокопоставленный чиновник (то есть тоже самое почти что)
в частности, я кодирую уже для удовольствия, а не за деньги… ))
в отношении кодинга, я бы добавил… в моем возрасте это явно уже как секс — главное быть в состоянии ;)))

… а «за деньги» или «по любви» это уже второстепенные детали ;)))))

ps — предупреждая инсинуации — кодирую я не «вместо»! одно другому не мешает" ;)))))))))))))

Если социальный статус не оставляет времени на то, что хочется, то зачем он тогда нужен?

глянул профиль… по ходу вам «социальный статус» не что не нужен… он вам не угрожает в принципе ))

Может, оно и к лучшему?

что именно? массовое… безграмотность? ))… казалось бы «передового класса»… )))

Что социальный статус не нужен.

да не вопрос… дорогу можно переходить не по зебре и не глядя на светофор

А какое это имеет отношение к социальному статусу?

ровно такое же как «у меня нет социального статуса» — асфальт есть — можно идти… нет — все равно можно…

Очень странная аналогия. Социальный статус — ярлык, который на тебя навешивают некоторые окружающие и соответственно ему относятся к тебе. Причём тут ПДД, которые требуют от тебя что-то?

del

ps… чет я не понял вложенность и отступ?
любое социальное соглашение — условность (ПДД, УК, ГК… какой то там «ярлык»)

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

и можно даже бравировать этим…

ПДД, УК, ГК — это не условности, это чётко оформленные правила поведения в обществе.


А социальный статус — это уже негласные правила.

да хоть до хрипоты спорь — это условности усиленные всеобщим согласием

уголовные понятия — гласные или не гласные правила? они приняты в обществе?

… однако реальны ситуации смертельной опасности при их нарушении

и кстати уж точно социальный статус не является «не гласным» правилом
ps — «четко прописанные правила», исторически вообще то с легкостью переписываются ))

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

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

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

Что значит "пренебрегать социальным статусом"? Вот допустим мой социальный статус "люмпен", как я могу им пренебрегать или не пренебрегать?

в частности ФНС тоже от тебя что то требует… но ты же бравировал тем что пытался их морочить

так что тут аналогия вполне себе…

автомобиль может сбить, но и ФНС может однажды «наехать» более чем бумажно… и снова аналогия вполне себе возможна
https://habrahabr.ru/post/324426/#comment_10208992 это я хотел тебе ответить… но что то сегодня у меня не клеится с правильным целеуказанием, прости ))
ярлык или конституция — по свой сути лишь социальное соглашение…

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

пока ты «один в поле бы.. воин» — можно спагетить, говнокодить, не каментить и не документировать…

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

я даже вспомнил анекдот:

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

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

За столом Наташа пытается завести светскую беседу:
— Вы знаете, я купила 17 свечек для праздничного торта, а на него влезло только 16. Ума не приложу, куда всунуть еще одну?..

Из-за стола встает поручик Ржевский и кричит:
— ГУСАРЫ МОЛЧАТЬ!
по ходу я забыл упомянуть выше, что «бомж» и/или «быдлокодер за еду» — все это тоже социальный статус

социальный статус — это ярлык, который вам навесят социологи при очередном исследовании/обзоре/переписи… а опера — при описи или в протоколе… и так далее

а еще это то что вы сами напишите в разного рода анкетах, когда идете на поклон к государству…

Термин "самоирония" вам не знаком?


Боюсь, что социологи затруднятся однозначно определить мой социальный статус, если выдать им всю информацию. А что пишу для государства зависит от цели обращения к государству, где-то лучше наемный работник, где-то иностранный предприниматель, где-то рантье, где-то безработный (увольнялся пару раз, чтобы государство потроллить — забавно смотрится безработный бомж с недвижимостью по всей стране и доходом на порядок большим средней зарплаты :), правда налоговая юмора не оценила, пришлось предоставлять первичные документы )

42 года. Профессионально в IT 23 года. Самая большая проблема — я вижу на 10 шагов вперёд к чему приведут те, или иные решения, в то время как более молодые коллеги видят, едва ли на 3. Поэтому возникает грусть и некий налёт скуки.
НЛО прилетело и оставило эту надпись здесь.
им не нужно повышать вот так свой уровень.

Сейчас «развлекаюсь» тем, что у новым задачам линкую писанные мною несколько лет назад, на эту же тему.

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

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

Плохим разработчикам что-либо рассказывать попросту бесполезно, а хорошие склонны мерить всё по себе.

Один пример: в одном из компонентов у нас был реализован «белый список» системных вызовов, которые этот компонент имел право совершать. Ну и комментарий типа «если нужно что-то то ещё — пишите, мы список расширим!»).

Как вы думаете сколько подобных запросов мы получили за пять лет? Предположение «нуль» всё же неверно — один запрос был. От нашей же команды. Зато какие «чудеса героизма» были обнаружены в компонентах, созданных партнёрами, которые были совершены для того, чтобы не писать письмо!

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

Вы сделали ограничения?
image

Chalenge accepted — фигня! NotInventedHere!

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

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

мне 36, в IT я с 18 лет. на собственном опыте отлично знаю, к чему может привести плохо продуманное решение. но попробуй докажи.

Вы не задумывались, что проблема может быть в способах, которыми вы высказываете своё мнение?


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


Другое дело:


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

На текущий момент у нас есть проблемы с архитектурой в %название_модуля%, заключающиеся в том, что %краткое_описание_проблем%. В дальнейшем, если мы продолжим развивать это решение, при разработке задачи Б, имеющейся в плане, нам придётся пойти на внедрение компромиссного решения %костыль%, что, впоследствии, в свою очередь, также увеличит трудозатраты на реализацию задачи В, в силу %список_причин%. Также возможно и %другое_решение% задачи Б, не имеющее этих недостатков, зато имеющее %другие_недостатки%. Кроме того, задача Г может оказаться и вовсе нерешаемой, так как %причины%.

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

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

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

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


Пробовали так?

А вот эти пламенные речи и презентации когда делать, в рабочее время, вместо основных задач? Или в свободное время, за свой счёт?

Это ваш выбор.


Если можете самостоятельно выделить время в рамках рабочего, без ущерба для остальных задач — замечательно (это вообще важный навык).


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


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


Но сначала — сформулируйте, для себя в первую очередь, какой цели вы хотите достичь с точки зрения бизнеса. К примеру, «Сейчас мы тратим по два-три дня дорогого разработчика на добавление нового отчёта, так как надо писать SQL-запросы и готовить шаблоны на PHP. Эти задачи у нас регулярны. Если мы потратим две недели на новый модуль отчётов — в дальнейшем будем делать новые отчёты за считанные часы силами дешёвого верстальщика, потому что будет знакомый верстальщику XSLT и простенький генератор запросов. Это реальная экономия денег и времени, плюс мы сможем быстрее реагировать на пожелания пользователей, а разработчики займутся реальными задачами», вместо «Наши отчёты шляпа, легаси и говнокод, давайте сделаем нормально». Редкий вменяемый руководитель наотрез откажет, видя заинтересованность работника в результате. А если и откажет — можно не смириться сразу, а выяснить причины решения, возможно, вы видите не всю картину. В конце концов, если в руководстве реально невменько — что вы делаете на такой работе?

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

Я повторю вопрос и вам — что вас держит на такой работе?


Если у вас есть аргументы, а у начальства нет — смысл такой работы? Быть инструментом автоматизации желаний? Чувствовать себя ничтожным? Просиживать штаны, выполняя тикет буква-в-букву?


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


Реально, я персонажей с такими настроениями повидал достаточно за пятнадцать лет в разработке. Всё плохо, руководство тупое, никто не слушает, накажут за инициативу… А чаще всего это означает завышенное самомнение, недостаток знаний и коммуникативных навыков, нерешительность и отсутствие инициативы. И, в конечном счёте — отрыв от реальности.


Можно быть гением программиррвария, но в жизни программирование должно приносить прибыль. Самая прекрасная архитектура бесполезна, если решает не те задачи, что требуются бизнесу. Для многих разработчиков профессия выросла из хобби, и удовольствие для них, по привычке, на первом месте. Но нанимают нас вовсе не для того, чтобы мы пускали радостные пузыри, полируя код, а чтобы мы эффективно решали задачи бизнеса в рамках имеющихся ограничений. И, на самом деле, это — реальный интересный вызов, а не игры в 30 строк на JavaScript.


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

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

Если десятичасовые объяснения не возымели эффекта, нужно выяснять, почему. Как минимум, разузнать (и понять) аргументацию отказа. Убедиться, что вы преследуете совместимые цели. В конце концов, вы либо поймёте, как правильно преподнести своё предложение, либо найдёте альтернативный вариант, всех устраивающий. Либо, в худшем случае — узнаете, что ваши старания напрасны, смысла в трате сил на попытки применить свои профессиональные качества нет, здесь уже не исправить ничего, Господь, жги.
С работы, на которой вы не востребованы, уходить вполне нормально, иначе есть риск демотивации и профессиональной стагнации, а то и личностной деградации. Это вас тоже должно волновать, а не судьба компании, которой вы не нужны. Стокгольмский синдром, ей богу.

А вы не пробовали с другой стороны посмотреть? Да, можно переписать «правильно». И в долгосрочной перспективе (5-10 лет) это выгодно. А в краткосрочной — это пустая трата времени.

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

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

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

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

Обе истории выдуманные, конец будет такой, как автор захочет, а оправдывать сообственный быдлокод сказками — низко.
Да вот в реальности — быдлокод побеждает. Сравните популярность Windows 95 c OS/2 Warp 4.0 или Windows NT 4.0. Из недавно мелькавшего на хабре — Duke Nukem 3D.

Для оценки уровня быдлокодовости движка Duke Nukem
Код движка находится в одном файле из 8503 строк и с 10 основными функциями (Engine.c) и в двух дополнительных файлах:

cache1.c: содержит виртуальную файловую систему (sic!) и процедуры системы кэширования.
a.c: реализация на C воссозданного обратной разработкой кода, который был высокооптимизированным x86-ассемблером. Код работает, но читать его — огромная мука!


Так что увы, в реальности как в сказке.

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

А быдлокод вообще-то называется технический долг. Просто использовать этот долг надо умеючи.
Видите ли, бывает и так и так, а вы просто ищите оправдания быдлокода и потому видите только то, что хотите) Вот, например, вы увидели Duke Nukem 3D, но не увидели Doom с хорошим кодом)

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

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

Заранее приготовьте валидол
void Error (char *error, ...)
{
	va_list argptr;

#ifndef __NeXT__
	if ( *(byte *)0x449 == 0x13)
		TextMode ();
#endif

	va_start (argptr,error);
	vprintf (error,argptr);
	va_end (argptr);
	printf ("\n");
	exit (1);
}



Ну если if ( *(byte *)0x449 == 0x13) для вас хороший код — то вы на 100% правы. Потому что до такого мы даже в прототипах не скатываемся.

Потому и живут у нас неоптимальные технические решения лет 5-10. И вполне переносят модификации. Та же Windows при всей её быдлокодовости проживала 25 лет.

Так что с вашей оценкой кода — у нас быдлокода вообще нет… :-) А с нашей оценкой — ну скорее всего весь ваш код плох.

но уже через 3 месяца внесения правок в продукт

Мда… Что это за анализ предметной области, если код нужно править в ближайшие полгода? Что-то мне вспоминается мой бывший коллега, который в своей книжке писал:

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

Результат неизменно стабильный…

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

Про аджайл и так далее — почитайте "дефрагментацию мозга". Серега там хорошо и по аджайлу прошелся.

Одна из цитат
5. Одни из наших конкурентов широко используют agile-методы и TDD[121], но что-то оно им не очень помогает писать безошибочный код. Мы сравнивали количество найденных проблем в течение месяца-двух после major release, у нас показатели лучше в разы, если не на порядок. Частый выпуск версий просто не позволяет им довести код до ума и провоцирует исправление старых и серьёзных проблем методом написания «залепени». (Прим. автора: исправление ошибок, добавляющее новые проблемы.)


А у нас что-то среднее. Не аджайл, но и не жесткий водопад. И да, мы обычно на 3-5 лет понимаем, что может потребоваться, а что — нет. Неожиданности бывают, но редко.

А претензии к красивому коду у меня как у Жванецкого — "включаешь – не работает.". То есть шик, блеск, красота… но работает только для сферической лошади в вакууме. А на реальных данных — валится, как бык по скользи. Потому что программер думал об архитектуре, декомпозиции, делегировании, концепции… о чем угодно, но не о том, что модуль должен работать. Работать на реальных данных. С выбросами, лакунами и прочим дерьмом отклонением от идеала.

И любой работающий быдлокод — в 100 раз лучше неработающей красоты.

Особенно, если автор этой «красоты» заявляет, что адаптировать к реальным данным нельзя, ибо «архитектура не позволяет» и «концепция сломается». :-)
Ну судя по статьям Мосигры — далеко не единственный

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

И да, мы обычно на 3-5 лет понимаем, что может потребоваться, а что — нет

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

Одни из наших конкурентов широко используют agile-методы и TDD[121], но что-то оно им не очень помогает писать безошибочный код

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

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

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

Я понимаю, с таким циклом как у вас и на редаксе можно поговнокодить. За 5 лет оно все утрясется, костыли покроются пылью, которая потом отвердеет, а если пользователям что-то не нравится, то проект уже сдан и пусть приспосабливаются.

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

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

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

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

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

Вы немного путаете качество кода со способностью кода к модификации.

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

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

P.S. Если не трудно, расскажите что такое редакс. А то «мужики-то и не знают». :-)
розовый танк с оборками и стразиками — невероятен


Некий российский режиссер с вами не согласен…
Обитаемый остров
image
Оборочек не вижу. И стразиков. Но +1!
Чтобы вам было понятней, почему качество и способность к изменениям -разные вещи, простой пример.

Вот есть восьмислойная плата, качественная-перекачественная. Чипы на BGA, рассыпуха 01005 (0,4 × 0,2 мм), дины линий выверены, импедансы рассчитаны и смоделированы, все фронты — ровненькие, хоть на 5 гигагрерц работой. Но вот только срок обновления у неё — 6 месяцев. И ремонтабельность нулевая.

А есть макетка. Быдло-быдло. Чипы — на DIP, рассыпуха — 1210 (3,2 × 2,5 мм), монтаж — проводом МГФТ, фронты завалены, об импедансе и длине линий и разговора нет, дай бог, чтобы на 150 мегагерц завелась. Но срок обновления у такой платы — 3 часа. И ремонт — хоть дома, хоть в офисе.

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

Ну у нас не макетки, но ближе к этому концу. За 3 недели можно новую плату сделать и софт на неё переделать. И даже за две, если печатную плату не заводским методом делать, а ЛУТом.
И любой работающий быдлокод — в 100 раз лучше неработающей красоты.

А вы действительно не путаете проблемность целевой области и заточку кода под неё (что может включать в себя массу адских хаков на частные случаи исключительно из-за ТЗ) и некачественность самого кода?
Если первое, то это не принято называть быдлокодом (разве что быдлозадачей).
Если второе, то покэпствую
1) красивый неработающий код, скорее всего, легче довести до рабочего состояния;
2) после этого он лучше сопровождаем, в отличие от быдлокода.


Пример с Doom и куском для NeXT, кстати, тут очень хорош дидактически :) С одной стороны, там очевидные странности в виде магических констант, которые по обычным нормативам надо описать в виде #define. Но я бы тут вместо этого поставил комментарий с описанием смысла конкретного числа.
С другой стороны, стиль, рекомендуемый во многих местах, потребовал бы сделать тут безусловно вызванное макро, которое было бы определено в непустое действие только для NeXT. А вот это уже хуже потерей локальности — надо идти неизвестно куда только для того, чтобы узнать, что для 99.999% случаев макро просто пустое.

Давайте разделим:

  1. Быдлокод — это простой, некрасивый код, написанный в лоб. Часто большие размеры процедур, связь через глобальные переменные и так далее.
  2. Хакерский код — эффективный, но очень сложный в понимании код с множеством трюков.
  3. «красивый» код — эффективный код, написанный по всем правилам (классы, методы на все случаи жизни).

И вот вам пример, чем быдлокод лучше. Известная "Задача о восьми ферзях": Простой вариант с 8 циклами — легко обеспечивает переход к доскам непрямоугольной формы. А вот быстрый алгоритм Дейкстры…

1) красивый неработающий код, скорее всего, легче довести до рабочего состояния;
2) после этого он лучше сопровождаем, в отличие от быдлокода.

Увы и ах. Если в «красивом, эффективном коде» неверно понято ТЗ — заставить его работать можно лишь путем хакережа. И сопровождаемость после этого падает в ноль.

Неужели непонятно, что красота и эффективность идет за счет специализации кода (по условиям ТЗ)? Что вся декомпозиция — хороша лишь, пока программист верно понимает ТЗ и спектр его изменений? А если программер построил "мост вдоль реки", то его разворот обойдется дороже, чем написать его заново.
Неужели непонятно, что красота и эффективность идет за счет специализации кода (по условиям ТЗ)?

EPARSE, простите. Красота и эффективность — следствие специализации кода, или они страдают (ухудшаются) из-за специализации кода?


Что вся декомпозиция — хороша лишь, пока программист верно понимает ТЗ и спектр его изменений?

Нет, непонятно. На практике можно считать, например, что через год изменятся 10% требований, а за десятилетний цикл развития — чуть менее, чем все. (Реальные цифры с одной из моих прошлых работ.) Но каким образом из этого следует рекомендация писать всё так, что не заботиться о его сопровождении? Ведь даже если 20% изменилось, остальные 80% те же, а сопровождать надо каждый день.


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

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


И вот вам пример, чем быдлокод лучше. Известная "Задача о восьми ферзях": Простой вариант с 8 циклами — легко обеспечивает переход к доскам непрямоугольной формы. А вот быстрый алгоритм Дейкстры…

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


Давайте разделим:
Быдлокод — это простой, некрасивый код, написанный в лоб.

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

На практике можно считать, например, что через год изменятся 10% требований, а за десятилетний цикл развития — чуть менее, чем все.

МДА. Ну или у вас аналитики действуют методом памяти покойного академика тыка, или вы путаете изменения ТЗ с дополнениями, из у вас какое-то "настоящее" программирование типа поддержи сайта.

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

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

Точно так же я понимаю, что на компе может не быть часов, а в байте неожиданно может оказать 32 бита,

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

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

Красота и эффективность — следствие специализации кода, или они страдают (ухудшаются) из-за специализации кода?

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

Чтобы было понятней — вспомните дискуссию про goto сороколетней давности. Код с goto -очень гибкий. Но и некрасивый.

очень вероятно, что те же решения (быки-опоры, пролёты, методы их сцепления, покрытие для дороги) годятся для моста что вдоль, что поперёк

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

A вот сами быки и пролеты — выкидываются. Ибо после демонтажа быка половина остается под руслом. Там же сваи, они глубоко забиваются.

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

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

Соглашусь, лоб у всех разный, естественный код — тоже.

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

Никакое понимание предметной области не спасёт от странных инновационных требований заказчика. Или, как вариант: "когда мы это обсуждали 5 лет назад, то было совсем другое правовое поле, никому в голову не могло прийти, что законодатель ограничит размер пени сверху". Не думаю, что следует считать своей ошибкой то, что никому в голову прийти не могло.

Уточню — моя ошибка как аналитика предметной области. Или другого аналитика.

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

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

Новый режим стирки — дополнение. Изменение параметров режима стирки — тоже дополнение, пишем новый режим, а старый просто не вызывается.

Замена датчика — опять дополнение. Пишем чтение с нового датчика и не вызываем код для старого датчика.

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

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

Посмотрите на историю развития World of Tanks, к примеру. Или Hearthstone. Или RimWorld. Или Minecraft.

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

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

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

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

Отнюдь. Вполне можно и по скраму.

У вас есть 200 сейчас продающихся моделей стиральных машин, 100 — уже не продающихся, но поддерживаемых и 50 в новой разработке. Реально это выливается в поддержку 7 вариантов процессорной платы, 30 вариантов пультов и по 3-5 вариантов каждого датчика и исполнительного устройства + не на всех моделях есть все датчики и все агрегаты.

И вот всю эту армаду в 350 моделей вы и делаете по скраму. По модели (или группе моделей на спринт).

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

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

Это не за три недели, в нормальном коде это делается за день, если не за час. Изменили константу в конфигурации, потестили и отдали в production.

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

я в таком режиме уже почти 10 лет работаю. 6 операционок (Linux, Windows, FreeRTOS, FreeBSD, MS-DOS, QNX, МСВС считаем за linux), 6 компиляторов (gcc, clang, lcc, borland C++, С Builder, MS VC++) 3 разных архитектуры (x86, ARM, SPARC) и куча конкретных продуктов. Ну не 350, но к двум десяткам. И всё это — на единой кодовой базе и с приличным техническим долгом.

Ниша у нас такая — полузаказные продукты делать. И поддержка пожизненная. То есть лет на 10-20.

Да, такой подход есть и он накладывает повышенную ответственность на аналитика и снимает эту ответственность с программиста.

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

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

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

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

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

Вы можете позволить себе сидеть без заказов и действовать методом тыка. Мы — не можем. Методом тыка мы можем только по миру пойти.

Мы знаем, что мы можем сделать за 2 месяца, за год, за 3 года… И постоянно ищем себе заказы. И постоянно выбираем, в какие ниши идти, а в какие нет.

Мы не можем сказать «а давайте попробуем вот такой вариант, вдруг он кому-то нужен». Прежде чем что-то делать, мы должны понимать, кому мы продадим, по какой цене, каким тиражом.

А что касается гавнокода, то я уже описывал вам, чем гавнокод лучше. И что именно из-за гавнокода мы имеем короткий цикл модификации. Увы, вы на это ничего не ответили.
Это не за три недели, в нормальном коде это делается за день, если не за час. Изменили константу в конфигурации, потестили и отдали в production.

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

У вас есть 200 сейчас продающихся моделей стиральных машин...

Вы теоретизуруете. А я говорю о реальных проектах.

А что касается гавнокода, то я уже описывал вам, чем гавнокод лучше. И что именно из-за гавнокода мы имеем короткий цикл модификации. Увы, вы на это ничего не ответили.

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

А вы — делаете для выдуманного, цифрового мира

Да, я знаю, что вы обижены на game-developer'ов и web-developer'ов, но эти факты не делают вас лучше. Вот я какое-то время писал медицинский софт, то не говорю ведь вам теперь: «вот, вы просто за бездушные деньги работаете, а я жизни людей спасал». Странно звучит, правда. А вы так постоянно говорите.

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

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

Это называется разработка методом тыка.

Я стараюсь не судить о том, в чем не разбираюсь. А зачем вы поступаете иначе?

Или вы настолько мелкая сошка, что аналитики не считают нужным ставить вас в известность.

Оскорбления. Прекрасный ход. Сразу видно силу и аргументированность вашей позиции.
Да, я знаю, что вы обижены на game-developer'ов и web-developer'ов

Отнюдь. Это отсылка к делению на хабр и гиктаймс. «Программирование» на HTML+CSS было признано настоящим, а всякое автовождение и прочий embeded — ненастоящим.

Логика тут есть. Настоящее — это то, что делается цифрового мира. А то, что делается для реального мира — ненастоящее. Цифровой мир — намного гибче и допускает работу методом тыка. И масштабы гигантские. 10 тысяч пользователей цифрового мира — это ерунда, 10 тысяч пользователей реального устройства — это очень много.

Я в разработке железа не разбираюсь и мне это не очень интересно.

Хорошо, вот вам аналогия из вашей работы. Насколько проще бы бы вам работать, если бы вместо реалистичного 3D использовалась анимация спрайтами или ASCII-Art? Чувствуете? Там, где вы месяцы будете отрисовывать красивую 3D-картинку, ASCII-art меняется за день. Вот так быдлотехнологии и ведут к быстрому циклу изменений.

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

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

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

Вы теоретизуруете. А я говорю о реальных проектах.

Реально — к паре десятков.

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

Аналогия — код для 350 разных стиральных машин. А то, о чем вы говорите — это даже не электроника, а механика.

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

Это специфика вашей отрасли. Огромное количество фирм сейчас живет по другим принципам.

Скорее должности, чем отрасли.

А итоговые тезисы такие:

  1. Применяя технологии вчерашнего и позавчерашнего дня (быдлокод) в современной обработке — мы уменьшаем цикл разработки (и легкость переделок) за счет скорости, эффективности, красоты кода и прочего. И наоборот — чем качественней код, тем сложнее он изменяется, оставаясь качественным.
  2. ООП — очень гибкая вещь, но только если угадали со структурой. В случае ошибки в выборе структуры — изменения становятся настолько тяжелыми, что проще все переписать заново.
  3. Технический долг — абсолютно нормальная штука, если им грамотно управлять. Но это уровень менеджеров, аналитиков и темлидов, а не рядовых программистов.
Хорошо, вот вам аналогия из вашей работы. Насколько проще бы бы вам работать, если бы вместо реалистичного 3D использовалась анимация спрайтами или ASCII-Art? Чувствуете? Там, где вы месяцы будете отрисовывать красивую 3D-картинку, ASCII-art меняется за день. Вот так быдлотехнологии и ведут к быстрому циклу изменений.

Какая-то аналогия левая. Это ведь просто разные жанры или стили. При чем тут быдлотехнологии? Или если делаешь 2д-игру вместо 3д-игры, то уже быдлятник? Если хорошо написан код, то легче работать и с 3д и с 2д.

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

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

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

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

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

Кстати да, напомнили вы мне своим комментарием о вопросе, который меня сильно мучает: как писать в рамках TDD (про BDD молчу — там я сам принцип не понимаю)… Сам принцип я понимаю, но вот как перейти к нему?..

как писать в рамках TDD

Я переходил волевым решением. Не знаю, насколько оптимально, но, в целом, доволен реультатом. Проще всего в новых проектах это делать (даже в рамках того же продукта).

Потихоньку. Пришел багрепорт — воспроизвели его тестом (падающим), а не руками. Добились, чтобы тест прошёл любым костылём — баг исправлен. Если костылём, то отрефакторили и убедились, что тест всё ещё проходит. Так же и с фичереквестами.


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

Мда… у нас в ответствественных местах баг закрывается трижды
1) Работа системы с деградацией функциональности при наличии бага
2) Адекватная диагностика поближе к точке возникновения бага
3) Правка самого бага.

Речь про ваш пункт 3, может 2 частично.

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

Было бы точно так же — уже давно больше половины разработчиков перешла бы на TDD… А я вот до сих пор вижу кучу проектов с покрытием юнит-тестами ниже 30%!


PS Да и на своём примере я чётко вижу, что это не так просто…


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

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

Немного дольше писать на TDD, но на выходе имеем покрытие 90%, а не 30. Если сначала писать код, а потом тесты, то заленитесь покрывать 100% и бросите после 30.
Для багфиксов это суперудобно: сначала фейлящийся тест, а потом багфикс, который его вылечивает (как вам тут уже написал VolCh). Очень удобно и логично. И покрытие не страдает.
Для фич пишем h-ники, потом заглушки тестов (покрытие 90%) по максимуму, потом функционал, потом тесты до конца, потом их отлаживаем.
Для багфиксов тест действительно пишется до правки. Только это не TDD.

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

Это просто элемент TDD, потому что TDD придуман не для этого.
Смысл TDD — это методология написания программ, а не исправления ошибок.

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

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

Откуда ж у вас ошибки, если теоретически TDD страхует от них? :-)

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

Пр этом альтернативные технологии контролируют поведение программы в production, то есть на всех реальных наборах данных,

Кстати, мой коллега в одной из ЭВМ обнаружил, что 2.0 равно 16.0, то есть сравнивались лишь мантисы, без ординат. Завод-изготовитель потом долго извинялся. :-) Машина была с серийным номером три.

TDD не является альтернативой какой-то технологии, она дополняет имеющиеся.

Это при бесконечном бюджете. А при конечном? TDD дает ложную уверенность в оттестированности кода без Q&A,

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

Потому что одновременно с введением TDD руководство снижает затраты на Q&A. Накладные расходы у TDD не нулевые. Если мы тратим бюджет на TDD — значит урезаем его в другом месте.

В первый раз о такой практике слышу. Внедрение и использование TDD входит в бюджет непосредственно разработки, кодирования, к Q&A оно никакого отношения не имеет, это как внедрение нового фреймворка.

А вы про экстремальное программирование почитайте. Там «представитель заказчика» заменят QA и вводится прямо в бригаду.
TDD — это один из вариантов Hype Driven Development. Некая модная тенденция, полезная в отдельных случаях и вредная во всех остальных.

TDD нарушает одну из основных заповедей тестирования — никогда не тестировать свой собственный код. Грубо говоря, при неверном прочтении ТЗ ошибка попадает и в тесты и в код, поэтому тестирование бесполезно.

Как говорит мой коллега, если программеры работают по TDD, то работает только то, что попало в тесты. Шаг влево, шаг вправо — не работает. При этом коллеги обычно не правится по 5 лет с момента написания, ибо багов просто нет.

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

А что удивляться? Тому, что кто-то до сих пор умеет писать программы, которые не падают после первой ошибки? Это нормально! У самолетов кроме постоянной забагованности, как ещё и цикл правки багов по 2-3 года.

Мы у себя в одном проекте решили, что баги есть, были и будут, в итоге работает 15 лет 365 на 24.

А потом и тесты, и код пуллреквестят специально обученные люди. Да и до написания и тестов, и кода обычно советуются с людьми, разбирающимися в бизнесе. Вероятность "неправильно понять" стремится к 0.

Равно как и выигрыш от TDD. Точнее он становится отрицательным. TDD — нишевая штука, все остальное — мода.

Основное назначение тестов в виде используемом в TDD — не обнаружение несоответствий поведения программы ТЗ, а фиксация понимания ТЗ и регулярная проверка соответствия поведения этому пониманию. Тесты в TDD — часть процесса кодирования, а не тестирования, чистый development, а не Q&A.

TDD — отличный инструмент, если вы рассчитываете довести код до состояния, когда почти любая правка багов вызывает новые баги. :-)

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

Если вы думаете, что подсчитанная вручную пара матриц понятнее ТЗ — вы сильно ошибаетесь. :-)

Я надеюсь, что Вы пошутили. Иначе не вижу никакого резона для таких утверждений.
TDD — отличный инструмент, если вы рассчитываете довести код до состояния, когда почти любая правка багов вызывает новые баги. :-)
Здрасти, приехали. Чего у TDD не отнять — это создания огромной базы для регрессионного тестирования. А полезнее всего эта база при усталости кода, когда каждая правка бага вызывает новые баги.

Если уж вы довели код до такой ситуации, то будете сильно рады, что работали по TDD.
А, тогда да, согласен.
При обсуждении TDD надо отделить мухи от котлет и принципы, лежащие в его основе, от их конкретной комбинации. У меня лично претензии именно к конкретной комбинации (ну и, естественно, к доведению её до статуса религии).

Первое: тут уже упоминалось, но повторю: формально TDD никак не требует собственно писать по ТЗ, оно требует только удовлетворения теста. Поэтому, если мы напишем умножение в виде

multiply(0, 0) -> 0;
multiply(2, 0) -> 0;
multiply(2, 2) -> 4.

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

Для объяснения этого вообще нужно вернуться к тому, зачем же тест нужен. Тут лучшая формулировка, что я слышал, дана коллегой landerhigh с RSDN: «Тесты — не для поиска багов, а для написания верифицируемого в контролируемых условиях кода.» Под верификацией имеется в виду любая верификация, начиная с банального просмотра глазами. Код должен обеспечивать своим содержимым выполнение ТЗ так, чтобы это могли проверить средства верификации (сам автор, коллеги-ревьюеры, автоматические средства...), а тест — чтобы ловить то, что верификация не ловит: человек не заметил опечатку или крайний случай; вместо символа ';' шутник подставил ';' (U+037E); не выловлен какой-то крайний случай; и тому подобное.

Далее, в классическом TDD объявляется, что
1) пишутся тесты до кода («test first»);
2) тесты должны быть проверены на то, что они не проходят (тупо — запустили, увидели отказ и только после этого имеем право двигаться дальше);
3) пишется код для их удовлетворения.

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

Чтобы допустить в принципе ситуацию, когда код меняется, надо допустить, если использовать правило «test first», что новые тесты соответствуют TDD, но старые — нет, часть тестов может работать и до новой разработки, и после. А это значит, что они уже нарушают этот принцип — их нельзя проверить на корректность той проверкой, что «тест до написания упал => он, вроде, правильный».

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

Также с этим напрямую связано, что TDD никак не решает вопрос качества самого тестового окружения. Например, что будет, если в результате сбоя в одном файле assertEqual() станет проверять не равенство результата ожидаемому, а просто наличия результата?

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

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

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

Вы вначале решите, что должен пройти тест для (1,2) и (2,1). OK, сравнили два элемента, прошли. Теперь добавляем третий… четвёртый… двадцатый… во что превратилась функция? В лучшем случае вы получите сортировку вставками (если кодер очень умён), а скорее всего это будет «пузырёк». При совсем тупом кодере это вообще не будет читаемо даже после ста грамм. И Вы никак не получите ни метод Хоара, ни тем более метод Бэтчера. Потому что для них надо сначала реализовать алгоритм, хотя бы в уме, со всеми циклами, ветвлениями и тому подобным, а уже затем тестировать.
Можно сказать тут, конечно, что алгоритм в уме и алгоритм в коде — разные. Но когда вы заранее знаете, какое именно ветвление вы напишете и почему — вы уже действуете не под тест, а под алгоритм. Идея предварительного разделения на два подмассива, как у Хоара, а тем более математически подобранная сортировка подпоследовательностей, как у Бэтчера — тестом не решится.

В вычислительной математике таких случаев ещё больше. Тестирование решения СЛАУ через построение треугольной матрицы — очень хреново ложится на TDD, а тонкие эффекты на долях эпсилона в принципе не могут быть заранее просчитаны.

Следующее — проблема уже состоявшихся требований. Пусть есть изменение ТЗ — добавилось новое требование. Но что будет, если оно уже реализуется кодом? Например, требование — чтобы сортировка была устойчивой — но она уже такая в реализации. Такая ситуация в принципе не покрыта TDD. Решение я уже описал выше — отказ от заложения на важность теста его изначальной неработой.

В каком же случае TDD может идеально работать, и даже быть полезным, в его каноническом виде? Это
1) написание только нового кода, или расширение на безусловно новые требования;
2) полное отсутствие необходимости R&D, весь проект ясен с самого начала (сюда входит и вариант изменения ТЗ на ходу — просто таких начал становится несколько);
3) большое количество низкоквалифицированных сотрудников и аналогичного управления ими, которое способно сэкономить на тестах, но для которого угроза административного наказания за нарушение инструкций важнее проблемы собственно качества выходного кода.

То есть это идеальная технология для классической оффшорной галеры. Антиполюс — то, где TDD способно только навредить — разработка собственного наукоёмкого продукта. (Как раз случай коллеги Jef239, поэтому я не удивляюсь его отношению. И мой случай для всех прошлых и нынешних работ.)

Резюмирую: я никак не против отдельных функциональных тестов всех уровней (от юнит- и до интеграционных верхнего уровня). Они должны быть. Более того, они должны быть и для нормального заказчика (который обязан не верить, пока не покажут работающее), и для собственного контроля. И разработчик должен сам писать тесты на все подозрительные и крайние случаи, и контроль должен это поощрять и требовать. Но требование 100% покрытия и «test first» — это то, что превращает разумный подход в религию.

И ещё один PS — речь не про тесты для прямой проверки ТЗ (я их отношу к BDD, а не TDD).

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

TDD про написание нового кода или изменение существующего. Если принято решение перевести развитие существующего проекта на TDD, то есть два глобальных варианта:


  • забиваем на существующую кодовую базу, тесты пишем только под задачи на развитие (новая или изменяющаяся функциональность), рефакторя существующий код без автоматизированного юнит-тестирования регрессий
  • перед переходом на TDD покрываем существующую кодовую базу тестами с применением практик из Q&A и прочих "tests last"
    Допустимы, конечно, комбинации, компромиссы между этими вариантами, но суть в том, что покрытие тестами существующего кода — процесс ортогональный TDD.

Представим себе написание метода сортировки по принципу TDD тем, кто не знает этих алгоритмов.

Вы при описании TDD забыли ещё один этап при разработке — рефакторинг. Замена пузырька или вставок на Бэтчера — это как раз и он есть: видимое поведение не изменяется, а код оптимизируется по какой-то метрике. Обычно по читаемости, улучшению поддерживаемости, но это не догма.

> перед переходом на TDD покрываем существующую кодовую базу тестами

Это никак не относится к тому, что я говорил — о проблемах работы с существующим кодом, уже покрытом тестами, согласно TDD.

> Вы при описании TDD забыли ещё один этап при разработке — рефакторинг. Замена пузырька или вставок на Бэтчера — это как раз и он есть: видимое поведение не изменяется, а код оптимизируется по какой-то метрике.

Это не к тому, что я описывал. Во-первых, Вы почему-то предположили, что какая-то сортировка уже была. Ну ладно, примем. Тогда, при буквальном следовании TDD данный вид рефакторинга просто невозможен — его не допустит догма.

TDD — отличный инструмент для создания легкотестируемой архитектуры с покрытием тестами близким к 100%.

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

Если вы меняете архитектуру ради TDD, то вы проигрываете в другом. Как минимум приходится выбирать кристалл с большей памятью данных и кода.

Не спорю, что в 20% проектов TDD дает выигрыш. Речь не о них. А о том, что для остальных 80% — это религия или маркетинговый код.

По вашим же словам:



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

Все остальное — зависит от проекта. Я, например, больше люблю «assert driven development», то есть агрессивное расставление ассертов с сохранением их в production. В сочетании со структурными исключениями и перезапуском подсистем по возникновению исключений получаем неубиваемое приложение.

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

Я не знаком с исследованиями насколько TDD увеличивает потребности в памяти в продакшене. Вы знакомы?


Ну, я выбираю TDD по субъективной оценке технических и не очень свойств:


  • не влияет на рантайм (кроме архитектурных решений)
  • при разумном применении уменьшает связанность кода (те самые архитектурные решения)
  • несёт документирующую нагрузку
  • обеспечивает покрытие тестами меньшей ценой чем тесты после кода
  • обеспечивает страховку (неполную, конечно) от нежелательных изменений
Возьмите hello world, замерьте размеры. А потом сравните с рабочей программой в 300 байт кода и 8 бит рабочей памяти (и 20 бит GPIO). Ну и подумайте, насколько больший кристалл вам будет нужен для TDD по сравнению ну скажем с PIC16F57 c двумя килобайтами ПЗУ и 72 байтами ОЗУ.

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

Ну что там верно именно для вашего кода — я не знаю, потому и не спорю. Но…

обеспечивает покрытие тестами меньшей ценой чем тесты после кода

Тесты (что железа, что софта) бывают двух видов. Одни — чтобы доказать, что ошибок нет. Другие — чтобы найти ошибки. TDD дает лишь тесты первого рода.

Пример (чуть утрированный) из моей бытности руководителем группы тестирования на похожем на ваш проекте. Один человек регулярно делает ошибку типа ±1. Ну то есть вообще считает, что «не меньше» — это «больше» (на самом деле — больше либо равно). Программер вполне нормальный, багов не больше, чем у других, просто часты ошибки вот такого типа.

Что у него будет в TDD? Да ровно то же, что и в коде. В тестах он тоже решит, что «не меньше — это больше». И так у каждого, хороший тестер — это психолог, он ещё до тестирования понимает, куда стоит «потыкать палочкой»

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

Но, если, вы хотите качества продукта, то есть несколько «золотых» правил":

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


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

При этом я не отрицаю полезности TDD в отдельных нишах. И сам использовал его больше 25 лет назад (лет за 10 до того, как TDD получил своё имя), при написании электронных таблиц для УКНЦ. Там основную сложность представлял компилятор формул, а придуманный мной язык формул — хорошо ложился на схему TDD.

Но массовое использование TDD, где надо и где не надо — это карго-культ.
TDD дает лишь тесты первого рода.

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


Никогда не тестировать свой собственный код.

Собственно отличие подхода tests first от других именно в том, что программер не тестирует свой собственній код по сути. Тест написан, а кода ещё нет, есть только декларация намерений написать код, который в таких-то случаях будет вести себя так-то, практически окончательная постановка задачи самому себе в формализованном и, главное, автоматически верифицируемом by design виде. Естественно, что если такая постановка изначально неверная, не соответствует ТЗ или иным ожиданиям заказчика, то реализация тоже будет неверной с точки зрения заказчика, как и в случае если он сам налажал и не смог поставить задачу корректно, не смог описать свои ожидания.

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

Зачем уж так маньячить и слепо следовать "концепции TDD"? Берите оттуда то, что вам нужно. Интерфейсы конечно пишите до тестов, если так лучше.

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

Программирование(оно же проектирование) — придумывание структур данных, интерфейсов и алгоритмов. Кодирование — их запись на языке.

Если мы пишем быдлокод методом экстремального программирования, то интерфейсы (классы, методы) будут примитивными, берем первый попавшийся вариант, фигак-фигак и в продакшн в работу. Тут тесты действительно пишутся рано.

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

В TDD по сути тесты и есть зримый результат вашего "неделю думаем над архитектурой модуля и его API". Придумали — зафиксировали в тесте.

И опять ошибка. Алгоритм не фиксируется в тесте. Фиксируются максимум его параметры, то есть скорость работы. Попробуйте, зафиксируйте в цикле, что факториал мы вычисляем именно рекурсивно. Получится?

А API и структуры данных — описываются до теста, они нужны, чтобы тест хоть как-то компилился.

А кто говорил о фиксации алгоритма? Фиксируются именно API, структуры данных для него и какие-то контрольные точки. Какой конкретно алгоритм используется в вычислении факториала дело десятое, это уже не функциональные требования или решения к разрабатываемому модулю. Главное, проверить, что это именно факториал, а не н-ое число Фибоначчи, например. Тесты в TDD про функциональные требования, независящие от способа реализации.

В том-то и беда, что тесты зависят и от API и от алгоритма

Ну вот вам пример. Одно дело — функция четырех аргументов (курс и путевой угол по GNSS, курсы по гироскопу и магнитометру). Другое дело — класс с четырьмя property для аргументов и функцией извлечения результата. Чувствуете отличия в тесте? Ну как минимум надо проверить, что будет, если входные данные ещё не заданы.

А третья реализация — с фильтром Калмана внутри. Тут уже и предыстроия влияет…

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

А ТЗ простое «сделать комплексирование курса от GNSS, гироскопа и магнитометра». :-) Просто это НИР — люди об этом реальные научные статьи пишут.

Даже с факториалов не так просто. Если мы будем кэшировать 10 вычисленных значений факториала и отталкиваться от подходящего закэшированного, то добавится много новых тестов. Как минимум — расчет с нуля, расчет с предыдущего значения и расчет делением с последующего значения. И вcё это надо будет учесть.
Если мы будем кэшировать 10 вычисленных значений факториала и отталкиваться от подходящего закэшированного, то добавится много новых тестов. Как минимум — расчет с нуля, расчет с предыдущего значения и расчет делением с последующего значения. И вcё это надо будет учесть.

Тестов производительности и корректности? Для корректности property-based-тестов по-прежнему будет хватать за глаза.

Property-based это само по себе сложно. Мы пробовали (на PropEr). В большинстве случаев получалось, что описать необходимое поведение отдельной функции в тех терминах, что ему нужно, во много раз сложнее, чем написать саму функцию. ;(
А вот что в нём хорошо — поиск маргинальных случаев. Но можно и не дождаться.
Мы использовали нечто вроде property-based. Но не для тестирования, а для ранней диагностики ошибки в production. Вместе с пересозданием объектов и перезапуском подсистем — вышло хорошо.

3 раза пересоздали подряд объект — перезапускаем нить (подсистему). 3 перезапуска подсистемы — уходим на резервный сервер, а этот перезапускаем.

Просто надо без фанатизма — контролировать лишь те инварианты, которые просто контролировать.
Ну ОК, вот вам ситуация. В кэше хранятся пары x и х! (то есть х-факториал). При редком стечении обстоятельств при заполненном кэше у одной из пар заменяется значение х!, но не заменяется значение х. Получаем ((6,6!), (8, 7!), (9, 9!).

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

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

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

Да ну? Написали модуль об одной функции — вычисления фактриала. Мы эут функцию как тестируем? Юнит-тестом, верно? А потом поменяли потроха этой функции, чтобы использовать кэш. Интерфейс функции остался прежним. Просто тесты — то проходят, то нет. :-)

И чего это нам тут включать модуль в состав большой программы и заниматься интеграционным тестированием? :-)

Не, тут юнит-тесты, только хитрые. И assertы внутри функции расставить на соблюдение инвариантов.

Но лучше — сразу писать корректно. А ошибки искать — вычиткой кода.

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

Это верно.


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

А вот это неверно. Интерфейс функции изменился, в него добавился модуль кеша. То, что тестируемо — оно лежит после вычитки из кеша (т.е. фактическое вычисление). А кеш должен тестироваться отдельно. А функция подсчёта факториала + кеш == интеграционное тестирование двух модулей.


И чего это нам тут включать модуль в состав большой программы и заниматься интеграционным тестированием? :-)

Вы видимо не совсем корректно понимаете термин интеграционное тестирование… или юнит-тестирование.


Не, тут юнит-тесты, только хитрые. И assertы внутри функции расставить на соблюдение инвариантов.

тут именно интеграционные тесты — тесты взаимосвязи двух модулей — вычислительного и кеширующего.


Но лучше — сразу писать корректно. А ошибки искать — вычиткой кода.

вообще не спасёт


Не путайте настоящую гонку с псевдогонкой, возникающей при работе кэша в одной нити. Выглядит как гонка, но намного более воспроизводимо,

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

А вот это неверно. Интерфейс функции изменился, в него добавился модуль кеша.

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

Дело в том, что 8! мы можем считать и как 7!*8 и как 9!/9, то есть нам надо ближайшее с любой стороны.

И добавление в кэш специфическое. Если в кэше есть 5! и 10! то между ними нет смысла добавлять элемент. 2-3 умножения-деления — это немного.

Может вы и memcpy на пяток модулей разобьете? А то в ней 286 строк. :-) Факториал с кэшем — это строк 60-70, не больше.

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

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

Все проще. Как только у функции появляется внутреннее состоянии, становится возможной и гонка при многонитевом исполнении и псевдогонка при однонитевом. С настоящей гонкой справится проще — ставим функцию в критсекцию и дальше гонка невозможна. А вот зависимость от предыдущего вызова так просто не устраняется.

Чтобы не спорить о том, стоит ли разбивать факториал на два (или четыре) модуля — можете подумать над тем же вопросом про memcpy. насколько массив нужных тестов будет зависеть от реализации? И что надо тестировать для приведенной выше версии в 286 строк.
И что надо тестировать для приведенной выше версии в 286 строк.
И что нужно будет делать если версия на 286 строк вдруг превратится в версию на 3000 строк.

Хм… 286 строк?.. Глянул исходники — так это же по сути ассемблер! Да я вижу там некоторые красоты с/с++, но это всё-же ассемблер. А он, скотина, многострочный… Для языков высокого уровня и 30 строк на функцию имхо слишком много.


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


Ну а в целом — тестировать надо исходя из степени приемлемости инструмента. Юнит-тестирование — для мелких блоков-функциональностей. В приведённом вами примере есть два блока-функциональности: вычисление факториала и кеш-машина. Неважно что обе функции в примере простые как полешки — это именно различные компоненты. Каждый из них можно тестировать своим юнит-тестом, но сопряжение этих элементов уже будет сложной функциональностью, которую надо гнать через интеграционное тестирование (даже если оно будет запущено силами юнит-тестирующих библиотек/фреймворков).

Глянул исходники — так это же по сути ассемблер!
Это не «по сути», а полностью ассемблер. Расширение .S (не путать с .s !) — это ассемблер с обработкой C препроцессором.

Что именно в нём тестировать я говорить не возьмусь — недостаточно знаю ассемблер, чтобы бегло прочитать и понять, а тратить много времени как-то не очень тянет...
«Краевые случаи», однако.

Современные CPU могут очень быстро копировать блоки в 16 или 32 байта, но хорошо бы, чтобы хотя бы один из них был ещё и выровнян на 16 и/или 32 байта, а «остатки» ведь тоже кто-то должен копировать? Отсюда — куча «краевых случаев»…

Неважно что обе функции в примере простые как полешки — это именно различные компоненты.
Это только если вам не интересует скорость. Но если вас не интересует скорость — нафига вам там кеш?

Я сам, лично, имел опыт ускорения программ на порядок (то есть в 10-12 раз) избавляясь от таких вот «различных компонент», так что не надо говорить о том, что это «копейки».
Современные CPU могут очень быстро копировать блоки в 16 или 32 байта, но хорошо бы, чтобы хотя бы один из них был ещё и выровнян на 16 и/или 32 байта, а «остатки» ведь тоже кто-то должен копировать? Отсюда — куча «краевых случаев»…

Вот только современные процессоры (от Intel — так точно) уже не имеют падения производительности при работе с невыровненными данными. MOVUPS = MOVAPS в общем случае.

Можно пруф? Речь идет о скорости только процессора или и процессора и всей подсистемы памяти?
Можно пруф? Речь идет о скорости только процессора или и процессора и всей подсистемы памяти?

Речь идёт о latency: PDF


Видно, что у AMD задержка для выровненных данных (FA/M) меньше. А у Intel, начиная с Sandy Bridge, задержка выполнения — 1 такт что для (V)MOVUPS, что для (V)MOVAPS.


Ну и дополнительно: Link


On the Sandy Bridge, there is no performance penalty for reading or writing misaligned memory operands, except for the fact that it uses more cache banks so that the risk of cache conflicts is higher when the operand is misaligned. Store-to-load forwarding also works with misaligned operands in most cases.
Если я не путаю, то речь идет только о процессоре и кэше L1. Задержки на границах блоков внепроцессорной памяти остаются прежними.
Вот только современные процессоры (от Intel — так точно) уже не имеют падения производительности при работе с невыровненными данными.
Вот только не надо рассказывать сказок, а? Кеши и шину данных никто не отменял.

MOVUPS = MOVAPS в общем случае.
Не путайте разные вещи. Необходимость использования двух инструкций — да, осталось в прошлом. При работе с выровняными данными MOVUPS так же быстр нонче, как и MOVAPS. При работе с невыровненными… увы. Необходимость делать два цикла на шине вместо одного никакой «современный процессов» изжить не может.
При работе с невыровненными… увы. Необходимость делать два цикла на шине вместо одного никакой «современный процессов» изжить не может.

Вы давно читали мануалы по использованию векторнх инструкций? Давно использовали векторные инструкции на практике?


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


Мне не важно, как Intel технически добилась этого, но то, что потери в скорости при работе с невыровненными данными больше нет, это факт, а не сказка.

Редкая ситуация, когда я с вами согласен. :-)

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

Задача была такая. Есть таблица, в ней строки и столбцы. Запоминаем для некоторых (маркированных строк) набор значений в столбцах. Потом берем иную таблицу, вообще говоря с другим (но пересекающимся набором) набором столбцов. И надо было отметить запомненные (маркированные строки). В худшем случае у нас даже первичный ключ менялся.
Редкая ситуация, когда я с вами согласен. :-)
Мы немного о разном.

У меня, кстати, есть опыт ускорения на 3 порядка и тоже за счет хитрого кэша и сортировки кэшей.
Я не про это говорю. Я не про кеширование.

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

Я переписал программу используя макросы, inline-функций и автогенерённого кода так, чтобы компилятор мог выкинуть вычисление ненужных данных. Алгоритм при этом не изменился, но программа стала работать в 12 раз быстрее (прототип работал в 20 раз быстрее на GCC и Clang'е, но пришлось от него отказаться из-за ограничений MSVC, который, видя функция на 20000 строк «поднимал лапки кверху» и отказывался её оптимизировать).

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

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

А час на проект последний раз было в 1993 году, на СУБД Libra… Тогда как раз компиляцией поисковых запросов в машинный код удалось достигнуть скорости полнотекстового поиска по базе в 2 раза меньше, чем скорость считывания файла базы с диска. И это на I386. Собственно компиляцию писал Антон
Я переписал программу используя макросы, inline-функций и автогенерённого кода так, чтобы компилятор мог выкинуть вычисление ненужных данных.

А кто мешает тестировать inline-функции, генератор автогенеренного кода и так далее?

А кто мешает тестировать inline-функции, генератор автогенеренного кода и так далее?
Никто не мешает. Так и было сделано. Но, понимаете ли, когда вы тестируете функцию реализующую конечный автомат на полторы тысячи состояний, то любимые фанатами TDD трёхстрочники как-то «не срастаются». Подсистема тестирования получается достаточно сложная сама по себе и на классические юнит-тесты с мокапами всего, в чём больше 10 строк кода — нифига не похожая.

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

Эх, а я даже ни разу ничего не мокапал в своих тестах, как-то даже не нужно было. Похоже, у меня ненастоящий TDD.

Но на самом деле вы оба, конечно, правы: TDD — неплохой способ разрабатывать сложные системы в миллионы строк, в которых вы готовы пожертвовать 90% производительности.

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

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

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

Все-таки даже в большой системе на производительность серьезно влияет пара процентов кода.
Тут khim уже привел вариант memcpy на 3180 строк..

Что именно в нём тестировать я говорить не возьмусь

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

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

Увы, факториал — это не синус. Для синуса вы были бы правы. Но 8! = 8*7! = 8*7*6! = 9!/9 = 10!/10/9. То есть кэш нам нужен особенный, выдающий ближайшее значение. И записывать в к кэш надо хитро, не все подряд, а так чтобы охватить диапазон. И само вычисление факториала хитрое — две ветки, от большего считать или от меньшего.

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

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

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


Вам не кажется, что это приговор всесильности TDD?

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


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

Было бы желание. Вы просто не хотите придумать модульный вариант.

слишком слабо знаком с языком, на котором она написана.

Для сферического TDD в вакууме неважно, на каком языке функция написана. Важно — её API на том языке, с которого на вызывается. Так что достаточно знать С или С++ или ещё что, основанное на POSIX.

Вы просто не хотите придумать модульный вариант.

Придумать модульный вариант как раз легко. Модуль-хранилище, модуль извлечения ближайшего значения, модуль выборочного запоминания в кэше, модуль расчета факторала об большего, модуль расчета от меньшего, модуль-дирижер. Итого — 7 модулей. Бинго! :-)))

А вот вам советую попробовать придумать двухмодульный вариант, и написать межмодульное API. Тогда и поймете, что это мягко говоря неправильно.
Так что достаточно знать С или С++ или ещё что, основанное на POSIX.

О чём я и говорю — контракта я не знаю. А попытка вычислить по коду проваливается из-за недостаточного знания языка.


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

А я где-то говорил, что должно получиться строго два модуля?

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

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

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

Думаю, что на примере memcpy очевидно, что быдлокод — прост для изменения, а хорошо оптимизированный, вылизанный код — для изменения сложен.
Вот только контракт — недостаточен для написания исчерпывающих тестов.

Не надо выдавать общую проблему программирования за нормальное явление.


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

Сильная оптимизация функций не должна ломать контракт. А TDD работает именно с контрактом.


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

очень зависит от соотношения затрат на вычисления.


Думаю, что на примере memcpy очевидно, что быдлокод — прост для изменения, а хорошо оптимизированный, вылизанный код — для изменения сложен.

Вы уверены, что там быдлокод? Быдлокод и вусмерть оптимизированный — вещи разного порядка.

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

Иными словами — полнота тестирования по TDD в отдельных случаях — липовая. В данном случае TDD проверит меньше процента того, что нужно проверить.

Что касается быдлокода — гляньте в вики. Первая версия — быдлокодистая. но простая для понимания и модификации. Оптимизированная версия — уже не быдлокод, но понимается и модифицируется хуже. А «вусмерть оптимизированный» код вообще модифицируется с большим трудом. В отличие от быдлокода.

P.S. Если под быдлокодом вы понимаете что-то иное, то опишите, что именно.
Сильная оптимизация функций не должна ломать контракт.
Я провильно понимаю, что TDD предлагается использовать там где некое априорное знание говорит нам о том, что фунция всегда будет соблюдать свой контракт?

А TDD работает именно с контрактом.
Нет, она, как бы с кодом работает. А дальше — возникает дилемма: что и как тестировать-то? Два случайно выбранных варинта или десять? А может сто? А что ещё вы можете придумать не имея кода (а в TDD, как бы, тесты пишутся до кода, да).

Я уже рассказывал про обработку x86 кода. Так вот там, среди прочего, был ещё и конечный автомат, который выделял опкоды и другие части валидных инструкций. В нём было порядка трёх тысяч состояний. Для тестирования — была написана программа, которая просто загружала этот автомат и строила все последовательности байт, которые этот автомат, в приниципе, мог принять, выкидывала «неинтересные» различия (иначе последовательностей получалось уж слишком много: один mov с 64-битным числом даст вам квинтиллионы допустимых последовательностей), затем проверяла разными способами результат работы на этих последовательностях.

А куда вы тут свой любимый TDD прикрутите? Как тут осмысленный тест до написания кода написать?

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

Нет, тесты в TDD как раз проверяют, исполняет ли функция контракт.


Два случайно выбранных варинта или десять? А может сто?

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


хотя, пожалуй, можно

Нельзя. TDD применим только к задачам, где есть чётко детерминированный результат заранее известный для тестируемого состояния системы. На моей практике в геймдеве и финансах TDD был не применим для моделирования процессов зависящих от ГСЧ и текущего времени. Можно тем или иным способом из тестируемого модуля вынести генерацию СЧ или получение времени в зависимость модуля и протестировать логику зависимости для конкретных значений, но полностью процесс — нет: на каком-то уровне приходилось хардкодить получение СЧ и времени и этот уровень (и вышележащие) тестированию уже не поддавался. И это только по тестированию контрактов, что в голову пришло.

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

Отсюда у вас и желание — узнать, что написано в коде.

Достаточно это узнавать в полуавтоматическом режиме. Мутационное тестирование выше уже предлагали.


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

Если даже компилятор не заинлайнит, -flto или {-# INLINE #-}, смотря что у вас там, никто не отменял.

А вот вам советую попробовать придумать двухмодульный вариант, и написать межмодульное API.

// SparsityStrategy<T>::ShouldStore(const T& value, const std::optional<T>& closestLess, const std::optional<T>& closestGreater);
// EvictionStrategy аналогично
template<typename K, typename V,
        template<typename> class SparsityStrategy,
        template<typename> class EvictionStrategy>
class Cache
{
public:
    std::optional<V> getNearest(const K& val) const;
    void insert(const K& key, const V& val);
};
Явная ошибка. Вы хотите найти 7!, вызываете getNearest(7), а в ответ получаете 6!.. То есть у вас пропадает информация о том, для какого числа найден факториал.

Это что, у меня сначало вместо отдельных K и V было T. Когда менял — забыл тут пару возвращать. Не надо писать код с утра еле проснувшись :)


Но суть от этого не меняется, замените на std::optional<std::pair<K, V>>

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

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

И это тоже будет вычиткой.

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

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

Возьмусь, если есть спека в том или ином виде.


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

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


Если упарываться, модуль с факториалом ещё определяет конкретные стратегии (sparse'вость кеша, политика его опустошения и тому подобное). Всё равно вполне тестируемо.

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

Если упарываться, модуль с факториалом ещё определяет конкретные стратегии (sparse'вость кеша, политика его опустошения и тому подобное)

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

Можно наоборот, определять стратегию в модуле кэша, а ему давать каждый вычисленный результат. Тогда кэш очень много будет знать о факториале. Этот способ чуть проще, ибо для того, чтобы решить, запихивать ли в кэш 37! нам надо знать, есть ли там 36! или 38! Модуль кэша об этом знает, а модулю факториала — надо для этого давать лишние ручки.

В итоге, что так, что так — нехорошо. Лучше уж единый модуль.
Спецификация на memcpy — общеизвестна. А вот тонкости оптимизации вы можете понять только из кода. Поэтому в этой ситуации, не зная алгоритмов оптимизации, вы не можете написать хороший набор тестов.

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


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


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


Стратегии кэша определяет вовсе не модуль кэша, а модуль факториала.

И их тоже можно тестировать.


В итоге модульные тесты ни модуля факториала, ни модуля кэша не проверяют эффективность стратегии. Для её проверки — надо тестировать оба модуля сразу.

А юнит-тесты и не должны проверять эффективность.

Знание применённых методов оптимизации позволяет вам существенно сузить это пространство.

Конечно. Вот только это уже не TDD. потому что сначала — разработка методов оптимизации, а уж потом — написание тестов. Этот пример показывает, что TDD — не универсален. Он хорошо работает, если логика очевидна из ТЗ. А если есть куча оптимизаций, не прописанных в ТЗ, то TDD лажает.

А юнит-тесты и не должны проверять эффективность.

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

В целом приложении неэффективность размазывается по модулям. Тестируя отдельную операцию, мы понимаем, правильный ли уровень оптимизации алгоритма выбран. Не надо ли переписать в 100 раз длиннее и в 10 раз быстрее. :-)
Этот пример показывает, что TDD — не универсален. Он хорошо работает, если логика очевидна из ТЗ. А если есть куча оптимизаций, не прописанных в ТЗ, то TDD лажает.

А ещё TDD налажает, если у вас в коде if (arr[3] == 'a') launch_missiles();. Какой из этого можно сделать вывод?


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

Ну, да. И это совсем другой уровень тестирования.

Но лучше — сразу писать корректно
Конечно, лучше. А еще лучше — сразу писать программу, которую Google за миллиард купит.
Написать корректно — дорого, но реально. Причем реально для любого программиста. А вот участвовать в том, что купил взял гугл — удалось лишь одному из моих знакомых.
Написать корректно — дорого, но реально.
Я вот не знаю методов, которые позволили бы получить гарантию соответствия кода ТЗ, а также соответствия ТЗ тому, что в голове у заказчика (внутреннего или внешнего). Если у вас есть какая-то релевантная информация — поделитесь, я бы с радостью изучил подходы, дающие такие результаты.
Метод простой — вычитка кода. В предельном случае — 300 строк вычитывали впятером. Плюс на те же 300 строк — 60 страниц документации, объясняющей, почему именно так. ТЗ на такой код кусок пишет сам разработчик, отсюда и соответствие кода ТЗ. А соответствие ТЗ и ПЗ (постановки задачи) проверяется при вычитке.

Подход дорогой, но у нас было 2 часа на отладку в месяц.

Мой коллега — так просто пишет без ошибок. Зато медленно…
Гарантии корректности кода я тут не вижу. В моей практике был случай, когда втроем вычитывали код и не заметили, что ветки «if» и «else» содержат одинаковый код, что привело к реальным необратимым увечьям для десятков человек.

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

Я бы и не против сразу все правильно написать,

А экономика? Можно писать со скоростью 5-10 строк в день и без ошибок, но эффективней — писать в 20 раз быстрее, но с отладкой.

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

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

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

Это КАК, простите? И сразу в голове багрепорт:

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


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

Если потери от баги будут больше, чем затраты на тестирование, то тестирование у вас будет

Не-а. Будет вычитка кода

Реальный пример. Комп MOXA IA3341-LX, ключ Guardant Code micro, linux 2.6.9. Незадолго до отправки заказчику обнаружили, что примерно через 3 часа обмен с ключом по USB прекращается и не восстанавливается вплоть до перезагрузки. Разработчики ключа чешут репу, разработчики компа — молчат как партизаны.

Для тестирования нужен минимум анализатор протокола USB со сроками поставки недели 3. Ну и согласие разработчиков ключа на реверс-инжениринг их протокола.

В итоге плюнули на тесты и я сел читать код USB-подсистемы linux. И за пару дней нашел подозрительный кусок. А в следующей версии — патч, который эту ошибку исправлял.

А теперь подумайте, сколько дней мы бы ждали логического анализатора USB и сколько дней бы занимались тестированием? :-)

Собственно именно это я и понимаю как «цена ошибки достаточно высока».

См. выше. Перезагрузка раз в 3 часа вместо пары раз в год была абсолютно неприемлема для заказчика. И баг был не наш. И все равно — обошлись без тестирования.

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

В процессе тестирования?

Стыдно признаться, но в процессе документирования. При тестировании мы не делали длительных тестов. Linux, древняя версия (то есть все патчи должны быть наложены), фирма с хорошей репутацией (moxa — это такой промышленный эталон для COM-преобразователей)… Ну в общем совсем не ожидали.

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

А вы? Вы тестируете ядро операционной системы?
Кстати, мы ещё и процессоры тоже обычно не тестируем. Хотя errata на них аж 40 страниц…
Это КАК, простите?
А вот так:
Пользователь купил стиральную машину, нажимает на кнопку «постирать», а она пищит и ничего не делает. Он обращается в техподдержку, которая, убедившись в наличии проблемы, создает во внутренней системе багу на команду, которая делала кнопку «постирать». Там команда разбирается, выясняет, что это из-за того, что драйвер датчика температуры отвечает медленнее, чем нужно, и создает багу на вашу команду.
Не-а. Будет вычитка кода
Стоимость поиска баги довольно быстро растет при повышении требуемого уровня качества. В какой-то момент станет просто дешевле сделать небольшое тестирование, помимо вычитки. То же самое, кстати, верно и в случае, если используется только тестирование, если оно довольно дешевое — при повышении требуемого уровня качества оно быстро становится дороже и заменяется на комбинацию этих подходов.
Я не против тестирования там, где затраты на него малы. Но там, где дешевле написать код заново, чем тестировать — там лучше вычитка кода вместо тестов.
Абсолютно согласен, вот мы и пришли к взаимопониманию.

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

А если на производстве не тот кварц поставили? А если процессор с другой тактовой? Ну в общем запас менее 10 раз по скорости работы датчиков — это профнепригодность прикладной команды. Скорее всего используют асинхронный датчик как синхронный и вообще таймаут не сделали.

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

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

Я сделал вывод, что вы знаете какой-то идеальный метод, позволяющий вам лично писать абсолютно безошибочный код,

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

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

Но мы его никому не отдадим. Такой монстр нам самим нужен. :-)
Если вы про обнаружение, есть ли баг вообше — то согласен. А если известно, что ошибка есть <...>, гарантирующие нахождение ошибочной строки за какой-то большой, но конечный срок.
Да, я про обнаружение неизвестного бага. Если есть сценарий воспроизведения, все гораздо проще.

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

У одного коллеги был секрет — всё, что я стараюсь юнит-тестами отловить, он отлавливал просто запуском программы (иногда специально "взломанной" для получения нужного исходного состояния) и ручным вводом тех же параметров.

Казалось бы, это ничем не лучше юнит-тестов. Баги находит те же, писать до кода нельзя, автоматически (да и вообще повторно) запускать нельзя.
Да, я про обнаружение неизвестного бага.

Согласен. Тяжело искать черную кошку в черной комнате, особенно когда её нет. Если известно, что баг есть — все становится немного проще.

Может, у него еще какой-то секрет есть?

Синтетик он, чистый. То есть в чужом коде разобраться — это для него огромный труд. Проще свой собственный код написать.

Извините, как-то не верится, что у него прямо никогда не бывает ошибок и в будущем никогда не будет,

При недостатке времени — бывают. А достаток времени — это год какая-то функция не пишется, потому что непонятно, как хорошо обработать частный случай, который бывает в 0.01% ситуаций.

Но реально, код очень простой и надежный.
В итоге плюнули на тесты и я сел читать код USB-подсистемы linux. И за пару дней нашел подозрительный кусок. А в следующей версии — патч, который эту ошибку исправлял.

Проблема в том, что ни тесты, ни вычитка не гарантируют формальной корректности, но вычитка не гарантирует куда больше.

Если мы хотим показать, что программа правильно работает — эффективней тесты.

Если мы хотим найти побольше ошибок — эффективней обычно вычитка.

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

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

Советую вам поспорить с любителями стат.анализа. В конце концов стат.анализ — это автоматизированная вычитка.

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


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

ручная вычитка менее тщательная, ем автоматическая. Зато количество проверок — больше, чем в любом статанализаторе.

Только по сравнению с машинами у людей attention span — как у золотой рыбки. Вы довольно легко и быстро найдёте ошибку, которая, как бы это сказать, локализована. Ошибку, которая размазана по куче модулей и возникает из-за особенностей их взаимодействия, вы найдёте только при большом везении (а достаточно мощная система типов, позволяющая точнее и строже описать их контракты, могла бы находить их с большим успехом).

Мне удобней отвечать в одном месте, поэтому всю дискуссию переносим сюда.

TDD (как и любое тестирование) — вообще не ищет ошибку. Оно лишь дает признаки, что ошибка есть. А сама ошибка в коде все равно находится вычиткой. Так что мала концентрация внимания или нет — но все равно все ошибки найдены вычиткой.

С концентрацией внимания в TDD все хуже, чем при вычитке. И дело не только в if (arr[3] == 'a') launch_missiles();, но больше в том, что тестирование — это вгляд на программу с другой стороны. Не со стороны синтеза, а со стороны анализа. Программеры — синтетики, а для написания хороших тестов надо быть аналитиком.

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

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

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

Я понял, что у нас проблемы с согласованием терминологии. Test-driven development, действительно, ошибки не ищет. Трудно искать ошибки, когда (как при каноничном TDD) вы сначала пишете тест на код, а потом уже код, поэтому по определению кода, в котором пишущийся тест мог бы найти ошибку, нет.


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


Мощная система типов — это полезно. Собственно за повышенную мощность типов (диапазонные типы, множества) мне и нравится Дельфи.

Это, конечно, хорошо, но этого, мягко скажем, недостаточно. Есть системы и помощнее.

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

А внимание замыливается и там и там.
Вот +100. Хотя с поправкой (мне совершенно безразличной, но формально актуальной) — это относится к тестированию в целом, а не к TDD.
Вычитка (слово понравилось, надо зафиксировать) и тесты ортогональны, тесты ловят проблемы, которые не замечаются человеком из-за специфики мышления (то же «замыливание» только первый эффект из многих) и проблемы соответствия внешней среды ожидаемому, а вычитка — что реализация в принципе делает то, что ожидает человек. Тесты без вычитки превращаются в формальность, не требующую работы кода за пределами тестов, а вычитка без тестов — в сферическую работу в вакууме. При хорошем качестве разработчиков (как Вы уверждаете про своих) глупых ошибок и мифов — очень мало, остаются ошибки и несоответствия внешней среды (тот пример с USB за 3 часа — очень показателен).
Тесты обычно решают прямую задачу «найти ошибки». А в TDD проблема в решении обратной задачи — «доказать, что ошибок нет». И вот эта обратная задача — решается ещё хуже, чем прямая.

Основная беда TDD — тестирование по модели белого ящика. То есть при написании кода заведомо известны тесты ( что они проверяют, а что не проверяют). При классическом тестировании (черный или серый ящик) вообще не встает вопрос о том, что пишется раньше — тесты или код. Они пишутся независимо. И шансы, что и тестер и девелопер пропустят какой-то случай — равны произведению шансов на пропуск одним из них.

Если девелопер или тест забывают какую-то сложную ситуацию с шансом 10%, то шансы, что забудут оба — всего лишь 1%. Таким образом, отказ от TDD в пользу независимых тестов в 10 раз улучшает работу программы в сложных ситуациях.
> Тесты обычно решают прямую задачу «найти ошибки».

Это только кратковременная задача для одноразовых тестов. Долговременная же задача — обеспечить условия для проверки по сути (вычитка, верификация).
В это может входить что угодно вплоть до проверки базовых свойств среды. У меня в одном проекте есть в тестах assert(sizeof(long)) == 8 — вот решили, что лучше завязаться и обложить проверками.
Но чаще ими покрываются крайние случаи.

Да, я видел и помню Ваши предыдущие реплики по поводу, например, невыгодности написания тестов авторами кода. У меня другая специфика, и >90% тестирования идёт таки авторами. Поэтому тут идеально общей позиции не будет.

> А в TDD проблема в решении обратной задачи — «доказать, что ошибок нет». И вот эта обратная задача — решается ещё хуже, чем прямая.

А её в принципе нельзя решать тестами. Для этого есть верификация. Тесты — средство обеспечения надёжности самой верификации.
TDD к этому ни при чём — это средство управления добавлением функциональности при разработке, а не обеспечения качества.

> Основная беда TDD — тестирование по модели белого ящика.
Не тестирование. А написание кода под тесты.

> Если девелопер или тест забывают какую-то сложную ситуацию с шансом 10%, то шансы, что забудут оба — всего лишь 1%. Таким образом, отказ от TDD в пользу независимых тестов в 10 раз улучшает работу программы в сложных ситуациях.

Это аргументы за независимое тестирование — они сильнее, чем просто против TDD.
У меня в одном проекте есть в тестах assert(sizeof(long)) == 8

Рекомендую использовать CCASSERT, то есть assert времени компиляции. И коли ваш проект кроссплатформенный, то делать это не в тестах, а в основном коде. Верещать о том, что компиляция идет не на ту платформу — надо при компиляции, а не при выполнении или тестировании.

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

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

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

TDD к этому ни при чём — это средство управления добавлением функциональности при разработке, а не обеспечения качества.

Как средство разработки я его тоже иногда использую (и использовал задолго до того, как этот метод получил имя). Но… вы видели менеджера, который при TDD со «100% покрытием» будет тратить средства на полноценное тестирование? Вот и получается, что или тестировании или TDD.
Рекомендую использовать CCASSERT, то есть assert времени компиляции.

А чем static_assert не угодил? Специально ж для этого.

Но… вы видели менеджера, который при TDD со «100% покрытием» будет тратить средства на полноценное тестирование?

Имею счастье с такими людьми работать, так что да, видел.

Но я не ненастоящий программист, сталепрокатные станки не программирую.
А чем static_assert не угодил?

Его нет в Си.
Тут сам собой напрашивается вопрос о том, зачем ограничивать себя С, ну да ладно.
С точностью до наоборот. Зачем писать код, компилируемый только С++, если нам не нужны классы? Капелька усилий — и код работает и в Си и в С++.

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

Чтоб два раз не вставать — в Си-style намного лучше с инкапсуляцией. Можно написать в заголовке struct internal_data; и пяток методов, в которые передается эта структура. Описание полей самой структуры и приватные процедуры будет только в.с/.cpp. А вот в С++ в заголовочном файле будут и приватные члены и приватные методы.

Ну и ещё один момент. Си намного проще запускается на голом железе. Обработчик прерываний на С++ написать можно, но это сложнее, чем на Си. Так что все, потенциально вызываемое из прерываний (а также асинхронных вызовов ОС и так далее) — сишное. Точнее — транслируемое и в Си и в С++.
Зачем писать код, компилируемый только С++, если нам не нужны классы? Капелька усилий — и код работает и в Си и в С++.

Потому что это проще. Потому что в C++ кроме классов есть много других замечательных полезных вещей.

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

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

В любом случае, мне не нужно, чтобы библиотечный код компилировался в С. Мне нужно, чтобы он умел предоставлять C API. Ну, как clang API, например.

Чтоб два раз не вставать — в Си-style намного лучше с инкапсуляцией. Можно написать в заголовке struct internal_data; и пяток методов, в которые передается эта структура. Описание полей самой структуры и приватные процедуры будет только в.с/.cpp. А вот в С++ в заголовочном файле будут и приватные члены и приватные методы.

Вы слышали что-нибудь про идиому pimpl?
Знаете, кому-то и на ассемблере писать проще. В С++ есть полезные вещи, но… размеры подключаемой для них системной библиотеки таковы, что на 512К flash-памяти их использовать не стоит. А если мы пишем библиотечные функции, то стараемся, чтобы они работали везде — Windows. linux, FreeRTOS и т.д.

Разумеется, если те же классы нужны по делу, то есть упрощают написание в 2-3 раза — надо их использовать. Как и все прочие фишки С++. но если получается 5% туда-сюда, то лучше на Си.

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

Тогда жду ссылку на ОС, написанную на С++. Или делающую вызовы в стиле С++. В конечном итоге любой С++ный код дергается через Cишный вызов. Когда это только main — это не так страшно, можно переходниками. В windows GUI -уже неудобно, сишных вызовов многовато. Ну а когда их совсем много…

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

А вам много чего не нужно. Обработчики прерываний писать не нужно, например. Работать на FreeRTOS не нужно. Упихиваться в 512К ПЗУ и 392 ОЗУ не нужно. А у нас обратная картина. Большинство возможностей С++ не нужно. Зато возможность компиляции на Си — очень полезна. Ну хотя бы потому, что есть шансы нарваться на машинку без С++.

Вы слышали что-нибудь про идиому pimpl?

Глянул. Сложный путь сделать то, что в Си делается просто. Хороший пример, как на пустом месте наворачиваются абстракции. Очень рекомендую почитать мнение моего бывшего коллеги про таких любителей ООП. Впрочем, рекомендую прочесть всю книжку — она реально полезная.
В С++ есть полезные вещи, но… размеры подключаемой для них системной библиотеки таковы, что на 512К flash-памяти их использовать не стоит.

Какая библиотека нужна для вариадиков? Для лямбд? Для move semantics? Для constexpr-функций?

Тогда жду ссылку на ОС, написанную на С++. Или делающую вызовы в стиле С++.

Я не понял этого логического перехода вот сейчас.

А вам много чего не нужно. Обработчики прерываний писать не нужно, например. Работать на FreeRTOS не нужно. Упихиваться в 512К ПЗУ и 392 ОЗУ не нужно.

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

На плюсах я упихивался в attiny, кстати, в своё время. Ну, да, -fno-rtti -fno-exceptions, какой ужас.

Глянул.

Значит, не слышали. И почему я не удивлён?

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

Что там сложного, какие абстракции? В хедере объявляется приватный класс, в .cpp он определяется и реализуется.

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

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

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

А вы слинкуйтесь без с++ библиотеки — и увидите. А самая гадость в том, что С++ библиотека вывод сообщений об ошибке делает через iostream, так что ещё и его за собой тащит.

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

Я не понял этого логического перехода вот сейчас.

Печалька! Увы, пока ОС не написана на С++, С++ный код будет вызываться из Сишного. Говоря вашим языком «дергаться из других языков». В нашем случае — в нескольких десятках мест.

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

Вы предлагаете мне сменить работу? Ну так, если менять работу — то на delphi, oberon, modulu-2, kotlin, rust, а не на С++. Нам — нужно. На этом точка.

На плюсах я упихивался в attiny, кстати, в своё время. Ну, да, -fno-rtti -fno-exceptions, какой ужас.

АГА, АГА. Без iostream, без new, без pure virtual и так далее. Многое там остается от С++? Многие части С++ будут жить без new?

И вот тут и встает вопрос. Что удобнее для задачи — писать на С++, но помнить, что большую часть использовать нельзя или писать на практически полном Си? Если есть полиморфизм — может быть удобнее и на С++. Если его нету — удобнее полный Си.

С полутора макросами это всё вообще начинает выглядеть вполне красиво и пристойно

А если ещё написать свой препроцессор — так и вообще красота. :-)) Не пугайтесь, это сарказм был. я просто RATFOR вспомнил.

отбросим необходимость перекомпилировать клиентский код при изменении хедера.

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

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

Увидел, не нужно для всего этого.

А самая гадость в том, что С++ библиотека вывод сообщений об ошибке делает через iostream, так что ещё и его за собой тащит.

Эм, я сходу не припомню, где в «C++ библиотеке» вообще вывод сообщений об ошибке?

Увы, пока ОС не написана на С++, С++ный код будет вызываться из Сишного. Говоря вашим языком «дергаться из других языков». В нашем случае — в нескольких десятках мест.

Как и любой другой код на любом другом языке. Всё, нет языка, кроме С, и ассемблер — пророк его?

Вы предлагаете мне сменить работу?

Я предлагаю не говорить «а вам много чего не нужно».

Ну так, если менять работу — то на delphi, oberon, modulu-2, kotlin, rust, а не на С++.

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

Нам — нужно. На этом точка.

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

АГА, АГА. Без iostream, без new, без pure virtual и так далее. Многое там остается от С++?

Ага. Без всего этого.

Зачем мне вообще хоть iostream, хоть printf на attiny? Байтики записал в порт и сиди занимайся дальше своими делами, а на большой машине это всё обработал уже и вывел хоть в лог, хоть в гуи.

Многие части С++ будут жить без new?

Вы таки не поверите, но значительная часть. Вот все эти пресловутые вариадики-мувы-лямбды-констеспры, а также половина STL уж точно, всякие там find_if'ы, сорты, lower_bound'ы, пермутейшны и прочее радости жизни. Да все алгоритмы, я бы сказал.

Если есть полиморфизм — может быть удобнее и на С++. Если его нету — удобнее полный Си.

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

А если ещё написать свой препроцессор — так и вообще красота. :-)) Не пугайтесь, это сарказм был. я просто RATFOR вспомнил.

Когда в сишечке надо дёргать препроцессор, чтобы получить аналог static_assert — это норма. Когда в плюсах его надо изредка дёргать ради красот — это у вас вызывает сарказм. Забавно.

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

Зачем? У вас наружу интерфейсы торчат, и всё. Ну, если уж правильно инкапсуляцию делать.

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

Кому удобнее? Они на 64К ПЗУ не только запускают, но и редактируют свои программы, что ли?
Кому удобнее?

Программисту удобнее, когда весь внешний интерфейс модуля — это страничка. Особенно это если это linux oldscool, то есть работа в командной строки с редактором vi (а GUI нет, ибо частенько ходим через ssh).

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

Одно дело, если у нас сотня программистов и трудоемкость человеко-годы.
Тут параллельная документация может и ускорить проект. Другое дело, если у нас 1-3 программиста и меньше человеко-года на проект. Тут лучше иметь смодокументируемые читаемые хеадеры.

Зачем? У вас наружу интерфейсы торчат, и всё. Ну, если уж правильно инкапсуляцию делать.

Ну сделайте на своем собственном проекте по вычислению производной в compile time. :-) Если сделаете — покажите. :-) Код у вас весьма неплохой, кстати.

Эм, я сходу не припомню, где в «C++ библиотеке» вообще вывод сообщений об ошибке?

Ну почитайте про __cxa_pure_virtual и __cxa_guard_acquire. Это чисто С++ прибамбасы, на Си они не нужны. Вывод сообщения есть в __cxa_pure_virtual и __cxa_deleted_virtual. Это старый С++, а что уж там добавлено в новом — чет его знает.

Как-то странно делить задачи по критерию «есть полиморфизм — нет полиморфизма».

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

Вариадики, лямбды, мувы и constexpr, которым нужна какая-то библиотека, ага.

Дались они вам! Не вы ли писали, что ни разу в жизни не использовали функции с переменным числом параметров? Правда потом выяснилось, что про printf забыли. Ну вот и у меня ни разу не было необходимости использовать вариардики.

Понимаете, С++ это язык, который плодит проблемы, а потом их мужественно затыкает. Зачем нужны move? Прежде всего для возврата объектов из функции через return. То есть вначале в С++ не запретили возврат объектов из функции. Ну мало ли, объект маленький, накладные расходы будут не так велики. А потом стали использовать это и для больших объектов. И тут жестко потребовался move. А если мы не передаем объекты через return — никакой move нам не нужен.

constexpr — ну может доделают когда-нибудь. Пока что inline работает предсказуемей.

Лямбды — пока в них нужды не было.

Зато есть куча других вещей, которых в C++ не хватает. Например finally, property, interafce, отображения hadrware exception в исключения и так далее. Вы ими никогда не пользовались, потому даже не буду пытаться объяснить, зачем они нужны. Но это не только синтаксический сахар, но и реально ускоряющие разработку некоторых программ вещи.
Программисту удобнее, когда весь внешний интерфейс модуля — это страничка. Особенно это если это linux oldscool, то есть работа в командной строки с редактором vi (а GUI нет, ибо частенько ходим через ssh).

Хожу на машины по ssh, GUI есть, clion есть.

Знающие люди к vim всякие там YouCompleteMe, ctags и прочие clang'и прикручивают, кстати. Мне лень было, я clion пользуюсь, а до того — kdevelop. vim у меня для набивания папиров в LaTeX и для хаскеля.

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

Ну сделайте на своем собственном проекте по вычислению производной в compile time.

Мы классы или темплейты обсуждаем? Это ж таки две большие разницы.

Ну почитайте про __cxa_pure_virtual и __cxa_guard_acquire. Это чисто С++ прибамбасы, на Си они не нужны. Вывод сообщения есть в __cxa_pure_virtual и __cxa_deleted_virtual. Это старый С++, а что уж там добавлено в новом — чет его знает.

Это не старый С++, это вообще не часть стандарта С++, это деталь реализации. Вы можете (или поставщик вашего рантайма) там не печатать ничего, послать байтик хосту вашего устройства или вообще светодиодом моргнуть.

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

А вы точно мне отвечали?

На всякий случай уточню: как-то странно делить задачи по критерию «есть полиморфизм — нет полиморфизма» в контексте спора C vs C++.

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

Не вы ли писали, что ни разу в жизни не использовали функции с переменным числом параметров? [...] Ну вот и у меня ни разу не было необходимости использовать вариардики.

Сишные не использовал, ага. А вот на плюсах с вариадиками я подобные вещи пишу достаточно регулярно. Только делать это на плюсах на этапе компиляции по очевидным причинам (типобезопасность, эффективность, совместимость с не-POD-типами) лучше, чем на С с va_args.

Правда потом выяснилось, что про printf забыли.

Как это забыл? Я им тоже не пользовался. Вернее, скажем так: ни одного коммита с ним от меня не было нигде.

А если мы не передаем объекты через return — никакой move нам не нужен.

А как вы их передаёте? Через out-параметры? Значит, вам надо уметь создавать их дефолтовые значения, уметь присваивать, и так далее. Это не всегда имеет смысл, и не всегда делает код проще и эффективнее.

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

constexpr — ну может доделают когда-нибудь.

В C++14 доделали.

Пока что inline работает предсказуемей.

Правда?

Во-первых, что вы не знаете, как с constexpr работать, я не удивлён, это же новьё ненужное, но семантику inline вы тоже не знаете, похоже. Компиляторы нынче даже __forceinline и иже с ними могут игнорировать, и inline влияет только на то, является ли ошибкой многократное определение символа в разных TU или нет (ну и ещё в паре мелочей).

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

Лямбды — пока в них нужды не было.

Ни разу ни один предикат не нужно было создать для std::find_if/all_of/partition/whatever?

Например finally

RAII.

Можно мувать наружу :]

property

Реализовывал ещё будучи школьником в бородатом 2003-ем. Ну, да, из коробки нет, ужас.

interafce

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

отображения hadrware exception в исключения

Вот это да, нету. Но это всё равно платформозависимо, как это может быть в рамках языка?

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

Я из всего этого не пользовался только последним, у вас хрустальный шар сломался.
Или вы на проде файлики редактируете?

Что такое прод? Вот есть у меня 20 роутеров, кто из них прод, а кто нет? Воткнули USB-флэшку — это ещё прод или уже нет? Загрузили полный linux с флэшки — 'это ещё прод? Воткнули консоль в разъем — это ещё прод?

Фактически прод от «не прода» отличается тем, что технологический разъем можно не впаивать. Но обычно впаивается — он для диагностики полезен. А так — памяти на linux-машинках на компиляцию хватает, а если веши железоспецифичные, то цикл отладки на самой железке хватает. Ну а на x86-машинках вообще стоит полный дебиан (только что без GUI), там просто нету смысла buildroot ставить. Там где linux нету — там иначе.

Про остальное — потом отвечу
Как обещал, продолжаю.
Это не старый С++, это вообще не часть стандарта С++, это деталь реализации.

Ну скажем так, это деталь реализации, обусловленная стандартом. Мы не можем выполнить чисто виртуальный метод, но что-то должны сделать. Разумнее всего — завершиться с выводом сообщения. Если бы стандарт требовал вызвать исключение — было бы проще. Впрочем, завершение по непойманному исключению — тоже требует вывода сообщения. Ну по стандарту, небось, можно и втихую завершиться, но разумнее — выдавать сообщения. Так что в каждом С++ рантайме какая-то выдача сообщений будет.

На всякий случай уточню: как-то странно делить задачи по критерию «есть полиморфизм — нет полиморфизма» в контексте спора C vs C++

Знаете, писать полиморфизм на Си — это ещё то извращение. Если уж полиморфизм есть, то С++ даст выигрыш во времени написания и отладки. А если полиморфизма нет — то Си может быть дешевле. Или как у нас — Си с элементами С++.

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

Угу, возвращаются указатели (или ссылки), а не сами объекты. Выделение памяти — быстрое, за счет предварительно накачанных список. Мы этой техникой ещё 25 лет назад пользовались. В куче выделяются области памяти, кратные 16. Создаем массивы с указателями на области памяти. Один массив — на длину 16 байт, другой на 32, третий на 48… И на каждый массив — битовую шкалу, кто выделен, а кто нет. Получается очень быстрое выделение памяти, намного быстрее копирования объектов.

У меня есть свои претензии, но чем именно вас не устраивают чистые абстрактные классы в плюсах?

Множественным наследованием. Но вы правы, можно и так делать.

Вот это да, нету. Но это всё равно платформозависимо, как это может быть в рамках языка?

Здрасти, приехали. signal — плаитформеннонезависим, почему его отображение в исключения будет платформенно зависимо?
От платформенной информации нам нужна только запись в лог. А основное, что нужно — не зависит от платформы. Это деление на классы:
  • Внешние действия, вроде SIGABRT и SIGINT
  • Ошибки в коде, вроде SIGILL, SIGSEGV
  • Что-то исправимое, вроде нехватки памяти (оно и так exception)l

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

Реализовывал ещё будучи школьником в бородатом 2003-ем. Ну, да, из коробки нет, ужас.

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

Ни разу ни один предикат не нужно было создать для std::find_if/all_of/partition/whatever?

Угу. Мы же пишем на Си с элементами С++. Так что STL не используется.
Зачем писать код, компилируемый только С++, если нам не нужны классы?

Потому что C++ — это не только классы.


С тем же успехом можно протестовать против перехода на C++11 и старше, по причине того, что хочется совместимости с C++03.


А вот в С++ в заголовочном файле будут и приватные члены и приватные методы.

Я не понимаю, почему для бибилиотеки нельзя сделать C-compatible заголовочный файл (т.е. extern C и #ifdef __cplusplus), а саму реализацию на C++?


Ну и закончу тем, что для компиляции C, C++03, C++11 и т.д. зачастую используется один и тот же компилятор с разными ключами. Но даже если компиляторы и разные, то линкер все равно один. Вы не получите преимущества от ограничения на использование только С.


Единственная причина, по которой сейчас стоит использовать именно C — отсутствие компилятора C++ под целевую архитектуру.

Потому что C++ — это не только классы.

А что в неё есть полезного для машинки с 64К ОЗУ? Ну или для довольно большой машинки с 512К ОЗУ?

Ну как пример — есть поточный ввод-вывод. Вот только размер библиотеки… Реальность такова, что даже обычный printf слишком велик. В итоге используется xprintf размером в 200 строк.

И так примерно со всеми возможностями С++. Или они не нужны в данной задаче или просто не лезут во все области применения.

С тем же успехом можно протестовать против перехода на C++11 и старше, по причине того, что хочется совместимости с C++03.

Единственная причина, по которой сейчас стоит использовать именно C — отсутствие компилятора C++ под целевую архитектуру.

Вы противоречите сами себе. Отсутствие компилятора С++11 под одну из возможных архитектур — достаточное основание.

Вы не получите преимущества от ограничения на использование только С.

Поржал. Для начала — попробуйте втиснуть ваше приложение ну скажем в 64К ПЗУ. А потом сами увидите, от чего стоит отказаться.

С++ очень полезен, когда в самой задаче есть полиморфизм. Но когда его нет, попытки искусственно приделать иерархию классов приводят к лишь увеличению кода.

Картиночка для затравки
image


Вам я тоже очень рекомендую почитать про ООП у Сергея Тарасова. А ещё лучше — всю книгу целиком.

Мы не планируем отказаться от С++ в тех местах, где он нужен. Но где не нужен — лучше писать на Си.
А что в неё есть полезного для машинки с 64К ОЗУ? Ну или для довольно большой машинки с 512К ОЗУ?

Вспомните Borland Turbo C++, который под DOS.


Ну вот навскидку вещи, которые не увеличивают объём кода, но делают программирование более приятным:


  1. Вышеупомянутый static_assert.
  2. enum class, не засирающий глобальное пространство имён.
  3. Сами пространства имён.
  4. Шаблоны. Если их использовать аккуратно, могут оказаться вполне полезны, чтобы избежать дублирования исходного кода (но, понятное дело, не скомпилированного). Конечно, в С можно использовать макросы, но тестировать и отлаживать шаблонный код намного приятнее.

Вы противоречите сами себе. Отсутствие компилятора С++11 под одну из возможных архитектур — достаточное основание.

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


С++ очень полезен, когда в самой задаче есть полиморфизм. Но когда его нет, попытки искусственно приделать иерархию классов приводят к лишь увеличению кода.

Ещё раз: C++ — это не классы. Вы можете использовать возможности С++, которых нет в С, но не прибегать к использованию классов.


Если вы скомпилируете C-код C-компилятором и C++-компилятором, вы не увидите разницы. Так в чём проблема, кроме отсутствия C++ компилятора, отказывать себе в удовольствии?

Вспомните Borland Turbo C++, который под DOS.

Помню. 640К обычно не хватало, надо было UMB задействовать. Кстати, или Turbo C++ для Windows или Borland C++ под MS-DOS.

Так в чём проблема, кроме отсутствия C++ компилятора, отказывать себе в удовольствии?

А! Так вы программируете для удовольствия? Тогда есть много хороших вариантов. Oberon, например. Или Kotlin. Да и вместо собственной платы весом в 110 грамм можно использовать 10 килограммовый сервер на x86. И сразу рассчитывать на 16 гигов ОЗУ и 120гигов диска.

Мы как бы работаем, а не программируем для удовольствия.

Беда С++ — в жирных библиотеках. А если от них отказываться — преимущества не так велики.

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

Напишите для начала обработчик прерывания на С++. Или вместите С++ код в 64К ПЗУ.

Так что на С++ — только то, где у С++ есть преимущества. А где нету — лучше на Си. Обратите внимание, что почти все системные библиотеки написаны на Си.
Помню. 640К обычно не хватало, надо было UMB задействовать. Кстати, или Turbo C++ для Windows или Borland C++ под MS-DOS.

Turbo C++ 3.0 был под DOS.


Мы как бы работаем, а не программируем для удовольствия.

А что, ситуация, когда работа приносит удовольствие, это плохо?


Беда С++ — в жирных библиотеках. А если от них отказываться — преимущества не так велики.

Я написал преимущества, которые использую лично я. Например, DLL для работы с изображениями, нужно сделать 32 варианта одной и той же функции для нескольких вариантов комбинации типов изображений и режима обработки. Функции низкоуровневые, жутко оптимизированные. Вариант на C++ использует классы-шаблоны, чтобы избежать дублирования кода, который неизбежно бы был на C.


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

Причём тут это?


Напишите для начала обработчик прерывания на С++. Или вместите С++ код в 64К ПЗУ.

Без проблем. Просто пишем на C++ в стиле С.


Обратите внимание, что почти все системные библиотеки написаны на Си.

А раньше на ассемблере писали, и что из этого?

Turbo C++ 3.0 был под DOS.
Не совсем. Если память вас подводит, то можете скачать и убедиться:
TC.EXE, TCC.EXE, and TLINK.EXE are now hosted under DPMI. These files
support protected-mode compilation and replace the files of the same
name in Turbo C++ Second Edition. Turbo C++ Second Edition should
continue to be used in instances where real-mode compilation is desired.
Минимальный обьём памяти был 2MB, кажется. Что больше всего бесило — так это то, что эта сволочь хотела DPMI, но компилировать подобные программы (в отличие от Pascal'я) не умела.

С другой стороны если вас интересует DOS, а не конкретно «компилятор в кофеварке», то и сейчас можно скачать свежий GCC 7.1.1 и поиметь поддержку C++14 (и частичную поддержку C++17)…

Я уже не помню, чем пользовался на старом 286-м. Но о protected mode там речи точно не шло. Значит, была более старая версия.

DPMI там настолько «под капотом», что вы его не заметили. Стартует он из реального режима, так что немудрено.
Вариант на C++ использует классы-шаблоны, чтобы избежать дублирования кода, который неизбежно бы был на C.

Ну если дублирование бинарного кода не важно (а важно лишь дублирование исходного кода) — я бы тоже так сделал. Но это у вас такая особая задача, где С++ полезен. У нас задачи другие. И в большинстве случаев — преимуществ от С++ нет.

Просто пишем на C++ в стиле С

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

Мне жаль, что вы не поняли аналогию. Попробуйте перечитать и понять её.

Кстати, системные библиотеки до сих пор пишут на Си. И с сишным интерфейсом. И переходить на С++ массово не собираются. И только там, где С++ дает реальное преимущество — библиотеки пишут на С++.

С++ные библиотеки вывод об ошибках через iostream делают.

Можно пример такой ошибки?
Eigen
Собственно с чего это С++ной библиотеке делать ввод-вывод через Сишные функции?
А, я как-то думал, вы о стандартной библиотеке говорите, а не о некоторой абстрактной сторонней.

Ну, не пользуйтесь такими библиотеками. Тем более, магически дёргать их из C так, чтобы они делали ввод-вывод так, как вам нравится, у вас всё равно не получится, так что я не понимаю этот аргумент как аргумент против языка, если честно.
Oberon, например.

Непонятно, что в нём хорошего, правда.

Да и вместо собственной платы весом в 110 грамм можно использовать 10 килограммовый сервер на x86. И сразу рассчитывать на 16 гигов ОЗУ и 120гигов диска.

Программировал на плюсах для удовольствия под attiny, что я делаю не так?

Мы как бы работаем, а не программируем для удовольствия.

Попробуйте совмещать.

Или вместите С++ код в 64К ПЗУ.

Вмещал что-то вроде интерпретатора команд по управлению лазером в attiny с примерно килобайтом флеша. Что я делаю не так?

Обратите внимание, что почти все системные библиотеки написаны на Си.

Потому что С чуточку проще дёргать из других языков.
Программировал на плюсах для удовольствия под attiny, что я делаю не так?

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

я вас понимаю, когда знаешь всего несколько языков — хочется писать на том языке, к которому привык. а когда знаешь языков много — выбираешь язык под задачу. Ваше управление лазером я бы предпочел на форте писать.
Чем же жутко? Всего лишь нет динамической памяти и RTTI, да и всё, по факту.

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

Начнем с малого — printf использует malloc. Даже в NewLib. Можно использовать xprintf, но возможности там обрезанные. Iostream нету по той же причине.

Как понимаете, std::vector тоже не будет. Как и 99% процентов STL. Соответственно не будет большей части boost и всех остальных библиотек.

Теперь конструкторы. Создавать объект в куче уже не получится — только статика и стек. А в статике с конструктором не очень. То есть придется реальные параметры передавать не в конструкторе, а через метод init. В стеке тоже размещать большие объекты не хорошо. Есть изврат с Placement new, но это ещё то извращение.

Дальше — многопоточность. Она обычно есть, но совсем не та, что в С++11. Потом exception. Их тоже обычно нету.

Ну вот и получается у вас С--, а не С++. А вот обычный Си страдает намного меньше.

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

я понимаю, вам сложно выучить Си после С++. Это известная проблема — тому, кто сразу учил С++ выучить Си очень сложно. Ну и потом — чем меньше языков знаешь, тем лучше к ним относишься. Мне легче — я довольно много языков знаю, почему предпочитаю подбирать язык под задачу, а не держаться за выученный однажды язык.
Если бы вы действительно знали С++, то легко бы поняли, насколько большие части С++ отрубаются, когда у вас «Всего лишь нет динамической памяти».

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

Как понимаете, std::vector тоже не будет. Как и 99% процентов STL.

Я тут где-то рядом писал про то, что отсутствие new не отключает алгоритмы в STL, а алгоритмы таки поболе, чем 1%. Наверное, даже поболе, чем 75%.

В стеке тоже размещать большие объекты не хорошо.

То у вас 64К ПЗУ, то большие объекты.

Ну вот и получается у вас С--, а не С++. А вот обычный Си страдает намного меньше.

А обычный ассемблер вообще не пострадает. Даже printf не пострадает!

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

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

Ну и потом — чем меньше языков знаешь, тем лучше к ним относишься. Мне легче

Чем кому?

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

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

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

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

Тогда всё-таки попробуйте выучить адекватный современный С++,

Нету такого языка. Верблюд — это монстр, созданный комитетом. Есть довольно адекватный С++, созданный Страустрапом. Лет через 30, наверное, С++ дорастет до нового адекватного состояния. Появятся модули и многое другое, чего мне в С++ не хватает.

А пока — никакой адекватности нет. Некая пародия на PL/1. Как говорил мой учитель химии «Без порток, но в шляпе».

Но если не знать иных языков — то и нынешний С++ может показаться адекватным.

То у вас 64К ПЗУ, то большие объекты.

«Большие» в стеке — это от пары сотен байт. Кстати, стек в ОЗУ, а комбинации ОЗУ и ПЗУ бывают очень разные.

А что до задач — ну, опять же, задач, где нужен именно С, исчезающе мало.

А какой структурный ассемблер вы предлагает? GO? Увы, он пока работает не на всех платформах. Но это хорошая замена.

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

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

Ох уж эти С++ники. Сколько раз вы должны жениться, чтобы иметь основания говорить, что вы жену себе выбираете? Раз 20, небось? :-)
В С++ два сорта плюшек: те, которых нет, и те, которые есть. Вначале апологеты С++ утверждают, что то, чего нет, им в принципе не нужно. А когда оно появляется в языке — начинают утверждать, что без этого не обойтись.

Интересный аргумент. Я, правда, не понял, за какой тезис, но всё равно интересный.

В любом случае, какие апологеты? Я вот вроде немножко апологет С++, но я считал, что лямбды нужны языку задолго до появления C++11, концепты — аналогично, нормальный доступ к AST, нормальные модули, нормальная рефлексия — аналогично.

И в то же время, вот добавили std::launder, а по моему скромному мнению (и не только по моему, оказывается), это феерически ненужная хрень, которая является костылем против криво определённой семантики placement new, которую можно починить более прямым способом, и отсутствие поломки старого кода при этом получится само собой.

Есть довольно адекватный С++, созданный Страустрапом.

А пока — никакой адекватности нет.

Ясно.

Лет через 30, наверное, С++ дорастет до нового адекватного состояния. Появятся модули и многое другое, чего мне в С++ не хватает.

Ага. Язык таки эволюционирует, и это нормально.

А пока — никакой адекватности нет. Некая пародия на PL/1.

Хорошо хоть не на K.

Но если не знать иных языков — то и нынешний С++ может показаться адекватным.

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

А какой структурный ассемблер вы предлагает? GO? Увы, он пока работает не на всех платформах. Но это хорошая замена.

Хорошая замена для структурного ассемблера с GC, ага.

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

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

А зачем, ещё раз, вам нужен структурный ассемблер?

Мой тезис-то в том, что задач, где именно он нужен (а не, скажем, интринсики для SIMD), исчезающе мало.

В итоге получается, что лучше всего Си.

Или C++.

Ох уж эти С++ники. Сколько раз вы должны жениться, чтобы иметь основания говорить, что вы жену себе выбираете? Раз 20, небось? :-)

Жениться можно и разок, но вот множество знакомых девушек желательно иметь мощностью больше единицы.
Язык таки эволюционирует, и это нормально.

Не совсем. Нормально — это чистые расширения. А у C++ ещё и семантика иногда меняется. Не читали, как мучались с компиляцией cfront современным компилятором?

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

Ну давайте посчитаем… Много писал: АЛГАМС (диалект ALGOL-60), FORTRAN(IV и 66), ассемблер (PDP-11), Cи, С++, Pacsal, Delphi, FORTH, Focal, MGR (язык командных скриптов RSX-11). Просто писал: PL/1, BASIC, JAVASCRIPT, bash, свой собственный куцый язычок, Ladder, Functional, IL, ST, ассемблер Z-80, ассемблер IBM-360, SQL, PHP, 1С, CMD. ассемблер 8086. аассемблер Минск-22, Б3-34… Наверное забыл половину… Плюс ещё пара десятков языков, которые изучал for fun: СНОБОЛ, JAVA, APL, LISP, OCCAM…

Конечно в старости как-то не хватает времени для изучения новых языков. Но будет подходящая задач — изучу. Потому и присматриваюсь и к Rust и GO и к Kotlin…

У С++ есть своя ниша. Но за пределами этой ниши — С лучше. Точнее С с некоторыми расширениями C++. Просто язык не поворачивается называть это С++. Скорее уж С++-------.
А у C++ ещё и семантика иногда меняется.

Первый стандарт С++ был выпущен в 1998 году. Что из семантики существенно поменялось с тех пор?

Не читали, как мучались с компиляцией cfront современным компилятором?

А pre-K&R-C-код сейчас успешно соберется всеми современными компиляторами?

Но за пределами этой ниши — С лучше. Точнее С с некоторыми расширениями C++. Просто язык не поворачивается называть это С++. Скорее уж С++-------.

Не, вы, конечно, можете назвать вашу область (ну, с 64К ПЗУ и вот этим всем) «всем остальным», а дополнение этого пространства — нишей C++, но это как-то не очень консистентно с обычными коннотациями.
Что из семантики существенно поменялось с тех пор?
Откройте стандарт и почитайте. Приложение C. Там довольно много всего. В частности валидный printf:
  printf ("argc is stored at address %"PRIdPTR" decimal.\n", &argc); 

вдруг в С++11 стал невалидным, хотя был валидным в C++03. Но в основном это порождает ошибки компиляции, что не слишком страшно…
Первый стандарт С++ был выпущен в 1998 году.

Это первый стандарт ISO. Первый стандарт С++ «вообще» — это 1985 год. Стандарт 90ого года называется C++ V2.0 и использовался много кем.

Что из семантики существенно поменялось с тех пор?

Мелочи поменялись. Чтобы точно описать, нужно порыться в коде, но в общем там дело в разнице семантики имени массива. у вас есть typedef double *d1 и typedef double d2[3]. Ну и ссылки на них. Ну и совместимость типов — разная в разных версиях.
А pre-K&R-C-код сейчас успешно соберется всеми современными компиляторами?

Это что? Язык B? Или вы про =+? K&R — 1978ого года. И более ранний код — найти сложно.

Не, вы, конечно, можете назвать вашу область (ну, с 64К ПЗУ и вот этим всем) «всем остальным», а дополнение этого пространства — нишей C++, но это как-то не очень консистентно с обычными коннотациями.

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

Если у вас товары, продажи, сделки, то есть неопределенное число однотипных объектов — это С++ или другой ООП язык. А если вам нужно рассчитывать координаты спутников — ООП тут лишнее. И С++ тоже не в тему. Ибо удоражает проект.

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

Вы не думали, почему в linux очень многие утилиты (и само ядро) написано на Си? Да вот потому, что на Си реально дешевле.
Стандарт 90ого года называется C++ V2.0 и использовался много кем.
V2.0 — это, вообще-то, не версия стандарта, а версия cfront'а. Вы с тем же успехом можете взять промежуточные версии C, в которые постепенно добавлялись и изменялись разные фичи — и пытаться их использовать.

Увы, слишком часто вижу, как мучаются программисты, пытаясь высосать иерархию объектов из пальца. И как хреново получается в итоге.
Ну так это от программиста зависит. Есть хорошее правило: не порождать иерархий обьектов пока у вас нет 2-3 разных рализаций, на которых вы сможете посмотреть — как эти абстракции будут работать.

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

Но если у вас ситуация «мне некогда точить топор, заказчик ждёт», то C++ вам не подходит, наверное.

Вы не думали, почему в linux очень многие утилиты (и само ядро) написано на Си?
Потому что принцип «работает — не трогай» блюдётся строго. Подавляющее большинство утилит написано в стародавние времена, когда у C++ было много «детских болезней». В частности лозунгом C++ всегда было «не плати за то, что не используешь» — но, увы, в GCC с этим были сложности примерно до GCC 4.0-4.2. А это, как бы, 2005-2007й годы. С одной стороны — уже почти 10 лет прошло, с другой — все те утилиты, о которых вы так вздыхаете, с тех пор почти не менялись.

То, что меняется активно (скажем, собственно, компилятор C, линкер и прочее) — потихоньку переходит на C++.

Да вот потому, что на Си реально дешевле.
Это уже довольно давно не так. До 2005-2007го — можно было получить выигрыш от некоторых фишек C++, но при этом — замедление и увеличение размера «на ровном месте» из-за проблем GCC. Сейчас — выбор для новых проектов, как бы, очевиден — но из этого вовсе не следует, что нужно всё бросать и переписывать работающий код только потому что появился более удобный язык.
V2.0 — это, вообще-то, не версия стандарта, а версия cfront'а.

Это версия (издание) книги "The C++ Programming Language", которая и была стандартом С++ до появления стандарта ISO.
Есть хорошее правило: не порождать иерархий обьектов пока у вас нет 2-3 разных рализаций

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

Отлично! Мы начинаем сходится во мнениях. Аналогично пишет и Торвальдс:

inefficient abstracted programming models where two years down the road you notice that some abstraction wasn't very efficient, but now all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app.

То есть живем 5-10 лет на Си, а потом, если приперло, то переводим на С++. Ну это разумный путь, но с одной оговоркой.

Подавляющее большинство утилит написано в стародавние времена, когда у C++ было много «детских болезней».

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

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

Чувствуете, что в итоге получается, что писать на С++ дольше? Потому что язык такой, что с первого раза сложно написать хорошо. А на Си — пишется сразу набело.

Это уже довольно давно не так. До 2005-2007го — можно было получить выигрыш от некоторых фишек C++, но при этом — замедление и увеличение размера «на ровном месте» из-за проблем GCC.

Да нет, до сих пор дороже. Я не про скорость выполнения и не про размер программы. Я про цену написания и отладки. Если мы берем дешевого юниора — то на С++ он будет отлаживаться намного дольше, чем на Си. И через пару лет случится затык в абстракциях и все придется переписывать. Если берем дорого сеньора — ну просто будем платить зарплату сеньора там, где на Си хватит зарплаты юниора.

Дело не в компиляторе, дело в размерах описания языка и сложности написания на нем.

Дело даже не в ООП. Вот в дельфи нет такой проблемы с неверно выбранными абстракциями. Могу объяснить почему:

  • Все классы — наследники TObject, там, где в С++ придется передавать void *, передается TObject с динамическим определением типа. Стоит это дешево — у экземпляров классов без виртуальных методов тоже есть ссылка на VMT.
  • Вместо указателей используются ссылки.
  • Все экземпляры классов сидят в куче.
  • Множественного наследования нет.
  • Макропрограммирования нет.
  • Есть указатели на метод конкретного экземпляра и их вызов

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

Сейчас — выбор для новых проектов, как бы, очевиден

И в большинстве случаев — это не С++. Дельфи, Си, Java, Rust, Кotlin, Go…

С++ — хороший выбор, если лично вы достигли стадии сеньора, а другие языки знаете плохо. Это выбор для вас, как для работника. А для работодателя — пока что Delphi и Си, как языки с меньшей стоимостью разработки.

И только при наличии специфики (linux, не только x86, довольно большой объем, задач хорошо ложиться на ООП, в компании есть хорошие С++ программисты) — С++ будет оптимальным выбором.
Чувствуете, что в итоге получается, что писать на С++ дольше? Потому что язык такой, что с первого раза сложно написать хорошо. А на Си — пишется сразу набело.

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


А, как вы уже писали ранее, не большие программы? А тогда и в плюсах-то 2-3-5 версий совершенно не нужно.


Если мы берем дешевого юниора — то на С++ он будет отлаживаться намного дольше, чем на Си.

Или ему, как уже писалось рядом, можно просто предоставить такие абстракции, на которых трудно сделать ошибку. На С++ это сделать существенно проще, чем на С.


Все классы — наследники TObject, там, где в С++ придется передавать void *, передается TObject с динамическим определением типа.

А когда и зачем в С++ нужно передавать void*? Мне за всю практику это нужно было делать только тогда, когда я взаимодействовал с С. В остальных случаях была либо ООП-ная иерархия классов с указателем на базу, либо ограниченный набор вариантов классов с boost::variant (а теперь уже std::variant).


Вместо указателей используются ссылки.

Ну запретите указатели в своём кодстайле или pre/post-commit-хуках.


Все экземпляры классов сидят в куче.

А чем это хорошо?


Множественного наследования нет.

С этим согласен, да. Правда, и на плюсах это решаемая проблема.


Макропрограммирования нет.

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


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

Жирные указатели-то небось?


А в плюсах можно сделать что-то по стилю и духу вроде такого.

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

В С++ и количество уровней абстракции больше, и цена ошибки в абстракции выше. То есть там, где в С++ «без рефакторинга идти дальше не можем», в Си или дельфи будет «гм, неэффективно, процентов 10 на этом теряем».

А, как вы уже писали ранее, не большие программы? А тогда и в плюсах-то 2-3-5 версий совершенно не нужно.

Ваши коллеги почему-то утверждают, что нужно. Если хотите — спорьте с ними. На С++ всех тянет написать что-то такое глобальное, суперабстракцию типа собственного STL. А в итоге получается плохо.

Собственно и на С++ можно писать в стиле Си или дельфи. И будут такие же предсказуемые результаты. В этом смысле и мы на С++ пишем. Вот только стоит ли называть это С++? Это будет Си с отдельными фичами С++.

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

Можно. Но в итоге товарищ будет писать не на С++, а на неком сильно урезанном диалекте. Фактически — на Си с расширениями.

А когда и зачем в С++ нужно передавать void*?

Когда ошиблись в иерархии классов, например. В Дельфи такие ошибки не являются фатальными и обходятся малой кровью.
А чем это хорошо?

О!!! А это запрещает передавать экземпляры в качестве результата функции. Вместо них передается ссылка на экземпляр. Так что нужда в move-семантике возникает крайне редко. И код в итоге более эффективен.

всё больше вещей можно сделать через темплейты или constexpr,

Темплейты — это тоже макропрограмирование. Тут у нас чуть термины разные, я к макропрограммиированию отношу все, что compile-time.
Жирные указатели-то небось?

Нет, не жирные, там 2 указателя (на экземпляр и на метод), то есть всего 8 байт для 32битной архитектуры. Безумно удобно, вместо наследования и виртуальных методов — просто привешиваем callback из экземпляра другого класса. Тоже сильно уменьшает цену ошибки в абстракциях.
Это первый стандарт ISO. Первый стандарт С++ «вообще» — это 1985 год. Стандарт 90ого года называется C++ V2.0 и использовался много кем.

Если бы semver был тогда распространён, оно бы называлось C++ V0.2.0.

Ну, как вот Idris назывался 0.что-то до релиза первой стабильной версии, Rust аналогично, и так далее.

Чтобы точно описать, нужно порыться в коде, но в общем там дело в разнице семантики имени массива. у вас есть typedef double *d1 и typedef double d2[3].

И в чём разница в разных версиях?

И более ранний код — найти сложно.

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

А если вам нужно рассчитывать координаты спутников — ООП тут лишнее. И С++ тоже не в тему. Ибо удоражает проект.

Я писал рассчитывалку одной хитрой штуки на С++. Без всяких классов (они там действительно не нужны), но с кучей темплейтов и вот этого всего. Не могу сказать, что на С вышло бы дешевле или быстрее (что с точки зрения разработки, что с точки зрения выполнения).

Вы не думали, почему в linux очень многие утилиты (и само ядро) написано на Си? Да вот потому, что на Си реально дешевле.

Потому что Торвальдс ненавидит С++.
И в чём разница в разных версиях?

Если очень надо — то найду. Просто лень тратить час времени, чтобы найти пример в исходниках.

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

У меня вагон такого кода. Специфика предметной области. Довольно часто надо если не собрать, то хотя бы понять, что там делается и зачем.

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

У вас вышло одинаково. У вас, а не у среднего программера. А ваш уровень — очень серьезный, я же видел код.

Потому что Торвальдс ненавидит С++.

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

It's made more horrible by the fact that a lot of substandard rogrammers use it, to the point where it's much much easier to generate total and utter crap with it.

Гм, ну вот ровно это я и втолковываю вам уже две недели.

inefficient abstracted programming models where two years down the road you notice that some abstraction wasn't very efficient, but now all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app.
И об этом я писал. Ну может не так четко, как Линус.

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

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


У меня вагон такого кода. Специфика предметной области. Довольно часто надо если не собрать, то хотя бы понять, что там делается и зачем.

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


У вас вышло одинаково. У вас, а не у среднего программера. А ваш уровень — очень серьезный, я же видел код.

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


А так вообще лучше Go выбрать какой-нибудь, он как раз для сих целей создавался.