# #1: Пилот: слепая печать, коды ошибок

- Выпуск: 1 · Сезон: 1 · Дата: 2022-10-03 · Длительность: 22:46
- Страница: https://apkhmv.xyz/podcast/episode-01/
- Аудио: https://traffic.libsyn.com/secure/173caf0e-b8e8-4056-8a73-2d60d94118ef/01_Pilot_1.mp3
- Ведущий: Александр Пахомов (https://apkhmv.xyz/people/apkhmv/index.md) · Гости: нет
- Темы: слепая печать, REST API, HTTP-коды, обработка ошибок, RFC 7807
- Расшифровка: reviewed · Источник: речь участников выпуска, цитируется как есть

## Кратко

Пилотный выпуск без гостей. Александр Пахомов рассказывает, почему десятипальцевая (слепая) печать — важный навык для программиста и где её освоить, а затем разбирает проектирование ошибок публичного REST API: какие HTTP-коды возвращать и почему тело ошибки стоит отдавать в формате RFC 7807 (problem+json).

## Главное

- Слепая (десятипальцевая) печать ценна не столько скоростью, сколько лёгкостью входа в состояние потока и комфортом работы без подсказок IDE.
- Осваивать слепую печать удобно короткими подходами на тренажёрах TypingClub и Clava; Clava умеет тренировать символы языков программирования.
- Для внутреннего API достаточно команде договориться о формате ошибок; публичный API обязан следовать внешним конвенциям HTTP.
- Коды 4xx сигнализируют об ошибке клиента и требуют понятного тела с объяснением, что не так и что делать; коды 5xx означают ошибку на стороне сервера.
- В ответах 5xx нельзя раскрывать детали (stack trace, версии библиотек) — это security-антипаттерн; возвращайте абстрактный «internal server error».
- Коды 503 и 504 обычно возвращает инфраструктура (например, Kubernetes), а не код приложения.
- Стандарт RFC 7807 (медиатайп application/problem+json) задаёт единое тело ошибки с полями type, title, status, detail и instance.
- В экосистеме Spring формат problem+json даёт библиотека Zalando Problem; на Micronaut обёртку легко написать самостоятельно.

## Главы

- [00:19](https://apkhmv.xyz/podcast/episode-01/?t=19) Вступление
- [00:56](https://apkhmv.xyz/podcast/episode-01/?t=56) Слепая печать: зачем она программисту
- [03:43](https://apkhmv.xyz/podcast/episode-01/?t=223) Как и где освоить слепую печать
- [05:31](https://apkhmv.xyz/podcast/episode-01/?t=331) Ошибки REST API: постановка задачи
- [05:52](https://apkhmv.xyz/podcast/episode-01/?t=352) Что такое REST и требования к ошибкам
- [07:47](https://apkhmv.xyz/podcast/episode-01/?t=467) HTTP-коды по группам (1xx–5xx)
- [16:00](https://apkhmv.xyz/podcast/episode-01/?t=960) Тело ошибки и RFC 7807 (problem+json)
- [21:49](https://apkhmv.xyz/podcast/episode-01/?t=1309) Заключение и ссылки

## Ссылки

- [TypingClub — тренажёр слепой печати](https://www.typingclub.com)
- [Clava — тренажёр печати для программистов](https://clava.org)
- [RFC 7807 — Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc7807)
- [Zalando Problem — библиотека problem+json для Spring](https://github.com/zalando/problem)

## Похожие выпуски


- [#2: Гундосая спека: комменты, Open API](https://apkhmv.xyz/podcast/episode-02/index.md) — общие темы: REST API
- [#40: Спэшл: Эргономика, NeoVim и TDD](https://apkhmv.xyz/podcast/episode-40/index.md) — общие темы: слепая печать

## Расшифровка


**[00:19]** Здарова! Меня зовут Саша Пахомов, и я инженер, который любит своё дело. Это пилотный выпуск подкаста, у которого пока нет ни названия, ни гостей — зато есть куча тем, которые я хочу вместе с вами разобрать. Сегодня я расскажу, почему слепая печать — необходимый навык для программиста, а потом мы погрузимся в дебри разработки публичного REST API: поймём, в каком формате возвращать ошибки, и разберёмся с популярными кодами ошибок. Поехали!

**[00:56]** Я помню первый урок информатики в школе — там мы изучали, как правильно печатать на клавиатуре, тот самый метод слепой печати. Но, как и большинство изученного в школе, изучено оно не было. По-настоящему погрузиться в десятипальцевую печать мне удалось, только уже будучи программистом, на четвёртом курсе университета.

**[01:16]** Давайте разберёмся, что такое десятипальцевая, или слепая, печать. Это не просто когда мы не смотрим на клавиатуру, используя два-четыре пальца, — это когда мы задействуем все десять пальцев при наборе символов.

**[01:35]** Кто-то может возразить, что скорость десятипальцевой печати не так сильно отличается от беглого набора четырьмя пальцами — мол, куда ты денешь сэкономленные две секунды жизни? Но лично для меня слепая печать — это не про скорость и не про сэкономленные секунды. Это про ощущения: в первую очередь — про удовольствие и удовлетворённость от самого процесса работы.

**[02:02]** К неоспоримым преимуществам я бы отнёс то, что состояние потока достигается гораздо проще: подсказки IntelliJ IDEA уже не так нужны в отсутствие самой IntelliJ IDEA, и ты чувствуешь себя комфортно. Приведу пример: когда нужно быстро напечатать пример кода во время объяснения коллеге в Zoom-митинге, имея под руками IDE, мы быстренько печатаем `PSVM`, жмём Tab, получаем `public static void main` и погнали дальше. Но если IDE под руками нет — это становится проблемой. Я скорее предпочту не печатать и не объяснять, чем заставлять коллегу смотреть, как я с ошибками набираю `public static void main`. Для меня это создаёт психологический барьер. А если ты умеешь быстро печатать — этого барьера почти нет.

**[02:58]** Следующее преимущество — так называемое «ощущение гика» на собеседовании. Когда тебя собеседуют, ты можешь спокойно зашерить экран, открыть любой редактор — хоть Notepad, хоть Vim — и начать показывать пример кода, печатая со скоростью своей речи. Это воспринимается намного профессиональнее и производит больше впечатления на собеседующего.

**[03:27]** И последнее преимущество — глаза не так сильно устают. Когда взгляд бегает между клавиатурой и экраном, это создаёт дополнительное напряжение для мышц глаз, и устают они сильнее.

**[03:43]** Как изучить слепую печать, ведь у нас так мало свободных ресурсов? Пока билдится проект или ждём CI; на скучных митингах — например, на daily, когда сам уже всё сказал и вроде слушаешь коллег, но не во всём внимании — можно открыть тренажёр и пройти 5–10-минутный урок. Почему бы и нет? Или, как делал я, — в начале дня: это был мой ритуал. Каждый рабочий день я садился за компьютер, включал приятную музыку и с пользой проводил полчаса.

**[04:19]** Где изучать? Лично я осваивал слепую печать на ресурсе typingclub.com — там в игровой форме, с ачивками и звёздочками, с ростом сложности и прохождением уровней, потихоньку начинаешь печатать вслепую. А после моего поста в Telegram-канале мне в комментариях подсказали ещё один интересный ресурс — clava.org. Его особенность в том, что он позволяет печатать не только предложения на английском или русском, но и на языках программирования — то есть тренировать специфичные символы: открывающие-закрывающие скобки, равно, точку с запятой и так далее. Признаюсь, после typingclub.com мне было не очень удобно ставить точку с запятой в конце строки на Java, и я дополнительно себя заставлял. Так что если вам интересна слепая печать именно с точки зрения программирования — посмотрите clava.org. Все ссылки приложу.

**[05:31]** А теперь — к основной теме подкаста. Я разрабатываю распределённую open-source базу данных, которая предоставляет публичные REST API. Недавно передо мной встала задача стандартизации формата возвращаемых ошибок, и эта тема оказалась очень интересной — сейчас поделюсь.

**[05:52]** Вообще, REST расшифровывается как Representational State Transfer. Простыми словами, это подход к разработке распределённых веб-сервисов, когда клиент получает доступ к ресурсам сервера поверх протокола HTTP. Чёткого определения REST в индустрии нет: первое историческое упоминание было в 2000 году в диссертации Роя Филдинга, но с тех пор прошло 22 года, и канонический REST претерпел некоторые инженерные адаптации — в частности, в том, что касается ошибок, возвращаемых клиенту.

**[06:24]** Поговорим о том, в каком формате возвращать ошибки из публичного REST-сервиса. Я делаю акцент на слове «публичного», потому что внутренний API должен соблюдать лишь один критерий — о нём нужно договориться. Если мы договорились, что все ошибки возвращаем с кодом 200, а реальный код передаём в теле, — давайте так и будем делать везде; главное, что договорились. Но когда мы разрабатываем публичный API, который используют за пределами компании, внутренних договорённостей уже нет — есть внешние. Странно возвращать 200 на internal error.

**[07:01]** И во внутреннем, и во внешнем сервисе к ошибкам есть негласные требования. Первое: должно быть понятно, что пошло не так и что с этим делать, если виноват клиент. Второе: формат тела ошибки должен быть всегда одинаковым — мы не должны на один 400 возвращать одно тело, а на другой 400 — другое. И третье: код должен соответствовать ситуации.

**[07:47]** Коды делятся на группы по первой цифре — 1, 2, 3, 4, 5. Всё, что начинается на 1 (100, 101, 102), — это informational; на практике я их почти никогда не использовал, и вам вряд ли понадобится.

**[08:09]** Дальше — двойки. 200 OK — всё хорошо, запрос выполнен. 201 Created — когда клиент попросил создать ресурс, и мы его создали: возвращаем не 200, а 201, подтверждая, что эффект произошёл. 202 Accepted — когда мы принимаем асинхронную задачу (например, «зашедуль мне таску»): можно вернуть и 200, но 202 более конкретно говорит, что задачу приняли и когда-нибудь выполним. 204 No Content — например, когда пытаемся удалить ресурс, которого нет.

**[09:03]** Тройки — это редиректы; скорее всего, вам их возвращать не придётся, но сталкиваться иногда приходится (всякий middleware). В своей практике я с ними сталкивался очень редко.

**[09:23]** Самое интересное — четырёхсотки: 400, 401, 403 и так далее. Суть в том, что это ошибки по вине клиента: он сформировал неправильный запрос, обратился к несуществующему ресурсу, превысил количество запросов. Причина не в баге на бэке, а в ошибке клиента. И раз мы, сервер, возвращаем ошибку и хотим сказать клиенту «чувак, ты не прав», нужно сделать это максимально понятно. Недостаточно вернуть 400 и «bad request» — нужно объяснить, что конкретно не так.

**[10:06]** Самая первая ошибка — 400 Bad Request: клиент сформировал запрос, который мы не можем распарсить или который невалиден (вариантов сотни). В ответе нужно вернуть максимально чёткое и понятное тело: что пошло не так и что сделать. 401 Unauthorized — говорящая сама за себя; тела обычно не нужно, и чаще всего её за нас возвращают Spring, Micronaut и другие фреймворки. То же и с 403 Forbidden — «запрещено»: когда пользователь без нужных прав пытается добраться до ресурса (например, обычный пользователь хочет открыть admin page). Скорее всего, Spring Security проверит, что пользователь есть и authorized, но в доступе ему отказано.

**[11:28]** Самая знаменитая — 404 Not Found: возвращаем, когда ресурса нет (например, запросили `account/3`, а у нас всего два аккаунта с id 1 и 2). Тело тут обычно не нужно — и так всё очевидно. 405 Method Not Allowed — экзотика: есть GET на `/users/pictures`, но нет POST на него — фреймворк вернёт 405. Из технических четырёхсоток ещё стоит знать 429 Too Many Requests — мы упёрлись в rate limiting на стороне сервера: например, клиенту разрешено 500 запросов в минуту, и на 501-м он получит 429. Про rate limiting, наверное, стоит записать отдельный выпуск. И 413 Payload Too Large — обычно генерируется фреймворком автоматически.

**[13:19]** Последняя группа — пятисотки, и их немного. На стороне кода мы, скорее всего, будем генерировать только 500 — internal server error. Важно: в отличие от четырёхсоток, 500 вызвана не клиентом, а сервером (упал, NPE, out of memory и так далее). Ключевая мысль: в пятисотках нельзя отдавать никакой детальной информации о том, что пошло не так.

**[14:17]** Почему нельзя, например, отдать null pointer exception из internal-сервиса? Раскрывать информацию о внутреннем устройстве кода — всегда антипаттерн, причём security-антипаттерн. Что плохого в том, чтобы выплюнуть stack trace в пятисотке? Мы показываем структуру кода и инфраструктуру: если это Spring-приложение — будет спринговый stack trace, возможно, Spring Security в фильтрах. Потенциальный злоумышленник поймёт, что здесь Spring, определит версию библиотеки по классам — и если в этой версии есть уязвимость, сможет её проэксплуатировать. Поэтому 500 нужно возвращать максимально абстрактной: «internal server error» — отличная формулировка, которая говорит, что что-то пошло не так, а что именно — знать не надо. Сорян, чувак.

**[15:17]** 503 и 504, скорее всего, вернёт не наш код, а инфраструктура — тот же Kubernetes. 503 Service Unavailable — например, один контейнер с сервисом уже остановлен, а второй ещё поднимается и не готов обслуживать (хотя в идеале должен быть и третий, который продолжает обслуживать). 504 — Gateway Timeout. На этом про коды всё.

**[16:00]** Дальше — про тело ошибки. Как я уже говорил, в четырёхсотках хочется возвращать не только код, но и то, что пошло не так: детали, типы ошибки, внутренние коды. Чётких спецификаций тут найти сложно — каждый возвращает тело в удобном ему формате. В Java с этим неплохо справляются современные фреймворки — Spring, Micronaut, Quarkus: они не показывают stack trace, а отдают структурированный JSON. В Spring это `timestamp`, `status` (error code), `error` (название, например internal server error), `message` и `path`, по которому произошла ошибка. Выглядит нормально. Но Micronaut возвращает ошибки в другом формате, с другими названиями полей — и клиенту от этого разнобоя довольно неудобно: нужно знать, какая там DTO и реализация.

**[17:32]** Разрабатывая публичный REST API, я подумал: наверняка есть какой-то принятый стандарт тела ошибки — и да, он есть. Это RFC 7807, который описывает стандартное тело ошибки. Называется оно Problem (problem+json) — это медиатайп `application/problem+json`. У него есть несколько хорошо специфицированных полей.

**[18:24]** Первое поле — `type`, строка, URI-ссылка на описание ошибки. Если у нас есть специфицированное описание внутренней ошибки, желательно иметь веб-страницу, которая её описывает. Честно говоря, у себя в сервисе я это поле не использую. Следующее — `title`, строка: короткое человекочитаемое описание проблемы (например, «User Not Found»). Затем `status`, число — HTTP-статус-код, который должен совпадать со статус-кодом в заголовке ответа. Поле `detail`, строка, — то самое, что объясняет клиенту, что пошло не так и что с этим делать; оно должно содержать минимум внутренних деталей и быть максимально простым (например: «У пользователя недостаточно средств: вы хотели списать 200 рублей, на счёте — 100. Пополните кошелёк»). И последнее поле — `instance`, строка, тоже URI: ссылка на конкретный инстанс, где возникла ошибка (полезно, когда сервис распределённый и ошибка возникает не везде).

**[20:24]** RFC определяет не только набор полей, но и конвенции между ними — какой URI должен быть относительным, какой нет. Если будете реализовывать problem+json — этот RFC легко нагуглить и почитать. Моя цель — сказать, что такой стандарт есть: если у вас нет причин делать иначе и вы думаете, какой формат ошибки выбрать, обратите внимание на problem+json. Он максимально понятный и простой, и главное — вы не изобретёте ещё один стандарт, потому что он уже существует. Введён в 2016 году — почему бы им и не пользоваться?

**[21:13]** Кстати, Spring поддерживает этот тип — точнее, есть библиотека от Zalando под названием Problem: она описывает DSL, которым удобно создавать problem+json и оборачивать все внутренние ошибки в единый стандарт. Очень удобно, советую. Я сейчас пишу веб-сервис на Micronaut, и написать обёртки над DTO самому — тривиальная задача: минут за 20 всё сделал, причём, как мне кажется, даже красивее, чем библиотека.

**[21:49]** На этом основная часть завершается. В заключение я делюсь полезными ссылками, видео и книгами. Сегодня это мой Telegram-канал «Душный enterprise», где я делюсь мыслями, иногда бомблю, а последнее время просто молчу. Оставляйте комментарии на подкаст-площадках или под постом в Telegram-канале — это очень важно для развития подкаста и важно для меня лично. На этом всё. Услышимся!


