Разработка программного обеспечения с низкими эксплуатационными расходами

Цели

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

Принципы

Помните, что "бесплатных обедов не бывает", вы не сможете получить проект программного обеспечения с низкими эксплуатационными расходами, ничего не делая. Ключ к достижению низких эксплуатационных расходов при разработке программного обеспечения заключается не в том, чтобы совершить меньше ошибок, а в том, чтобы максимально правильно выполнить каждую мелочь, что, несомненно, довольно сложно.
Здесь обобщены некоторые общие принципы, которые помогут достичь разработки программного обеспечения с низкими эксплуатационными расходами:
  • Отказаться от всех ненужных функций.
  • Перестать использовать языки и цепочки инструментов с нездоровой экосистемой.
  • Использовать языки с высокой выразительной силой, избегая искажения и отклонения от общих паттернов языка в процессе кодирования.
  • Перестать использовать макросы для любых целей, которые могут привести к "плохим запахам" кода.
  • Использовать только языки с определениями типов или аннотациями типов.
  • По возможности использовать проверенные временем технологии, поскольку эти технологии имеют шанс просуществовать дольше.
  • Мыслить глобально, избегать тактического программирования.
  • Активно рефакторить код, допуская большое количество изменений строк кода для снижения энтропии программного обеспечения.
  • Отказаться от менее необходимых дизайнов, перенося сложность на пользователя, поскольку удобные для пользователя дизайны часто наиболее сложны в поддержке. В реальности уже существует множество подобных примеров. Рассмотрим редакторы Markdown и WYSIWYG-редакторы: Первые требуют от пользователя (очень небольших) затрат на обучение, в то время как вторые практически не требуют обучения; однако последние значительно хуже первых в плане поддерживаемости:
    • Редакторы Markdown облегчают написание четко структурированных, читаемых человеком текстовых файлов. WYSIWYG-редакторы создают либо трудно переносимые внутренние модели документов, либо полностью запутанный HTML.
    • Документы Markdown могут использоваться в 2050 году, редакторы Markdown часто минималистичны и взаимозаменяемы. WYSIWYG-редакторы быстрее устаревают, их сложнее поддерживать обратную совместимость, и их сложнее поддерживать разработчикам, а поставщики к 2050 году, скорее всего, уже не будут существовать.
  • Перестать использовать крупные программные фреймворки, которые могут привести к привязке к поставщику. Когда программный фреймворк адаптируется ко всем средам или пытается предоставить универсальное решение, он перестает быть программным фреймворком и становится бомбой замедленного действия.
  • Максимально сократить зависимость от некачественных пакетов. Это означает проверку качества кода зависимостей; к счастью, плохой стиль кода легко распознать, и часто можно сделать вывод, не понимая, как работает пакет.
  • Не писать программное обеспечение для платформ без обратной совместимости.
  • Если программное обеспечение не имеет единственной функции, следует всеми силами избегать его кроссплатформенности.
  • Запретить использование проприетарных сервисов, предоставляемых облачными платформами.
  • Запретить использование менеджеров пакетов, которые не могут фиксировать версии дочерних зависимостей, запретить использование репозиториев Git в качестве источников пакетов.
  • Без необходимости не следует следовать существующим стандартам и протоколам:
    • Большинство стандартов и протоколов - это мусор, большая часть из них даже не имеет справочной ценности; следование им только затормозит развитие программного обеспечения. Например, вы можете, к несчастью, столкнуться с требованиями, которые невозможно реализовать с помощью этих стандартов и протоколов, и тогда окажетесь в безвыходной ситуации.
    • Популярность тех стандартов и протоколов, которые слишком популярны, чтобы их игнорировать, в основном обусловлена их преимуществом первопроходца и гарантией обратной совместимости. Это приводит к тому, что их реализация в производстве изобилует всевозможными странными пограничными случаями, а сбор и обработка этих пограничных случаев истощают.
  • Сосредоточиться на уровне абстракции цепочки инструментов, перестать использовать цепочки инструментов, которые трудно переносить или обновлять.
  • Писать большое количество автоматизированных интеграционных тестов для повышения уверенности при обновлении зависимостей.
  • Среда, необходимая для работы программного обеспечения, должна быть выделенной, изолированной, а не общей с другими проектами.
  • Ценить владение технологиями, максимально отстраняться от зависимостей, которые могут быть затронуты внешними факторами.
  • Максимально сократить количество разработчиков, не относящихся к основному составу, чтобы избежать снижения эффективности разработки проекта из-за трений в сотрудничестве. Это похоже на пробку: лучшее решение - не расширять дорогу, а уменьшать количество одновременно едущих автомобилей.

Заново изобретать колесо

Заново изобретать колесо — это антипаттерн, потому что всегда существует одно или несколько уже изобретенных колес, готовых к повторному использованию. Однако повторное использование существующих колес часто приводит к проблемам с управлением зависимостями: сначала все идет гладко, но проблемы быстро следуют одна за другой. Самый простой путь часто является путем к максимальному техническому долгу. Двигаясь по этому пути, часто испытываешь разочарование. Со временем даже самый уверенный и страстный человек будет испытывать выученную беспомощность, считая разработку программного обеспечения бесконечной рутиной.
Определение того, стоит ли вводить зависимость, — это важная способность, которую легко упустить из виду, и за ней стоит множество невыразимых слов опыта. Когда вам не хватает этой способности, вас легко обмануть, и вы станете жертвой или даже пособником будущего злоумышленника. Поскольку большинству разработчиков не хватает этой способности, в отрасли очень распространено явление, когда плохая валюта вытесняет хорошую.
Проекты с открытым исходным кодом, достигшие определенного масштаба, легко превращаются в "кучу дерьма":
  • Ошибочные абстракции, плохая архитектура, ужасная поверхность API.
  • Отсутствие убедительных тестов.
  • Документация не активно поддерживается, не соответствует действительности, либо скрывает, либо игнорирует полезные детали.
  • Слишком много тактического кодирования, слишком мало стратегического кодирования; объем нового кода всегда больше объема удаленного кода.
  • Слишком много времени и средств тратится на несуществующие требования, что приводит к раздуванию и разложению программного обеспечения.
  • ……
Если в сообществе нет авторитетных мейнтейнеров, способных осуществлять стратегический надзор и строго выполнять ревью кода, то модель участия в проектах с открытым исходным кодом естественным образом будет приводить к появлению таких "куч дерьма", независимо от уровня квалификации участников. Программное обеспечение с закрытым исходным кодом имеет те же проблемы, просто непрозрачность программного обеспечения с закрытым исходным кодом еще больше маскирует эти проблемы; проблемы, спрятанные под ковром, не означают, что они исчезли.
Чтобы освободиться от технического долга, иногда приходится надевать шляпу NIH. Заново изобретая колесо, вы можете заметить, что, помимо избавления от плохих сторонних зависимостей, переизобретение колеса приносит преимущества, о которых никогда не узнают разработчики, погрязшие в техническом долге:
  • Есть вероятность изобрести лучшее колесо.
  • Вы будете лучше справляться с изобретением решений.
  • Вы будете лучше различать хорошие и плохие зависимости.
Когда вы стремитесь к низким эксплуатационным расходам, выгода от переизобретения колеса может в будущем еще больше увеличиться: Избавившись от долгов, остается только проценты — как это просто. Со временем, наслоение этих положительных отзывов приведет к тому, что вариант переизобретения колеса займет все более важное место в вашем сознании. Однако, прежде чем переизобретать колесо, всегда следует тщательно оценивать его экономическую целесообразность. Переизобретать все по-прежнему нецелесообразно; очевидно, что некоторые колеса имеют очень низкую рентабельность при переизобретении и высокий риск провала разработки, поэтому вам следует быть осторожным.

Создание "батарейных" зависимостей

Некоторые языки программирования и фреймворки заявляют, что они поставляются с "батарейками в комплекте" (batteries included), что означает наличие встроенных высокоуровневых функций, которые можно использовать напрямую. Суть этих "батареек" заключается лишь в относительно стабильных, высокоуровневых API, которые максимально избегают раскрытия низкоуровневых деталей; правильная абстракция и тестирование обеспечивают редкое возникновение критических изменений.
Концепция "батареек" полностью применима к управлению зависимостями; можно создать такую "батарейную зависимость": Эта зависимость является промежуточным слоем между проектом, фактически использующим зависимость, и самой зависимостью. Она предоставляет набор упорядоченных и аккуратных API, основанных на зависимостях, тем самым позволяя избежать прямого использования бизнес-кодом функций или классов с гигантским или огромным количеством параметров, изобретенных "варварами".
Поскольку вы сосредоточены только на используемых частях этих зависимостей, вы можете написать свой собственный тестовый код в промежуточном слое, чтобы убедиться, что при обновлении версии зависимости ее поведение по-прежнему соответствует ожиданиям. Если зависимость перестанет работать в будущем, вы можете выбрать использование старой версии зависимости, заменить зависимость или реализовать ее самостоятельно, при этом ранее написанные тесты будут использоваться для обеспечения эффективности при рефакторинге. Это ключевое различие между собственными "батарейками" и "батарейками", созданными другими: вы всегда можете зарядить или расширить свои собственные "батарейки", в то время как для чужих "батареек" вы обычно можете только наблюдать, как они постепенно разряжаются.
Из-за формального сходства может возникнуть путаница между "батарейными" зависимостями и инкапсулированной бизнес-логикой; основное различие между ними заключается в их удаленности от зависимостей. "Батарейные" зависимости обычно находятся очень близко к зависимостям, в то время как бизнес-логика обычно находится очень далеко от зависимостей. Только в редких случаях, например, когда бизнес-логика напрямую строится на соответствующих зависимостях, эти два понятия сходятся.
"Батарейные" зависимости кажутся святым граалем в решении проблем управления зависимостями, но все же есть несколько проблем:
  • В конце концов, это дополнительный промежуточный слой; когда вам нужна новая функция или вы вносите критические изменения, это будет сложнее, чем прямое использование зависимости.
  • Создание правильной абстракции очень сложно. На пути к поиску правильной абстракции иногда неизбежно приходится начинать все заново. Людям также часто не хватает предвидения будущих потребностей, поэтому временно правильная абстракция может в будущем оказаться неправильной. Люди боятся, что проект застрянет в бесконечном цикле изменений из-за неправильной абстракции, поэтому они предпочитают втягивать себя в больший водоворот; по крайней мере, в случае ошибки всегда легче винить других, и эту мысль трудно опровергнуть.

Использование небольших программных пакетов

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

Выбор читаемости вместо производительности

В процессе реального кодирования возникают ситуации, когда приходится выбирать между читаемостью и производительностью; в этот момент разработчик легко поддается сиюминутной близорукости и выбирает производительность вместо читаемости. Выбор производительности обычно не имеет веских оснований, и компромисс, жертвующий читаемостью ради производительности, в дальнейшем часто оказывается невыгодным. В некоторых случаях разработчик выбирает производительность даже из-за наивной психологической выгоды, возникающей из мысли: "написание высокопроизводительного кода может показать, насколько я умён".
Когда вы колеблетесь между читаемостью и производительностью, всегда следует выбирать читаемость, и это невозможно переоценить. Вы абсолютно уверены, что этот код будет написан только один раз и никогда не изменится за всю вашу жизнь? Выбирайте читаемость. Код, который более читабелен, в 1000 раз медленнее? Выбирайте читаемость. Если действительно возникает наблюдаемое негативное влияние на производительность, тогда напишите ещё один фрагмент кода: Этот код представляет собой высокопроизводительную версию высокочитаемого кода, и вам нужно написать очень подробную документацию рядом, чтобы объяснить, как этот код был создан.
Исключением является ситуация, когда вам нужно написать высокопроизводительную программу; поскольку вашей первоначальной целью является высокая производительность, читаемость как вариант исчезает. Однако, даже в высокопроизводительных программах все еще есть возможности для улучшения читаемости кода, просто эти возможности трудно заметить —часто их замечают только при повторном написании кода. И как только такая возможность улучшения читаемости замечена, нет причин продолжать игнорировать её —при сохранении производительности всегда следует максимально повышать читаемость.

Цените программное обеспечение, не требующее обслуживания

Следует ценить "необслуживаемость" программного обеспечения как нечто прекрасное. Чем дольше "время необслуживания" программного обеспечения, тем дольше его "необслуживаемость" выдерживает испытания, и тем больше красоты накапливается.
Самое главное, вы ясно осознаете, что программное обеспечение не не подлежит обслуживанию и не является сложным в обслуживании, а просто не требует обслуживания. Код программного обеспечения находится там, но вам не нужно с ним возиться, потому что он по-прежнему удовлетворяет потребности, нормально функционирует и функционирует очень хорошо.
Даже когда наступит время обслуживания, сопротивление обслуживанию программного обеспечения должно быть как оконная бумага, которую можно устранить легким проколом. Поскольку нет технического долга, вам не нужно платить проценты. Навязчивое желание обновить зависимости? Без проблем, потому что это обновление можно выполнить за десять минут, а у вас есть много десятиминутных отрезков, которые можно потратить впустую. Но лучше, если вы одержите победу над собой и своевременно прекратите свои действия —не только потому, что ещё не возникли новые потребности, но и потому, что вы не хотите разрушать красоту, накопленную программным обеспечением со временем.
Развитие такой способности к оценке довольно сложно, потому что она очень контринтуитивна. Люди подсознательно ошибочно принимают новизну вещи за фактор оценки её качества —иногда кажется, что быть в тренде важнее всего —небольшое размышление покажет, что это нерационально. Просто в повседневной жизни можно обнаружить, что прочные, качественно изготовленные устройства часто служат дольше, чем ожидалось —холодильник, работающий без перерыва, — самый типичный пример; мало кто меняет холодильник каждый год. Программное обеспечение, будучи цифровым продуктом, не имеет причин устаревать быстрее, чем аппаратное обеспечение; лёгкость обновления не должна быть причиной для обновления.