Это классика, это знать надо: DRY, KISS, SOLID, YAGNI и другие полезные сокращения
Эти сокращения каждый программист должен знать так же хорошо, как количество и имена истребителей адмирала Ямамото.
Иллюстрация: Artur Kovalev / Wikimedia Commons / Colowgee для Skillbox Media
Фернандо Дольо
(Fernando Doglio)
Об авторе
Один из топовых авторов на Medium. Пишет о технологиях, образе жизни, личном опыте и многом другом.
Переводчик
Руслан Гаджиев
За 17 лет работы в индустрии я каких только сокращений не повидал — и серьёзных, и забавных. Да и, если честно, всем разработчикам нравятся аббревиатуры. Одни просто облегчают речь — например, MVP или PoC (ясно же, что, создавая их, никто особо не усердствовал). Другие ещё и звучат забавно: например, SOLID, DRY и KISS.
Вообще, аббревиатур в сленге разработчиков так много, что я подготовил небольшой обзор наиболее (и парочки — менее) распространённых из них — вдруг вы подзабыли, что они означают.
DRY
Начнём с элементарного сокращения, которое наверняка попадалось вам много раз. DRY (англ. dry — сухой, сушить) — основополагающий принцип разработки. Он расшифровывается как Don’t repeat yourself — «не повторяйтесь».
Когда пишете код, всегда думайте о том, как можно переиспользовать тот или иной фрагмент, что можно выделить в универсальную функцию или класс, сделать модулем. При этом речь не идёт о создании библиотек под каждую неодноразовую функцию — я имею в виду очень похожую логику, которая встречается в нескольких местах, которую, возможно, есть смысл вынести в функцию. А если в нескольких местах определена одна и та же функция, то её можно вынести в общий модуль. Ну и, наконец, если вы часто используете один и тот же модуль, вероятно, из него можно сделать библиотеку.
Другими словами, don’t repeat yourself, понимаете?
Этот принцип полезен всегда, вне зависимости от платформы или языка. Допустим, вам надо автоматизировать некое поведение. Чтобы не прописывать несколько раз одну и ту же логику и не раздувать код без нужды, попробуйте обобщить её и вынести в отдельный элемент.
KISS
Эта аббревиатура (англ. kiss — поцелуй, целовать) мне самому всегда нравилась — как по форме, так и по значению: Keep it simple, stupid («Сделай это проще, тупица») или, если кому-то не нравится называться тупицей, есть вариант Keep it stupid simple («Пусть всё будет простым до безобразия»), который ещё лучше передаёт смысл аббревиатуры.
Решая какую-нибудь проблему, можно так увлечься, что сам не заметишь, как уже занялся оверинжинирингом или, как я люблю говорить, вовсю палишь из пушки по воробьям. Задача в итоге, конечно, будет решена — но её можно было бы выполнить куда проще и изящнее.
Не спорю, бывают, конечно, и обратные ситуации. Слишком простой код или простая архитектура могут оказаться неэффективными, и тогда в логику придётся добавлять чуть больше сложности. Но всё равно надо каждый раз себя спрашивать, соблюдается ли принцип KISS.
Проверяйте, достаточно ли понятны ваши логические цепочки. Хватит ли знаний вашим коллегам, чтобы в них разобраться? Простой код и простой дизайн уменьшают риск ошибок, да и читать такой код проще. В общем, не забывайте про KISS!
SOLID
Это ещё одно общее правило программирования. Расшифровывается оно так:
- Single responsibility principle (принцип единственной ответственности).
- Open-closed principle (принцип открытости/закрытости).
- Liskov substitution principle (принцип подстановки Лисков).
- Interface segregation principle (принцип разделения интерфейса).
- Dependency inversion principle (принцип инверсии зависимостей).
Получается, что это пять разных принципов в одном (англ. solid — твёрдый, плотный, прочный). Рассмотрим каждый по отдельности.
1. Single responsibility principle, принцип единственной ответственности
Он говорит о том, что каждая ваша функция должна выполнять только одну задачу.
Если вы знакомы с *NIX-системами, такими как дистрибутивы Linux, macOS и прочие, то наверняка имели дело с их терминалом и командами, например ls или cd. Они строго следуют принципу SRP — выполняют только один тип задачи (скажем, меняют директорию или выводят список её содержимого). Вы не найдёте такой утилиты, которая позволяла бы выполнить сразу несколько задач (в нашем примере — изменить директорию и вывести список её содержимого). Это даже называется UNIX-way.
Чтобы иметь только одну ответственность, функции должны быть простыми. Если же нужно более сложное поведение, придётся объединять вводы и выводы нескольких функций и делать композицию.
И хоть я и говорю сейчас о функциях, принцип SRP применим практически ко всему. Взять, например, архитектуру какой-нибудь платформы. Её гораздо проще обслуживать и развивать, если вместо мегамодуля, ответственного вообще за всё, у вас будет несколько микросервисов, каждый из которых отвечает за свою маленькую задачу. Но — ещё раз — то же касается и функций: более простые и понятные функции легче поддерживать, читать, понимать и даже просто писать.
Например, если вам нужна функция getUserAndRelatedBooks, в которой прописана логика двух задач, подумайте о том, чтобы разбить её на две функции: getUser и getUsersBooks, где вторая будет получать на вход результат выполнения первой. Так вы сможете изящно реализовать getUserAndRelatedBooks, просто указав getUsersBooks (getUser), то есть объединив их в композицию.
2. Open-closed principle, принцип открытости/закрытости
Он говорит о том, что ваши модули или библиотеки (в зависимости от того, как экспортируется код) должны быть открыты для расширения (например, расширения поведения), но закрыты для модификации (ибо ни у кого нет желания возиться с чужим модулем).
Если вы заметили, что для добавления в ваш код нового поведения или расширения уже существующего его приходится править, — поздравляю, вы успешно пренебрегли принципом открытости-закрытости! :)
Вот наглядный пример — приведённый код нарушает этот принцип. Потому что, если понадобится добавить ещё один город, придётся открывать сам файл и вносить изменения в массив knownCities.
А вот как можно решить эту проблему и соблюсти принцип открытости/закрытости.
Как видим, с помощью метода addValidCity можно расширить поведение кода под свои задачи, без необходимости править файл.
3. Liskov substitution principle, принцип подстановки Лисков
Этот принцип, также известный как LSP, максимально приближает нас собственно к теории программирования. Сильно углубляться в неё я не буду — просто опишу суть принципа подстановки.
Функции, которые используют указатели или ссылки на базовые классы, должны иметь возможность использовать подтипы базового типа, ничего не зная об их существовании.
Сразу же бросается в глаза, что мы здесь явно имеем дело с принципом ООП, который помогает правильно применять наследование, когда это необходимо, и использовать альтернативные варианты, когда наследование не нужно.
Рассмотрим это на примере. В геометрии квадрат — это разновидность прямоугольника. По сути, это прямоугольник с одинаковыми шириной и длиной. Если попытаться смоделировать это с помощью кода, можно получить примерно следующее:
В абстракции тут нет смысла: методы setWidth и setHeight ничего не меняют, квадрата из прямоугольника не получится — а это не то, что нам нужно. Итак, приведённый выше код нарушает принцип подстановки Лисков.
Другими словами, LSP гарантирует правильное использование наследования в вашем коде. Так что, как бы странно он для вас ни звучал, помнить о нём при создании классов всё-таки стоит.
4. Interface segregation principle, принцип разделения интерфейса
Он говорит о том, что нельзя заставлять программистов, работающих с вашим кодом, использовать ненужные методы. Ведь интерфейс — это всего лишь взаимодействие классов (набор методов, которые необходимо реализовать). Поэтому при создании интерфейсов (например, в TypeScript) убедитесь, что методы, которые требуется реализовать, действительно нужны вашим пользователям, — и не городите в одном интерфейсе кучу функциональности. Разделяйте и властвуйте.
Рассмотрим этот код. Возможно, при работе с MyModule кому-то и захочется реализовать методы close и open, но если не требуется обеспечить совместимость с IE8, то последний из трёх перечисленных в коде методов точно не понадобится.
Иными словами, принцип разделения интерфейсов имеет дело с дизайном. Соблюдая его, отделяйте методы друг от друга — пусть пользователи сами решают, какие из них применять и для каких задач.
5. Dependency inversion principle, принцип инверсии зависимостей
Концепцию SOLID замыкает принцип инверсии, или внедрения, зависимостей.
По классике он звучит так:
- Модули верхних уровней не должны импортировать сущности из модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Это очень полезный инструмент для многих сценариев, например для юнит-тестирования. Если вы тестируете код, который зависит от сторонней библиотеки, вы можете внедрить мок этой библиотеки для контроля поведения кода. Давайте покажу на примере:
Здесь мы видим, что подключение к базе данных объявлено и выполняется внутри одного модуля, а функцию saveUser вы экспортируете. Если теперь попытаться протестировать код, эта функция автоматически постарается выполнить свою первоначальную задачу — подключиться к базе данных и сохранить данные пользователя в ней.
Однако если разрешить внедрение зависимостей и принять соединение с базой данных (которое и является зависимостью) в качестве второго параметра, то можно внедрять мок дальше:
Теперь можно даже переключать зависимости — например, использовать другой модуль для подключения к базе данных — на коде это не отразится, он всё так же будет отрабатывать необходимую логику.
Внедрение зависимостей — замечательный инструмент. Он позволяет создавать расширяемый дизайн и очень нужен тем, кому при разработке важна расширяемость. Учитывайте это в своей работе.
YAGNI
Принцип, иначе известный как You ain’t gonna need it («Вам это не понадобится»), пришёл из экстремального программирования. Согласно ему создавать какую-то функциональность следует только тогда, когда она действительно нужна.
Дело в том, что в рамках Agile-методологий нужно фокусироваться только на текущей итерации проекта. Работать на опережение, добавляя в проект больше функциональности, чем требуется в данный момент, — не очень хорошая идея, учитывая, как быстро могут меняться планы.
Что я имею в виду, говоря про смену планов? Итерации в Agile довольно короткие — то есть вы будете получать какой-то фидбэк уже на ранних этапах разработки, и потенциально он может изменить направление всей работы над проектом. Так зачем вам тратить время на функцию, которая в итоге окажется совершенно ненужной?
Однако замечу напоследок, что если вы используете методы каскадной разработки, где вся работа планируется заранее и все стараются чётко придерживаться плана, принцип YAGNI неприменим.
BDUF
И раз уж мы заговорили о каскадной модели, то нельзя не вспомнить о принципе Big design up front («Масштабное проектирование прежде всего»), который идеально для неё подходит. Он утверждает, что большую часть времени, отведённого на проектирование приложения, вы тратите далеко не на написание кода.
Сама каскадная модель подразумевает, что нужно продумать заранее абсолютно всё — рассчитывая, что благодаря этому не придётся потом тратить время на поиск и устранение недостатков.
Конечно, как и у многих других описанных здесь принципов, у BDUF есть свои противники. Особенно склонны нападать на него сторонники гибких методологий — они полагают, что в мире победившего Agile он просто бесполезен.
SoC
Separation оf concerns (принцип разделения ответственности) — один из моих любимых. Я использую его при проектировании платформ или при создании внутренней архитектуры проекта.
Не путайте его с уже упоминавшимся Single responsibility principle (принципом единственной ответственности). SoC помогает объединять функции или модули в отдельные сервисы. Суть в том, что при проектировании многофункциональной системы — а так обычно и бывает — можно группировать функции в модули в зависимости от задач, которые каждая из них выполняет.
Пример: блог-площадка, где пользователи могут публиковать посты. Одна система на такой платформе вполне может отвечать за всё (управление пользователями, посты в блоге, аналитика и так далее) — и даже справится со своими функциями. Но если следовать принципу SoC, можно прийти к более интересному решению:
Это, конечно, очень грубый пример, но суть такой архитектуры в том, что вы делите ответственность по модулям. В результате:
- Вы получаете масштабирование каждого блока функций. Теперь при необходимости вы сможете легко масштабировать тот же модуль управления пользователями (User management) — например, потому, что он тянет на себе слишком много трафика, а остальная часть платформы остаётся незагруженной.
- Вносить изменения становится легче: связь элементов кода ослаблена, и вы, например, сможете чуть ли не полностью переписать модуль управления постами, не затрагивая другие разделы.
- Платформа становится более стабильной. Если один из модулей сломается, то система потенциально сможет сохранять работоспособность. С меньшей функциональностью, конечно, но тем не менее это возможно.
SoC также может применяться при создании API, архитектур библиотек и тому подобного. Его суть в том, чтобы группировать функции по своему усмотрению и с пользой для тех, кто будет их применять.
MVP
Если отойти от программирования, можно вспомнить и другие аббревиатуры, которые часто используются в нашей отрасли. MVP означает Minimum viable product (минимально жизнеспособный продукт) и предполагает создание минимально необходимой функциональности, которая помогает понять, как продукт работает в реальных условиях и нравится ли он пользователям.
Этим приёмом пользуются сплошь и рядом — чтобы выяснить, стоит ли вообще тратить время на продукт и доводить его до ума. Благодаря MVP целевая аудитория уже может опробовать продукт и дать обратную связь.
PoC
В отличие от MVP, который требует серьёзного планирования и больших затрат на разработку, Proof of concept (доказательство концепции) обычно представляет собой его урезанную версию. Он используется на этапе до MVP и предназначен только для того, чтобы подтвердить или опровергнуть необходимость дополнительной функциональности.
По сути, PoC — своего рода расходный материал, временный код. Его пишут не для реализации, а для демонстрации проекта. Включать PoC в реальный продукт, в принципе, можно, но имейте в виду, что для доказательства одной концепции, бывает, приходится создавать несколько PoC. Корпеть над ними всеми, чтобы получить в итоге один актуальный продукт, — так себе идея. Так что его намеренно говнокодят, чтобы потом было не жалко выбрасывать.
Лично мне очень нравится концепция PoC — но именно как расходного материала. Это как Железный Человек и его броня Mark I: она показала, на что он способен, даже если имеет под рукой только дуговой реактор; а Mark II была уже на несколько порядков лучше.
Заключение
Все аббревиатуры, упомянутые в этой статье, вы наверняка встречали в инструкциях и мануалах или слышали от коллег. Теперь вы знаете, что они означают и в каком контексте их употреблять.