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

Позвольте мне рассказать вам историю. Возможно, вы уже слышали это раньше.

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

Они привлекают опытных программистов, которые привносят все новейшие и лучшие технологии: TypeScript, React, Redux, TypeORM, MongoDB, GraphQL, микросервисы и многие другие. Первые пару месяцев они тратят на настройку, обсуждение общей архитектуры и создание сервисов как можно быстрее.

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

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

У этой команды было достаточно ресурсов, времени и опыта, чтобы создать надежный и успешный продукт к установленному сроку. Итак, что случилось?

Зная, где нажать

«Мы стартап со сжатыми сроками». — Аноним

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

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

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

Эти «маленькие» повседневные вещи могут быть намного менее привлекательными и труднее продать руководству, чем Framework X, но они гораздо важнее. Я создавал успешные приложения без GraphQL, но не без комплексной стратегии обработки ошибок.

Когда его спросили о некоторых из этих недостатков, технический директор ответил: «Да, я тоже это заметил… но мы не Microsoft. Мы стартап со сжатыми сроками».

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

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

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

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

Заблуждение качества против скорости

«Если отладка — это процесс устранения ошибок, то программирование должно быть процессом их добавления». — Эдсгер Дейкстра

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

Много говорят о качестве кода. Но что это? Одно определение:

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

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

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

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

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

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

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

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

Весьма интересно и впечатляюще то, что эта проблема была предсказана Аланом Тьюрингом в 1940-х годах:

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

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

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

Чем выше качество кода, тем выше эффективность команды. Низким качеством кода вы работаете против себя!

Яма успеха

«Главная задача ученого-компьютерщика — не запутаться в сложностях собственного творения». — Эдсгер Дейкстра

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

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

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

Простота

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

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

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

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

«Композиционность — это САМЫЙ способ контролировать сложность».
— Брайан Бекман.

Читабельность и повествование

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

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

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

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

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

Инструменты

При правильном использовании инструменты незаменимы для эффективной команды разработчиков программного обеспечения. Лучшие инструменты предотвращают ошибки, применяют передовой опыт и автоматизируют трудоемкие повторяющиеся задачи, в которых легко ошибиться. Я говорю о таких вещах, как языки со статической типизацией, линтинг и инструменты выпуска CI/CD (такие как GitHub Actions и semantic-release). Rust, относительно новый язык программирования, не только имеет сильную систему статических типов, но и пытается свести к минимуму условия гонки и проблемы с памятью.

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

Модульное тестирование

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

«Настоящая ценность тестов не в том, что они обнаруживают ошибки в коде, а в том, что они выявляют недостатки в методах, концентрации и навыках тех, кто разрабатывает и производит код». — Сэр Тони Хоар

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

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

У Кевлина Хенни есть отличный доклад на эту тему О чем мы говорим, когда говорим о модульном тестировании.

Конфигурация и обработка ошибок

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

Заворачивать

Не путайте качество с функциональностью. Качество важно независимо от того, сколько у вас пользователей или насколько мала ваша команда. Качество тоже не купишь.

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

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

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

Хотите узнать больше о методах написания надежного программного обеспечения в больших масштабах с максимальной эффективностью разработчика? Прочтите статью ниже:



Марк Х. Вайнер — ветеран стартапов, проявляющий особый интерес к критически важным для безопасности системам. Он стал соучредителем NextEMR, одного из первых веб-приложений для электронных медицинских карт. Он также был ведущим инженером по программному обеспечению для клинических испытаний, Marvel.com и инфраструктуре в Shutterstock.