Процесс анализа программного кода должен быть максимально автоматизирован. Когда вы создаете запрос на включение изменений, как минимум, вам нужно запустить модульные тесты и статический анализ программного кода в функциональной ветке. Средства автоматизации могут многое рассказать о качестве кода: метрики, покрытие кода модульными тестами, обнаружение дублированных строк и т.д.
Однако есть как минимум 50 вещей, которые нельзя проверить автоматически. Они нуждаются во внимательном взгляде опытного проверяющего (это дает нам хоть какую-то надежду на то, что роботы не заменят разработчиков в ближайшем будущем).
Требования
- Программный код реализует все функциональные требования, которые необходимы заказчику?
- Программный код удовлетворяет всем нефункциональным требованиям, таким как производительность и безопасность? Если нефункциональные требования не были упомянуты заказчиком, то этот вопрос необходимо уточнить у проектировщика или у самого заказчика.
Условия сопровождения
- Помещены ли все интерфейсы, классы и т.д. на соответствующий прикладной уровень в соответствии с архитектурой Onion/Clean?
- Не изобретаете ли вы колесо, когда пишете программный код? Можно ли его заменить чем-то, что уже существует и что предоставляет какая-либо сторонняя библиотека?
- Есть ли уже реализованная логика или какие-то ее фрагменты в кодовой базе?
- Правильно ли была выбрана область жизненного цикла для интерфейса и реализации в контейнере внедрения зависимостей?
- Являются ли реализованные функции детерминированными (то есть всегда ли они выдают один и тот же результат для одних и тех же входных данных)?
- Все ли зависимости явно внедряются через конструктор типов?
- Есть ли сильная связанность между классами, которая может затруднить повторное использование кода?
- Используются ли объекты-значения вместо элементарных типов данных для того, чтобы избежать проблемы одержимости элементарными типами?
- Соответствуют ли реализованные компоненты, такие как функции, классы, интерфейсы и модули, принципу единственной обязанностей?
- Расширяются ли существующие функциональные возможности при помощи декораторов, технологий аспектно-ориентированного программирования (принципа открытия-закрытия) или они модифицируются на месте?
- Правильно ли реализованы механизмы синхронизации потоков при доступе к объектам-одиночкам в веб-приложениях?
- Используются ли по возможности неизменяемые типы данных вместо изменяемых для того, чтобы избежать побочных эффектов?
- Добавлена ли функция ведения журнала с верными уровнями ведения протокола в основные места кода, которые требуют отслеживания?
Производительность
- Правильно ли были выбраны структуры данных? Например, используется ли структура Hashtable вместо массива, когда нужно часто искать значения, для того, чтобы избежать линейного поиска?
- Распараллелены ли длительные операции между всеми доступными ядрами для того, чтобы использовать ресурсы компьютера максимально эффективного?
- Выполняет ли программный код большое количестве операций по выделению памяти для объектов в куче, оказывая тем самым дополнительную нагрузку на программу сборки мусора?
- Кэшируются ли данные, которые были считаны из базы данных, локально или в удаленном кэше?
- Сколько раз текущий код обращается к базе данных? Возможно стоит получить все данные за одно или несколько обращений?
- Выполняет ли код все обращения к базе данных, ввод-вывод и другие блокирующие вызовы асинхронно?
- Использует ли код пул потоков по максимуму вместо того, чтобы создавать новые потоки?
- Правильно ли выбран баланс между нормализацией и денормализацией при создании дополнительных таблиц базы данных?
- Правильно ли добавляются или исправляются индексы, если запрос на включение изменений содержит новые SQL-запросы?
- Возникает ли проблема с N+1 запросами при извлечении данных из базы данных при помощи фреймворка ORM?
- Установлен ли правильный уровень изоляции транзакций в хранимых процедурах?
- Возвращают ли SQL-запросы избыточные данные из базы данных, которые не требуются для кода приложения? Используется ли что-то вроде SELECT * или что-то подобное?
Модульное и интеграционное тестирование
- Полностью ли модульные тесты покрывают дополнительную логику?
- При появлении исправлений в логике, появляются ли изменения в соответствующем модульном тесте?
- Всегда ли все реализованные модульные или другие виды тестов ведут себя детерминировано? Например, приостанавливают ли они выполнение потока на какой-то определенный период времени перед утверждением (что по своей сути является ошибочным шаблоном)?
- Все ли модульные тесты реализованы в соответствии с принципами F.I.R.S.T.?
- Есть ли какие-либо признаки проблем в модульном тестировании, такие как проблемы с логикой проверки условий, рулеткой с утверждениями, дублированием утверждений и другие?
- Добавлен ли интеграционный тест, как минимум, для happy-path-сценария (сценария счастливого пути) реализованной функции?
- Все ли зависимости тестируемого объекта имитируются для того, чтобы модульный тест случайно не превратился в интеграционный и не выполнился быстрее положенного?
- Изолированы ли модульные и интеграционные тесты друг от друга?
Конечные точки API
- Выбираются ли HTTP-команды, такие как GET, POST, PUT, DELETE и другие, в соответствии с действием их конечной точки?
- Отвечает ли каждая конечная точка API за выполнение лишь одной бизнес-операции? Или все же нескольких?
- Возвращает ли конечная точка API правильный код состояния? Например, не возвращает ли она код 401 вместо 500 при несанкционированном запросе?
- Сжимаются ли объемные ответы перед их отправкой вызывающей стороне?
- Защищены ли конечные точки API политиками аутентификации и авторизации?
- Позволяет ли API, который возвращает большой список объектов, фильтровать его и разбивать на страницы?
- Является ли конечная точка API GET идемпотентной?
- Используются ли имена существительные вместо глаголов в именах конечных точек API?
Критические изменения
- Имеются ли в конечной точке API такие критические изменения, как переименование API, удаление или переименование его параметров?
- Имеются ли критические изменения в полезных данных сообщения (в случае, если используется брокер сообщений), например, удаление или переименование его свойств?
- Повлияют ли такие изменения в схеме базы данных, как удаление столбцов или таблиц, на другие службы системы?
Системная среда
- Насколько загружен ЦП и сколько оперативной памяти потребляет код при выполнении запроса на включение изменений? Будет ли в средах, в которых будет развернут код (среда тестирования, среда приёмочного пользовательского тестирования, производственная среда), достаточно мощный процессор и достаточный объем оперативной памяти для эффективного выполнения кода?
- Будет ли реализованная логика, алгоритмы, структуры данных и т.д. работать достаточно быстро на большом наборе данных, который может быть в производственной среде?
Документация
- Была ли изменена документация для того, чтобы отразить новые изменения программного кода (документация API, документация по структуре, проектная документация)?
- Создается ли тикет технических недоработок, если запрос на внесение изменений содержит неэффективный или «грязный» код, который сейчас невозможно перестроить из-за недостаточного количества времени?
Заключение
Количество пунктов, на которых проверяющий должен заострить свое внимание, зависит от конкретного проекта и даже от конкретного запроса на внесение изменений. Ваш с коллегами мозговой штурм (если вы примите во внимание вышеприведенные пункты) может значительно снизить риск того, что вы забудете о чем-то важно при анализе программного кода.