Нет, это вы смеетесь, если не понимаете, что такое суррогатный и естественный ключ.
Т.е. вы не хотите правильно в реляционной архитектуре выразить предметную область? Ну тогда вот вы и получили проблемы с тестами. Да вообще, проблемы, не только с тестами.
Поясняю. С чего вдруг у каждого документа будет свой уникальный набор состояний? Если это так, то эти колонки обязаны входить в первичный ключ. Да и нет никакого смысла в таких состояниях. Обычно, количество состояний ограничено. Какие вы их там придумали себе, я не знаю. Я задачи не знаю. Из кода предположил, что набор одинаковый для всех документов. Не одинаковый? Заводите таблицу и внешний ключ DocumentType, и в таблицу состояний DocumentTypeId. Ничего сложного.
Да, я завязать тесты на естественный ключ. Эти дескрипшины не лишние. Где-то в приложении наверняка будут комбобоксы именно с ними. И пользователь их будет именно так воспринимать. По сути, тест тогда отразит требование: «при таких-то условиях документ переходит в состояние „В обработке“». В требованиях не было состояния 2, не так ли?
Если дескрипшин будет на 5-ти языках, закрепите это в тесте через AND.
А если вы хотите пользоваться преимуществом функционального языка, то не пишите числа, а первичный ключ сделайте строковым и пишите там то, что будет видеть пользователь, что точно отражает требования.
Да и что он гарантирует? Ничего, он упадет только если Вы поменяете описание в Description.
А что тест вообще должен по вашему гарантировать? Он гарантирует, что код выполняет то, что задумали. Если вы не хотите моделировать предметную область, то это и есть говнокод. Я себе так и представил, что вы где-то не в таблицах, а в коде шарпа (или чего там), прописали перечисление хардкодом, а не вытащили с таблицы, а теперь ругаете то, что дескрипшины ни на что не влияют? Так они по нормальному должны влиять.
Ой, не лишние. Кстати, написание юнит-тестов вместе с написанием кода изменяет и представление об архитектуре в лучшую сторону.
Рассмотрим данный пример.
Код, хотя и имеет «магические» числа, вполне может существовать и даже правильно написан. Если эти числа — не что иное, как идентификаторы в базе. Идентификаторы могут быть естественными и суррогатными. Это суррогатные.
Если вы пишете DocState = '2', то судя по всему, если грамотно архитектура базы данных создана, есть таблица DocStates. И поле будет внешним ключом. Это необходимо, чтобы СУБД контролировало целостность, а с другой стороны чтобы можно было тест написать.
Не знаю, зачем идентификатор делать текстовым, но сути не меняет, — это идентификатор. А таблица-справочник имеет может быть такое наполнение:
Id Description
'1' 'Новый'
'2' 'В обработке'
'3' 'Отправлен в бухгалтерию'
Ну, так к примеру. В рамках базы данных очень правильно пользоваться идентификаторами, т.к. только они обозначают уникальность самой сущности. А для человека идентификатор — ничего не значащее число. Вот для человека как раз естественный ключ — Description.
Как пишется юнит-тест, чтобы не продублировать числа? Правильно: вытянуть их из таблички-справочника:
declare @DocStateId char
select @DocStateId = Id from DocStates
where Description like N'В обработке'
А далее уже тест не завязан на айдишки и не повторяет логику апдейта. Мало того, он четко передает суть, что хотели от этого апдейта. С самого апдейта это не скажешь. Мало того, в коде самой базы данных, а не теста, делать лайки на дескрипшины — плохо. Потому что в рамках базы, повторюсь, именно первичный ключ является идентификатором.
И вот у нас профит. Переименовали справочник — изменили суть. Тест упал.
Видите, как просто само требование писать юнит-тесты проясняет глюки в архитектуре? Это один из параметров оценки кода: если тест написать тяжело, значит в коде что-то не так.
Да, этот код тоже чистый SQL и как код его тестировать не обязательно. Он сделает то, что там написано.
Но здесь куча магических чисел и строк. Так что уже кандидат на покрытие. И не потому, что он «будет глючить». А потому, что логику надо закрепить. Магические числа имеют свойство в будущем пропадать (заменяться переменными) или изменяться. Но очень легко забыть про эти числа.
Судя по апдейту, тут есть некоторая логика. Значит покрыть нужно. При этом, необходимо писать юнит-тест так, чтобы в нем не было этих магических цифр. Чтобы он не продублировал логику апдейта.
Тест будет очень полезен, когда вы поменяете таблицу-справочник, где описаны эти числа. Тест ляжет и заставит вас исправить апдейт.
А представьте, его не было бы. Где гарантия вообще, что вы ничего не упустили?
Один селект не покрываю. Дело в том, что SQL по сути функциональный язык. В нем описывается «что» вы хотите. Тестировать селект — всё равно что тестировать, а не глючит ли СУБД?
А вот хранимые процедуры, в которых не только SQL, покрываю.
Тест — это утверждение. Нет самых удачных или самых неудачных тестов. Я пишу тесты, проверяющие утверждения, которые считаю важными для работы кода.
Кстати, опыт у меня немаленький и я до того, как начал применять юнит-тесты, уже даже радовался, что мог написать тысячи строк и запустить — и оно работает. Казалось бы очень редко ошибаюсь.
Но потом, начал писать тесты и оказалось, что как бы я внимательно не писал, как бы ясно не держал в голове картинку, всегда нахожу с помощью тестов небольшое число багов. Часто они не заметны. И это еще хуже. Такой баг будет работать, приносить убытки компании, а найти его будет тяжело.
Так автор не всегда авторитет, даже если он именитый.
То, что он сказал про пользователя и юнит-тесты, можно перефразировать:
«Зубы чистить совершенно не обязательно. Главное чтобы они в старости не выпали».
Юнит-тесты — это как чистоплотность. Пользователю не обязательно и знать про них. Но когда вы идете на кухню готовить еду или кушать, моете руки. Вот примерно так и с юнит-тестами.
Важно или не важно — это субъективный фактор. Если человек не видит в них важности, то пусть выбрасывает.
Может и знаете, но явно не понимаете, раз называете сказками. В моей практике тесты очень хорошо выручали. Я как раз так и пишу — никогда не планируя серьезно архитектуру, наобум. Но тесты и рефакторинг довершают дело, когда код действительно стабильным становится. И архитектура «более-менее». Не идеальна, но самая простая для задачи.
Может и не поверите, но я делал довольно непростые проекты и за сравнительно короткое время. Полностью движок и логику переписал для большой нашей системы (ушло 2 месяца), а также DSL вместе с редактором, полностью своими руками. За три месяца. Учитывая, что делал сам и еще на другие проекты отвлекался.
Так вот за год работы ни одного бага. Выкатилось бесшовно в существующую систему буквально на ходу.
Но это именно благодаря юнит-тестам. Так что не сказки это. Это всего лишь привычки. Кто не наработал привычку, не будет писать их быстро и эффективно с первого раза.
значит, вы не совсем понимаете, зачем нужны юнит-тесты.
В императивном языке можно что-то написать. Но это что-то далеко не факт, что делает то, что надо. Юнит-тесты — это как рельсы для поезда. Хорошая аналогия, поэтому часто повторяю. Представьте, что вы сделали транспорт. Куда угодно он может поехать и куда не следует. В лес, потоптать поле ))) Рельсы — это ограничения, которые говорят, что хотят от этого транспорта.
Юнит-тесты и код программы как бы неразрывны. Код — это решение задачи. Юнит-тесты — это требования. Не просто даже требования от заказчика, а любые предположения, которые делал программист во время написания кода. Утверждения.
Тут какая-то тонкая грань в покрытии тестов должна быть. Как бы механически считается, что 100 процентное покрытие лучше. Но на самом деле нет, немного меньше. Если тесты только бы бизнес-требования отражали, то отлично можно было бы менять код и сразу видеть, что сломалось. Но юнит тесты — несколько подробнее. Баланс нужен, чтобы не тестировать каждый чих. И чтобы тесты были достаточно подробные, чтобы не могли прятаться баги.
Тесты помогают рефаторить код. Т.е. если вы хотите поменять код, не меняя фунционал, тесты сразу говорят, что пошло не так. Без тестов рефакторить вообще почти невозможно. Психологически страшно. «работает? пусть работает». Потому что отлаженый как-то код стремно менять — вероятность, что что-то сломается, есть.
Поэтому тесты помогают быстрее менять код, легче отлаживать новые изменения. Но если тесты будут со 100 процентным покрытием, то хоть такие дают лучшую гарантию качества кода, рефакторинг кода влечет за собой рефакторинг большого числа тестов. И есть где-то максимум, когда тесты наиболее полезны в производительности программиста.
Потом, юнит-тесты являются спецификацией. Для хорошего программиста, пришедшего на работу, хорошо написанные юнит-тесты дают возможность лучше и быстрее разобраться с кодом. Потому что они говорят «что хотят от кода».
Ну и на самом деле, отработанные навыки в написании тестов и написание кода с учетом тестов по ТДД, сокращает время создания продукта. С одной стороны не так растет энтропия. С другой стороны любой уважающий себя программист не комитит код, не проверив его работу. На проверку кода всегда будет уходить время. Изменив привычку ручной проверки на автоматическую, — время не так много и съедать будут тесты.
Юмор я заметил. Но и сама идея, кроме юмора именно о подавлении багов, а не выявлении и лечении.
Опять подмены понятий.
1. Перфекционисты считают, что могут писать без багов. Это неверно, баги есть всегда.
2. Если они есть всегда, то давайте учиться с ними сосуществовать. Писать заплатки, чтобы программа «делала вид», что работает верно. А потом (возможно) по логам будем лечить, если будет время.
Вот во втором пункте ложный вывод. Надо так:
Если баги есть всегда, давайте прилагать все усилия, чтобы их было как можно меньше. Не будем надеяться на то, что их нет. Наоборот, будем делать всё, чтобы они обнаружились.
Критичность багов бывает разной. Но как бы не так ;) Повторюсь, такая ситуация довольно редкая. Например, думать о выживаемости можно в случае, когда баги не связаны с кодом. А связаны, например, с разрывом сети, со сбоем в электропитании. Также баги бывают не критичными, если программа выполняет сложную работу, а баг связан с какими-то глюками в отрисовке. Черточка откуда-то под комбобоксом иногда появляется — может быть не критично (хотя здесь уже спорно).
Но сама природа бага в коде в том, что причину его работы и появления не знают. Если бы знали, то наверное бы не писали этот баг. А если узнали, то починить уже не такие большие проблемы.
Поэтому, если установлено существование бага, говорить о его критичности — слишком самонадеянное занятие. Это всего лишь предположении о причинах, не желая разобраться в причинах. Лишь когда причина будет найдена точно, только тогда можно сказать, критичный он или нет. Если вы замазали его внешнее проявление какой-то картинкой, вы его скрыли, не зная масштабов вреда, который он возможно далее продолжает делать.
А точную причину бага узнают только после починки. По такому алгоритму:
1. Фиксация бага. Т.е. нахождение условий, когда он стабильно повторяется.
2. Написание теста, воспроизводящего условия бага и проверяющего, есть ли он. В это время тест не проходит.
3. Поиск причин бага.
4. Исправление когда.
5. Проводим тест. Если тест не проходит, то снова в пункт 3.
Где-то так. Как видим, причины известны более менее точно после исправления кода.
Не кошерно. В докладе слишком много фантазий и за уши притянутых вещей.
Генетические алгоритмы и самоорганизующиеся системы — это немного другое, на разработку ПО такая логика не должна распространяться.
То, что баги есть всегда, далеко не значит, что их одинаковое количество при разных подходах к разработке.
Есть очень неприятная особенность у новичков в программировании. Это эксепшинофобия. Новички панически боятся падения продукта. Когда надо панически бояться неправильной его работы. И тогда у них появляется желание ловить ексепшины, подавлять баги. У них цель — выживаемость программы. (!!!!!!)
Более худшей цели придумать наверное нельзя. Программа работает выполняя какую-то цель. Программы не создаются просто ради программирования. Выживаемость — это полная противоположность целям программы и нанесение вреда. В очень редких случаях это не так, например, при отрисовке ГУИ.
Подавление ошибок — редкое зло. В нормальных методиках программирования перфекционистам нет места. Существование багов учитывается обязательно. Поэтому прикладываются все усилия на их выявление. Юнит-тесты — раз. Падение в случае бага — два. Баг должен быть виден, привлекать внимание. При нормально поставленном процессе и опыте, багов, зависящих от кода, не так много. И как раз падения позволяют их быстрее отловить.
А вот, встречался с ситуацией, когда давили баги, и когда за год один баг в работе мог очень солидные суммы денег съесть у заказчика, пока обратили внимание, что что-то не так.
Павел Кудинов на докладе идею выживаемости довел просто до абсурда.
Тут подмена понятий и не вся совокупность программистов. А давайте на ТДД больше обращать внимания.
Есть такой общераспространенный миф, что возможны только два случая: «хорошо продуманная архитектура» и «костыли».
А вот и нет. Кто продумывает архитектуру, продумывает костыли. Просто они более сложные. Когда какой-то код есть и когда время прошло от начала проекта, а значит и требования уточнились, то гораздо удобнее провести рефакторинг и понять, как лучше именно для текущих текущих требований. Обычно, ругаются на плохую архитектуру люди, уже на поздней стадии проекта. Когда требования новые пришли. Или когда обкатали проект и увидели что плохо. Т.е. думают об архитектуре «задним числом». Что есть правильно.
Но допускают систематическую ошибку. Думая, что архитектуру нужно и можно было предугадать в самом начале.
Когда же ее продумывают вначале, то это грозит астронавтикой.
Тут не «золотая середина» нужна. Тут нужно понимать, в какой последовательности что уместно делать. Делать в самом простом виде на текущий момент — это не костыли. Но чтобы не накапливался мусор и чтобы простота разработки продолжалась долгое время — нужен рефакторинг. И какие-то юнит-тесты. Не 100 процентное покрытие.
Так работать могут не все, нужен хороший опыт. Ощущение, что плохо в коде (архитектуре), особенно с т.з. оверинжениринга.
Поэтому ни те, кто «архитектурят» не правы, скорее всего получится говнокод (странно, но они всегда думают, что только учатся, а в следующем проекте обязательно всё угадают ))), так и не правы костыльщики, которые «не подметают за собой».
С# конечно же, более распространенный язык. Но тут много факторов. Далеко не доказывающих, что шарп — лучший язык. Наоборот, часто распространенность — признак посредственности. Программирование — это и индустрия тоже. Крутятся деньги. Нужно много программистов. А не только очень умных. И большое количество людей, освоивших язык и далее положительно влияет на рост изучающих. Работодатель себя лучше чувствует, когда работники легко заменяемые.
Плюс, возможно, лень моя. Я не изучил еще до сих пор Хаскел. Но и работу на нем тяжело найти. Многие говорят о языке, но вакансии я видел очень редко. На F# дела вроде лучше обстоят, даже в моем городе. А тот язык, на котором я писал, вообще редкий. Работы нет, пишу на шарпе.
А шумиха на счет ФП не зря. И майкрософт давно понял положительные стороны ФП и создал язык, да и шарп развивает в функциональную сторону. И даже в плюсах лямбды собираются ввести в стандарт.
я тут под рефакторингом не совсем честно термин использовал. В том смысле, что компилятор функционального языка и рантайм определяют часто последовательность действий. Т.е. компилятор на свое усмотрение двигает методы, превращает рекурсивные вызовы в циклы, заботится о кеше и т.д. Т.е. двигает и разворачивает код как ему удобно.
Конечно, у него цели другие, чем у рефакторинга человеком. Но тем не менее, действия могут быть похожи. Функциональное программирование говорит «почему» один код хуже другого. А книга Фаулера по рефакторингу — скорее просто методы изменений кода. При этом даны противоположные методы. Т.е. «выделение метода» и «встраивание метода».
Вот поэтому знание хотя бы принципов функционального программирования полезно. В нагрузку к Фаулеру.
Рефакторинг Фаулера и ФП — никак не взаимоисключающие понятия. У языков ФП часто сам компилятор проводит такие рефакторинг. А в шарпе нам приходится.
Устроены так элементы управления, что я для их работы либо должен что-то присвоить, чтобы они отобразили, либо прибиндить. Я бы с радостью передал бы ему делегат, чтобы он сам брал из первоисточника данные. Но так нельзя прямо делать. Зато биндинг — это приблизительно одно и то же. И у каждого класса есть зона ответственности. Мне дела нет, дублируются там данные или нет. Если меня нет, пусть меня хоть и бьют. ))
И в принципе я говорил о минимизации дублирования данных и ссылок. Не вижу в своих доводах и примерах противоречий :)
Я показал код, где одна ссылка была явно лишней, а потом способ, как ее избежать. Что там делает текстбокс — не мое дело. копирует данные или нет.
Хотя да, копирует ссылку, если учесть, что геттер есть. И что. Я просто показал направление, в какую сторону думать. А вообще, биндинг для таких случаев лучше. DataSource — контроллер, DataMember — свойство персон. Тогда точно не мое дело, как работает элемент управления.
1. MVC паттерн слишком много имеет реализаций, чтобы говорить о том, как реализовывать простые вещи на нем. Но и там то же самое. Если у вас представление знает о контроллере, то так писать нехорошо:
Пример упрощенный, не говорю о биндингах, просто о сути. Правильно писать этот же код так:
textBox.EditValue = controller.GetPerson();
Если хочется не дергать метод всегда у контроллера, можно обернуть его в приватное свойство.
Если представление не знает о контроллере, а используется MVP паттерн, то логика приблизительно одна и та же: данные брать из одного и того же места и стараться не дублировать ни данные, ни ссылки.
2. В общем-то нет особой разницы, кроме того факта, что геттеры и сеттеры — это функции. Во первых в один вид всё приводится. Во-вторых мышление тогда немного иначе работает. Вы не оперируете данными напрямую, а оперируете функциями. Добавлю еще к сказанному выше об отражении, то, что иногда может быть тяжело станет рефакторить — поле заменить на свойство. Если кто-то вдруг уже начал пользоваться ссылками на поле (ref).
Недавно был случай, когда коллега занялся оптимизацией. Но он в коде вот так поля напрямую (приватные) использовал. Кеширование он сделал верно. Но усилий у него на понимание, как это делать — ушло намного больше, чем нужно. Он рассматривает класс с разбросанными данными. Оптимизация для него — это попытка выбрать из множества нафантазированных вариантов.
Когда если бы он использовал только методы, то стало бы очевидно, что другого пути просто нет. Только ставишь вопрос, что тормозит и на какие жертвы можно пойти, ответ — что делать — возникает автоматически.
Объяснить, что такое указатель — просто. Грубо — адрес. Объяснить, что такое ссылка, если понял, что такое указатель — вроде тоже просто. А объяснить, что такое указатель на указатель… Вы думаете, это не нужно? В шарпе есть такое слово ref, и с помощью него можно передать ссылку на ссылку в метод. Так вот я встречал людей, даже собеседовавших меня, ведущих программистов, которые задавали мне вопросы по ref, но оказывалось, что сами не знали, в чем отличие ссылки и ссылки на ссылку. Дотнет не совсем и тривиальная вещь. Кругом работают с ссылками. Что несет определенные нюансы, даже если не работают с памятью напрямую.
Со строками в С, конечно, лишние проблемы. Но не сложно и пережить. Тем более легче будет объяснить, почему в дотнете задолбались с этим типом и сделали имутабельным. А с числами, ну вроде ж надо знать. Не лишнее. Оно и в других языках где-то так.
Потом, повторю, задачи курса — изучение основ работы с алгоритмами и памятью. А не изучение всех особенностей языка С. Не обязательно опускаться на низкоуровневое программирование. Простые алгоритмы сортировок, беганий по массивам, вполне на стеке реализуются. Структуры данных в куче. Тоже ничего такого страшного из С там не нужно. Кроме указателей. А вот они то и нужны, чтобы понять, как сделать двусвязный список, дерево т.д. Вот и всё, что требуется от курса. Шишки понабивать. Понять принцип.
Без этого особо не поймут, что делает сборщик мусора. И как оно вообще работает. Вообще-то у нас давно не берут на дотнете на работу, если таких вещей кто-то не знает.
Т.е. вы не хотите правильно в реляционной архитектуре выразить предметную область? Ну тогда вот вы и получили проблемы с тестами. Да вообще, проблемы, не только с тестами.
Поясняю. С чего вдруг у каждого документа будет свой уникальный набор состояний? Если это так, то эти колонки обязаны входить в первичный ключ. Да и нет никакого смысла в таких состояниях. Обычно, количество состояний ограничено. Какие вы их там придумали себе, я не знаю. Я задачи не знаю. Из кода предположил, что набор одинаковый для всех документов. Не одинаковый? Заводите таблицу и внешний ключ DocumentType, и в таблицу состояний DocumentTypeId. Ничего сложного.
Да, я завязать тесты на естественный ключ. Эти дескрипшины не лишние. Где-то в приложении наверняка будут комбобоксы именно с ними. И пользователь их будет именно так воспринимать. По сути, тест тогда отразит требование: «при таких-то условиях документ переходит в состояние „В обработке“». В требованиях не было состояния 2, не так ли?
Если дескрипшин будет на 5-ти языках, закрепите это в тесте через AND.
А если вы хотите пользоваться преимуществом функционального языка, то не пишите числа, а первичный ключ сделайте строковым и пишите там то, что будет видеть пользователь, что точно отражает требования.
А что тест вообще должен по вашему гарантировать? Он гарантирует, что код выполняет то, что задумали. Если вы не хотите моделировать предметную область, то это и есть говнокод. Я себе так и представил, что вы где-то не в таблицах, а в коде шарпа (или чего там), прописали перечисление хардкодом, а не вытащили с таблицы, а теперь ругаете то, что дескрипшины ни на что не влияют? Так они по нормальному должны влиять.
Вы исходите с неправильной архитектуры базы и не видите возможности написать правильный тест.
Рассмотрим данный пример.
Код, хотя и имеет «магические» числа, вполне может существовать и даже правильно написан. Если эти числа — не что иное, как идентификаторы в базе. Идентификаторы могут быть естественными и суррогатными. Это суррогатные.
Если вы пишете DocState = '2', то судя по всему, если грамотно архитектура базы данных создана, есть таблица DocStates. И поле будет внешним ключом. Это необходимо, чтобы СУБД контролировало целостность, а с другой стороны чтобы можно было тест написать.
Не знаю, зачем идентификатор делать текстовым, но сути не меняет, — это идентификатор. А таблица-справочник имеет может быть такое наполнение:
Id Description
'1' 'Новый'
'2' 'В обработке'
'3' 'Отправлен в бухгалтерию'
Ну, так к примеру. В рамках базы данных очень правильно пользоваться идентификаторами, т.к. только они обозначают уникальность самой сущности. А для человека идентификатор — ничего не значащее число. Вот для человека как раз естественный ключ — Description.
Как пишется юнит-тест, чтобы не продублировать числа? Правильно: вытянуть их из таблички-справочника:
А далее уже тест не завязан на айдишки и не повторяет логику апдейта. Мало того, он четко передает суть, что хотели от этого апдейта. С самого апдейта это не скажешь. Мало того, в коде самой базы данных, а не теста, делать лайки на дескрипшины — плохо. Потому что в рамках базы, повторюсь, именно первичный ключ является идентификатором.
И вот у нас профит. Переименовали справочник — изменили суть. Тест упал.
Видите, как просто само требование писать юнит-тесты проясняет глюки в архитектуре? Это один из параметров оценки кода: если тест написать тяжело, значит в коде что-то не так.
Но здесь куча магических чисел и строк. Так что уже кандидат на покрытие. И не потому, что он «будет глючить». А потому, что логику надо закрепить. Магические числа имеют свойство в будущем пропадать (заменяться переменными) или изменяться. Но очень легко забыть про эти числа.
Судя по апдейту, тут есть некоторая логика. Значит покрыть нужно. При этом, необходимо писать юнит-тест так, чтобы в нем не было этих магических цифр. Чтобы он не продублировал логику апдейта.
Тест будет очень полезен, когда вы поменяете таблицу-справочник, где описаны эти числа. Тест ляжет и заставит вас исправить апдейт.
А представьте, его не было бы. Где гарантия вообще, что вы ничего не упустили?
А вот хранимые процедуры, в которых не только SQL, покрываю.
Кстати, опыт у меня немаленький и я до того, как начал применять юнит-тесты, уже даже радовался, что мог написать тысячи строк и запустить — и оно работает. Казалось бы очень редко ошибаюсь.
Но потом, начал писать тесты и оказалось, что как бы я внимательно не писал, как бы ясно не держал в голове картинку, всегда нахожу с помощью тестов небольшое число багов. Часто они не заметны. И это еще хуже. Такой баг будет работать, приносить убытки компании, а найти его будет тяжело.
То, что он сказал про пользователя и юнит-тесты, можно перефразировать:
«Зубы чистить совершенно не обязательно. Главное чтобы они в старости не выпали».
Юнит-тесты — это как чистоплотность. Пользователю не обязательно и знать про них. Но когда вы идете на кухню готовить еду или кушать, моете руки. Вот примерно так и с юнит-тестами.
Важно или не важно — это субъективный фактор. Если человек не видит в них важности, то пусть выбрасывает.
Может и знаете, но явно не понимаете, раз называете сказками. В моей практике тесты очень хорошо выручали. Я как раз так и пишу — никогда не планируя серьезно архитектуру, наобум. Но тесты и рефакторинг довершают дело, когда код действительно стабильным становится. И архитектура «более-менее». Не идеальна, но самая простая для задачи.
Может и не поверите, но я делал довольно непростые проекты и за сравнительно короткое время. Полностью движок и логику переписал для большой нашей системы (ушло 2 месяца), а также DSL вместе с редактором, полностью своими руками. За три месяца. Учитывая, что делал сам и еще на другие проекты отвлекался.
Так вот за год работы ни одного бага. Выкатилось бесшовно в существующую систему буквально на ходу.
Но это именно благодаря юнит-тестам. Так что не сказки это. Это всего лишь привычки. Кто не наработал привычку, не будет писать их быстро и эффективно с первого раза.
В императивном языке можно что-то написать. Но это что-то далеко не факт, что делает то, что надо. Юнит-тесты — это как рельсы для поезда. Хорошая аналогия, поэтому часто повторяю. Представьте, что вы сделали транспорт. Куда угодно он может поехать и куда не следует. В лес, потоптать поле ))) Рельсы — это ограничения, которые говорят, что хотят от этого транспорта.
Юнит-тесты и код программы как бы неразрывны. Код — это решение задачи. Юнит-тесты — это требования. Не просто даже требования от заказчика, а любые предположения, которые делал программист во время написания кода. Утверждения.
Тут какая-то тонкая грань в покрытии тестов должна быть. Как бы механически считается, что 100 процентное покрытие лучше. Но на самом деле нет, немного меньше. Если тесты только бы бизнес-требования отражали, то отлично можно было бы менять код и сразу видеть, что сломалось. Но юнит тесты — несколько подробнее. Баланс нужен, чтобы не тестировать каждый чих. И чтобы тесты были достаточно подробные, чтобы не могли прятаться баги.
Тесты помогают рефаторить код. Т.е. если вы хотите поменять код, не меняя фунционал, тесты сразу говорят, что пошло не так. Без тестов рефакторить вообще почти невозможно. Психологически страшно. «работает? пусть работает». Потому что отлаженый как-то код стремно менять — вероятность, что что-то сломается, есть.
Поэтому тесты помогают быстрее менять код, легче отлаживать новые изменения. Но если тесты будут со 100 процентным покрытием, то хоть такие дают лучшую гарантию качества кода, рефакторинг кода влечет за собой рефакторинг большого числа тестов. И есть где-то максимум, когда тесты наиболее полезны в производительности программиста.
Потом, юнит-тесты являются спецификацией. Для хорошего программиста, пришедшего на работу, хорошо написанные юнит-тесты дают возможность лучше и быстрее разобраться с кодом. Потому что они говорят «что хотят от кода».
Ну и на самом деле, отработанные навыки в написании тестов и написание кода с учетом тестов по ТДД, сокращает время создания продукта. С одной стороны не так растет энтропия. С другой стороны любой уважающий себя программист не комитит код, не проверив его работу. На проверку кода всегда будет уходить время. Изменив привычку ручной проверки на автоматическую, — время не так много и съедать будут тесты.
А то выкладывают руками каждый кирпич и стоимость заоблачная
Опять подмены понятий.
1. Перфекционисты считают, что могут писать без багов. Это неверно, баги есть всегда.
2. Если они есть всегда, то давайте учиться с ними сосуществовать. Писать заплатки, чтобы программа «делала вид», что работает верно. А потом (возможно) по логам будем лечить, если будет время.
Вот во втором пункте ложный вывод. Надо так:
Если баги есть всегда, давайте прилагать все усилия, чтобы их было как можно меньше. Не будем надеяться на то, что их нет. Наоборот, будем делать всё, чтобы они обнаружились.
Критичность багов бывает разной. Но как бы не так ;) Повторюсь, такая ситуация довольно редкая. Например, думать о выживаемости можно в случае, когда баги не связаны с кодом. А связаны, например, с разрывом сети, со сбоем в электропитании. Также баги бывают не критичными, если программа выполняет сложную работу, а баг связан с какими-то глюками в отрисовке. Черточка откуда-то под комбобоксом иногда появляется — может быть не критично (хотя здесь уже спорно).
Но сама природа бага в коде в том, что причину его работы и появления не знают. Если бы знали, то наверное бы не писали этот баг. А если узнали, то починить уже не такие большие проблемы.
Поэтому, если установлено существование бага, говорить о его критичности — слишком самонадеянное занятие. Это всего лишь предположении о причинах, не желая разобраться в причинах. Лишь когда причина будет найдена точно, только тогда можно сказать, критичный он или нет. Если вы замазали его внешнее проявление какой-то картинкой, вы его скрыли, не зная масштабов вреда, который он возможно далее продолжает делать.
А точную причину бага узнают только после починки. По такому алгоритму:
1. Фиксация бага. Т.е. нахождение условий, когда он стабильно повторяется.
2. Написание теста, воспроизводящего условия бага и проверяющего, есть ли он. В это время тест не проходит.
3. Поиск причин бага.
4. Исправление когда.
5. Проводим тест. Если тест не проходит, то снова в пункт 3.
Где-то так. Как видим, причины известны более менее точно после исправления кода.
С багами мириться нельзя
Генетические алгоритмы и самоорганизующиеся системы — это немного другое, на разработку ПО такая логика не должна распространяться.
То, что баги есть всегда, далеко не значит, что их одинаковое количество при разных подходах к разработке.
Есть очень неприятная особенность у новичков в программировании. Это эксепшинофобия. Новички панически боятся падения продукта. Когда надо панически бояться неправильной его работы. И тогда у них появляется желание ловить ексепшины, подавлять баги. У них цель — выживаемость программы. (!!!!!!)
Более худшей цели придумать наверное нельзя. Программа работает выполняя какую-то цель. Программы не создаются просто ради программирования. Выживаемость — это полная противоположность целям программы и нанесение вреда. В очень редких случаях это не так, например, при отрисовке ГУИ.
Подавление ошибок — редкое зло. В нормальных методиках программирования перфекционистам нет места. Существование багов учитывается обязательно. Поэтому прикладываются все усилия на их выявление. Юнит-тесты — раз. Падение в случае бага — два. Баг должен быть виден, привлекать внимание. При нормально поставленном процессе и опыте, багов, зависящих от кода, не так много. И как раз падения позволяют их быстрее отловить.
А вот, встречался с ситуацией, когда давили баги, и когда за год один баг в работе мог очень солидные суммы денег съесть у заказчика, пока обратили внимание, что что-то не так.
Павел Кудинов на докладе идею выживаемости довел просто до абсурда.
Тут подмена понятий и не вся совокупность программистов. А давайте на ТДД больше обращать внимания.
Есть такой общераспространенный миф, что возможны только два случая: «хорошо продуманная архитектура» и «костыли».
А вот и нет. Кто продумывает архитектуру, продумывает костыли. Просто они более сложные. Когда какой-то код есть и когда время прошло от начала проекта, а значит и требования уточнились, то гораздо удобнее провести рефакторинг и понять, как лучше именно для текущих текущих требований. Обычно, ругаются на плохую архитектуру люди, уже на поздней стадии проекта. Когда требования новые пришли. Или когда обкатали проект и увидели что плохо. Т.е. думают об архитектуре «задним числом». Что есть правильно.
Но допускают систематическую ошибку. Думая, что архитектуру нужно и можно было предугадать в самом начале.
Когда же ее продумывают вначале, то это грозит астронавтикой.
Тут не «золотая середина» нужна. Тут нужно понимать, в какой последовательности что уместно делать. Делать в самом простом виде на текущий момент — это не костыли. Но чтобы не накапливался мусор и чтобы простота разработки продолжалась долгое время — нужен рефакторинг. И какие-то юнит-тесты. Не 100 процентное покрытие.
Так работать могут не все, нужен хороший опыт. Ощущение, что плохо в коде (архитектуре), особенно с т.з. оверинжениринга.
Поэтому ни те, кто «архитектурят» не правы, скорее всего получится говнокод (странно, но они всегда думают, что только учатся, а в следующем проекте обязательно всё угадают ))), так и не правы костыльщики, которые «не подметают за собой».
Плюс, возможно, лень моя. Я не изучил еще до сих пор Хаскел. Но и работу на нем тяжело найти. Многие говорят о языке, но вакансии я видел очень редко. На F# дела вроде лучше обстоят, даже в моем городе. А тот язык, на котором я писал, вообще редкий. Работы нет, пишу на шарпе.
А шумиха на счет ФП не зря. И майкрософт давно понял положительные стороны ФП и создал язык, да и шарп развивает в функциональную сторону. И даже в плюсах лямбды собираются ввести в стандарт.
Конечно, у него цели другие, чем у рефакторинга человеком. Но тем не менее, действия могут быть похожи. Функциональное программирование говорит «почему» один код хуже другого. А книга Фаулера по рефакторингу — скорее просто методы изменений кода. При этом даны противоположные методы. Т.е. «выделение метода» и «встраивание метода».
Вот поэтому знание хотя бы принципов функционального программирования полезно. В нагрузку к Фаулеру.
Устроены так элементы управления, что я для их работы либо должен что-то присвоить, чтобы они отобразили, либо прибиндить. Я бы с радостью передал бы ему делегат, чтобы он сам брал из первоисточника данные. Но так нельзя прямо делать. Зато биндинг — это приблизительно одно и то же. И у каждого класса есть зона ответственности. Мне дела нет, дублируются там данные или нет. Если меня нет, пусть меня хоть и бьют. ))
И в принципе я говорил о минимизации дублирования данных и ссылок. Не вижу в своих доводах и примерах противоречий :)
Хотя да, копирует ссылку, если учесть, что геттер есть. И что. Я просто показал направление, в какую сторону думать. А вообще, биндинг для таких случаев лучше. DataSource — контроллер, DataMember — свойство персон. Тогда точно не мое дело, как работает элемент управления.
Пример упрощенный, не говорю о биндингах, просто о сути. Правильно писать этот же код так:
Если хочется не дергать метод всегда у контроллера, можно обернуть его в приватное свойство.
Если представление не знает о контроллере, а используется MVP паттерн, то логика приблизительно одна и та же: данные брать из одного и того же места и стараться не дублировать ни данные, ни ссылки.
2. В общем-то нет особой разницы, кроме того факта, что геттеры и сеттеры — это функции. Во первых в один вид всё приводится. Во-вторых мышление тогда немного иначе работает. Вы не оперируете данными напрямую, а оперируете функциями. Добавлю еще к сказанному выше об отражении, то, что иногда может быть тяжело станет рефакторить — поле заменить на свойство. Если кто-то вдруг уже начал пользоваться ссылками на поле (ref).
Недавно был случай, когда коллега занялся оптимизацией. Но он в коде вот так поля напрямую (приватные) использовал. Кеширование он сделал верно. Но усилий у него на понимание, как это делать — ушло намного больше, чем нужно. Он рассматривает класс с разбросанными данными. Оптимизация для него — это попытка выбрать из множества нафантазированных вариантов.
Когда если бы он использовал только методы, то стало бы очевидно, что другого пути просто нет. Только ставишь вопрос, что тормозит и на какие жертвы можно пойти, ответ — что делать — возникает автоматически.
Объяснить, что такое указатель — просто. Грубо — адрес. Объяснить, что такое ссылка, если понял, что такое указатель — вроде тоже просто. А объяснить, что такое указатель на указатель… Вы думаете, это не нужно? В шарпе есть такое слово ref, и с помощью него можно передать ссылку на ссылку в метод. Так вот я встречал людей, даже собеседовавших меня, ведущих программистов, которые задавали мне вопросы по ref, но оказывалось, что сами не знали, в чем отличие ссылки и ссылки на ссылку. Дотнет не совсем и тривиальная вещь. Кругом работают с ссылками. Что несет определенные нюансы, даже если не работают с памятью напрямую.
Со строками в С, конечно, лишние проблемы. Но не сложно и пережить. Тем более легче будет объяснить, почему в дотнете задолбались с этим типом и сделали имутабельным. А с числами, ну вроде ж надо знать. Не лишнее. Оно и в других языках где-то так.
Потом, повторю, задачи курса — изучение основ работы с алгоритмами и памятью. А не изучение всех особенностей языка С. Не обязательно опускаться на низкоуровневое программирование. Простые алгоритмы сортировок, беганий по массивам, вполне на стеке реализуются. Структуры данных в куче. Тоже ничего такого страшного из С там не нужно. Кроме указателей. А вот они то и нужны, чтобы понять, как сделать двусвязный список, дерево т.д. Вот и всё, что требуется от курса. Шишки понабивать. Понять принцип.
Без этого особо не поймут, что делает сборщик мусора. И как оно вообще работает. Вообще-то у нас давно не берут на дотнете на работу, если таких вещей кто-то не знает.
А далее, уже ООП с юмлями. ФП.