Pull to refresh

Comments 198

Ага, продукты у netscape выходили настолько хорошие, что очередную версию, ставшую в последствии фаерфоксом, они писали несколько лет, полностью выкинув весь старый код где-то в середине процесса.
Это же Джоэль Спольски :) Все его тексты продекорированы sarcasm.
Блин. Надо было ник забить.
> продекорированы
хм. кажется я вижу здесь иронию
Сарказм и ирония в одном посте, вот это мастерство!
И тем не менее давайте все же оценивать результат. Результат есть и очень даже не плохой.
Это не спорт, тут главное не участие, а как раз победа. Результат.
Netscape 3 действительно был хорош, это та самая версия которая завоевала мир.
Netscape 4 был более-менее юзабельный.
А вот Netscape 6 уже превратился в тормозное монстроподобное угрёбище, видимо это как раз в нем «воспользовались множественным наследованием, унаследовавшись всего от 17 темплейтов». И после этого пришлось всё выкинуть и переписать.
А сарказма в статье вобще нет.
Я не утверждаю конечно, что на 100% прав, но, скорее, мне кажется, нацкап 6 стал таким как раз из-за всей этой костыльности. Я просто не знаю его историю, и тем более что там у него под капотом было в какой версии, но обычно костыльность как раз и приводит к этому
костыльность хороша здесь и сейчас, вылизывание может отодвинуть проект очень далеко. Вот и приходится выбирать компромис.
О чем я где-то в этом треде и говорил:)
NN4 был уже достаточно бажным, чтобы на равных тягаться с IE. Проявлялось в сложных сайтах того времени. И потенциала развития в нём не было из-за архитектурного долга (про отсутствие которого успели позаботиться в IE, и он жил на том движке по 6-ю версию; впрочем, там несколько подсистем, не принимайте строго). Вот всё и развалилось (в NN), потому что выработало себя.
UFO just landed and posted this here
Программирование такая вещь, где помогает только фабрика фабрик здравого смысла и хорошего вкуса
… и глубокое понимание рекурсии.
Если шаблон фабрика появился, то он, очевидно, был нужен для чего-то и решал какие-то проблемы, не надо так о нем судить только из-за того, что его стали овер-юзать там и тут.
А по сабжу — я лично вижу два типа людей: одни быстро думают, а другие глубоко. Первые могут быстро набросать кода, и сделать ваш проект за две недели, но потом, увидев код вы ужаснетесь, нет, не потому что там не используются паттерны, а потому, что эти люди пишут сейчас, не думая, что они напишут через пять-десять-двадцать минут. В итоге если они видят, что то, что они написали 30 мин назад немного мешает им сделать что-то сейчас, они придумают какой-нибудь (естественно очень неочевидный) костыль (пробросят какую-нибудь переменную через какой-нибудь реестр, или воспользуются eval для чего-нибудь, и т.д.), и так по нарастающей, в итоге это как снежный ком из дерьма, а тебе надо продолжать допиливать там что-то, какие-то фичи, и этот ком из дерьма все растет и растет, до тех пор пока его масса не станет настолько большой, что он коллапсирует и до свидания.

Почти идеальный цикл разработки продукта получается такой — нанять этих костыльных программистов, поднять систему быстро, работать, работать, может выйти на самоокупаемость (зависит от масштабов конечно), после этого нанять тех программистов, которые думают и делают долго, для рефакторинга, профит?
Никита, вы, безусловно, правы, что шаблон фабрика зачем-то нужен и решает какие-то проблемы. В частности, шаблон «фабрика» решает проблему «отсутствие в языке first-class functons», но это совершенно не принципиально.
Дело в том, что ваша аргументация никуда не годится, потому что слово «фабрика» можно заменить на что угодно. Вот например: «Если непомерно раздутый метод более чем из 3 тысяч строк появился, то он, очевидно, был нужен для чего-то и решал какие-то проблемы, не надо так о нем судить только из-за того, что его стали овер-юзать там и тут». Абсурдно? Вот и ваш оригинальный довод про фабрику тоже абсурден. Вывод у вас верный — шаблон фабрика действительно нужен, но способ, которым вы пришли к этому выводу, неправильный.
Могли бы вы рассказать про решение проблемы «отсутствие first-class functions»?
ну, тут я неправильно выразился. я хотел сказать, что шаблон «фабрика» решает проблему, которой не было бы, если бы были first-class functions. и что если бы first-class functions были, то фабрика вообще бы не появилась на свет.
Так ведь были. Только в других языках (не в тех или том, где началось повальное офабричивание всего).

Или вы считаете что они должны были быть во всех языках?
неверно. Наоборот, то, что вы называете «first-class functions» в некоторых языках является синтаксическим сахаром, позволяющим реализовать шаблон фабрика «в пару строк»

ещё раз: это не замена шаблона фабрика, это встроенное в язык средство для его реализации
ээээ. то есть по вашим словам, «first-class functions» нужны для того, чтобы было удобнее делать фабрики?

нет.
Не всегда профит.
Например, если костыльщики придумали какой-нибудь безумный API, использующийся на внешних системах, то придется либо его поддерживать, либо договариваться с внешними программистами на правки.
А если «внешние системы» — это, например, смартфоны, то придется даже в случае успешного изменения саппортить сразу два протокола, поддерживая и тех, кто не обновил версию программы.
Читаю комменты и становится весело :)
Костыльный программист — плохо (пользуется нестандартными вещами и имеет поверхностный взгляд)
Глубоко думающий программист — плохо (работает слишком медленно, страдает тягой к овер-инжинирингу). Дык вам шашечки или ехать? :)
Если мы говорим в первую очередь о выпуске продуктов, а не о написании докторских диссертаций по теории программирования, то я выбираю костыльных программистов!
Кстати ИМХО перевод не очень хороший. Скорее не костыльный программист, а программист-прагматик (смысл вроде имелся в виду такой).
Ну ок. Я сейчас работаю в одной конторе, где делают црм. На битриксе. С инфоблоками. От них уходят. Потому что с самого начала ее начал делать костыльщик. Любую фичу впиливают овер 9000 часов. Потому что тут просто нет архитектуры. О ней никто никогда даже и не задумывался. Один класс генерирует хтмл, в каком-то из методов там обращения к базе идут. Везде полный ппц. Вы понимаете что я говорю? Костыльные продукты невозможно поддерживать. Если вы делаете «сайты под ключ», то штат костыльщиков — это лучшее, что может быть для вас, если же лонг-терм проект, да еще и с тысячами клиентов, то никаких костылей быть не должно. Но овер-инжинирить конечно тоже плохо. Очевидно нужно находить середину
>Очевидно нужно находить середину
Именно! Но все-таки, как верно подметили некоторые комментаторы, в контексте данной статьи под костыльным программистом подразумеваетя не джуниор, который еще не умеет что-то делать, а опытный программист, который осознанно использует «свои простые» решения вместо «разжиревших общеупотребительных».
Не надо никого выбирать ))

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

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

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

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

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

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

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

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

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

Нет костылей и нет верной архитектуры.
Есть только текущие требования, разработка с эволюционным рефакторингом и метрики соответствия текущего уровня проекта к текущей версии требований. Цифры и проценты и никаких прилагательных.
Сонар в помощь
UFO just landed and posted this here
И вы выиграли эбстракт синглтон прокси фэктори бин. Мой любимый пример. Я даже примерно представляю, зачем это может понадобиться. Ой, нет, вру.
А вы как считаете, это действительно, как пишут в документации, удобный суперкласс для являющихся beanами прокси-фабрик который создают только синглтоны?
Это немного нечестный пример.
Название может быть и пугающее, но это специализированный класс, который практически никогда не будет использоваться рядовым пользователем. Для человека, который разрабатывает фреймворки на основе Спринга и знаком со спринговой терминологией, это отличное название, которое четко говорит для чего он нужен и как его использовать.
Это всеравно что говорить: «эти химики все усложняют! Это ж надо такое придумать: аллилпропилдисульфид.»
Ваша ссылка сделала мой день :) Спасибо!
Банки дают деньги только если докажеш что деньги они тебе не нужны.

Так и тех долг — если бы процесс подразумевал наличие времени на рефакторинг то долга просто бы небыло.
Вы задолжали русскому языку. Судя по всему, ипотека на 15 лет.
Факт (был рад что в 11-м классе нет русского), но как уже говорилось вероятность возврата тех долга крайне невелика.
Жалко. Рекомендую всё-таки использовать юнит-тесты и юнит-диктанты. Материалы можно найти в книге Д. Э. Розенталь Шаблоны проектирования. Пособие.
Вы и сами главу про знаки препинания перечитайте.
Если бы я использовал кавычки, то название книги перестало бы гуглиться.
Собственно, я про точки и запятые. 😈
Покапитанствую немного.
Тут, наверно, нужно уяснить, что термин «костыльный программист» в контексте статьи относится только к очень опытным разработчикам, которые пяткой чуют, где и что можно использовать. Не стоит себя считать тем самым сабжом и не учить теорию, если опыта мало.
Вот этот комментарий бы да в предисловие к статье.
Последний кусок все испортил :( Объясните мне, он за или против заумных паттернов и множественного наследования? Спасибо.
Если ты в полной мере понимаешь, что происходит и делаешь это не потому что считаешь это заумным и крутым, а потому что это нужно — делай это.
Главное — не пытаться вместить все и сразу, а использовать только то, что нужно.
Бритву Оккама никто не отменял.
Когда читаю подобные вещи, на ум проходить древняя мудрость, которую я часто слышал от отца: «Любая крайность — это плохо». Эти самые «симпатичные парни» привлекательны тем, что умеют найти золотую середину между дебрями и костылями
Я не симпатичный парень :( Пойду создам абстрактную фабрику фабрик.
То есть пригодно для употребления в пищу иудеями?
Решать что хорошо а что нет могут лишь гуру. Они могут пользовать костыли или темплейты или придумать свою фишку.
Да простота решения это прежде всего меньше вероятность ошибки, что крайне положительно. Но костыли сами по себе плохо поддерживаются и расширяются. Решить задачу на оптимум тут на глаз сложно…

И да, то что позволено Юпитеру, то быка делает колбасой
«Костыльный программист» действительно очень хорош, но только в том случае, если скорость выпуска продукта имеет приоритет, во много раз превосходящий приоритет всех остальных требований.
Что-то Спольски слишком категоричен стал, нет? Выбросить нафиг юнит-тесты, не использовать многопоточность, не делать рефакторинг… Для продукта, который надо «выпустить за шесть недель» и забыть о нем как о страшном сне — может, и сойдет. А вот то, что такой подход сработает для продукта, живущего несколько лет, и который будет сопровождать несколько поколений разработчиков — что-то слабо верится. Ибо доводилось видеть, во что очень быстро превращается код, если главная и единственная цель — выпуск в срок.
Я думаю, что он скорее про программиста, который удерживает проект от попадания в другую крайность. Что то вроде — когда в обычном сайте на несколько страниц и одинвсе надписи пытаются делать через i18n API
Видимо Спольски набрал себе в команду супер звезд, и теперь у него перекос в другую сторону :)
Как говорится, сбылась мечта идиота.
Ну статье уже три года, так что на счет «стал» — это как-то запоздало :)
А вообще — он не категоричен. Он просто предостерегает от ухода от цели.
Статья зачет, описан тот тип программиста к которому я стремлюсь и хочу быть.
Это РПП. Реально полезные программисты.
Я тоже выпустил в с срок продукт со всем тем не реальным количеством фич, который должен был быть впихнут в релиз любой ценой. Я был достаточно хорош для этого.

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

Следующий релиз был посвящен багофиксам.

Профит?
Профит, профит.

Возможно, что многие из фич не переживут рефакторинг и багофиксы по причине их невостребованности рынком. Но понять, что выживет, а что — нет, нереально без выпуска продукта.
Следующий релиз будет посвящен рефакторингу, а следующий за ним — микрооптимизациям. Если потребуется и найдутся ресурсы.
Не так плохо. 45 переменных. Все goto, кроме одного, идут на одну и ту же метку — вполне по делу, если не бояться этого приема. Кроме этих goto там есть примерно столько же continue, выполняющих сходную функцию — и их отследить намного сложнее, приходится искать цикл, к которому они относятся.
Думаю, что если бы пришлось этот код поддерживать, он бы оказался вполне читаемым и понятным. Могло быть гораздо хуже.
Пожалуй, это лучшая статья из всех, что я прочитал за последний месяц.
Спасибо.
Статья — великолепна. Сам ее как-то перевести хотел. Но столкнулся с тем, что не могу передать весь ее колорит, т.к. duct tape, все же, ближе к этому

нежели к костылю, который имеет яркий негативный смысловой окрас. А «сине-изолентовый программист» — не звучит. Так и забил.

И смотрите, что получается: Вы — duct tape переводчик, который взял и перевел, а я, полировавший свою мысль… ну Вы поняли.
Синюю ленту с вашей картинки:
— американцы используют для изоляции электропроводки
— советские люди использовали для герметизации и соединения всего со всем

В статье идет речь об этом:

image
Вы правы. Но эта клейкая лента чужда «нашему» человеку. А статью я собирался переводить для «наших» людей, поэтому, считаю синюю изоленту лучшей альтернативой. Цель — не точность перевода, а колорит, дух статьи.

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

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

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

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

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

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

Ну и третье соображение. Мы таки движемся к сингулярности. Пока экспонента еще не совсем крута, но она круче с каждым днем. И поэтому все новые разработки и проекты гораздо быстрее становятся старыми и никуда не годными. Вы два года разрабатывали шикарный продукт по всем правилам, еще год он продавался, а потом предметная область уехала за горизонт и приходится разрабатывать новый продукт с нуля. При таком раскладе кривенький костыльный но быстрый продукт выигрывает — он быстро вышел, выдержал пяток обновлений, и ушел на свалку истории. Но он принес денег.
В gamedev'е костыльных программистов наверное больше всего. Требования часто меняются на лету, любая заранее созданная архитектура часто ломается. Приходится искать золотую середину между «блин, надо переписать, эта фича никак сюда не влазит» и «хм, пара статических переменных, singleton, и вроде заработает».
Не кошерно. В докладе слишком много фантазий и за уши притянутых вещей.

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

То, что баги есть всегда, далеко не значит, что их одинаковое количество при разных подходах к разработке.

Есть очень неприятная особенность у новичков в программировании. Это эксепшинофобия. Новички панически боятся падения продукта. Когда надо панически бояться неправильной его работы. И тогда у них появляется желание ловить ексепшины, подавлять баги. У них цель — выживаемость программы. (!!!!!!)

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

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

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

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

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

Критичность багов бывает разной. Но как бы не так ;) Повторюсь, такая ситуация довольно редкая. Например, думать о выживаемости можно в случае, когда баги не связаны с кодом. А связаны, например, с разрывом сети, со сбоем в электропитании. Также баги бывают не критичными, если программа выполняет сложную работу, а баг связан с какими-то глюками в отрисовке. Черточка откуда-то под комбобоксом иногда появляется — может быть не критично (хотя здесь уже спорно).

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

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

А точную причину бага узнают только после починки. По такому алгоритму:
1. Фиксация бага. Т.е. нахождение условий, когда он стабильно повторяется.
2. Написание теста, воспроизводящего условия бага и проверяющего, есть ли он. В это время тест не проходит.
3. Поиск причин бага.
4. Исправление когда.
5. Проводим тест. Если тест не проходит, то снова в пункт 3.

Где-то так. Как видим, причины известны более менее точно после исправления кода.

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

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

Вообще название статьи и его содержание не совсем вяжутся. У нас «костыльными» программистами называют тех, кто не умеет нормально пользоваться предоставленными инструментами, в следствии своей неопытности и нежелании читать книги. А в статье наоборот, это программист с большим опытом, которому новомодные тренды до лампочки, потому что когда он слышит задачу, он знает как ее решить. И он точно знает, что его решение будет работать так как надо и во всех возможных сценариях. А именно это от него и требуется.
UFO just landed and posted this here
Ага-ага… А теперь я хочу почитать интервью того парня, который после этого «крутого» Завински разгребал его… хм, ну вы поняли. Только без купюр, со всеми матами, pls… Вот тот чувак я думаю реально крутой программист, после таких «подарков» со стороны такого босса.

Жизненный цикл приложений не заканчивается релизом. Потом встают задачи поддержки и развития продукта.

Впрочем, если вы выпускаете «г%#но», то есть вероятность, что поддерживать ничего не придется…

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

Теперь мы знаем какому «великому» программисту надо сказать спасибо за то, удивительно пахнущее чудо, которое выпускал Netscape перед тем как стать помереть…
Костыльное программирование — это не подстава, подставу делает сама себе команда поддержки и развития, когда накладывает свои костыли поверх релизных. Можно создавать пресловутый «технический долг», нельзя допускать чтобы на него «капали» проценты, тем более сложные проценты.

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

Если lead developer позволяет себе строить приложение из костылей, то не понятно почему senior'ы и junior'ы вдруг должны начать писать чистый код, с тестовыми покрытиями и прочая… Это в нормальных командах, прививание правильных навыков TDD отдельная непростая тема, а тут… В общем извините, но это бред…

«Дедлайн на носу! Работает — не трогай!» — Да ну!? Как вы это определили это без тестового покрытия и полновесного тестирования (дедлайн, помните?)? Это вот тот «крутой костыльный перец» сказал, что работает? Ну-ну…

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

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

Видел таких не раз и не два… И сырцов этого проекта вы никогда не увидите. Или все будут от него потом открещиваться… Хвастаться то нечем будет. Костыли они и в Африке костыли, будь они хоть хормированные, со светодиодами в стиле hi-tech…
Уровень з/п кстати тоже.

Как-то не замечал, где много legacy кода там как-то зарплаты выше предлагают. Субъективно.

Если lead developer позволяет себе строить приложение из костылей, то не понятно почему senior'ы и junior'ы вдруг должны начать писать чистый код, с тестовыми покрытиями и прочая…

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

А половина ваших клиентов к тому времени уже выбросит ваш продукт в корзину…

А вторая половина купит версию 2.0 :) А если серьёзно, то чаще не «выстреливали» проекты на моей практике, где мне давали оттачивать код до неразумных пределов, предусматривать всё, что не предусмотрено в ТЗ, а к тому времени как проект выходил на стадию хотя бы беты, рынок занимали пускай бажные, но хоть как-то работающие приложения. А сырцов, да, не увидите. За одни стыдно (зато работали и прибыль приносили), а другие показывать нечестно — незавершенные по сути проекты, если только в качестве демки по раздуванию сроков и, как следствие, бюджета показывать — где оверинженеринг, где ненужная оптимизация, где «on edge» технологии по которым на тот момент и доков-то не было кроме исходников.

Теперь я кажется знаю, какой я программист :)
Только не давно говорил о ряде «темплейтов, множественного наследования, многопоточности, COM, CORBA», + Функциональное программирование… — как полном отстои. Оказывается я не одинок :)
Я смотрю «функциональное программирование» для вас больная тема?
:) — интригующая :)
Про юнит-тесты — так вообще в точку!
значит, вы не совсем понимаете, зачем нужны юнит-тесты.

В императивном языке можно что-то написать. Но это что-то далеко не факт, что делает то, что надо. Юнит-тесты — это как рельсы для поезда. Хорошая аналогия, поэтому часто повторяю. Представьте, что вы сделали транспорт. Куда угодно он может поехать и куда не следует. В лес, потоптать поле ))) Рельсы — это ограничения, которые говорят, что хотят от этого транспорта.

Юнит-тесты и код программы как бы неразрывны. Код — это решение задачи. Юнит-тесты — это требования. Не просто даже требования от заказчика, а любые предположения, которые делал программист во время написания кода. Утверждения.

Тут какая-то тонкая грань в покрытии тестов должна быть. Как бы механически считается, что 100 процентное покрытие лучше. Но на самом деле нет, немного меньше. Если тесты только бы бизнес-требования отражали, то отлично можно было бы менять код и сразу видеть, что сломалось. Но юнит тесты — несколько подробнее. Баланс нужен, чтобы не тестировать каждый чих. И чтобы тесты были достаточно подробные, чтобы не могли прятаться баги.

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

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

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

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

Может и не поверите, но я делал довольно непростые проекты и за сравнительно короткое время. Полностью движок и логику переписал для большой нашей системы (ушло 2 месяца), а также DSL вместе с редактором, полностью своими руками. За три месяца. Учитывая, что делал сам и еще на другие проекты отвлекался.

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

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

Так автор не всегда авторитет, даже если он именитый.

То, что он сказал про пользователя и юнит-тесты, можно перефразировать:
«Зубы чистить совершенно не обязательно. Главное чтобы они в старости не выпали».

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

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

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

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

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

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

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

Ну, если хотите серьезно — давайте попробуем найти границы применимости unit-тестов. Начнем с SQL — его Вы покрываете тестами?

вот вам кусок SQL

SELECT DocNum, DocNumExt, DocHeader, DocComment
FROM StandBy

что тут тестировать? Надо ли?
Один селект не покрываю. Дело в том, что SQL по сути функциональный язык. В нем описывается «что» вы хотите. Тестировать селект — всё равно что тестировать, а не глючит ли СУБД?

А вот хранимые процедуры, в которых не только SQL, покрываю.
Ок, теперь посмотрим более серьезный пример

UPDATE CPDoComPay
SET DocState = '2' 
FROM CPDoComPay AS a
        LEFT OUTER JOIN CPServices AS i ON i.ServiceID=a.ServiceID
WHERE a.TransferMode = 2  AND
  i.ServSettlMode = 3 AND
  (a.DocSource = '3' AND a.DocState = 'b' ) AND 
  (ISNULL(a.ExecutionDateTime,'')<>'' AND a.ExecutionDateTime>@DateTimeFrom 
   AND a.ExecutionDateTime<=@DateTimeTill) AND
 
  ((Contractor = 1 AND i.ServSettlMode = 3) OR
  (Contractor = 2 AND 
        i.ServSettlMode = '2' AND 
        a.ServiceID = @ServiceID AND
        a.FinalDate <= GETDATE())
   )
 


Такое тестировать тоже самое, что «не глючит ли СУБД»?
Перед Contractor стоит собака @ :)
Да, этот код тоже чистый SQL и как код его тестировать не обязательно. Он сделает то, что там написано.

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

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

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

А представьте, его не было бы. Где гарантия вообще, что вы ничего не упустили?
Ну, давайте начнем тогда с мелочей. Как Вы предлагаете убрать эти магические числа? Для понимания, например, ServSettlMode — это виды договоров по расчетам, DocState — это определенные состояния обработки документа, DocSource — источник/канал по которому пришел документ.
Таблиц-справочников в данном случае нет — они лишние.
Ой, не лишние. Кстати, написание юнит-тестов вместе с написанием кода изменяет и представление об архитектуре в лучшую сторону.

Рассмотрим данный пример.

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

Если вы пишете DocState = '2', то судя по всему, если грамотно архитектура базы данных создана, есть таблица DocStates. И поле будет внешним ключом. Это необходимо, чтобы СУБД контролировало целостность, а с другой стороны чтобы можно было тест написать.

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

Id Description
'1' 'Новый'
'2' 'В обработке'
'3' 'Отправлен в бухгалтерию'

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

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

declare @DocStateId char
select @DocStateId = Id from DocStates
where Description like N'В обработке'


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

И вот у нас профит. Переименовали справочник — изменили суть. Тест упал.

Видите, как просто само требование писать юнит-тесты проясняет глюки в архитектуре? Это один из параметров оценки кода: если тест написать тяжело, значит в коде что-то не так.
Вы наверное смеетесь. Такого рода таблиц как вы описали для DocStates — в системе будет тогда порядка Документы*5, т.е. каждый документ имеет свои специфические состояния — и что мне для каждого документа создавать таблицу с комментариями статуса? Кроме того, в любом документе будут определенные другого рода, статусы, виды обработки и т.п. Вы создадите сущностей больше чем их есть. При этом пользователь не увидит их в 60% случаев. Но допустим, что состояния действительно нужно как то представить — но это совершенно не дело базы — это дело текстового описания, причем ваш Description там будет на 5 языках. А тест вы на каком будите делать, на всех? Да и что он гарантирует? Ничего, он упадет только если Вы поменяете описание в Description. Это лишь вас напргет — т.к. языковым переводов состояний и как это хочется видеть пользователю занимается другой отдел, или сами пользователи. А вы предлагаете на это завязать тесты? Вы будите заниматься тогда ерундой, пользователь позвонит скажет — знаешь перевод "'В обработке'" — не точен, давай заменем на 'В процессе обработки'. И у вас завтра лахнул тест, и зачем, вам копаться в том, что вас не касается?
Нет, это вы смеетесь, если не понимаете, что такое суррогатный и естественный ключ.
Т.е. вы не хотите правильно в реляционной архитектуре выразить предметную область? Ну тогда вот вы и получили проблемы с тестами. Да вообще, проблемы, не только с тестами.

Поясняю. С чего вдруг у каждого документа будет свой уникальный набор состояний? Если это так, то эти колонки обязаны входить в первичный ключ. Да и нет никакого смысла в таких состояниях. Обычно, количество состояний ограничено. Какие вы их там придумали себе, я не знаю. Я задачи не знаю. Из кода предположил, что набор одинаковый для всех документов. Не одинаковый? Заводите таблицу и внешний ключ DocumentType, и в таблицу состояний DocumentTypeId. Ничего сложного.

Да, я завязать тесты на естественный ключ. Эти дескрипшины не лишние. Где-то в приложении наверняка будут комбобоксы именно с ними. И пользователь их будет именно так воспринимать. По сути, тест тогда отразит требование: «при таких-то условиях документ переходит в состояние „В обработке“». В требованиях не было состояния 2, не так ли?

Если дескрипшин будет на 5-ти языках, закрепите это в тесте через AND.

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

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


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

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

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

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

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

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

А переименуют немного колонку — это и значит, что изменили суть (смысл) с т.з. заказчика или пользователя.

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

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

Ну так не надо разрывать модель данных
Выразить с помощью таблиц предметную область — это ли оверинжиниринг???

Вообще говоря, может быть. Если в ТЗ на разработку приложения в целом у нас зафиксированы состояния, то вынос их в отдельную таблицу может быть не самой лучшей идеей даже не с точки зрения производительности, а с архитектурной. Приложение в целом должно выражать предметную область, а не отдельные его части. Другое дело, что связь «число в запросе»<->«понятие предметной области» я бы не стал выражать литералом, а выразил бы, скажем, перечислением или константой класса на основном ЯП. Ну оверинженеринг записывать HTTP-статусы в БД во всех случаях, встречающихся мне на практике :)
С т.з. производительности вообще нет никаких проблем. Код апдейта, как видим, не джойнится к ней. Зато наличие этой таблички гарантирует, что не апдейтните какое-то бессмысленное число. А табличка статусов говорит о смысле.

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

Тогда непонятен смысл поля «Description», если мы можем апдейнуть на произвольное число как бы имеющее смысл. Коль скоро DocState используется у нас в приложении на уровне управления логикой то, что записано в Description особого значения не имеет, если мы не производим выборку id по захардкоженной «В обработке» — максимум будем показывать пользователю, но от изменения на «Обработано» ничего не изменится в логике, только пользователь будет. А если хардкодим, то дублирование, и особого смысла в таблице всё равно нет, кроме FK ограничения, в котором Description не участвует.

Я сам использовал подобные таблицы когда-то, но когда один оператор случайно изменила в такой таблице «Нет улучшений» на «Полностью вылечен» (дословно не помню), то всё подобное захардкодил, не допуская двоякого толкования или изменений операторами ни на каком уровне. Хорошо система была чисто статистической на тот момент и никто не пострадал. И это никакой логики завязано не было, просто информация для пользователя на базе которой он принимал решения. С тех пор если в ТЗ нет явного требования на возможность изменения операторами и даже администраторами БД статусов/этапов/..., то только хардкод, максимум таблица с суррогатным id, только чтобы соблюдать целостность, никаких decription, пускай и глупо выглядит при взгляде только на БД без анализа приложения.
Ах, да, тесты в базе я не пишу. Пишу тесты запросов в приложении.
Так должен быть смысл у дескрипшина.

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

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

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

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

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

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

Давайте представим идеальный язык. В требованиях сказано: бывают статусы у документа «в обработке», «отослан» и т.д. И вы создаете множество из элементов, которые ровно так и звучат. С пробелами. Без никаких дополнительных айдишек. Вот оно — вы точно тогда передаете смысл своей программой. Если еще были бы у этого языка конструкции, близкие к выражению смысла на человеческом языке — мы бы ВООБЩЕ не кодили, а просто описывали задачу, перечисляя требования.

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

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

Не в конце привязано, а сверху, в начале для пользователя. На уровне абстракций пользовательского интерфейса (самом верхнем) — представление сущностей предметной области в терминах предметной области. Уровнем ниже (собственно приложения, логика) — константы, перечисления и т. п. и их связь как с уровнем пользователя, так и с суррогатными ключами хранилища (БД, самый нижний уровень). Программист оперирует именно этими константами, они для него первичны, основа его стандарта, на них завязана логика. Надо будет завтра изменить их представление, вместо «в обаботке » выводить «in progress» в связи с покупкой заказчика иностранцем — 5 минут. И без изменения БД, просто заменой бинарника, в отдельных случаях даже без остановки приложения. Надо будет заменить суррогатный идентификатор с, например, числового на GUID — сложнее, но тоже решаемо без изменения пользовательского слоя. Они с хранилищем вообще никак не связаны напрямую — как база представлена пользователю и как его действия отражаются на базе, знает только приложение. Да, ТЗ размазано по слоям, но в этом и прелесть — каждому слою свой уровень абстракции. Изменилось ТЗ в интерфейсе — в слой логики и хранилища даже лезть не нужно. Изменилась логика — нужно лезть только в её слой. Это же суть трёхзвенных приложений. Разделяй и властвуй. Принцип единственной ответственности. Функциональная декомпозиция. И т. п. Есть вариант — вынести всю логику кроме интерфейсной в БД, но по разным причинам он далеко не всегда применим.
Вот смотрю, все мы втроем не глупые люди, а запоролись на чистой науке — как там — коне в вакууме :)… И собственно об этом и статья, если понимать её немного глубже. Пока кто-то трендит — другие делают, плохо/хорошо — но оно фурычит, и фурычит лучше если рассуждать… А так не знаю почему, но VolCh говорит правильнее, хотя будет день и мы с ним поспорим :), да и логику m36 можно понять, но извини она немного или точнее за слишком идеалистична. В теории оно так и есть, и если студенты говорят не как m36 их надо долго жучить на экзамене. Но практика чуть-чуть изменяет мировоззрение, как говорит мне тут «Сковородка» становишься жудким зеленым болвано-троллем, которые слушает только себя :) Но это не из-за злобности, или не понимая студенческих вещей, и совсем не во имя «императорского величия» — ну, просто так получается «царь, просто царь» :) ну или

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

а это все-таки кое что значит :))
Нет, ну об этом мы не спорим ))
Конечно, если работодателя устраивает и вы делаете свою работу, то профит. В конечном счете работает экономика, а работодателя не интересует идеализм и красота.

Так что если получается и получается без «много багов», то все мы красавчики ))
никто не спорит. Особенно играет роль время. Может кто-то будет идеальную систему год разрабатывать, а второй напишет кривую внутри, но работающую и за два месяца. Точно — второй сделал лучше. Тут вообще без вопросов.

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

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

У меня есть свой взгляд. Что так и надо наобум писать и упрощать всё что можно. Но только — тесты не лишние. Ну и это не только мое мнение. Но например и VolCh. Только разошлись в мнениях — как именно писать. А это более тонкая черта.

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

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

Где-то так. Главный критерий — пишете код, получаете удовлетворение, получает удовлетворение заказчик — это пять.
> оверинженеринг записывать HTTP-статусы в БД во всех случаях, встречающихся мне на практике

Вот вот, именно об этом и речь… и уж точно чудовищной ошибкой является базироваться на текстовом описании HTTP-статуса для теста, это только геморрой и ничего не тестирует.
млин )))

DocState — это HTTP-статусы? Нет. Это артефакт нашего приложения? Да. Они могут поменяться в рамках нашего приложения? Да.

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

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

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

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

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

Две крайности — всё в базе и в тестах номера :) Связь ид-название/описание можно захардкодить в коде, чтобы можно было удобно и осмысленно писать тесты и сам код, и в то же время не допустить даже преднамеренной порчи связи администратором системы (не в смысле пользователем ОС с рутовскими правами, способного изменять бинарники, а в смысле роли в системе).

Поэтому не лишнее (даже обязательное) хранить предметную область в базе.

Ни разу не слышал, что это обязательно. Более того, на своём опыте убедился, что это бывает вредно. Связывать суррогатный id с названиями/описаниями зафиксированными в ТЗ я предпочту в коде. Приложение в целом должно отражать предметную область, а не каждый его слой. На уровне бизнес-логики и её (тестов) будет зафиксирован смысл константы IN_PROGRESS, на уровне БД она будет отображаться в 2, на уровне представления — в «В обработке». Пройти от одного уровня к другому можно за несколько кликов (с нормальной IDE :)) Это просто внутренний стандарт — надо будет, можно будет зафиксировать обе связи одновременно в тестах, в приёмочных или функциональных.
Две крайности — всё в базе и в тестах номера :) Связь ид-название/описание можно захардкодить в коде, чтобы можно было удобно и осмысленно писать тесты и сам код, и в то же время не допустить даже преднамеренной порчи связи администратором системы (не в смысле пользователем ОС с рутовскими правами, способного изменять бинарники, а в смысле роли в системе)


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

Почувствуйте разницу. Есть требование (а может нет, сами ставите, как для юнит-теста):
«У нас есть статус документа „отложен“ и если пришло событие (что там еще придумать… ) „сервер готов к обработке“, то документ переходит в статус „ожидает обработки“ ».

Пишем тест. Предусловие, запуск, постусловие.
Создаем документ со статусом «отложен». (Не со статусом 2!!!, даже если он в базе это значение имеет, а находим по названию).
Запускаем хранимку, даем статус сервера (находим по названию, это наш тип, наш артефакт)
Проверяем постусловие. Ищем статус по названию «ожидает обработки». Сравниваем. Нет — тест не прошел.

Второй случай:
Есть требование — документ в статусе «отложен». Приходит HTTP статус «использовать прокси» (совершенно надуманный пример, я не веб-программист), переходим в статус «ожидает обработки».

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

Ни разу не слышал, что это обязательно.

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

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

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

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

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

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

И не с одним из этих видов НЕЛЬЗЯ поступать так как вы предлагаете.

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

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

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

Почти одно и то же написали :)
и не смотря… дай пять :) хлоп…
Отметьте, почему подход описанный VolCh имеет реальное право на существование и существует в крутых конторах, а ваш нет. Что он говорит: "заполню в тестовой базе CPDoComPay и CPServices разными значения, которые имел (буду иметь в случае ТДД) в виду при составлении запроса, при тестировании чужого кода — «ручками» составлю таблицу состояний всех полей в WHERE и JOIN и буду проверять соответствие результата запроса"

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

Он не ошибается. Джойны делает как надо. Толку от тестирования такого. Разве что жизнь себе усложнить. В том плане, когда логика поменяется, джойн другой напишут, тест упадет. И тест еще придется менять. Это хорошо было бы в случае если тест логику бы тестировал. Т.е. в тесте закрепили логику работы чего-то. Но логики без смысла не бывает. Фиксировать поведение без цели — это не юнит-тест. Юнит тест — это утверждение.
Так это утверждение, что при таком-то наборе входных данных данных (снэпшот таблицы — тоже входные данные для приложения, как и пользовательский ввод) будет такой-то результат. В тесте закреплена логика запроса, мы проверяем, что мы джойн правильно написали, то есть чётко и однозначно выразили свою мысль в синтаксисе SQL. Грубо говоря, чтобы не было ситуации, что мы не смогли объяснить SQL-серверу, что нам собственно нужно. И для юнит и интеграционных тестов это утверждение вовсе не должно быть сформулировано в терминах задачи.
Ну так всё верно: предусловие, запуск, постусловие.

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

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

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

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

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

Здесь «снэпшот» просто к слову пришлось. Имелось в виду, что если мы хотим тестировать запрос возвращающий записи из таблицы с DocState = IN_PROGRESS, то создаём в тестовой базе пяток записей с IN_PROGRESS, пяток NEW, пяток FINISHED, пяток ABORTED, выбираем, и смотрим чтобы все были IN_PROGRESS и было их именно пяток, если развита параноя, то можно проверти всё до последнего поля :)
Учу. Полей вида DocStates на самом деле есть три вида: системные состояния, которые вообще не видны пользователю, состояния, наименования которых он задает сам (как правило админ этим занимается), состояния, наименование которых жестко задает разработчик.

И не с одним из этих видов НЕЛЬЗЯ поступать так как вы предлагаете.


Жесть. А то реальный код был, который выслан? Бьюсь головой об стол ))

Вы там прописали номера (т.е. по моему разумению айдишки, пусть без таблицы). И с чего вдруг:

SET DocState = '2' 


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

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

Сами ничего не ощущаете, что что-то не то в вашем описании? Интересно, как вы себе задачу представляете.
> Если пользователь определяет что-то «на ходу», то с чего вдруг хардкод в логике?

Оооо, дорогой мой друг… боюсь сегодня мне вам не объяснить. Вы все спутали. Имеет ли мне смысл распутывать? Думаю нет.

Это не хардкод. Это процедура которая выполняет свое предназначение — называется «сформировать пакеты платежей», а DocState = '2' зафиксировать отказ тем платежам, у которых истек срок оплаты. Если связывать это с GUI — то это как раз третий вариант, но на этом примере я показывал вам, что есть другие варианты.

Не ужели Вы реально полагаете, что я буду писать болгарским или арабской вязью, чтобы связать GUI с однозначным значением '2'? И по вашему в этом заключается предметная область?
Это не хардкод. Это процедура которая выполняет свое предназначение — называется «сформировать пакеты платежей», а DocState = '2' зафиксировать отказ тем платежам, у которых истек срок оплаты.


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

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

declare @DocStateId char
select @DocStateId = Id from DocStates
where Description like N'зафиксировать отказ тем платежам, у которых истек срок оплаты'


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

Не ужели Вы реально полагаете, что я буду писать болгарским или арабской вязью, чтобы связать GUI с однозначным значением '2'? И по вашему в этом заключается предметная область?


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

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

Извините это бред. Все это «нужно» зачем? Чтобы вы по коду расшифровали суть/содержание? Так зачем же это кодировать — для этого достаточно написать комментарий в коде, который будет более полезен, чем вся ваша технология совершенно лишних артефактов тестирования. А пользы приносит ровно столько же, но напрягает до маразма… это человек реально решил, что программист должен писать на болгарском участки теста…
ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D0%BC%D0%BE%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5

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

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

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

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

другие рекомендуют писать комментарии. К говнокоду главное, при этом, никаких вопросов у них нет. Им нужны комментарии, потому что читать код не умеют.

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

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

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

// Fix Me!!!

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

Такого бреда я давно не слышал.
Это не бред. Это вполне логично. Представьте, что перед вами человек с приобретенным деффектом речи. Пока он общается с людьми, которые понимают, про что он говорит, то все хорошо, и на эту особенность никто не обращает внимание. Но стоит этому человеку выйти в общество, как его коммуникативные возможности резко падают. Для нормального общения требуется переводчик (в нашем случае комментарии), который пояснит, что на самом деле хотел сказать человек. Может все-таки лучше научиться нормально говорить?
Нет. код должен быть хорошо откомментирвоан, это дурной стиль, когда в коде нет комментариев (или это не промышленное программирование). Представление о коде как о языке не верно. Языки программирования — это нечто аналогичное математическому языку. представьте книгу по математике только из формул. Да, так даже математики часто и пишут, но это опять же доведение до абсурда. Кодируя мы вводим определенные мнемоники — сокращения понятий (абстракции) предметной области, и роль комментариев показать как эти абстракции соотносятся с реальным миром.
Проблема не в комментариях как таковых, а в дополнительных сложностях, связанных 1) с необходимостью эти комментарии читать 2) с необходимостью эти комментарии поддерживать в актуальном состоянии по отношению к коду. Зачастую при выполнении рефакторингов/реинжинирингов код меняется на полу-автомате (специальными командами IDE, поиском-заменой и т.п.), а комментарии при этом никто не читает (это слишком трудозатратно). Вот и получается, что например остаются комментарии, описывающие действие какого-нибудь параметра метода, а сам параметр либо уже удален, либо по-другому называется (и что самое опасное, он может даже означать уже совсем другое). Хорошо, когда код является самозадокументированным, т.е. когда конструкции языка, названия методов и их параметров, названия классов и переменных не требуют дополнительного комментирования. Комментарии пишутся только для тех участков, которые содержат какой-то очень хитрый код, хак, обход какого-то бага компонента, элемент оптимизации и т.п., т.е. в тех случаях, когда для человека, читающего этот код, смысл написанного может быть не всегда очевиден. В остальных случаях за комментарии foreach (obj in List) /*пробегаем по всем объектам списка*/ надо просто бить по рукам.
Каждый останется при своем мнении. Отсутствие комментариев стимулирует писать как можно более понятно, чтобы в любой момент времени можно было быстро восстановить логику.
Тут с другого конца аналогия нарушена. Язык математики — это язык выражения мыслей ))))
У языков математики и естественных языков нет качественных различий. И настоящие математики это знают. А система образования этому не учит, она учит представлению, что языки математики — это аппарат вывода, который надо применять в определенной последовательности, чтобы что-то доказывать. И это да, но главное предназначение — это выражение мыслей. Более точных мыслей, которые естественным языком не выражаются.
Даже очень неглупые люди, изучавшие математику, но не понявшие эту мысль, математиками не становятся. Это не их язык. Они могут применять только время от времени ее. Но не мыслить ею.

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

Ну и напомню про функциональные языки. Разные языки имеют разную возможность для выразительности. функциональные чаще имеют бОльшую выразительно. Что еще меньше позволяет думать о комментариях.

А про «дурной стиль»… Я бы дурным стилем комментарии назвал. Вы, вроде, на шарпе пишете? Довольно выразительный язык.
Кстати, вы наверняка играли в игры, знаете как выполняется русификация? По вашей технологии, ваши тесты должны падать каждый раз когда какой-то умелец решает русифицировать, и не дай бог если он переведет go — как стоять, наверняка ваши тесты это отловят, и скажут дорогой мой человек — это же не стоять, зачем ты нам обрушил все тесты и отнял 30 минут на нахождение бага? Замечательный дружественный интерфейс, прям на ИИ тянет… только не маразм ли это идеалистических представлений о кодировании?
точно! :) Go — это не стоять. Я всё пытаюсь донести, что программирование делается не ради программирования. Также как искусство ради искусства — это полный отстой.

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

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

И на все контролы, где нужны названия, у вас будет что-то вроде Dictionary. Заполняется или со сборок, как обычно, или с БД, где есть таблица, описывающая наши докстатусы.

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

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

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

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

Конечно соответствие техническому заданию, а если есть то построенному по этому проектной документации (UML). И конечно, она пишется на основании объектного представления, которое лишь СИМВОЛИЧЕСКИ отражает предметную область. И класс Person — я не перевожу на все языки в тестах, а пользуюсь его символическим представлением. Ровно также как для состояний я использует другие мнемонические обозначения — цифры, или буквы алфавита. И это не магические цифры в данном случае, а лишь мнемоника обозначения, того, что под этим я понимаю в реальности. Излишних сущностей для этого вводить не надо.
«Конечно соответствие техническому заданию, а если есть то построенному по этому проектной документации (UML)»

Только не понимайте это буквально, конечно, главный критерий это в общем случае — удовольствие клиента, но пока оно не зафиксировано в ТЗ — его нет, за него не заплачено. Это работа других людей понять КАК хочет клиент видеть предметную задачу. Да я тоже на это влияю уточняя четкость ТЗ с другой стороны. Т.е. это циклический двухсторонний процесс. Но точка отсчета для кодирования это ТЗ.
Вы не поняли, что развели голую науку на пустом месте? Тогда ни могу ни чем помочь.
Вообще говоря, нормальных форм под десяток, и одна из них тут, похоже, нарушена — значения полей типа DocState в БД не контролируются СУБД на соответствие предметной области. Естественный ключ или суррогатный — не суть, но СУБД не помешает записать туда -1 или 65535 (если конечно нет соответствующего триггера, поле не является перечислением или нет в приложении интерпретации значений, отличных от 1, 2, 3,… как «Не определено» или «Ошибка» — но именно интерпретации в рамках бизнес-логики, не исключения или что-то подобное типа BSOD :) ).

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

declare @DocStateNew char, @DocStateInProcessing char, @DocStateSendToBuh char
select @DocStateNew = '1', @DocStateInProcessing = '2', @DocStateSendToBuh = '3'

Только не затрахаетесь ли Вы в каждой процедуре, где это нужно такое писать/или даже вытаскивать по идентификатору? Вот и речь в этой статье как раз о том, что

"На 50% идеальное решение, которое уже есть у людей, решит больше проблем и дольше проживёт, чем 99% идеал, которого нет ни у кого, потому что он валяется в вашей лаборатории, где вы до бесконечности полируете чёртову штуковину."
> этот код тоже чистый SQL и как код его тестировать не обязательно

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

Например, посмотрите на части i.ServSettlMode = 3 и i.ServSettlMode = '2' — ничто не настораживает? SQL легко это пропускает — хотя тут явная ошибка не совпадения типов.

Далее, стоит условие

i.ServSettlMode = 3 AND


((Contractor = 1 AND i.ServSettlMode = 3) OR
(Contractor = 2 AND
i.ServSettlMode = '2' AND
a.ServiceID = @ServiceID AND
a.FinalDate <= GETDATE()

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

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

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

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

Точно. А тесты помогают увидеть глюки в архитектуре. Код пишется не в угоду тестам. Просто тесты — это требования. И если код никак не соединяется с требованиями, значит код кривой и не может их выполнять
Нет, смогу. Правда он не будет чистым юнит-тестом, поскольку идёт, скорее всего, внедрение кода на одном языке в другой, да и тестирующих фреймворков для SQL как-то не встречал. А тестировать будут так: заполню в тестовой базе CPDoComPay и CPServices разными значения, которые имел (буду иметь в случае ТДД) в виду при составлении запроса, при тестировании чужого кода — «ручками» составлю таблицу состояний всех полей в WHERE и JOIN и буду проверять соответствие результата запроса (состояния таблиц после него) ожидаемым. Это не будет проверкой масляное ли масло, это будет проверкой соответствует ли результат ожиданиям. В тестах не пишут assert_equal (2*2, mul(2,2)), а пишут assert_equla(4, mul(2,2)). Сложнее всего будет с GETDATE() и прочими LAST_INSERT_ID(), но решаемо.
И чем это будет по производительности отличаться от ручного тестирования? Только в минус.
Чисто по производительности — зависит от соотношения времени на разработку теста T1, времени его прохождения T2, времени ручного тестирования T2 и количества проверок N. При N стремящемся к бесконечности (или хотя бы к количеству релизов Firefox :) ) перевес T1+T2*N по сравнению с T3*N будет в пользу автоматического тестирования при любом реальном соотношении T2 и T3.

Плюс исключаются ситуации, когда тестировщик по легкомыслию или небрежности пропустил какой-то ручной тест при очередном релизе (коммите).
Знаете, или у нас сильно хорошие программисты, или отдел тестирования, или понимающие пользователи :) (что, в последнем случае, конечно желаемая шутка). Но багов в чистом виде от пользователей с рабочих продуктов к нам приходя ну порядка 5%, и как правило в остальных случаях это не баг — а фича (за которую уже платят сверх платы за поддержку). Кроме того, из этих 5% половина доходит до разработчика. Конечно, ошибок до поставки клиенту — измеряется горбом биноминального распределения, и конечно есть места которые я очень не хочу трогать. Но ради этого писать авто-тесты — это по моему блашь. А продукт мы поддерживаем уже более 18 лет (из которых 10 на моем веку).
Или в абсолютных цифрах 5-10 ошибок за год по всем закрытым проектам.
Т.е. в своем расчете производительности — вы упустили главное — позволяет ли это выявлять новые ошибки во время поддержки.
Это не главное, раз у вас есть отдел тестирования в принципе. Это повышение производительности их труда. Не будут выявлять новые ошибки (а пользователи будут продолжать), не смотря на в разы выросшую производительность, — разогнать половину :) Или переквалифицировать в программистов… По одному :)
Но как тестировать — по сути Вы правы, в отличии от моего второго оппонента m36. Эту технологию обсуждать уже серьезнее. Но увы, она требует ужасно много не сопоставимых затрат.
Кстати, для рефакторинга (оптимизации, нормализации и т. п.) есть менее затратный в некоторых случаях вариант. Снимаем снэпшот с рабочей базы, пишем логи действий пользователей (для серверов — логи запросов, для десктопов — «макросы» а-ля AutoIt и т. п.), снимаем второй снэпшот, подключаем первый к тестовому серверу (при необходимости ставим часы на время его снятия), подаём на вход логи, снимаем третий снэпшот, сравниваем со вторым — diff покажет различия в логике. Но это уже совсем не юнит, даже по сравнению с первым, локализовать ошибку не получится «с полпинка».
Ну, это вы описали автоматизированный вариант ручного тестирования. Когда-то я хотел это юзать, но на практике так руки и не дошли. Как — то ручное тестирование дает больше выгод. Хотя возможно, если потратить порядка года на внедрение такой системы это имело бы смысл. Но для этого я считаю должен быть совершенно отдельный человек, а лучше команда, которая ТОЛЬКО этим и занимается. Так сказать отдел автоматического тестирования. Но для нашей фирмы это роскошь.
Я сам на практике не пробовал, как-то в подобном топике посоветовали, а тут к слову пришлось. Опять-таки сколько потратить зависит от масштабов проекта. А так, по сути, всё автоматическое тестирование является автоматизированным ручным.
Ладно. Можете не соглашаться, но я очевидным образом доказал, что если не использовать технику описанную VolCh (а она затратная) — юнит-тесты ни помогут выявить такого рода ошибки.

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

i.ServSettlMode = '2' AND a.ServiceID = @ServiceID

на самом деле условия a.ServiceID = @ServiceID достаточно, чтобы однозначно выбрать нужную строку. А вот вторая строка казалось бы ничего не добавляет i.ServSettlMode = '2' — она как раз и содержит тест-ограничение. И это ограничение должно быть не в тесте, а в реальном коде. По сути это строка говорит, что «ты мне передал указатель на услугу, но если ты передал такую услугу в которой не тот договор, что вообще-то бред — то фиг тебе ты ничего не получишь.» и ошибку схватит вызывающая процедура. Т.е. проверять адекватность передаваемых параметров никто не отменял — и это и есть своего рода покрытие тестами.

Тестировать селект — всё равно что тестировать, а не глючит ли СУБД?

Вот тут не согласен, другое дело, что тест селекта не будет юнит-тестом в привычном понимании. Если стремимся к 100% покрытию кода или просто предположительно в селекте есть баг, то нужно создавать тестовую базу (в особо изощренных случаях дампать рабочую), наполнять её и проверять, что селект возвращает ровно то, что ожидается — не отсекает нужного и не добавляет ненужного. Для простого селекта это будет вроде оверхидом (хотя помогает обнаруживать некоторые ошибки при рефакторинге, типа переименовали столбец/таблицу, а в селекте забыли или переименовали немного не так), но для селектов со сложными where, join, union и т. п. идёт уже проверка логики, а не опечаток/забывчивости и тесты, как правило, необходимы. Правда, это будут скорее интеграционные или даже функциональные тесты, поскольку тестироваться будет взаимодействие нашего кода и внешней системы (SQL-сервера).
И вот мы плавно приходим к тому, что тесты разные нужны, тесты разные важны. Иногда тестирование невозможно или экономически нецелесообразно. Важен результат — минимум багов в коде. Как он достигается, это уже дело техники.
Иногда — да, невозможно. Ещё чаще — нецелесообразно экономически в абсолютных цифрах на этапе разработки. Но чаще мне на практике встречаются ситуации, что этой нецелесообразностью в относительных цифрах можно легко пренебречь и всё же тесты написать заранее (или, хотя бы, иметь легко тестируемую архитектуру). А оценивать целесообразность написания тестов заранее нужно не путём оценки вероятности допустить ошибку, а путём оценки вероятности необходимости изменения кода при фиксированном (или близкому к нему) поведению.
Рефакторинг чаще всего обратно пропорционален фиксации поведения, поэтому тесты пишутся с прицелом на один раз.

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

или, хотя бы, иметь легко тестируемую архитектуру

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

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

На селект тоже можно писать тесты, в случае «закрепления» логики, если она еще и подкреплена хардкодными идентификаторами.
Я всё же стараюсь разделять юнит-тесты и остальные. Бизнес-требования это, как минимум, функциональные. Когда пишу репозиторий сущностей, не пользуясь универсальным ORM, то предпочитаю тестировать и SQL-выборки при, например, миграциях в интеграционных. Оно как-то спокойней, с одной стороны, и быстрее, чем функциональные с другой (приёмочные, напрямую эмулирующие пользовательский ввод, никогда не писал).
Не передёргивайте. Unit test'ы тестируют код. А SQL это не код это декларация. Там тестировать нечего. В SQL'е вы можете сделать unit-test'ы на хранимые процедуры, но не более того…

Для примера посмотрите, что тестируют разработчики SQLite. Вот там ребята оторвались на тестах… И качество продукта говорит само за себя.
да, tac, я думаю, что вы не знаете, зачем нужны unit-testы (и когда они не нужны, тоже не знаете), вы не знаете, зачем нужно функциональное программирование, в чём принципиальный плюс немутабельных структур данных, зачем нужны монады, соответственно, вы не понимаете и (при такой позиции — «вы все дураки и не лечитесь один я в белом стою красивый») никогда не поймёте (не потому, что вы такой тупой что не способны, а потому что вы такой тупой что не готовы приложить усилия), как эта в целом теоретическая область программирования воздействовала и воздействует на широко распространённые языки.
ещё вы не поймёте книжку с дракон на обложке, не поймёте зачем нужны темплейты, как вообще компьютер работает

но при всём при этом вы будете говорить совершенно космически безумные вещи. вы долбоёб, tac, ничего личного и я не хочу вас обидеть, но вы действительно такой какой есть ссы в глаза, всё божья роса.
Вот, хамло — так хамло… у чем с вами говорить?
tac, не обижайтесь. помните вы или нет, но я не первый раз встречаюсь с вами в комментариях, и я только сейчас высказался напрямую, без ненужного жеманства. убеждён, что я не переборщил; убеждён, что вы не начинающий программист который только начал учиться; если бы это было так, мне было бы сейчас стыдно и неловко. но вы не учитесь. вы заматерелый, ничего не желающий знать и слушающий только себя болван.
Ну, не хамов же мне слушать, а с умными людьми я говорю и слушаю…
Тесты помогают рефаторить код.

В этом, имхо, их основная цель, а не «чтобы не могли прятаться баги». Да, каждый выделенный баг, должен быть покрыт тестом, фиксирующем (изолирующим) его. Да, перед любым небезопасным (в целом перед любым) рефакторингом, да и вообще изменением кода, должны быть тесты. Грубо говоря, если вернулся к куску кода второй раз, то его нужно покрывать тестами. Безусловное следование догамам ТДД типа «ни строчки кода без тестов» полезно только на первых этапах, чтобы создавать тестируемую архитектуру, потом тесты следует, имхо, создавать только на участки кода вероятность рефакторинга которых заведомо (до написания кода) велика. В случае, конечно, если спецификация требований/предположений к коду ведётся другим способом, если тесты единственная спецификация, то без них никуда.
Ну, и на самом деле тут описано не костыльное программирование — а самое что не на есть нормальное. Дело в том, что все что было перечислено это сознательное не согласие с «модным программистским безумием». И это правильно не только практически, но и теоретически. А «модное программистское безумие» — это как раз от отсутствия опыта понять, что это за хрень и к чему ведет, и не может привести.
Странное противопоставление костыльного и правильного программирования. Достаточно просто внимательно посмотреть на цикл разработки продукта:

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

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

Когда мы начинаем делать продукт, то мы слишком мало о нем знаем. Прототип и последующая итерационная разработка позволяют шаг за шагом узнавать что-то новое о продукте. Шаг за шагом выстраивать архитектуру, набираясь опыта и понимания.
Архитектура должна выкристализовываться сама в процессе работы, а не изначально навязываться.
С опытом прихожу, что вылизывать сильно не надо. Теряется время, и когда довылизал продукт уже никому не нужен.
Замечательная статья! Теперь я знаю, что я «костыльный программист» и горжусь этим :) Бывают ситуации, когда у заказчика есть проблема и решить её надо сейчас. Не обязательно красиво и идеально, но решить, очень быстро получить работающее решение. Оно может быть не идеальным, но должно работать и необходимо немедленно. А вот потом, когда катастрофы уже нет, можно задуматься об оптимизации.
Sign up to leave a comment.

Articles