Почему ошибаются программисты?

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

Почему ошибаются программисты?

Меня зовут Константин Митин, я сооснователь и руководитель компании АйТи Мегастар/АйТи Мегагруп. Когда-то был простым разработчиком, работал в 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 часов. Задачу программиста мы считаем сделанной, только когда она прошла тестирование. Багфикс записываем в трудозатраты по задаче. К тому же, при передаче задачи на тестирование программист проводит демонстрацию работы функционала. Это организационный способ убедиться, что разработчик провёл отладку.

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

Проблема оптимизации скорости и потребления памяти

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

Почему ошибаются программисты?

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

Вас когда-нибудь интересовало, почему со временем ваш телефон либо компьютер работает все медленнее и медленнее?

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

На самом деле, я уже частично касался её в своей статье «Почему всё ломается даже у хороших программистов?», когда описывал свой личный опыт. В какой-то момент оказалось, что я не могу получить из базы данных фрагмент данных в 64 мегабайта потому, что внутри кода платформы бэкенда объём затрачиваемых данных начинал превышать 2 гигабайта. Для компании это всё выливалось в заметные денежные затраты на вычислительные мощности и на работу программистов, которые были вынуждены работать на такой платформе.

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

Преждевременная оптимизация - это тоже плохо.

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

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

Схожие подходы нужно использовать и при написании MVP (minimum viable product - минимально жизнеспособный продукт). На этой стадии реализуется продукт для проверки гипотезы, что он вообще нужен рынку. Делать его оптимальным по быстродействию — терять больше ресурсов в случае ошибки. Делать его совсем неоптимальным — закрывать себе возможность быстрого изменения продукта.

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

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

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

Но мы всё же о негативном паттерне поведения разработчиков. Как, собственно, сложился этот паттерн? Аналогично с разработкой через багфикс соединилось несколько обстоятельств:

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

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

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

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

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

Недостаточность фундаментальных знаний

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

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

Почему ошибаются программисты?

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

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

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

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

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

Реальный уровень человека чувствуется сразу же, когда нужно использовать конструкцию типа array.append() в цикле раз так сто тысяч. Либо человек понимает, что для добавления нового элемента в массив из N элементов нужно:

  1. выделить память в N+1 элементов;

  2. скопировать N значений из старого массива в новый массив;

  3. добавить в новый массив N+1 элемент;

  4. освободить память из N элементов старого массива.

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

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

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

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

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

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

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

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

Тем не менее, ещё лет 10 назад мощности смартфона с избытком хватало на управление спутниковой группировкой СССР. Сегодня этой мощности не хватает ни на что.

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

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

Если вы хотите научиться оптимально работать с PostgreSQL, то вам сюда.

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

Слепое следование ритуалам без понимания их сути

Негативных паттернов поведения в программировании осталось ещё очень много, а вот места в статье нет. Рассмотреть каждый по отдельности не получится. Но многие из них объединяет именно слепое следование ритуалам без понимания их сути. Как такое получается? Давайте рассмотрим на простом примере «код-ревью».

Почему ошибаются программисты?

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

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

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

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

Наставник должен передавать понимание о важности того либо иного ритуала и причин его возникновения. Один из таких ритуалов является «код-ревью», либо инспекция/рецензирование кода одного разработчика другим разработчиком. Рецензирование кода придумали для того, чтобы более опытные разработчики могли учить менее опытных разработчиков до того, как их код сможет что-то сломать на стенде разработки либо на тестовом стенде.

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

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

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

Инспекция кода — не бесплатная процедура.

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

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

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

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

А если это будет не три джуна, а три мидла? Сработают ровно те же ограничения. И с тремя сеньорами — тоже.

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

Примеров неверного применения тех либо иных практик очень много.

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

Подводя итоги

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

Почему ошибаются программисты?

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

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

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

99
15 комментариев

Много букаф.

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

2

Фигасе какая длинная и умная статья. :)
Я вижу большую проблему в обучении программистов в том, что учат неправильно - то есть объясняют всё по-отдельности и сам язык, и его библиотеки. То есть нет такого, что сразу говорят - сейчас мы построим каркас приложения с самыми эффективными библиотеками и покажем вам как надо. Если бы так обучали программистов в компании, тогда никакого большого кода ревью и не надо бы было. А то начинающий программист как слепой щенок начинает программировать так, как нашел в гугле пример и зачастую этот пример не совсем удачный и неполный. Я вижу большую проблему в курсах для программистов, когда на учеников вываливают кучу информации и сам язык и библиотеки. Он оканчивает курс у него голова распухла и ему говорят а вот теперь создай приложение сам - и тут он начинает придумывать что-то, потому что на курсе этому учат совсем мало.

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

1

Уважаемый Константин Митин, сооснователь и руководитель компании АйТи Мегастар/АйТи Мегагруп, вам в пору примерить шкуру сооснователя и руководителя тесктогенератора МегаВода и МегаНеинформатив. Ну серьезно. Господи. Вы рассказываете в статье об ошибках программистов кто такие программисты? Прошу прощения. Не осилил ваш титанический труд

К слову сказать, в тексте статьи есть определение и для программистов. То, что вы сей труд не осилили, не страшно. Возможно вы просто не входите в мою целевую аудиторию, а я не вхожу в круг интересных для вас авторов. Обычное дело, если честно.
А по большому счёту, меня как-то попросили написать про Job Safety Driven Development (JSDD), а это не простая тема с кучей нюансов. Теперь приходится описывать, что является этим JSDD, а что лишь имеет с ним внешнее сходство. Эта статья лишь треть от того текста, который пришлось написать. И явно придётся писать ещё одну статью о том, почему у людей не получится собрать дрим-тим из суперкрутых программистов, которые решат им все технические и бизнесовые проблемы.

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