Блог itMegastar

Почему ошибаются программисты? Часть 1/2

Полезные статьи
После несерьёзной статьи на серьёзную тему Job Safety Driven Development возникла идея написать о том, как появляются ошибки разработчиков. Вместо этого появилась статья «Почему всё ломается даже у хороших программистов?» (Часть 1 и Часть 2). Мысль нужно закончить. Уже рассмотрено два краевых случая, давайте посмотрим и на «обычные» причины ошибок программистов. Тема необъятная, поэтому затронем только основные моменты. Эту статью тоже пришлось разбить на 2 части (Часть 2). Как всегда, попробую писать простым языком, понятным широкой аудитории.


Меня зовут Константин Митин, я сооснователь и руководитель компании АйТи Мегастар/АйТи Мегагруп. Когда-то был простым разработчиком, работал в L3, дорос до тимлида, затем и до руководителя филиала разработки крупной ИТ-компании. Теперь я в АйТи Мегагруп.

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

Об ошибках


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


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

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

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

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

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

Часто бездействие — очень тяжёлая и дорогая ошибка.

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

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

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

Детство — счастливое время, когда цена ошибок минимальна. Во взрослой жизни всё не так.

Негативные паттерны поведения


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


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

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

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

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

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

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

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

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

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

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

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

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

Интерактивное программирование


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


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

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

В своё время Фредерик Брукс в своей книге «Мифический человеко-месяц» ввёл термин «интерактивное программирование». В какой-то момент удалось резко снизить стоимость ошибки в процессе компиляции и отладки кода. В результате чего ушла необходимость проверять всё самым тщательным образом, что привело к многократному росту скорости и качества разработки.

Интерактивное программирование — это разработка через пробы и ошибки с минимальной ценой.

Если компиляция вашего кода занимает несколько минут (вместо ожидания в несколько недель для доступа в вычислительный центр), а потом компилятор вам говорит, что нужно исправить, то какой смысл вручную проверять код в течение часа? Вы просто запускаете компилятор, получаете сообщения об ошибках, исправляете их и запускаете компилятор опять. И так, пока ошибки не закончатся. Час сокращается до 5−10 минут.

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

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

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

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

Разработка через багфикс


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


То есть если разработчик написал код, то он ещё не сделал задачу. Нужно же посмотреть на то, что сделалось. Оно должно проходить смок-тестирование, отрабатывать базовые позитивные и негативные сценарии. Работать с адекватной скоростью, кстати. То есть быть уже пригодным к использованию в большинстве случаев (например, в 95%-99%).

Но есть объективная проблема. Хороший разработчик — это созидатель, поэтому он плохо выполняет функцию QC (quality control — оценка качества). Забавно, но именно в руках хорошего разработчика начинает правильно работать даже чужой код, который не работает в других руках. Человек просто инстинктивно обойдёт те места, где что-то может сломаться и пойти не так. Пользователи на продуктиве ведут себя не так, и иногда сильно удивляют техническую команду своей фантазией.

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

Хороший разработчик — это созидатель, поэтому он плохо тестирует.

Есть ещё QA-специалисты (quality assurance — обеспечение качества). Они больше нацелены на то, чтобы не ломалось, а не на то, чтобы сломать. Поэтому они помогают программистам. Например, заранее пишут тесты для задачи и отдают их разработчикам, чтобы они могли использовать их при отладке. Участвуют в CI/CD, который изначально предполагал наличие автоматического тестирования, которое не выпускает сбойный код даже на тестовый стенд. От того, что вы умеете быстро доставлять дефектный код на продуктив, счастья никому не будет.

На самом деле, существует такой процесс, как управление качеством. Обычно никто не стремится, чтобы код был идеальным (это очень дорого) и без ошибок. Стремятся к тому, чтобы в критически важных и наиболее используемых сценариях работы не было ошибок. Поэтому ошибки могут просачиваться через процесс отладки программистом, процесс тестирования QA/QC и попадать на продуктив. Если это не останавливает и сильно не затрудняет работу пользователей, а после обнаружения быстро исправляется, то это в целом нормальная ситуация.

На самом деле, существует такой процесс, как управление качеством.

Чаще всего проблемой становятся не нормальные ошибки программиста, а сформированные негативные паттерны поведения, которые могут искажать восприятие целых команд разработки. Например, в месте сопряжения работы разработчика и QC-специалиста возникает негативный паттерн поведения: «Разработка через багфикс».
Разработчик говорит, что:

  1. ошибки будут всегда, и это нормально;
  2. разработчики — плохие тестировщики;
  3. работа QC-специалиста состоит в поиске ошибок.

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

Дополнительным фактором является неправильный учёт времени разработки в организации. У задач на разработку обычно есть оценка времени на выполнение, превышать которую не очень хорошо. Однако баги (найденные ошибки) QC-специалисты оформляют отдельными задачами. Со своими оценками, отметками времени, которые не попадают в общую сумму выполнения задачи.

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

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

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

У себя мы сделали просто. Оценки и отметки времени делаются в рабочих часах, идеальные часы, майки и иные суррогаты времени мы не используем. Задачей считаем только то, что можно потрогать руками (с точки зрения пользователя), поэтому оценка выполнения зачастую выше 4 часов. Задачу программиста мы считаем сделанной, только когда она прошла тестирование. Багфикс записываем в трудозатраты по задаче. К тому же, при передаче задачи на тестирование программист проводит демонстрацию работы функционала. Это организационный способ убедиться, что разработчик провёл отладку.

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

Подводя промежуточные итоги


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


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

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

Если вы дочитали до конца и что-то для себя поняли, то спасибо вам.