Алексей / Backend Interviewer
1.75K subscribers
66 photos
8 links
Провёл и прошёл 200+ собеседований

Пишу про Java собесы: от устройства JVM до переговоров по зп

Начни с закрепа https://t.me/backend_interviewer/41
Download Telegram
Карточки про синхронизаторы Java

Синхронизаторы позволяют управлять потоками более гибко, мощно и безопасно, чем низкоуровневые и примитивные synchronized, wait, notify и join.

Примеры использования:

– Ограничить количество одновременных действий
– Дождаться завершения нескольких потоков
– Запустить все потоки одновременно
– Обменяться данными между потоками

Виды:

– Semaphore
– CountDownLatch
– CyclicBarrier
– Phaser
– Exchanger
🔥1810👍8
Что происходит, когда мы пишем new в Java: от байткода до оптимизаций

Кажется, мы просто создаём объект:

var user = new User();


Но под этой строчкой скрыт целый каскад операций внутри JVM: от байткода и выделения памяти до вызова конструктора, настройки объекта и запуска JIT-оптимизаций.

Разберём весь процесс пошагово.

Шаг 1. Компиляция в байткод

Чтобы JVM могла исполнять код, он сначала компилируется в байткод.

Строка var user = new User() превращается во что-то вроде:

0: new #2
3: dup
4: invokespecial #1
7: astore_1


new – резервирует место в heap, но не вызывает конструктор.

dup – после new ссылка на объект оказывается на вершине стека. dup нужен, чтобы использовать её дважды: и для вызова конструктора, и для присваивания переменной.

invokespecial – вызывает <init>-метод (конструктор).

astore_1 – сохраняет ссылку в переменную user.

Шаг 2. Выделение памяти в heap

Когда JVM встречает команду new, она просит менеджер памяти выделить место под объект. Обычно объект попадает в Eden (часть Young Generation).

Есть два пути.

Быстро: через TLAB (Thread Local Allocation Buffer)

Если у потока есть TLAB (специальный буфер в Eden для быстрой аллокации), объект создаётся без синхронизации.

Просто смещается указатель: nextFree += objectSize.

Почти так же, как malloc в C, только безопаснее.

Медленно: с синхронизацией

Если буфер заполнен, происходит более дорогая аллокация: с блокировками и участием GC (если мало места). Возможно, произойдёт Minor GC.

Шаг 3. Инициализация объекта: header и zeroing

Перед тем как конструктор выполнится, JVM инициализирует внутренние структуры объекта.

Mark Word – служебная информация: hashCode, флаг GC, состояние блокировки.

Class Pointer – ссылка на метаданные класса (Class<?>).

Zeroing – все поля объекта инициализируются значениями по умолчанию (0, null, false).

В этот момент объект уже существует, но он ещё "пустой". Конструктор не выполнялся.

Шаг 4. Вызов конструктора (<init>)

Теперь JVM вызывает конструктор через invokespecial.

Конструкторы родительских классов вызываются первыми.

Порядок важен: сначала родитель, потом поля, потом логика конструктора.

Шаг 5. Присваивание переменной

После вызова конструктора ссылка на объект остаётся на вершине стека.

Её можно:

– сохранить в переменную (var user = ...)
– передать в метод (someMethod(new User()))
– или не использовать вовсе (будет кандидатом на GC)

Шаг 6. Оптимизации от JIT

На этом этапе JVM может вмешаться и оптимизировать создание объекта, особенно если мы много раз создаём один и тот же тип объектов в горячем методе.

Escape Analysis

JVM анализирует: уходит ли объект за пределы метода?

Если нет – можно не выделять память в heap и положить объект в стек (stack allocation).

Если JVM докажет, что объект локален – он даже не будет существовать в памяти как полноценный объект.

Scalar Replacement

Если объект используется как набор примитивов, JVM может вообще разложить его на переменные и не создавать объект вовсе.

Inlining конструктора

JVM может встроить код конструктора прямо в место вызова. Это снижает накладные расходы вызова и помогает следующим оптимизациям (например, loop unrolling, constant folding).

Почему всё это важно

Мы можем думать, что просто вызываем new, но на деле:

– JVM аллоцирует память
– происходит инициализация внутренних структур
– вызывается цепочка конструкторов
– активируются JIT-оптимизации
– включается GC и мониторинг

Мы не должны бояться заглядывать под капот – мы обязаны это делать. Потому что именно там находятся ответы на вопросы "почему прод тормозит" или "откуда утечка памяти".
🔥189👍9
Как говорить про проект на собеседовании: 3 удачные схемы

Что делает большинство?

Ну… это был корпоративный портал. Мы там использовали Spring. И делали интеграцию с 1С. Я там пилил фичи.


Скучно, без фокуса, без смысла.

Даже если человек реально писал сложные вещи – этого не видно.

Я предлагаю использовать одну из трёх схем, которые превращают рассказ в сильную демонстрацию своего уровня.

Схема 1: Боль – Роль – Архитектура

Боль: С чем пришел бизнес? В чем была проблема, вызов, ограничения?
Роль: Что делал лично ты? Какие решения принимал?
Архитектура: Как ты это реализовал технически?

Бизнес хотел ускорить расчёт скидок в корзине: было слишком медленно, 3+ секунды. Я переписал калькулятор на чистом Java, оптимизировал SQL-запросы и закешировал правила. Это был отдельный сервис на Spring Boot с Redis и асинхронной обработкой. Время ответа – 300 мс.


Звучит как реальная польза + инженерная работа + понимание архитектуры.

Схема 2: Цель – Механика – Технологии

Цель: Зачем делался проект? Какая была бизнес-задача?
Механика: Как это работало? Что происходило под капотом?
Технологии. Какие библиотеки, паттерны, фреймворки использовались?

Делали сервис нотификаций для внутренних систем. При получении события генерировались уведомления по шаблону и отправлялись через Kafka -> Email/Push. Использовали Spring Cloud Stream, FreeMarker, RetryTemplate, Prometheus, Zipkin для трейсинга.


Отлично показывает масштаб и понимание потоков данных.

Схема 3: Что было – Что стало – Как дошли

Что было: Начальное состояние, проблемы, ограничения.
Что стало: Чего добились? Метрики, улучшения, профит.
Как дошли: Какие действия, подходы, решения применялись?

Была монолитная система, которую тяжело было масштабировать. После перехода на микросервисы – катим фичи независимо, проще локализовать баги, уменьшили время отклика на 30%. Внедрили Spring Cloud, API Gateway, Eureka, и распилили 3 ключевых модуля: аутентификацию, биллинг и отчёты.


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

Главное не просто что ты делал, а зачем, как и с каким эффектом

Каждый проект – это шанс показать свой уровень, если правильно его подать.
👍10🔥85
Как я учился говорить "нет" менеджерам и не чувствовать себя мудаком

Менеджер пишет:

Там мелкая доработка, надо просто поле добавить. Сможешь сегодня?


И я говорю "да".

Через полчаса:

Срочно надо пофиксить расчёт формулы, иначе отчёты не сходятся


Я снова говорю "да", потому что умею в многозадачность.

Но вечером я понимаю, что:

– не сделал свою задачу из спринта
– злюсь на всех
– и сам виноват, потому что никому не сказал "нет"

Почему говорить "нет" сложно

– Хочешь быть надёжным
– Боишься показаться не командным
– Боишься, что скажут: «он токсичный»
– Или просто не хочешь конфликта

Но в итоге ты теряешь фокус, скорость, мотивацию.

И постепенно превращаешься в человека, на которого всё скидывают на всякий случай.

Что я начал делать

1. Спрашивать про приоритеты

Я не говорил "нет". Я говорил: «У меня сейчас задача Х. Это важнее?»

Обычно это ставит менеджера в режим выбора, а не давления.

Он либо говорит «ладно, потом», либо помогает снять что-то из текущего.

2. Выводить цену вопроса

«Ок, но это займёт полдня. Мы сдвигаем релиз фичи?»
«Ок, но тогда не будет покрытия тестами – норм?»

Менеджер начинает думать. А не просто кидать хотелки.

3. Заранее обозначать зону ответственности

«Я не буду поддерживать это в проде – давайте назначим владельца»
«Я могу помочь, но не беру это на себя полностью»

Ты просто честно обозначаешь, что можешь, а что нет.

Что поменялось

– Я стал спокойнее
– Мой день стал предсказуемым
– Я наконец начал делать то, что запланировал
– И, что неожиданно: менеджеры стали уважать это, потому что у них тоже есть задачи, сроки и нервы

Вывод

Говорить "да" – легко.
Говорить "нет" – трудно.

Но трудное "нет" сегодня – спасает тебя от сгоревшего "я увольняюсь" завтра.

Это не про конфликты. И не про борьбу с бизнесом.

Это про выгорание, здравый смысл и то, что никто, кроме тебя, не поставит границы.
👍2912🔥10
4 ошибки, которые я делал, когда думал, что "всё понимаю"

Каждый из нас проходит через это.

Вроде бы уже не джун, копаешься в исходниках, дебажишь прод, можешь на ходу объяснить, как работает gc и зачем нужен circuit breaker.

И в какой-то момент в голове появляется опасная мысль:

Ну всё, я вроде понял, как оно работает


Вот с этого момента и начинается веселье и детские ошибки.

Ошибка 1: Прометей соберёт всё, что надо

Я считал, что если в сервисе есть метрики и алерты – значит, всё под контролем.

Но я не задал себе простой вопрос: а какие метрики вообще имеют смысл?

У нас был процессинг заказов. Всё шло по плану, но однажды мы обнаружили: все заказы старше 2 часов внезапно зависают в статусе PROCESSING.

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

Но мы не мониторили время жизни заказов в разных статусах.

В итоге пришлось в панике руками чистить залипшие заказы.

Мониторинг ≠ контроль. Алерты должны быть бизнес-осмысленными, а не только технарскими.

Ошибка 2: CompletableFuture.runAsync без контроля

В какой-то момент мы начали распараллеливать вызовы к сторонним сервисам.

Казалось бы удобно:

CompletableFuture.runAsync(() -> callExternalService());


Всё летает, нагрузка падает.

Пока не словили дикие замедления на живом трафике.

Почему?

runAsync использует ForkJoinPool.commonPool()

Этот пул общий на всё приложение. Если callExternalService() тормозит и блокирует поток – пропускная способность падает. И буум.

Если используешь параллельность – контролируй её сам. Используй выделенные пулы. Ограничивай очередь. Не доверяй удобным API.

Ошибка 3: volatile – замена synchronized

Типа, если переменная volatile, то это потокобезопасно.

Реальность: volatile даёт гарантии видимости, но не атомарности.

В условиях конкуренции можно получить неконсистентное состояние.

Один раз я словил баг, где поток продолжал работать после установки флага stop. Причём в логах всё выглядело ок.

Ошибка 4: Kafka гарантирует порядок сообщений

Реальность: порядок гарантируется только внутри одной партиции.

Был случай, когда мы отправляли OrderCreated, OrderShipped, OrderDelivered, и в консьюмере они приходили не по порядку.

Потому что не было стратегии партиционирования и всё улетало в разные партиции.

Вывод

Самое опасное состояние – это не когда ничего не знаешь. А когда знаешь 80%, и думаешь, что знаешь 100%.

Вот тут и подставляешься.

Поэтому теперь, даже если я вроде бы всё понял, я иду и:

– читаю спеку
– лезу в исходники
– проверяю поведение в тестах
– спрашиваю себя: «А как это сломается?»
18🔥16👍12
This media is not supported in your browser
VIEW IN TELEGRAM
Очередная причина любить Питер
23👍10🔥9
Когда перфекционизм мешает писать хороший код

Я просто хочу, чтобы было красиво

Надо всё обобщить

Этот сервис можно сделать универсальным


…а потом 4 часа рефакторишь то, что вообще-то уже работало.

Где-то на пути своего становления учишься писать не идеальный код, а поддерживаемый:

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

Но перфекционизм подкрадывается.

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

Примеры, которые видел и делал сам.

1. Сделаю универсальный клиент

Зачем писать под один сервис, если можно сделать generic-клиент, который подойдёт для всех?

В результате 3 уровня абстракции, интерфейсы, которые никто не понимает и обобщённый код, который никто не переиспользует.

Лучше сделать простой клиент под одну задачу. Если появится вторая такая же – тогда уже и обобщить.

2. Не могу зарелизить, пока не будет тестов на всё

Один разработчик из нашей команды тормозил исправление бага, потому что там было покрытие 60%, а не 80%.

Лучше выкатить минимально рабочее и уже после обложить тестами. Не потому что тесты не важны, а потому что приоритет – починить систему, а не обогнать сонар.

3. Надо бы зарефакторить – в самый неподходящий момент

У нас была фича, которую надо было выкатить к концу недели.

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

В результате 2 дня потеряно, баг в проде, фичу не выкатили, а старый код всё равно остался.

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

Что я понял

Хороший код – это не идеальный, а поддерживаемый.

Перфекционизм часто – это страх ошибиться, быть непонятым, показать плохую работу.

Но в реальности людям нужно, чтобы работало, было понятно и легко развивалось.
14👍10🔥9
hashCode() по умолчанию

Мы привыкли думать, что если не переопределяем hashCode(), то по умолчанию он равен адресу объекта в памяти.

Но так ли это?

Может ли hashCode быть равен рандомному числу или вообще единице?

В этой статье провел расследование.
👍9🔥64
Роадмап подготовки к Java собеседованиям

Цель роадмапа – предоставить список тем и источников для быстрой подготовки к собеседованиям.

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

Это не конечная версия. Она будет дополняться и поддерживаться в актуальном состоянии. Поэтому следите за обновлениями.

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

Темы:

– Java (архитектура jvm, gc, многопоточность)
– Spring (aop, transaction, cloud)
– SQL/NoSQL (acid, base, уровни изоляций, explain)
– Kafka/Docker/Kubernetes
– Паттерны проектирования, ООП, SOLID
– Алгоритмы и структуры данных
– Системный дизайн
– Soft skils

Полная версия роадмапа со всеми темами и источниками лежит здесь
👍1511🔥10
Делаем свой ThreadPool

Создавать новый поток под каждую задачу – всё равно что покупать новый ноутбук каждый раз, когда нам нужно открыть вкладку в браузере.

Дорого, долго и бессмысленно.

Поэтому существует пул потоков.

Он состоит из четырёх ключевых компонентов:

1. Очередь задач: в неё кладутся новые задачи от пользователей, а потоки берут их в работу.

2. Потоки: каждый поток в пуле работает в бесконечном цикле. Берёт задачу из очереди -> выполняет -> берёт следующую. Если задач нет, поток ждёт.

3. Механизм управления потоками: нужно решать, сколько потоков поддерживать - фиксированное количество или динамически расширяемое.

4. Политика отказа: принимаемые действия, когда пул не может принять новую задачу.

Зачем вообще делать свой пул, когда есть готовые решения? Ответ простой. Чтобы разобраться как он работает под капотом.

Полную версию с кодом, объяснениями, стратегиями остановки и скейлинга выложил здесь
👍9🔥87
Сравнимаем производительность потокобезопасных счётчиков

Что обычно отвечает человек, когда его просят реализовать потокобезопасный счётчик?

– synchronized, как что-то стыдное
– AtomicLong, как что-то величественное

Но всегда ли synchronized проигрывает в производительности и есть ли что-то быстрее атомиков?

На самом деле потокобезопасный счётчик можно реализовать множеством разных способов:

– synchronized
– Semaphore
– ReentrantLock
– ReentrantReadWriteLock
– StampedLock
– AtomicLong
– LongAdder

Рассмотрим все эти варианты, сравним производительность в многопоточной среде и в разных сценариях использования чтения/записи.

Полная статья здесь
👍9🔥76
На удивление, многие не знали про существование LongAdder.

Почему же он такой быстрый на запись, но такой медленный на чтение?

В основе LongAdder лежит Striped64.

Зачем он вообще нужен?

Когда мы используем AtomicLong, то под высокой нагрузкой все потоки дерутся за один атомарный long.

Striped64 делит счётчик на ячейки, каждая из которых может инкрементироваться независимо.

Каждый поток работает в своей ячейке, снижая конкуренцию.

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

Внутреннее устройство

volatile long base – основное значение (используется, если конкуренции нет).

volatile Cell[] cells – массив ячеек, каждая хранит long value. Каждая ячейка расширена так, чтобы не было false sharing (об этом позже).

probe (через ThreadLocalRandom) определяет, в какую ячейку будет писать поток.

cellsBusy – CAS-замок на инициализацию/расширение массива cells.

Алгоритм обновления

Попробовали сделать CAS на base. Если получилось – готово.

Если CAS на base не удался:
– Берём cells. Если они ещё не созданы – пробуем создать массив на 2 ячейки.
– Вычисляем индекс ячейки. Пробуем сделать CAS в ячейку cells[index].

Если CAS в ячейку не удался (потоки попали в одну и ту же ячейку):
– Меняем probe (перемешиваем хэш потока).
– При сильной конкуренции пробуем расширить массив ячеек в 2 раза.

Размер cells может расти до числа ядер процессора. Так достигается баланс: чем больше конкуренция, тем больше ячеек.

false sharing

У процессоров память кешируется не побайтно и не по переменной, а блоками фиксированного размера – cache line (обычно 64 байта).

Когда поток читает или пишет переменную, процессор подгружает в кеш всю линию (64 байта).

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

В итоге кеш постоянно инвалидируется, даже если логически данные не связаны.

Это и есть false sharing – ложное совместное использование данных, которых на самом деле не должно быть.

В Striped64 разные потоки пишут в разные ячейки.

Если бы ячейка была просто volatile long value, то несколько ячеек массива подряд оказались бы в одной кеш-линии.

Поэтому Striped64 использует аннотацию Contended, которая дополняет поля внутри классов пустыми байтами.

То есть каждая ячейка раздута, чтобы точно не пересекаться с соседями по кеш-линии. Это убирает false sharing и позволяет потокам работать независимо.
🔥118👍6
Сегодня столкнулся с OOM в микросервисе, который вообще ничего не делал. Только был запущен. Трафик не принимал. Фоновые задачи не выполнял.

Прикол в том, что до этого, трафик в него поступал. Но из-за проблем в сторонней системе, трафик перевели в другой неймспейс кубера.

И через день стрельнул OOM. Контейнер перезапустился. Через сутки снова ООМ.

Размер хипа 256 МБ.

В логах пусто.

Снял хипдамп.

75% хипа забито очередью из класса org.springframework.cloud.sleuth.autoconfig.actuate.BufferingSpanReporter

Что это за класс?

Он активируется при включённом конфиге spring.sleuth.enabled и хранит трассировки.

Размер очереди ограничен 10.000 элементов. При переполнении самый старый элемент удаляется.

Все трассировки, которые мне попались при изучении хипдампа, относились к ошибкам интеграции со spring-boot-admin.

Микросервис пытался постоянно зарегистрировать себя в spring-boot-admin, но был указан невалидный урл и регистрация завершалась ошибкой.

Эта ошибка со стектрейсом и попадала в очередь BufferingSpanReporter. Что в итоге приводило к ООМ.

Почему ООМ не возникал при трафике?

Думаю из-за того, что другие трассировки были небольших размеров, их было больше и они вытесняли тяжёлые ошибочные трассировки от интеграции со spring-boot-admin.

Это лишь предположение. После указания валидного урла проблема ушла. Но я буду копать дальше, чтобы дойти до сути.
🔥15👍1312
0xCAFEBABE

Вот кто не любит пасхалки?

Давайте создадим простой класс:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}


Скомпилируем его:
javac Main.java


Проанализируем hex дамп:
xxd Main.class


Увидим следующее:
00000000: cafe babe 0000 003d 001d 0a00 0200 0307  .......=........
00000010: 0004 0c00 0500 0601 0010 6a61 7661 2f6c ..........java/l
00000020: 616e 672f 4f62 6a65 6374 0100 063c 696e ang/Object...<in
00000030: 6974 3e01 0003 2829 5609 0008 0009 0700 it>...()V.......
00000040: 0a0c 000b 000c 0100 106a 6176 612f 6c61 .........java/la
00000050: 6e67 2f53 7973 7465 6d01 0003 6f75 7401 ng/System...out.
00000060: 0015 4c6a 6176 612f 696f 2f50 7269 6e74 ..Ljava/io/Print
00000070: 5374 7265 616d 3b08 000e 0100 0b48 656c Stream;......Hel
00000080: 6c6f 2057 6f72 6c64 0a00 1000 1107 0012 lo World........
00000090: 0c00 1300 1401 0013 6a61 7661 2f69 6f2f ........java/io/
000000a0: 5072 696e 7453 7472 6561 6d01 0007 7072 PrintStream...pr
000000b0: 696e 746c 6e01 0015 284c 6a61 7661 2f6c intln...(Ljava/l
000000c0: 616e 672f 5374 7269 6e67 3b29 5607 0016 ang/String;)V...
000000d0: 0100 044d 6169 6e01 0004 436f 6465 0100 ...Main...Code..
000000e0: 0f4c 696e 654e 756d 6265 7254 6162 6c65 .LineNumberTable
000000f0: 0100 046d 6169 6e01 0016 285b 4c6a 6176 ...main...([Ljav
00000100: 612f 6c61 6e67 2f53 7472 696e 673b 2956 a/lang/String;)V
00000110: 0100 0a53 6f75 7263 6546 696c 6501 0009 ...SourceFile...
00000120: 4d61 696e 2e6a 6176 6100 2100 1500 0200 Main.java.!.....
00000130: 0000 0000 0200 0100 0500 0600 0100 1700 ................
00000140: 0000 1d00 0100 0100 0000 052a b700 01b1 ...........*....
00000150: 0000 0001 0018 0000 0006 0001 0000 0001 ................
00000160: 0009 0019 001a 0001 0017 0000 0025 0002 .............%..
00000170: 0001 0000 0009 b200 0712 0db6 000f b100 ................
00000180: 0000 0100 1800 0000 0a00 0200 0000 0300 ................
00000190: 0800 0400 0100 1b00 0000 0200 1c .............


Hex дамп начинается c cafe babe.

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

Что же это за магическая штука?

Она позволяет JVM быстро идентифицировать и проверять файлы классов.

Если мы отредактируем файл Main.class, изменим первые числа и попытаемся запустить, то JVM выбросит исключение:
java.lang.ClassFormatError: Incompatible magic value ...


Это магическое число ввёл сам Джеймс Гослинг:
Мы раньше ходили обедать в одно место под названием St Michael's Alley. По местной легенде, в далёком прошлом там выступали Grateful Dead, ещё до того, как стали знаменитыми. Это было довольно странное, но атмосферное заведение – определённо «место в стиле Grateful Dead». Когда Джерри (Гарсия) умер, там даже сделали что-то вроде маленького буддийского алтаря.

Когда мы туда ходили, мы называли это место Cafe Dead. В какой-то момент кто-то заметил, что это ещё и HEX-число. Я как раз перерабатывал код для одного файлового формата и мне нужны были парочка «магических чисел»: одно – для формата файлов с объектами, другое – для классов.

Я использовал CAFEDEAD для объектного формата. А потом, просматривая четырёхсимвольные HEX-слова, которые подходят после CAFE (тема показалась удачной), я наткнулся на BABE и решил использовать его.

Тогда это не казалось чем-то особенно важным или судьбоносным – скорее временным решением, которому место на свалке истории. Но в итоге CAFEBABE стало «магическим числом» для class-файлов, а CAFEDEAD – для объектного формата. Правда, объектный формат со временем исчез, и вместе с ним ушло использование CAFEDEAD – его в итоге заменил RMI.


Вот такая пасхалка.
👍2211🔥9
Почему документацию никто не пишет, но все хотят её читать

У каждой команды есть такая мечта:

Вот бы была нормальная документация…


И у каждой команды есть такая же реальность:

– Напиши доку сам?
– Да ну нафиг, потом


Почему мы не пишем документацию?

Представь, ты только что закончил сложный фикс, потратил 8 часов на отладку хитрого бага. Мозг кипит. Последнее, чего хочется – это переключаться в режим «писателя» и нудно описывать, что ты сделал.

Почему?

Проклятие знания

Вот есть метод getUserData(..). Кажется, что всё очевидно. Ну что тут непонятного, он получает данные юзера!

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

Это же скучно!

Писать код, решать проблемы – это драйв, креатив.

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

Давай быстрее, дедлайн вчера!

Бизнес требует результат, а не талмуды с описаниями. Документация – первое, что летит под нож, когда горят сроки. Это «внутренняя кухня», которую не показать заказчику.

Польза где-то там, в будущем

Выгоду от доков получит кто-то другой (или ты, но потом). А силы нужно тратить сейчас. Наш мозг не любит такого.

Так почему мы все так хотим читать документацию?

А теперь перевернем ситуацию. Ты приходишь на новый проект. Перед тобой 100500 файлов и непонятная структура. Ты как слепой котенок.

Что спасает? Правильно, хорошая документация.

Это карта сокровищ

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

Независимость

Тебе не нужно каждые 5 минут дергать тимлида с вопросом: «А как тут…?». Ты можешь работать автономно. Это круто и для тебя, и для команды, которую ты не отвлекаешь.

Единый источник правды

Когда в команде споры, как должна работать та или иная функция, документация – это ваш судья. Она спасает от хаоса, когда «Петя говорил одно, а Вася делал другое».

Что в итоге?

Получается замкнутый круг.

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

Документация – это не побочный продукт, а часть самого продукта. Пока мы относимся к ней как к рутине, а не как к обязательному этапу разработки, мы так и будем тратить часы на то, что можно было бы узнать за 5 минут.

Поэтому на собеседованиях я обязательно спрашиваю у интервьюеров, как у них обстоят дела с документацией. Это очень хорошо показывает зрелость проекта и команды.
👍217🔥6