Это также не повредит вашей продуктивности или рассудку в долгосрочной перспективе.

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

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

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

1. Уменьшите технический долг

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

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

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

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

2. Будьте проще

Когда НАСА проектирует космический корабль, обычно существует два основных подхода к обеспечению надежности. Один из них - избыточность, т.е. резервные компьютеры, резервное питание и т. д. Когда резервирование невозможно, они используют простоту.

Двигатель ступени подъема на лунном модуле «Аполлон», ракетный двигатель, который доставил астронавтов с поверхности Луны обратно на их базовый корабль, был на удивление простым. В нем использовалось «гиперголичное» топливо, которое мгновенно сгорает при контакте друг с другом. Вот и все. Это были очень токсичные материалы для работы, но выгода от надежности того стоила.

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

  • Чем проще что-то, тем меньше может пойти не так.
  • Чем проще что-то, тем меньше вещей нужно тестировать и тем тщательнее это можно проверить.
  • Чем проще что-то, тем легче это понять и тем меньше вероятность ошибок.

Фактически, можно утверждать, что избыточность также имеет свои недостатки, поскольку она увеличивает сложность и увеличивает возможные SPOF (единая точка отказа).

3. Сделайте ваш код читабельным

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

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

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

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

Один из моих любимых докладов на эту тему - Семь неэффективных навыков программирования многих программистов »Кевлина Хенни.

4. Следуйте стратегии управления инцидентами.

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

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

Если ваши ошибки не имеют большого усиления сигнала, их ценность значительно уменьшается.

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

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

Несколько предложений:

  • Только предупреждения и ошибки, которые каким-либо образом ухудшают вашу службу (например, отключение зависимой службы, тайм-ауты, ошибка и т. Д.), Должны стать инцидентом.
  • Всем инцидентам следует уделять первоочередное внимание и устранять их, независимо от воздействия на клиента.

Улучшите свой сигнал и замкните петлю.

5. Следуйте передовым методам обработки ошибок.

Независимо от вашей стратегии, хорошая обработка инцидентов начинается на уровне приложения (программиста). Помните аксиому «Мусор на входе, мусор на выходе».

Почти все катастрофические сбои являются результатом неправильной обработки нефатальных ошибок, о которых явно сигнализирует программное обеспечение. (Https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf)

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

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

Будьте очень осторожны, не допускайте появления неожиданных ошибок. Ошибки, которые не обрабатываются непосредственным кодом, всегда должны распространяться вниз по стеку, и каждый уровень должен обрабатывать только те ошибки, с которыми он знает, что делать. В конце концов, если ошибка доходит до глобального «универсального» обработчика ошибок, ее следует обрабатывать как можно более аккуратно и превращать ее в инцидент (см. # 4) .

Не делайте этого:

catch(e){ 
  // this won't happen
}

Потому что да, будет.

Я очень рекомендую это выступление Льюиса Эллиса. Хотя он адаптирован для приложений Node, его советы хорошо переносятся на другие платформы, и им очень легко следовать.

6. Используйте безопасный язык программирования.

Типы сводят на нет большинство глупых ошибок, которые могут проникнуть в кодовую базу, и создают быстрый цикл обратной связи, чтобы исправить все мелкие ошибки при написании нового кода и рефакторинге. (Https://serokell.io/blog/why-typescript)

Javascript похож на игру со спичками. Я не могу сказать, сколько раз Typescript спасал мою задницу за последние несколько лет. Строго типизированные языки выдают ошибку, когда вы неожиданно смешиваете типы. Языки со статической типизацией, такие как Typescript, Java и C #, сделают это во время компиляции и выдадут ошибку еще до того, как вы развернете свой код. Это замечательно, потому что он может обнаруживать проблемы еще до того, как они возникнут, прямо в вашей среде IDE. Статическая типизация - одна из вещей, которых мне больше всего не хватало в программировании на Java. Сегодня с Typescript мы даже получаем поддержку таких необычных вещей, как дженерики!

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

7. Код с GUT (хорошие модульные тесты)

Я думаю, когда вы слышите фразу« это всего лишь тестовый код . Для меня это запах кода ». - Алан Пейдж

Модульные тесты дают «зеленый свет» программистам, что они ничего не сломали. Умные программисты часто запускают модульные тесты в процессе разработки, особенно во время рефакторинга. Без этого зеленого света может быть сложно провести эти рефакторинги, которые необходимы для предотвращения накопления технического долга (см. №1).

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

Вот несколько предложений:

  • Используйте функциональное программирование и многоуровневую архитектуру, чтобы свести к минимуму зависимости, которые нужно имитировать. Для чистых функций намного проще писать модульные тесты. Рассмотрим разделение уровней приложения, например, с гексагональной архитектурой. Таким образом, вы можете выдвинуть сложный для модульного тестирования код до краев вашего приложения.
  • Тестируйте поведение, а не реализацию. Это называется разработкой на основе поведения (BDD). Вам не нужно тестировать каждую внутреннюю функцию напрямую, чтобы достичь 100% покрытия.
  • Упростите свои утверждения. Вам не нужно учить новый язык и писать новеллу, чтобы утверждать, что две вещи равны. Фактически, некоторые предполагают, что одно утверждение - это все, что вам действительно нужно.
  • Разбейте тесты на разделы. Мне нравится шаблон «дано, когда, тогда». Ваш тестовый код должен рассказывать историю.
  • Будьте проще. Не пытайтесь сделать слишком много за один тест. Небольшое повторение - это нормально. Я лучше увижу повторение, чем тест, который проводится на сотнях строк.

8. Используйте инструменты тестирования сквозной интеграции.

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

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

9. Выполните ручное тестирование.

Открывать неожиданное важнее, чем подтверждать известное - Джордж Э. П. Бокс

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

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

Пока мы обсуждаем эту тему, вы всегда должны тестировать в производственной среде после запуска. «Но зачем тестировать в« производственной среде », если вы уже прошли тестирование в« стадии »?», - спросите вы. «Разве постановка - не то же самое, что производство?». Не совсем. Это разные среды, выполняющие разные конфигурации, указывающие на разные физические базы данных (даже если они имеют идентичные данные) и, вероятно, с множеством других тонких различий (например, внешнее сжатие, ведение журнала, режим отладки, безопасность и т. ). Прежде чем вы запустите vim и начнете писать производственные тесты, я должен предупредить вас о запуске автоматизированных тестов в производственной среде. Ошибки могут возникать даже в ваших тестах, поэтому убедитесь, что они не разрушительны и не имеют ограниченных возможностей.

Кстати, ручное тестирование не обязательно должно полностью ложиться на инженеров или ваш отдел контроля качества. Вы можете передать это на аутсорсинг! Во всем мире есть множество компаний и независимых профессионалов, которые могут провести ручное тестирование для вас по очень доступной цене, 24 часа в сутки, 7 дней в неделю. Фактически, я работал в разрозненном стартапе (Advizr), где мы передали это ручное тестирование на аутсорсинг вместо автоматических интеграционных тестов (у нас это хорошо сработало).

10. Учитесь на своих ошибках

«Неудача не фатальна, но неудача может быть смертельной». - Джон Вуден

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

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

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