Качество кода
Качество кода - это возможность понимать, проверять и изменять его без лишнего риска. Хороший код не просто «чистый» или стилистически красивый. Он должен работать правильно, выражать намерение, соответствовать архитектуре и оставаться удобным для тестирования и сопровождения.
Эти принципы также используются во время ревью кода, где оценивают и текущее поведение, и стоимость будущих изменений.
Что такое качество кода?
Качество зависит от контекста. Одноразовой миграции, небольшому внутреннему инструменту и долгоживущей функции Android-приложения не нужны одинаковые структура и тестовое покрытие. Цель - выбрать простейший дизайн, который делает важное поведение явным и сохраняет вероятные изменения безопасными.
Полезно задать следующие вопросы:
- Правильно ли работает код в обычных, ошибочных и связанных с жизненным циклом сценариях?
- Поймёт ли другой разработчик намерение без восстановления скрытых предположений?
- Понятны ли обязанности компонентов и владение состоянием?
- Можно ли проверить важное поведение без запуска всего приложения?
- Останется ли вероятное изменение локальным или затронет несвязанные модули?
Ни одна отдельная метрика не отвечает на все эти вопросы.
Читаемость
Читаемый код делает намерение видимым. Разработчик должен понимать, что делает код, зачем он существует и кто владеет изменяемым состоянием, не собирая сначала мысленную модель из низкоуровневых операций.
Читаемости обычно помогают:
- имена, описывающие смысл предметной области, а не детали реализации;
- функции с одной связной ответственностью;
- предсказуемый поток управления и явные побочные эффекты;
- небольшая вложенность и ранняя обработка недопустимых состояний;
- единые соглашения Kotlin по форматированию и именованию;
- комментарии, объясняющие ограничения и решения, а не каждую строку.
Маленькие функции не становятся автоматически понятными. Разделение простой операции на множество переходов может скрыть настоящий ход выполнения. Предпочитайте ясную единицу поведения произвольному ограничению по числу строк.
Сопровождаемость и границы
Сопровождаемость означает, что изменение можно внести, не ломая несвязанное поведение. Она зависит от связных обязанностей, ограниченного зацепления, стабильных контрактов, полезных тестов и ясного владения.
В Android разделяйте части, которые меняются по разным причинам:
- интерфейс отображает состояние и передаёт действия пользователя;
- владелец состояния координирует поведение экрана;
- доменная логика выражает правила продукта, когда такое выделение приносит пользу;
- репозитории задают границы доступа к данным;
- источники данных работают с конкретными сетевыми, локальными или платформенными API.
Это рекомендации, а не требование создавать каждый слой для каждой функции. Небольшой функции может быть не нужен отдельный доменный слой. Дополнительное перенаправление оправдано, когда изолирует изменчивость, позволяет повторное использование, упрощает тестирование или проясняет владение.
Не прячьте важное поведение в глобальном изменяемом состоянии, глубоком наследовании, неочевидных обратных вызовах или неявных предположениях о жизненном цикле. Граница полезна, только если её контракт яснее заменённого кода.
Технический долг
Технический долг - это осознанный компромисс или накопленная сложность, которые делают будущую работу медленнее или рискованнее. Он появляется из-за нехватки времени, изменившихся требований, временных решений, отсутствующих тестов, устаревших зависимостей или дизайна, который больше не соответствует продукту.
Не всякий долг является ошибкой. Осознанное упрощение может быть разумным, если понятны его область, риск и предполагаемый срок жизни. Опаснее неуправляемый долг: он невидим, регулярно удивляет команду или распространяется по критическим участкам.
Для значимого долга зафиксируйте:
- текущее ограничение и его влияние;
- причину принятого компромисса;
- владельца или затронутую область;
- условие возврата к проблеме, например следующую функцию в этом коде;
- конкретный план удаления или локализации, если он известен.
Не превращайте каждое несовершенство в задачу. Отслеживайте долг, который влияет на риск, скорость разработки, надёжность, безопасность или стоимость запланированной работы.
YAGNI и абстракции
YAGNI означает «You Aren't Gonna Need It»: не добавляйте возможности, точки расширения или абстракции до появления конкретной потребности.
Интерфейс с одной реализацией не обязательно ошибочен. Он может представлять настоящую границу, изолировать внешний API или обеспечивать детерминированные тесты. Он вызывает сомнения, если лишь повторяет другой тип, не имеет собственного контракта и существует ради гипотетической будущей реализации.
Перед добавлением абстракции спросите:
- Какую существующую вариативность или зависимость она изолирует?
- Делает ли она вызывающий код проще?
- Можно ли точно назвать её контракт?
- Будет ли существенно сложнее добавить её позже?
Проектируйте под известные требования и сохраняйте возможность рефакторинга, когда появятся основания.
Запахи кода
Запахи кода - это признаки возможных проблем с дизайном или сопровождением, а не доказательство ошибки и не автоматическая команда переписать код.
Типичные примеры:
- длинные методы или большие классы с несколькими причинами для изменения;
- дублирование продуктовых правил;
- глубокая вложенность или множество логических флагов;
- зависть к данным другого объекта и неясное владение;
- примитивные значения, скрывающие ограничения предметной области;
- изменения, требующие правок во множестве несвязанных файлов;
- скрытые побочные эффекты и зависящие от порядка обратные вызовы;
- абстракции, которые усложняют навигацию, но не уменьшают сложность.
Перед исправлением оцените контекст. Дублирование двух похожих строк может быть дешевле общей абстракции, связывающей независимые функции. Но дублированные бизнес-правила способны привести к разному поведению и заслуживают раннего внимания.
Безопасный рефакторинг
Рефакторинг изменяет внутреннюю структуру, сохраняя внешне наблюдаемое поведение. Он должен делать следующее изменение безопаснее или текущее поведение понятнее.
Практическая последовательность:
- Определите поведение, которое должно остаться неизменным.
- Добавьте или найдите тесты на изменяемой границе.
- Вносите по одному структурному изменению.
- После каждого шага запускайте целевые проверки.
- Убедитесь, что сложность уменьшилась, а не просто переместилась.
Примеры: вынести преобразование данных из ViewModel, разделить большой процесс на именованные шаги, убрать доступ к данным из интерфейса, объединить дублированные правила или сделать владение состоянием явным.
Не объединяйте масштабное переписывание, изменение поведения, обновление зависимостей и форматирование в одном ревью, если их можно разделить. В небольших изменениях проще заметить регрессии и оценить проектные решения.
Автоматические проверки качества
Автоматизация должна находить повторяемые проблемы до того, как ревьюер потратит на них внимание:
Обычный конвейер Android-проекта сочетает компиляцию, модульные тесты, Android Lint, единое форматирование и настроенный для проекта статический анализ. Инструментальные и интеграционные тесты добавляются там, где их покрытие рисков оправдывает стоимость.
Android Lint находит проблемы корректности, безопасности, производительности, доступности, локализации и использования Android API. Запускайте его явно в CI: он не является автоматической частью каждой сборки Gradle. Настройте важность правил под проект и подавляйте срабатывание только узко и с зафиксированной причиной.
В существующем проекте с большим числом нарушений baseline позволяет блокировать новые проблемы и постепенно сокращать старые. Baseline помогает при миграции, но не доказывает безвредность записанных проблем. Не пересоздавайте его только ради зелёного CI.
Метрики и компромиссы
Покрытие, сложность, дублирование, число предупреждений и время сборки помогают замечать тенденции, но остаются косвенными показателями.
- Высокое покрытие не доказывает, что важное поведение действительно проверяется.
- Низкая сложность не гарантирует хорошую модель предметной области.
- Нулевое дублирование может указывать на преждевременную абстракцию.
- Большее число слоёв не означает автоматически лучшую архитектуру.
Используйте метрики как повод для исследования и сравнивайте изменения во времени. Сочетайте их с производственными сигналами: сбоями, ANR, ушедшими в релиз дефектами, частотой откатов и временем выполнения типичных изменений.
Частые ошибки
- Считать предпочтения форматирования главным показателем качества.
- Добавлять архитектурные слои без конкретной ответственности.
- Оптимизировать процент покрытия вместо покрытия рисков.
- Подавлять статический анализ без объяснения причины.
- Совмещать рефакторинг и изменение поведения в одном большом и сложном для ревью наборе правок.
- Оставлять временные решения без владельца и условия пересмотра.
- Механически исправлять запах кода без оценки контекста.
- Переписывать стабильный код только из-за появления нового шаблона.