Упрощение кода
Вдохновлено плагином Claude Code Simplifier. Здесь адаптировано как процессный скилл, не привязанный к конкретной модели, для любого AI-агента для кода.
Обзор
Упрощай код, снижая сложность и в точности сохраняя поведение. Цель — не меньше строк, а код, который легче читать, понимать, менять и отлаживать. Каждое упрощение должно проходить простую проверку: «Поймёт ли новый человек в команде это быстрее, чем оригинал?»
Когда применять
- Фича работает и тесты проходят, но реализация ощущается тяжелее, чем должна быть
- В ходе ревью отмечены проблемы читаемости или сложности
- Встретилась глубоко вложенная логика, длинные функции или невнятные имена
- Рефакторишь код, написанный в спешке
- Собираешь воедино связанную логику, разбросанную по файлам
- После вливания изменений, которые внесли дублирование или несогласованность
Когда НЕ применять:
- Код уже чистый и читаемый — не упрощай ради упрощения
- Ты ещё не понял, что делает код — сначала разберись, потом упрощай
- Код критичен по производительности, и «более простая» версия будет измеримо медленнее
- Ты вот-вот перепишешь модуль целиком — упрощать одноразовый код бессмысленно
Пять принципов
1. Сохраняй поведение в точности
Не меняй, что код делает, — меняй только, как он это выражает. Все входы, выходы, побочные эффекты, поведение при ошибках и краевые случаи должны остаться идентичными. Если не уверен, что упрощение сохраняет поведение, — не делай его.
СПРАШИВАЙ ПЕРЕД КАЖДЫМ ИЗМЕНЕНИЕМ:
→ Даёт ли это тот же результат на любом входе?
→ Сохраняется ли то же поведение при ошибках?
→ Сохраняются ли те же побочные эффекты и их порядок?
→ Проходят ли все существующие тесты без правок?
2. Следуй соглашениям проекта
Упрощение означает приведение кода к большей согласованности с кодовой базой, а не навязывание внешних предпочтений. Прежде чем упрощать:
1. Прочитай CLAUDE.md / соглашения проекта
2. Изучи, как соседний код решает похожие задачи
3. Подстройся под стиль проекта в части:
- Порядка импортов и модульной системы
- Стиля объявления функций
- Соглашений об именовании
- Паттернов обработки ошибок
- Глубины аннотаций типов
Упрощение, ломающее согласованность проекта, — не упрощение, а перемешивание.
3. Ясность важнее изобретательности
Явный код лучше компактного, когда компактная версия требует умственной паузы на разбор.
// НЕЯСНО: плотная цепочка тернарников
const label = isNew ? 'New' : isUpdated ? 'Updated' : isArchived ? 'Archived' : 'Active';
// ЯСНО: читаемое сопоставление
function getStatusLabel(item: Item): string {
if (item.isNew) return 'New';
if (item.isUpdated) return 'Updated';
if (item.isArchived) return 'Archived';
return 'Active';
}
// НЕЯСНО: цепочка reduce со встроенной логикой
const result = items.reduce((acc, item) => ({
...acc,
[item.id]: { ...acc[item.id], count: (acc[item.id]?.count ?? 0) + 1 }
}), {});
// ЯСНО: именованный промежуточный шаг
const countById = new Map<string, number>();
for (const item of items) {
countById.set(item.id, (countById.get(item.id) ?? 0) + 1);
}
4. Держи баланс
У упрощения есть свой способ провалиться — переупрощение. Следи за этими ловушками:
- Слишком агрессивное встраивание — удаление хелпера, дававшего понятию имя, делает место вызова менее читаемым
- Объединение несвязанной логики — две простые функции, слитые в одну сложную, не проще
- Удаление «ненужной» абстракции — некоторые абстракции существуют ради расширяемости или тестируемости, а не от сложности
- Оптимизация по числу строк — меньше строк не цель; цель — быстрее понять
5. Ограничивайся тем, что изменилось
По умолчанию упрощай недавно изменённый код. Избегай попутного рефакторинга постороннего кода, если тебя явно не просили расширить границы. Неограниченное упрощение создаёт шум в диффах и рискует непреднамеренными регрессиями.
Процесс упрощения
Шаг 1: Пойми, прежде чем трогать (забор Честертона)
Прежде чем что-то менять или удалять, пойми, зачем оно существует. Это забор Честертона: если видишь забор поперёк дороги и не понимаешь, зачем он там, не сноси его. Сначала выясни причину, потом решай, действует ли она до сих пор.
ПЕРЕД УПРОЩЕНИЕМ ОТВЕТЬ:
- За что отвечает этот код?
- Что его вызывает? Что вызывает он?
- Каковы краевые случаи и пути с ошибками?
- Есть ли тесты, задающие ожидаемое поведение?
- Почему это могло быть написано именно так? (Производительность? Ограничение платформы? Историческая причина?)
- Посмотри git blame: каков был исходный контекст этого кода?
Если не можешь на это ответить — ты не готов упрощать. Сначала прочитай больше контекста.
Шаг 2: Найди возможности для упрощения
Ищи эти паттерны — каждый из них конкретный сигнал, а не размытый «запах»:
Структурная сложность:
| Паттерн | Сигнал | Упрощение |
|---|---|---|
| Глубокая вложенность (3+ уровня) | Тяжело проследить поток управления | Вынести условия в охранные выражения или хелперы |
| Длинные функции (50+ строк) | Несколько зон ответственности | Разбить на сфокусированные функции с описательными именами |
| Вложенные тернарники | Требуют держать стек в голове | Заменить цепочкой if/else, switch или объектом-справочником |
| Булевы параметры-флаги | doThing(true, false, true) |
Заменить объектом опций или отдельными функциями |
| Повторяющиеся условия | Одна и та же проверка if в нескольких местах |
Вынести в предикат с понятным именем |
Именование и читаемость:
| Паттерн | Сигнал | Упрощение |
|---|---|---|
| Обобщённые имена | data, result, temp, val, item |
Переименовать по содержимому: userProfile, validationErrors |
| Сокращённые имена | usr, cfg, btn, evt |
Использовать полные слова, если сокращение не общепринято (id, url, api) |
| Вводящие в заблуждение имена | Функция с именем get, которая ещё и меняет состояние |
Переименовать так, чтобы имя отражало реальное поведение |
| Комментарии, объясняющие «что» | // увеличиваем счётчик над count++ |
Удалить комментарий — код и так понятен |
| Комментарии, объясняющие «почему» | // Повторяем, потому что API нестабилен под нагрузкой |
Оставить — они несут замысел, который код выразить не может |
Избыточность:
| Паттерн | Сигнал | Упрощение |
|---|---|---|
| Дублирующаяся логика | Одни и те же 5+ строк в нескольких местах | Вынести в общую функцию |
| Мёртвый код | Недостижимые ветки, неиспользуемые переменные, закомментированные блоки | Удалить (убедившись, что он действительно мёртв) |
| Ненужные абстракции | Обёртка, не добавляющая ценности | Встроить обёртку, вызывать нижележащую функцию напрямую |
| Переусложнённые паттерны | Фабрика фабрик, стратегия с одной стратегией | Заменить простым прямым подходом |
| Избыточные приведения типов | Приведение к типу, который и так выведен | Убрать приведение |
Шаг 3: Вноси изменения инкрементально
Делай по одному упрощению за раз. После каждого изменения гоняй тесты. Отправляй рефакторинг отдельно от фич и багфиксов. PR, который и рефакторит, и добавляет фичу, — это два PR, разбей их.
ДЛЯ КАЖДОГО УПРОЩЕНИЯ:
1. Внеси изменение
2. Прогони набор тестов
3. Тесты прошли → коммить (или переходи к следующему упрощению)
4. Тесты упали → откати и подумай ещё раз
Не сваливай несколько упрощений в одно непротестированное изменение. Если что-то сломается, тебе нужно знать, какое именно упрощение виновато.
Правило 500: если рефакторинг затронет больше 500 строк, вложись в автоматизацию (кодмоды, sed-скрипты, преобразования AST), а не правь руками. Ручные правки такого масштаба чреваты ошибками и изматывают ревьюера.
Шаг 4: Проверь результат
После всех упрощений отойди на шаг и оцени целое:
СРАВНИ «ДО» И «ПОСЛЕ»:
- Упрощённая версия действительно понятнее?
- Не внёс ли ты новых паттернов, несогласованных с кодовой базой?
- Дифф чистый и пригодный для ревью?
- Одобрил бы это изменение коллега?
Если «упрощённая» версия труднее для понимания или ревью — откатывай. Не каждая попытка упрощения удаётся.
Рекомендации по языкам
TypeScript / JavaScript
// УПРОСТИТЬ: ненужная async-обёртка
// Было
async function getUser(id: string): Promise<User> {
return await userService.findById(id);
}
// Стало
function getUser(id: string): Promise<User> {
return userService.findById(id);
}
// УПРОСТИТЬ: многословное условное присваивание
// Было
let displayName: string;
if (user.nickname) {
displayName = user.nickname;
} else {
displayName = user.fullName;
}
// Стало
const displayName = user.nickname || user.fullName;
// УПРОСТИТЬ: ручная сборка массива
// Было
const activeUsers: User[] = [];
for (const user of users) {
if (user.isActive) {
activeUsers.push(user);
}
}
// Стало
const activeUsers = users.filter((user) => user.isActive);
// УПРОСТИТЬ: избыточный возврат булева значения
// Было
function isValid(input: string): boolean {
if (input.length > 0 && input.length < 100) {
return true;
}
return false;
}
// Стало
function isValid(input: string): boolean {
return input.length > 0 && input.length < 100;
}
Python
# УПРОСТИТЬ: многословная сборка словаря
# Было
result = {}
for item in items:
result[item.id] = item.name
# Стало
result = {item.id: item.name for item in items}
# УПРОСТИТЬ: вложенные условия с ранним возвратом
# Было
def process(data):
if data is not None:
if data.is_valid():
if data.has_permission():
return do_work(data)
else:
raise PermissionError("No permission")
else:
raise ValueError("Invalid data")
else:
raise TypeError("Data is None")
# Стало
def process(data):
if data is None:
raise TypeError("Data is None")
if not data.is_valid():
raise ValueError("Invalid data")
if not data.has_permission():
raise PermissionError("No permission")
return do_work(data)
React / JSX
// УПРОСТИТЬ: многословная условная отрисовка
// Было
function UserBadge({ user }: Props) {
if (user.isAdmin) {
return <Badge variant="admin">Admin</Badge>;
} else {
return <Badge variant="default">User</Badge>;
}
}
// Стало
function UserBadge({ user }: Props) {
const variant = user.isAdmin ? 'admin' : 'default';
const label = user.isAdmin ? 'Admin' : 'User';
return <Badge variant={variant}>{label}</Badge>;
}
// УПРОСТИТЬ: прокидывание пропсов через промежуточные компоненты
// Было — подумай, не решает ли это лучше контекст или композиция.
// Это вопрос суждения — отметь его, но не рефактори автоматически.
Типовые самооправдания
| Самооправдание | Как на самом деле |
|---|---|
| «Работает — не трогай» | Работающий код, который трудно читать, будет трудно чинить, когда он сломается. Упростив сейчас, экономишь время на каждом будущем изменении. |
| «Меньше строк — всегда проще» | Однострочный вложенный тернарник не проще пятистрочного if/else. Простота — про скорость понимания, а не про число строк. |
| «Заодно быстренько упрощу и вот этот посторонний код» | Неограниченное упрощение создаёт шумные диффы и рискует регрессиями в коде, который ты менять не собирался. Держи фокус. |
| «Типы делают код самодокументируемым» | Типы документируют структуру, а не замысел. Хорошо названная функция объясняет почему лучше, чем сигнатура типа объясняет что. |
| «Эта абстракция может пригодиться потом» | Не сохраняй умозрительные абстракции. Если она не используется сейчас — это сложность без ценности. Удали и добавь снова, когда понадобится. |
| «У автора наверняка была причина» | Возможно. Посмотри git blame — примени забор Честертона. Но у накопленной сложности часто нет причины, это просто осадок от итераций под давлением. |
| «Отрефакторю заодно с добавлением фичи» | Отделяй рефакторинг от работы над фичей. Смешанные изменения труднее ревьюить, откатывать и понимать в истории. |
Тревожные признаки
- Упрощение, потребовавшее правки тестов, чтобы они прошли (скорее всего, ты изменил поведение)
- «Упрощённый» код, который длиннее и труднее для понимания, чем оригинал
- Переименования под свои вкусы, а не под соглашения проекта
- Удаление обработки ошибок, потому что «так код чище»
- Упрощение кода, который ты понял не до конца
- Сваливание множества упрощений в один большой коммит, который трудно отревьюить
- Рефакторинг кода за пределами текущей задачи без запроса
Проверка
После прохода упрощения:
- Все существующие тесты проходят без правок
- Сборка успешна, новых предупреждений нет
- Линтер/форматтер проходит (стилевых регрессий нет)
- Каждое упрощение — отдельное инкрементальное изменение, пригодное для ревью
- Дифф чистый — посторонних изменений не подмешано
- Упрощённый код следует соглашениям проекта (сверено с CLAUDE.md или аналогом)
- Обработка ошибок нигде не удалена и не ослаблена
- Мёртвый код не остался (неиспользуемые импорты, недостижимые ветки)
- Коллега или агент-ревьюер одобрил бы изменение как чистое улучшение