Ваше Android-приложение как место преступления

Технический аудит iOS и Android приложений стал неотъемлемой частью нашей повседневной работы в Karumi. Хоть это и выглядит просто, имеется немало деталей реализации такой проверки, которые стоит рассмотреть. В этом документе мы рассмотрим то, что мы считаем наиболее важным при проведении аудита и разделим это по техническим областям.

Система контроля версий

Используется ли система контроля версий, какая система и как организованы рабочие процессы - это многое расскажет о процессе разработки.

  • Есть ли у вас правильно настроенный *ignore файл, чтобы файлы метаданных IDE и другие посторонние элементы не попадали в хранилище?

  • Сторонние библиотеки сконфигурированы в качестве внешних зависимостей или лежат в хранилище?

  • Достаточно ли краткие и выразительные комментарии к коммитам (commits) вы используете?

  • Является ли размер ваших коммитов обоснованным?

  • Все ли файлы в коммите связаны с одной и той же проблемой или функциональностью?

  • Используете ли вы какие-либо схемы ветвления типа “feature branch” или “git-flow”?

  • Достаточно ли информативны названия веток?

  • Используете ли вы систему pull request/code review до слияния кода в master?

    • Есть ли у вас какой-либо регламент относительно того, на что нужно обращать внимание при рассмотрении PR (pull request)?

    • Сколько комментариев в среднем приходится на каждый PR?

    • Сколько людей рассматривает каждый PR?

    • Сколько “+1” к PR вам нужно для слияния?

    • Кто несет ответственность за закрытие ветки?

  • Вы используете release branches для каждого релиза?

  • Как долго открыт процесс подготовки (staging process)?

  • Сколько исправлений вы вносите в release candidate, прежде чем выпустить его?

  • Имеется ли возможность переключиться на точный код какой-либо из опубликованных версий вашего приложения?

  • Сколько исправлений (hotfix) вы выпустили в прошлом году?

  • Вы объединяете (squash) коммиты до слияния (merging) его в master/develop ветку?

  • Готовы ли master/develop ветки к выпуску в любое время?

Инструменты сборки

Определяющим фактором является возможность запустить процесс сборки на машине разработчика и на любой другой внешней системе, например на системе непрерывной сборки (continuous integration).

  • Сколько библиотек используются в проекте?

  • Разделен ли проект на модули?

  • Используют ли модули Maven или Gradle для разрешения зависимостей, или используются локальные .jar-файлы?

  • На опасное ли расстояние приближен проект к лимиту количества методов в .dex файлах? Или уже за гранью?

  • Вы используете библиотеки, которые не нужны в проекте?

  • Используется ли multidex?

  • Все ли внешние зависимости обновлены до современных версий?

  • Все ли лицензии сторонних библиотек соблюдены?

  • Используются ли устаревшие или не поддерживаемые сторонние библиотеки?

  • Обусловлен ли минимальный SDK требованиями технического задания (product description)?

  • Актуален ли целевой SDK (target SDK)?

  • Используется ли ProGuard или любой другой инструмент обфускации (obfuscation - запутывание)? Он включен и правильно настроен?

  • Учетные данные хранилища ключей (keystore credentials) и учетные данные Google Play хранятся в надежном месте?

  • Хранилище ключей приложения (application keystore) и учетные данные хранятся в надежном месте?

  • Должным ли образом настроены build types?

  • Правильно ли используются flavors?

  • Правильно ли настроен релизный тип сборки?

  • Включена ли опция резервного копирования?

  • Включен ли Lint? Он успешно проходит проверку?

  • Имеется ли инструмент статического анализа? Он настроен и успешно проходит проверку?

  • Есть ли Checkstyle? Он настроен и успешно проходит проверку?

  • Правильно ли настроен id приложения и version name/code?

  • Вы используете какую-либо структуру или стратегию версионирования id?

  • Используется ли инструмент непрерывной сборки (continous integration), правильно ли он настроен?

  • Автоматизирован ли процесс выпуска новых версий?

Использование Android ресурсов

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

  • Существуют ли какие-либо недостающие ресурсы для densities, flavors и build types?

  • Требуется ли поддержка приложением экранов с различной плотностью пикселей (density) по техническому заданию?

  • Используются ли в приложении drawable/mipmap, шрифты или векторные ресурсы?

  • Существуют ли какие-либо недостающие переводы?

  • Автоматизирован ли процесс перевода?

  • Какой язык выбран по умолчанию для перевода?

  • Использует ли приложение сторонние шрифты?

  • Использует ли приложение значения конфигурации внутри файла строковых ресурсов?

  • Соблюдается ли соглашение для присвоения однородных имен ресурсам?

  • Есть ли параметры конфигурации, связанные с аппаратными средствами устройства, правильно ли они настроены?

  • Поддерживаются ли планшеты?

Использование Android Layout

Как мы уже говорили ранее, существует широкий спектр Android устройств в мире, каждый из них с собственным размером и плотностью экрана. Определяющим фактором является правильное использование Android Layouts.

  • Возникают ли проблемы с производительностью из-за количество слоев в макетах (layouts) приложения?

  • Используете ли вы темы и стили?

  • Используются ли повторно макеты (layouts) с использованием “include” тега?

  • Вы используете правильный тип группировок в макетах?

  • Учтены ли различные размеры экранов в макетах?

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

  • Списки реализованы с использованием ListView или RecyclerView?

  • Правильно ли используется Android Support Library?

Права доступа

Запрос возможных действий приложения (permissions) увеличивает доверие пользователей к нему, а также расширяет его возможности за счет “прозрачной” интеграции с другими сервисами.

  • Все ли запрашиваемые разрешения (permissions) действительно необходимы?

  • Разрешение используется намеренно?

  • Есть ли отсутствующие разрешения?

  • Если целевой SDK больше, чем 23, то “опасные разрешения” запрашиваются с помощью системы разрешений совместимости (compatibility permissions system)?

  • Разрешение запрашиваются тогда, когда они будут использоваться?

  • Есть ли обратная связь с пользователем, объясняющая, почему какое-либо разрешение необходимо?

Проблемы с безопасностью

Как разработчики, мы должны сознательно относиться к безопасности наших приложений. Мы не хотим, чтобы данные наших пользователей “утекли” или их сессии были украдены.

  • Настроен ли HTTP клиент на использование HTTPS?

  • Настроен ли HTTP клиент на использование встроенного сертификата (certificate pinning) и сообщений аутентификации с HMAC?

  • Сохраняет ли приложение конфиденциальную информацию пользователя? Где?

  • Сохраняет ли приложение информацию вне внутренней системы хранения данных?

  • Логируется (logging traces) ли приложение при сборке релиза?

  • Обфусцирован (obfuscated) ли код приложения?

  • Предоставляет ли ваше приложение поставщика контента (Android content provider), приемника (receiver) или сервис другим приложениям?

  • Отключена ли опция “debuggable” в релизной сборке?

Push уведомления (Push Notifications)

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

  • Используется ли сторонняя библиотека для реализации системы Push-уведомлений?

  • Используется ли система GCM для передачи информации приложению, или просто, чтобы показывать сообщения пользователю?

  • Как ведет себя приложение при получении Push-уведомления?

  • Как ведет себя приложение, когда связанная с Push-уведомлением информация не та, что ожидалась?

  • Показываются ли уведомления с использованием API совместимости (compatibility API)?

Производительность

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

  • Есть ли в приложении какие-либо утечки памяти?

  • Настроен ли в develop сборке какой-либо анализатор памяти, как, например, “LeakCanary”?

  • Android Strict Mode подключен и настроен в develop сборке?

  • Как в приложении используются потоки? Вы используете async tasks, intent services или любые другие сторонние библиотеки?

  • Вызывает ли количество фоновых потоков проблемы с производительностью?

  • Используете ли вы какие-либо политики планировщика (scheduler policy) или просто создаются потоки по требованию?

  • Поддерживаете ли вы Android Doze Mode?

  • Прослушиваются ли события, связанные с состоянием сети или любого другого повторяющегося события от операционной системы?

  • Основной поток используется только для выполнения задач, связанных с кодом пользовательского интерфейса?

  • Имеются ли в приложении какие-либо политики кеширования?

  • Настроен ли клиент HTTP на использование тайм-аута?

  • Настроен ли клиент HTTP на использование GZIP?

  • Работает ли пользовательский интерфейс приложения со скоростью 60 кадров в секунду?

  • Нет ли таких custom view, для которых выделяется слишком большой объем памяти, или которые выполняют “дорогостоящие” задачи в потоке пользовательского интерфейса (UI thread)?

  • Вы тестируете ваше приложение на lower-end (бюджетных, непроизводительных) устройствах?

  • Не “тормозит” ли прокрутка recycler view?

  • Для загрузки изображений используется какая-либо сторонняя библиотека или у вас есть своё собственное решение?

  • Изображения масштабируются под размеры экрана или сразу загружаются под определенный экран устройства?

  • Разумно ли использование памяти?

  • Правильно ли используется модификатор “Static” в Java?

  • Все ли задачи, связанные с обработкой изображений, могут обрабатывать несколько изображений за раз?

  • Работает ли система статистики в фоновом потоке и сконфигурирована ли она с правильным приоритетом?

  • Оптимизируется ли код в релизной сборке?

Структура Java Packages

Хорошая структура пакетов сделает наш код более масштабируемым.

  • Используются ли пакеты для разделения кода по функционалу (features) или концепциям (например, Login vs User)?

  • Используются ли модификаторы видимости Java, чтобы скрыть детали реализации внутри пакетов?

  • Все ли пакеты выходят из корневого пакета?

  • Повторяет ли директория с тестами структуру исходной папки?

  • Организованы ли различные функции (features) с помощью одной и той же структуры пакетов?

  • В правильных ли пакетах лежат классы?

  • Соблюдаются ли соглашения об однородном названии пакетов?

  • Связано ли название корневого пакета с именем компании?

Code Style

Согласованная с точки зрения стиля кодовая база помогает нашим инженерам читать код проще. Инженер читает ГОРАЗДО больше кода, чем пишет, так что это важное понятие.

  • Является ли codestyle однородным?

  • Используете ли вы венгерскую нотацию?

  • Есть ли какой-либо инструмент Checkstyle? Он настроен и работает?

  • Соответствует ли код Java codestyle?

  • Вы используете табуляцию или пробелы?

  • Правильно ли названы классы?

  • Используете ли вы “I” в качестве префикса интерфейсов или “Impl” как суффиксы реализации?

  • Используете ли вы правильные имена переменных?

  • Используете ли вы правильные имена для полей (fields)?

  • Используете ли вы правильные имена для методов?

  • Используются ли атрибуты и модификаторы видимости методов должным образом?

  • Код написан на английском языке?

  • Используете ли вы Javadoc?

  • Вы пишете комментарии к коду?

  • Используете ли вы константы или перечисления (enums), чтобы избежать дублирования литералов?

Offline реализация

Обеспечение хорошей работы в режиме offline является отличительным фактором наших приложений.

  • Может ли приложение использоваться, когда нет подключения к сети Интернет?

  • Каково поведение приложения при медленном сетевом соединении?

  • Какое поведение приложения при потере запроса из-за сбоя в сети?

  • Синхронизируются ли изменения данных приложения с бекэндом после того как соединение было восстановлено?

  • Настроен ли тайм-аут на сетевое соединение?

  • Настроена ли политика кеширования HTTP?

  • Сеанс пользователя восстанавливается автоматически?

Архитектура

Архитектура приложений, с точки зрения кода, является одной из частей аудита, которая дает нам более глубокое представление о приложении. В ходе обзора архитектуры приложения мы будем сосредоточены на понятиях, связанных с S.O.L.I.D и Clean Code принципами.

Реализация слоя представления (Presentation Layer Implementation)

  • Используется ли какой-либо паттерн, связанный с реализацией GUI (графического интерфейса пользователя)? Model View Presenter или Model View ViewModel два из наиболее часто используемых шаблонов для разработки приложений. Правильно ли они реализованы?

  • Связана ли реализация слоя представления (presentation layer) с реализацией вида (view implementation)?

  • Связана ли реализация вида (view implementation) c реализацией модели (model implementation)?

  • Включает ли слой представления бизнес-логику?

  • Правильно ли реализация вида использует Android SDK tools?

  • Используете ли вы сторонние библиотеки для упрощения реализации вида?

  • Разделена ли реализация разных функций по отдельным activities или fragments?

  • Однородно ли поведение пользовательского интерфейса?

  • Используете ли вы custom views для повторного использования кода пользовательского интерфейса?

Реализация предметной области (Domain Implementation)

  • Есть ли отдельный слой предметной области (domain layer) или вся бизнес-логика реализована в слое представления (presentation layer)?

  • Выражены ли правила предметной области и различные требования к приложению в основных объектах бизнес-логики?

  • Реализован ли слой предметной области с применением принципов ООП?

  • Связан ли слой предметной области с Android SDK или любой сторонней библиотекой?

  • Связан ли слой предметной области со слоем представления?

  • Является ли модель предметной области (domain model) “анемичной”?

  • Используете ли вы “толстые” модели предметной области?

  • Основан ли код на слабо связанных и, в то же время, хорошо взаимодействующих друг с другом компонентах?

  • Реализована ли обработка ошибок с использованием исключений или какого-либо другого механизма обработки ошибок?

  • Сопоставляются ли данные (mapped) между различными слоями?

  • Влияет ли архитектура внешних компонентов (например, схемы базы данных или парсинга JSON) на архитектуру модели предметной области?

  • Разработчики злоупотребляют наследованием?

  • Дублируется ли код?

  • Есть ли dependency injection библиотеки или сконфигурированный service locator?

  • Не слишком ли высока сложность классов и методов?

Реализация API

  • Привязана ли реализация API к Android SDK?

  • Происходит ли “утечка” через API каких-либо деталей реализации из-за клиента HTTP или используемой библиотекой для реализации сетевого слоя?

  • API клиент посылает правильные заголовки?

  • Каково поведение API клиента для различных ответов HTTP?

  • API клиента реализует механизм аутентификации?

  • Правильно ли реализован процесс возобновления сеанса?

  • Есть ли поддержка обфускации JSON?

  • Разделена ли реализация API для различных клиентов?

Реализация Хранения

  • Где хранится информация?

  • Используются ли транзакции при чтении и записи информации в хранилище?

  • Надежно ли хранение конфиденциальной информации пользователя?

  • Использует ли слой хранения какие-либо сторонние библиотеки?

  • “Утекают” ли детали реализации через слой хранения?

  • Правильно ли спроектированы tables/schemas?

  • Запросы, отправленные в хранилище, оптимизированы?

  • Используются ли Android SDK APIs для хранения данных в правильном месте? Сохраняются ли данные в базу данных, а настройки (preferences) и небольшие по объему данные в Shared Preferences и файлы на диске?

Тестируемость

  • Есть ли у приложения тесты?

  • Является ли приложение тестируемым?

  • В приложении используются различные виды (unit/integration/end-to-end) тестов?

  • Правильно ли названы тесты?

  • Достаточно ли тесты охватывают проект?

  • Есть ли чрезмерное документирование (overspecification) в тестах?

  • Время выполнения разумно?

  • Не слишком ли низко покрытие кода?

  • Существуют ли какие-либо игнорируемые тесты?

  • Есть ли нестабильные тесты (flaky tests)?

  • Используете ли вы современные фреймворки для тестирования?

  • Существуют ли какие-либо тесты без утверждений (tests without asserts)?

  • Тесты и продакшен код написаны одними и теми же разработчиками?

  • У вас есть команда QA?

  • У вас есть команда QA, автоматизирующая часть тестов?

  • Есть ли у вас какие-либо системы непрерывной интеграции (continuous integration system)?

  • Используете ли вы builders, factories или mothers, чтобы уменьшить затраты на создание некоторых объектов, которые необходимы для тестов?

  • Тесты утверждений (assertions) правильно написаны?

  • Проводите ли вы более одного логического утверждения на тест?

  • У вас есть различные наборы тестов, связанные с одним и тем же проектом?

  • Вы используете различные подходы тестирования для различных частей приложения?

  • Используете ли вы какой-либо monkeyrunner?

  • Следуете ли вы какой-либо TDD или BDD методологии?

  • Используете ли вы Java, чтобы написать тестовые варианты (test cases)?

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

Автор: Pedro Vicente Gómez Sánchez.