Skip to content

Instantly share code, notes, and snippets.

@kinda-neat
Last active October 13, 2023 13:16
Show Gist options
  • Save kinda-neat/9f2d2fcec5757a5e6fa2bc5f0c337471 to your computer and use it in GitHub Desktop.
Save kinda-neat/9f2d2fcec5757a5e6fa2bc5f0c337471 to your computer and use it in GitHub Desktop.
Ubiquitous ideas that underpin code I write | Идеи пронизывающие мою работу

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

Зачем это всё? Центральный аспект - чтобы кодовая база была и оставалась поддерживаемой.

cost_maintain

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

image image

Что такое поддержка? Поддержка это всё то, что происходит после реализации фичи.

Что входит в поддержку кода?

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

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

Стоит ли оно того?

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

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

На градус/уровень поддерживаемости влияет то, как вы пишете код. Об этом и эта заметка. А теперь собственно идеи:

Когда бОльшая часть кодовой базы написана чистыми функциями и соблюдается the functional interaction law, открывается куча преимуществ, некоторые них:

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

Как понять что ваша архитектура достаточно functional?

Плюсы чистых функций легко гуглятся (раз, два, три).

2. Модульность. Low coupling / high cohesion. Rates of change.

In software design, modularity refers to a logical partitioning of the "software design" that allows complex software to be manageable for the purpose of implementation and maintenance. The logic of partitioning may be based on related functions, implementation considerations, data links, or other criteria.

Для меня модульность говорит о наличии явно заданных границ между разными кусками логики.

  • Это означает что код решающий задачи X живёт в модуле X, а код для решения задач Y живёт в модуле Y. Детали реализации X и Y изолированы друг от друга.
  • В результате приложение это набор модулей/юнитов собранных/скомпозированных вместе для решения задач бизнеса.

Важнейшими свойствами такой организации являются свойства Cohesion and Coupling. Эти два свойства пронизывают всю кодовую базу, от организации папок и файлов, до содержимого внутри файлов; на разных уровнях абстракции. Для удобства ниже использую слово "код".

Cohesion говорит о том насколько код в модуле сфокусирован и един в своей функциональной цели (в том что он делает).

Coupling говорит о том как много модуль знает о других модулях и как много модулей знают о нём. Насколько один модуль связан с / зависим от других, объем этой зависимости.

Почему стоит следить за свойствами cohesion/coupling? Потому что это напрямую влияет на поддержку кода, на:

  • то сколько модулей затронет потенциальное изменение X (произойдет оно локально или вызовет снежный ком изменений по всей системе?).
  • способность рассуждать о коде и возможности максимально сузить область кода которую нужно знать здесь и сейчас для того чтобы решить проблему.

Отсюда имеет смысл стремиться к тому, чтобы модули/кодовая база обладала свойствами:

  • high cohesion - чтобы код в модуле был как можно больше сфокусирован и един в своей функциональной цели
  • low coupling - минимизировать знание одного модуля о другом, делается это множеством способов (контракты, паттерны типа фасада; организация кодовой базы/логики таким образом чтобы разные модули знали о друг друге в строго отведенном, созданном для этого месте; введение слоя коммуникации между модулями типа медиатора, и т.п.)

Чем руководствоваться/от чего отталкиваться при выстраивании границ между модулями/кусками логики? Что драйвит решения по тому, где проводить границы? Как это сделать корректно? Два инпута:

  • знание доменной области, понимание бизнес процессов которые автоматизируется через написание кода. Чем больше вы работаете над кодовой базой, над разными ее частями, тем больше знаний и контекста вы накапливаете (что из себя представляет какая-то фича, как она связана с другими, где, в какой момент времени, как ей пользуются юзеры) тем более корректно вы можете это учитывать в coupling/cohesion балансе.
  • опыт. Здесь чем больше у вас опыта организации кодовой базы, пробования разного баланса cohesion/coupling тем лучше вы будете интуитивно чувствовать где стоит добавить, а где убавить. Правило такое, как и с многими сферами, скиллами: the more experience you earn, the more you can find the right balance for each given situation.

Больше по теме:

3. Simplicity. Complexity. Being Boring. KISS.

Чтобы понять что такое simple/simplicity и почему это важно, советую начать с key takeaways здесь и пробежаться по show notes. И затем посмотреть доклады:

Ну и в целом я фанат Rich Hickey и советую посмотреть/пересмотреть все его доклады. Можете начать с этого.

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

Например, если уже существуют решения для:

То после должного анализа (размера либы, насколько она tree-shakable, насколько активная связь мейнтейнеров с коммьюнити и т.п.) стоит брать их, а не переизобретать велосипед/оверинжинирить.

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

Велосипеды писать круто и полезно в 99% времени только в одном случае - в целях обучения, более глубокого понимания как работает та или иная штука. Но им не место в продакшене, где вы не один, где есть, были и будут другие люди. Хочу ли я поддерживать ваши велосипеды/тратить на это время? Нет. На что хочу тратить время? На продвижение продукта вперед, the most important goal is the product success.

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

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

В начале бизнес сам может до конца не понимать чего хочет (раз, два), что должна делать та или иная фича, будут ли они решать задачи юзеров, насколько удобно это будет, но итерируя, пробуя, бизнес и вы будете обретать лучшее, более глубокое понимание - и это нормально, validation is a mirage. И часто нужно быть готовым к тому что это займет время прежде чем продукт примет какую-то стабильную форму. Good Software Takes Ten Years. Get Used To it (здесь думаю Joel хотел сказать что формирование хорошего софта/продукта займет приличное время, будьте к этому готовы/смиритесь с этим).

Вам следует учитывать природу развития продукта, именно из-за нее, из-за всего выше сказанного вам следует десять раз подумать, семь раз отмерить стоит ли вам сейчас что-то генерализовать, абстрагировать или стоит подождать, позволить юз-кейсам накопиться/абстракциям проявиться со временем. Очень часто генерализации/оптимизации делаются наоборот - преждевременно, отсюда происходят YAGNI, Premature Generalization/Optimization. Как и везде здесь нужен баланс который нащупывается через практику/понимание что делает/куда движется бизнес. Must-read ресурсы - раз, два, три (все они так же есть в заголовке пункта).

Don’t solve a problem that doesn’t exist.

Don’t solve a problem that doesn’t exist. Don’t do speculative programming. Only make the code extensible if it is a validated assumption that it’ll be extended. Chances are by the time it gets extended, the problem definition looks different from when you wrote the code. Don’t overengineer: focus on solving the problem at hand and an effective solution implemented in an efficient manner.

5. Costs / benefits / value balance.

Здесь пока наброски мыслей.

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

Мне нравится деление костов на setup cost и operational cost (понятия взял отсюда). Стоит всегда думать о operational cost, чего нам будет стоить поддерживать решение, насколько легко его расширять, изолировать при необходимости.

К чему стоит стремиться, чтобы о вас можно было сказать: When taking action, weighs cost and value in order to take the most economic action.

Вопросы которыми стоит задаваться:

  • каких усилий будет стоить реализовать эту фичу и как мы можем снизить эти усилия максимально без потери в качестве?
  • где и как реализуемое решение пересекается с текущей кодовой базой? Как можно минимизировать эти связи?
  • как часто эта часть кода будет меняться? планируются изменения в следующих итерациях? никогда?
  • есть ли альтернативные, более простые способы решить задачу бизнеса, о которых бизнес не знает, а вы могли бы предложить? (стоит быть готовым к back and forth с бизнесом)

N. To be continued

В процессе написания я понял что есть еще ряд вещей, о которых я хотел бы сказать - implicit vs explicit, accidental and essential complexity, naming, некоторые идеи из SOLID, тесты, и еще то что я на данный момент не вспомнил. Всё это буду дописывать со временем.


Нашли что-то полезным? Еще заметки по-другим темам здесь.

Больше источников:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment