ЗАЩИТНОЕ И ЗАЩИЩЁННОЕ ПРОГРАММИРОВАНИЕ МОБИЛЬНЫХ ПРИЛОЖЕНИЙ

В настоящее время важную роль в активном развитии отрасли разработки мобильных приложений сыграли компании-гиганты, такие как Apple и Google, выпустившие операционные системы для мобильных устройств и удобные средства разработки и распространения приложений.  Новая модель распространения и использования приложений, появившихся в эпоху мобильного бума, меняет устоявшиеся правила. Большинство мобильных приложений распространяются по принципу AS IS, т.е. в том виде, как его представляет издатель, со своим видением пользовательских сценариев и отказом от ответственности в случае некорректной работы программы. Вместе с этим огромное количество придирчивых пользователей, разнесённых территориально по разным странам и даже континентам, ожидают, что приложение будет качественно выполнять заявленный функционал.

Консультации для создания мобильных приложений на Android

Бизнес в свою очередь ожидает повышения лояльности к бренду, в том числе за счёт нефункциональных параметров системы. Как известно, не бывает программного обеспечения без ошибок, и такие противоречивые требования заставляют разработчиков писать приложения, которые будут гарантировать корректность обработки данных в любых условиях окружения. Задача обеспечения качества и надёжности программного обеспечения ставит под угрозу рентабельность и возможность существования мобильных приложений в бизнес сегменте.

Качество и надёжность программного обеспечения могут быть достигнуты при помощи двух подходов к разработке: защитное или защищённое программирование.

ЗАЩИТНОЕ ПРОГРАММИРОВАНИЕ

Защитное программирование – это стиль написания компьютерных программ, призванный сделать их более отказоустойчивыми в случае возникновения серьезных функциональных отклонений. Обычно подобное незапланированное поведение возникает из-за наличия ошибок в программе.

Принцип работы защитного программирования — если входящие параметры могут быть некорректны, то они должны быть проверены и обработаны таким образом, чтобы функция возвращала некий результат, или просто заканчивала выполнение, даже если основные расчёты невозможно произвести при заданных значениях параметров. К плюсам такого подхода можно отнести тот факт, что приложение, написанное в соответствии с такими требованиями, внешне выглядит очень стабильным, и в случае ошибок не сообщает о них, а только заканчивает выполнение некоторого запрошенного действия. Минусы – в ряде случаев пользователь не может использовать необходимый ему функционал приложения без объяснения причин со стороны приложения, но гораздо хуже то, что защитное программирование скрывает ошибки на этапах разработки и тестирования. На уровне исходного кода методы и функции обрастают всевозможными проверками валидности данных, что в свою очередь ещё сильнее запутывает код, усложняя его дальнейшую поддержку и всё больше препятствуя поиску ошибок. Такой подход можно встретить у программистов, проработавших в данной отрасли десятки лет и видевших различные программно-аппаратные комплексы и проблемы, связанные с разработкой программного обеспечения для таких машин. При попытке переиспользовать исходный код разработчики сталкиваются с невозможностью гарантировать, что вызываемые методы выполняют свою функцию корректно при любом наборе исходных данных, что по цепочке заставляет их писать лишние проверки на входящие и исходящие аргументы, параллельно запутывая свой участок кода.

ЗАЩИЩЕННОЕ ПРОГРАММИРОВАНИЕ

Альтернативой использованию принципов защитного программирования является использование подхода, именуемого «защищённое программирование» или иногда «агрессивное программирование» и «принцип быстрого отказа» (fail fast). Такие подходы декларируют быстрое проявление ошибок как в процессе разработки и тестирования, так и у конечных пользователей приложения.

В последние несколько десятков лет сформировалось достаточно мощное и важное надмножество защищенного программирования, которое иногда называют тем же самым именем, но которое бы следовало называть «защищенное от уязвимостей программирование». Это понятие включается в себя техники и инструменты для разработки программного обеспечения, призванные не допустить появления в разрабатываемых системах потенциальных уязвимостей, которые могли бы быть использованы злоумышленниками для противоправных действий. Для этого направления защищенного программирования есть термины secure software development и secure development lifecycle. Данная тема относится к информационной безопасности и намного шире, чем простое защищенное программирование, в частности защищенное от уязвимостей программирование уделяет огромное внимание контролю полученной от пользователя информации, тогда как для защищённого программирования эта проблема не актуальна.

В основе защищённого программирования лежат следующие принципы:

1) Контрактное программирование (design by contract) – это метод проектирования программного обеспечения, предполагающий, что проектировщик должен определить формальные, точные и верифицируемые спецификации интерфейсов для компонентов системы. Основная идея контрактного программирования – это модель взаимодействия элементов программной системы, основывающаяся на идее взаимных обязательств и преимуществ. Как и в бизнесе, клиент и поставщик действуют в соответствии с определённым контрактом. Контракт некоторого метода или функции может включать в себя:

а) конкретные обязательства, которые любой клиентский модуль должен выполнить перед вызовом метода – предусловия, которые дают преимущество для поставщика – он может не проверять выполнение предусловий;

б) конкретные свойства, которые должны присутствовать после выполнения метода – постусловия, которые входят в обязательства поставщика;

в) обязательства по выполнению конкретных свойств – инвариантов, которые должны выполняться при получении поставщиком сообщения, а также при выходе из метода.

Блок схема разработки по контракту представлена на рисунке 1.

Новый точечный рисунок

Рисунок 1. Схема контракта

Для регистрации и гарантированного выполнения контрактов при разработке Android приложений можно воспользоваться следующими аннотациями:

а) @NonNull и @Nullable, которые позволяют описывать входные и выходные параметры методов. Среда разработки при помощи инструмента Lint автоматически подсветит проблемные места в коде, как например, в случае попытки передать результат выполнения метода, помеченного аннотацией @Nullable, в метод, входной параметр которого принимает @NonNull аргумент. Подобная запись защищает разработчиков от одной из самых распространённых ошибок – ошибки доступа к неинициализированному объекту (null pointer exception) и указывает им на потенциальные ошибки, скрывающиеся в программе.

б) аннотации @DrawableRes, @StringRes указывают на тип ресурса в приложении и в случае попытки получить строку по идентификатору изображения ошибки будут подсвечены.

в) аннотация @IntDef позволяет задавать собственный набор целочисленных значений, допустимых в рамках контекста использования.

Конечно, список сторонних библиотек, позволяющих описывать контакты на языке Java, не ограничивается библиотекой support annotations, но начав пользоваться этими простыми аннотациями, можно значительно уменьшить количество незаметных ошибок.

2) Информирование об обнаруженных нарушениях контрактов. Причем таким способом, который крайне тяжело, а то и невозможно проигнорировать. Например, при попытке вычислить квадратный корень из отрицательного аргумента, нет смысла пытаться вычислить значение в виде вещественного числа. Работу функции нужно прервать и об обнаруженной проблеме нужно сообщить вызывающему коду.

Если язык программирования не позволяет заявить об обнаруженной проблеме так, чтобы это заявление невозможно было проигнорировать, то нормального использования практики защищённого программирования в этом языке не получится. В языке Java, который используется для написания нативных Android приложений, существует инструмент информирования о нарушениях контрактов – исключения (exceptions).

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

При проектировании программного интерфейса модуля следует выяснить, возможна ли обработка исключений внутри этого модуля или её следует делегировать вызывающему коду. Например, мы передаёт строку ‘abc’ в функцию преобразования строки в число. Разумеется, преобразовать такую строку в число невозможно и функция должна сгенерировать исключение, информирующее программиста об этом. Но что, если бы функция сама обработала исключительную ситуацию и вернула некоторое значение по умолчанию? Программист бы не получил информацию о некорректных входных аргументах и, как следствие, некорректном состоянии системы после вызова указанной функции. В таком случае предлагается программисту самостоятельно выбрать способ обработки исключительной ситуации, а чтобы зарегистрировать контракт такого вызова функции, после объявления её сигнатуры следует указать ключевое слово throws и перечислить все исключения, которые программист может получить при вызове описанной функции.

Таким образом, если до регистрации контракта на обработку исключений, функция преобразования строки в число могла иметь такую сигнатуру:

public int parse(String s) {…},

то после регистрации контракта объявление будет иметь следующий вид:

public int parse(String s) throws NullPointerException, NumberFormatException {…},

а с использованием аннотаций:

public int parse(@NonNull String s) throws NumberFormatException {…}.

Компилятор не позволит программисту вызвать функцию parse(), если она находится вне блока try-catch или родительская функция не генерирует NumberFormatException или более общее исключение, а анализатор Lint не позволит передать в функцию значение null.

3) Воспрепятствование возникновению ошибок подразумевает использование таких приемов и практик при кодировании и проектировании, которые исключают целые классы ошибок. Разработчики должны стараться вместо проверки корректности параметров или последовательности вызовов программных интерфейсов строить свой API так, чтобы его можно было использовать единственным способом. Например, если некий модуль предоставляет класс DatabaseConnection, у которого нужно вызвать метод openConnection(), а лишь затем использовать методы query() или insert(), то такой модуль может быть использован неправильно. В результате ошибки кто-то может запросить данные через вызов метода query() до вызова метода openConnection(). Если разработчик программного модуля предоставит класс DatabaseConnection, который позволял бы вызывать только методы query() и insert(), а также определил статическую функцию openConnection(), которая бы возвращала экземпляр класса DatabaseConnection, он явно задекларирует порядок использования этого класса. Таким образом, если нет открытого соединения к базе данных, то нет возможности делать запросы и манипулировать данными.

Ключевыми принципами для успешного использования защищённого программирования являются диагностирование и информирование о проблемах. Если эти два пункта должным образом используются, то защищённого программирования ни в коем случае не скрывает ошибки, как об этом некоторые пытаются говорить. Таким образом, защищённое программирование дает разработчику (именно разработчику, а не пользователю ПО) инструмент как можно более раннего выявления проблем во время выполнения приложения. Учитывая многоэтапную схему внедрения программного обеспечения в эксплуатацию, включающую в себя разработку, тестирование и ревью кода разработчиком, первичная приёмка заказчиком, тестирование приложения на различных конфигурациях устройств на стороне заказчика и окончательный приём сборки, можно заключить, что большая часть критических ошибок будет найдена до момента публикации приложения.

В виду большого разнообразия моделей устройств под управлением операционной системы Android невозможно полностью гарантировать корректную работу приложения в любой ситуации. Мобильные приложения для андроид, вследствие этого, могут работать не корректно. Многие производители устройств модифицируют операционную систему и методы SDK. Вместе с этим модель распространения мобильных приложений через магазины приложений большому количеству пользователей затрудняет сбор статистики использования приложения напрямую с устройств пользователей. На помощь приходят такие системы, как Google Analytics, Yandex Metrica, Flurry, Crashlytics, собирающие метрики использования мобильного приложения и информацию о падениях приложения на устройстве пользователя. Метрики использования приложения помогают бизнесу определить наиболее привлекательные для конечного пользователя аспекты мобильного приложения и направить силы и средства на развитие этих направлений с целью получить максимальную выгоду. Информация о падениях позволяет разработчикам модифицировать приложение, повышая качество и стабильность от итерации к итерации.

Следует отметить, что защищённое программирование может обходиться недешево. И сколько стоит разработка мобильного приложения на андроид, можно только после полного изучения технического задания. Снабжение программного кода должным количеством проверок увеличивает время разработки. Вставка в код необходимых проверок может сказаться и на сложности разработки. Хотя здесь не всё так однозначно. С одной стороны формальная фиксация контрактов для модулей системы заставляет лучше и глубже их прорабатывать. Но, с другой, облегчается использование уже написанных модулей, т.к. их контракты помогают быстрее понять, каким именно образом модуль должен использоваться, а как его применять ни в коем случае нельзя.

Описанный подход к проектированию и разработке программного обеспечения позволяет быстро выявлять ошибки разработчиков приложений и производителей мобильных устройств и оперативно вносить изменения в программный продукт, повышая качество и надёжность работы. Подобная практика успешно внедрена в процесс разработки компании «Глобус-ИТ» и апробирована на нескольких важных проектах, сокращая издержки производителя и заказчика на разных этапах разработки программного обеспечения. Проконсультируйтесь с нашими специалистами, cколько стоит сделать мобильное приложение для андроида?

Globus-47-20150718_092748

Устимов Дмитрий,

Старший Android разработчик ООО «Глобус-ИТ»