[Перевод] 10 идей, о которых стоит знать всем программистам
Я пишу на Python и на Go, а в последние годы занимаюсь крупномасштабными приложениями. Речь идёт о том, что каждый день мне и моей команде приходится поддерживать системы, ответственные за обеспечение работы примерно двух миллионов пользователей. Это — непростая задача. Здесь я хочу поделиться несколькими ценными идеями, которые встретились мне за годы работы.
1. Не надо решать вопросы безопасности в последнюю очередь
Системы безопасности приложений крайне не рекомендуется реализовывать в последнюю очередь, или сводить всё к такой мысли: «Сделаем безопасность как-нибудь потом». Для того чтобы создать безопасное приложение, вопросы безопасности должны быть на повестке дня с самого первого дня работы над проектом. Если оставлять разработку системы безопасности на последний дедлайн, то это, на самом деле, лишь увеличит сроки разработки, так как придётся возвращаться к уже написанному коду и рефакторить его для приведения в соответствие с нуждами безопасности. Или всё может сложиться ещё хуже: из-за нехватки времени на «затыкание» всех «дыр» всё закончится тем, что в продакшн попадёт код, в котором будут уязвимости. А это, как несложно убедиться, проанализировав опыт разных компаний, вроде Yahoo, может закончиться очень плохо.
2. Все приложения разные, у них разные нужды, поэтому выбирать технологии для разработки нужно, исходя из особенностей проектов. Не стоит ориентироваться на популярность технологий или поддаваться давлению руководства
Пожалуй, об этом можно и не говорить, но я всё же скажу о том, что все приложения отличаются друг от друга. Нет некоего свода непреложных правил, применимого ко всем проектам (включая и это правило). Когда вы начинаете работу над новым проектом, выбор технологий и платформ, на которых будет основано приложение, должен определяться лишь особенностями самого приложения и его архитектуры. Если принимать решение о том, использовать ли gRPC или Kubernetes, до того, как будет задан вопрос: «А что нужно приложению?», — это означает — ставить препятствия на собственном пути ещё до начала работы над кодом. Именно так разные компании и попадают в абсурдные ситуации. Например, в ситуации вроде той, когда Canonical предлагает Kubernetes для IoT-устройств. Позволю себе процитировать тут фразу Джеффа Голдблюма из фильма «Парк Юрского периода»: «Да, но ваши учёные столь увлеклись, добиваясь этого, что даже не задумались: «Стоит ли?».
3. Микросервисы нужны далеко не всем
Микросервисы крайне привлекательны. Мне это понятно. Идея, в соответствии с которой можно независимо масштабировать разные части приложения, восхитительна. Она способна оправдать дополнительные объёмы работ, которые необходимы некоему проекту. Но давайте будем честны с собой: вам, возможно, микросервисы не нужны. Предположим, идею применения микросервисов пытаются поддержать следующими соображениями: «Хочу отделить код возможности X от кода возможности Y», «Хочется, чтобы плохой код из одних частей приложения не проникал бы в другие его части», «Нужно минимизировать последствия возможного взлома приложения». Но всё это нельзя назвать причинами, руководствуясь которыми необходимо переходить на микросервисы. Это, соответственно, симптомы применения плохих подходов к разработке (не лезь в чужие дела, меняй только то, что необходимо), низких стандартов код-ревью (некачественному коду нечего делать в ветке master) и недостатков в сфере безопасности.
Нужно ли вам независимо масштабировать различные части приложения? Есть ли у вас сейчас проблемы с быстродействием каких-то компонентов проекта, вроде механизмов для входа в систему? Если так — то, вероятно, стоит исследовать возможность выделения тех компонентов, которые нуждаются в масштабировании, в отдельные сервисы.
Может, в вашем проекте используется архитектура, основанная на виртуальных серверах, и вам хочется снизить расходы? В таком случае микросервисы вам не нужны. Вы, используя их, в лучшем случае, останетесь на том же уровне расходов. А в худшем случае вам придётся использовать дополнительные серверы. Предположим, у вас имеется монолит с пятью сервисами,. Вы разбили его на микросервисы. Теперь у вас имеется пять приложений. А это значит, что возможны два варианта развития событий. Первый — это если вам придётся запускать каждое из них на отдельном сервере, что увеличивает количество ваших серверов. Второй вариант — это если вы будете всё это запускать на существующей инфраструктуре, больше тратясь на поддержку и администрирование проекта.
4. Стандартизированное окружение разработки — это хорошо
Если вы руководите командой разработчиков, то одно из самых полезных решений, которое вы можете принять, это решение о применении единого окружения разработки. Причём, я тут не имею в виду необходимость строить нечто вроде контейнеризованного виртуального окружения. Вы вполне можете сделать и это, если хотите, но даже применение простого правила, вроде использования одной и той же версии языка, может творить чудеса. Например, серьёзно потрепать себе нервы можно, попытавшись отладить ошибку в Go 1.12, в то время как ваш коллега, обратившийся к вам за помощью, пишет код на Go 1.11. Координация обновлений может быть достаточно сложной задачей, но если подойти к организации этого процесса правильно, всё будет делаться легко и приятно.
5. Реализация подсистемы, отвечающей за настройки приложения, может оказаться более сложной, чем кажется на первый взгляд. Поэтому такие вещи нужно как следует планировать
В отличие от того, что можно прочитать на некоторых популярных сайтах, создать подсистему, отвечающую за настройки приложения, немного сложнее, чем «положить всё в переменные окружения». Я полагаю, что нужно предусматривать не менее четырёх способов конфигурирования приложений: значения по умолчанию, задаваемые в коде, локальный конфигурационный файл, флаги командной строки, переменные окружения, удалённый источник настроек (вроде HashiCorp Consul). Удалённый источник настроек я назвал бы необязательным. Но другие четыре механизма просто необходимы. Если в процессе разработки, только для того, чтобы запустить приложение, нужно записать в переменные окружения 27 значений, то это, если мягко выразиться, не так уж и удобно. Кроме того, может быть вам нужен более высокий уровень автоматизации и Makefile? Если предусмотреть настройку приложения через локальный файл, вроде application.yaml
, это может значительно облегчить описание конфигурации, применяемой, по умолчанию, при разработке. Кроме того, если в коде есть значения, используемые по умолчанию, это означает, что задавать эти значения нужно только тогда, когда нужно что-то, отличающееся от стандартных настроек. Флаги командной строки весьма полезны в ситуациях, когда приложения запускают с помощью разных средств автоматизации, вроде systemd
. Это облегчает анализ конфигурации при трассировке процессов. Конечно, переменные окружения крайне полезны при запуске приложений в контейнерах, но есть некоторые задачи, для решения которых они не подходят. Например, переменные окружения не стоит использовать для хранения паролей.
В переменные окружения категорически не рекомендуется записывать что-то секретное: пароли, токены аутентификации, сертификаты, в общем — всё, что вы не хотели бы сделать достоянием широкой общественности. Проблема тут в том, что значения переменных окружения может прочитать практически любой процесс, работающий на компьютере. Для хранения секретных данных нужно использовать какое-то специализированное решение. Лично я для этих целей выбрал HashiCorp Vault, но я не призываю пользоваться именно этим сервисом. Выбирайте то, что лучше всего подходит для вашего проекта.
6. Зависимости стоит использовать тогда, когда они нужны
Полагаю, все помнят о left-pad-апокалипсисе? Как 11-строчный пакет, который убрали из репозитория, поломал, в сущности, весь интернет? Этот инцидент приводит к одной простой мысли: «Не стоит, без реальной необходимости, подключать к проекту дополнительные зависимости». Зависимости стоит использовать только тогда, когда в них есть обоснованная необходимость. Какие зависимости по-настоящему нужны в проектах? Например — это может быть пакет, представляющий собой SDK для платформы, с которой вы работаете (SDK AWS, например). Это может быть некая хорошая абстракция над неудобными стандартными библиотеками (именно поэтому программисты пользуются Python-библиотекой Requests вместо urllib
). Это может быть какой-то широко используемый фреймворк — вроде HTTP-сервера Echo, написанного на Go, или WSGI-сервера Flask, написанного на Python. Некие библиотеки, реализующие удобные методы, которых нет в языке, вроде библиотеки Lodash для JavaScript-проектов, тоже относятся к «обоснованным» зависимостям.
Внешние зависимости должны упрощать разработку и не должны заставлять программиста самого писать какой-то вспомогательный код. Именно в упрощении разработки и заключается сильная сторона менеджеров пакетов. Но, как и в случае с left-pad, пользуясь менеджером пакетов легко попасть в ловушку, когда к любой задаче применяется такой подход: «Для этого есть библиотека, значит я ей воспользуюсь». С каждой импортированной в проект зависимостью растёт риск того, что проект может стать нестабильным, небезопасным, или попросту необслуживаемым.
Этот риск увеличивает и каждый пакет, используемый новой зависимостью. Такие пакеты называются транзитивными зависимостями. Если вы импортируете в проект всего одну зависимость, которая, в свою очередь, импортирует пять пакетов, то ваш проект тоже становится зависимым от этих пакетов, а значит — все проблемы этих пакетов становятся и его проблемами. И по моему мнению, и по мнению других программистов, прямые зависимости проектов не должны иметь транзитивных зависимостей. Но это не всегда возможно. Хотя, если речь идёт об очень больших пакетах, то они должны иметь как можно меньше зависимостей. А если пользователям таких пакетов нужен какой-то дополнительный функционал, у них должна быть возможность самостоятельно подключать такой функционал.
Я, принимая решения об импорте зависимостей в свои проекты, следую простому правилу: если я могу что-то написать за 10–15 минут, то я так и делаю. В противном случае я, если есть такая возможность, импортирую в проект некую библиотеку. Это правило может уберечь вас от импорта в приложение всякого хлама, но оно, в то же время, достаточно гибкое, и не принуждает программиста к тому, чтобы он каждый раз, когда ему нужно организовать работу API, писал бы собственный HTTP-сервер.
7. Не нужно писать абстракции до тех пор, пока в этом не возникнет реальная необходимость
Современному программисту очень легко попасть в ловушку абстрагирования всего и вся. Мысли о том, что нечто может понадобиться использовать повторно, способны завести разработчика в тёмные и страшные дебри ООП. И мне удалось понять причину этого. Принципы DRY (Don«t Repeat Yourself, не повторяйтесь) глубоко укоренились в нашем сознании. И не без причины. Но есть граница, пересекая которую, мы тратим на абстрагирование сущностей слишком много времени, а на написание кода — слишком мало. Поэтому вот вам совет: «Просто пишите код!». Если вы столкнулись с необходимостью реализовать функцию, похожую на что-то такое, что вы уже написали, тогда вы можете вернуться к старому коду и заняться созданием абстракции. Но, опять же, тут надо знать меру. Я применяю здесь такое правило: если речь идёт о маленькой функции в три строчки, то, вероятно, её можно просто написать, пусть и повторившись. Но если в функции содержится всего три строки кода, то, вероятно, я задамся вопросом о том, что, возможно, этот код и не стоит оформлять в виде функции.
8. Иногда свой проект стоит «сжигать» и «возрождать из пепла»
Это — самая страшная идея из тех, которыми я тут делюсь. Эта идея заставляет менеджеров нервничать. Она портит настроение владельцам продукта. Она злит программистов. Но «возрождение из пепла» — это совершенно необходимо. Время от времени начинать всё сначала — это хорошо. Это позволяет выбросить из кода всякий мусор. Это даёт возможность реализовать новые идеи, не переписывая половину существующей кодовой базы. Это принуждает всех, кто работает над проектом, переоценить его.
Представьте, что ваш проект — это огромный лес. Каждая строка кода — могучая сосна. По мере того, как лес стареет, он зарастает подлеском, его засыпает сосновыми иголками, шишками, сухими ветками… Это — ваши проблемы, ваш технический долг. Всё это имеет свойство накапливаться. Этот процесс, сам по себе, никогда не останавливается. Остановить его могут лишь некие радикальные изменения. В лесу изменения приносят пожары. Огонь очищает лес от всего ненужного. Сильные деревья остаются, а всякая мелочь сгорает. Хотя пожар может показаться катастрофой, которая уничтожит лес, тут есть один секрет: лес ждёт пожара. Ждёт терпеливо, годами. Лес надеется на то, что огонь очистит его и станет причиной изменений. Дело в том, что пока пламя бушует под кронами сосен, в их шишках созревают семена, из которых вырастут новые мощные деревья. В то время как огонь сжигает лесную подстилку, он освобождает место для нежных молодых деревьев, которые займут своё место рядом с выжившими ветеранами леса, опалёнными огнём. Именно такими должны быть и приложения: их надёжные, хорошо написанные части переживут «пожар», а место того, что будет «сожжено», займут новые идеи и новый код. И проект, как Феникс, возродится из пепла.
9. Ваша компания — это не Google
Нет, если вы работаете в Google, то я ничего против не имею. Но если это так, то скажите, а зачем вы вообще это читаете? Собственно говоря, моя идея заключается в том, что ваша компания — это, скорее всего, не Google, Microsoft, Amazon, Twitter или Facebook. Вам не нужно думать о том, как управлять 150000 контейнеров на 10000 «железных» серверов, расположенных в 17 дата-центрах, разбросанных по всему миру. Проблемы, которые перед вами встают, обычно не затрагивают каждого жителя Земли. Кстати, а зачем я об этом всём говорю? А затем, что размеры вашей организации должны определять масштабность используемых вами платформ. Например, вы пользуетесь несколькими сотнями контейнеров. Вам и правда не обойтись без Kubernetes? И обязательно ли вам запускать эти контейнеры на собственных серверах? Может, вы просто, поступая так, хотите казаться солиднее? Для решения подобных мелко- и среднемасштабных задач отлично подходит что-то наподобие HashiCorp Nomad. Подобные платформы легко настраивать. Их не нужно поддерживать, а если они и нуждаются в поддержке, то особых усилий на это не требуется. Они хорошо документированы. На них очень легко переносить приложения, так как они рассчитаны на работу с контейнерами, с системными процессами и с JVM-приложениями. А если вам никак не обойтись без Kubernetes, то почему бы не пользоваться этой платформой, прибегнув к чему-то вроде Rancher? Это позволит снять с себя задачи по настройке всяческих мелких деталей. Нагрузка, связанная с использованием сложных систем, вроде Kubernetes, слишком велика для некоей отдельной команды разработчиков, скажем, для команды стартапа. Эти системы, кстати, разработаны крупными компаниями, вроде Google, и предназначены для таких же компаний. Я бы даже сказал так: стартапам стоит любой ценой избегать Kubernetes — за исключением тех случаев, когда их продукт неразрывно связан с данной платформой. Пожалуй, тут есть одно исключение: облачные предложения от Google, Amazon и Microsoft. Ничего плохого я о них сказать не могу, так как эти компании снимают с пользователей нагрузку по управлению сложными системами. Но я очень вас прошу: никогда не пользуйтесь Kubernetes на IoT-устройствах.
10. Не стоит формировать свои взгляды на разработку ПО, полностью полагаясь на идеи случайных людей из интернета
Вам нужно составить собственное мнение о том, какие из освещённых здесь идей применимы к вашему приложению и к вашему стилю разработки. И учитывайте, что то, о чём я тут рассказывал, далеко не истина в последней инстанции. Ведь я — всего лишь незнакомец, текст которого вы нашли в интернете.
Что вы добавили бы к тем идеям, о которых мы только что говорили?