Краткий рассказ о разработке SDK для Android. Часть 2.

В предыдущей части серии я поделился с вами своим опытом разработки под Android и некоторыми полезными приемами, которые я использую во время работы в Estimote. У меня есть еще пара соображений, которые могут быть вам полезны при разработке собственного SDK/библиотеки/приложения.

Мы остановились, когда бармен скрылся за дверями. Вы сидите в милой таверне, полной счастливых людей, потягиваете крепкое темное пиво с прекрасной пеной и ждете, когда вернется бармен и продолжит свой рассказ о восстановлении города Эсдикья.

Псс… вот вам еще тавернная музыка, чтобы обогатить чтение!

1. Рассмотрите возможность использования Dagger 2 для внедрения зависимостей

«Я вернулся, мой дорогой искатель приключений, и я вижу, что ты все еще здесь и ждешь продолжения истории, да? Надеюсь, вам понравится пиво!» Он взял тряпку для посуды и начал полировать стойку. «Итак, мы говорили о том, как строители сделали все необходимое для модернизации всего города Эсдикья. Я рассказал тебе, странник, обо всех приготовлениях, инструментах, которые они использовали, и проектах. Теперь самое время поговорить о самой работе». Он почесал свою густую черную бороду. «Вы должны знать, мой дорогой незнакомец, что работы по реконструкции города были разделены на отдельные части. Самым умным было отделить людей, обрабатывающих материалы, таких как каменотесы или столяры, от фактической рабочей зоны. Они были членами нескольких гильдий, занимались проектированием, предварительным изготовлением и доставкой материалов, необходимых рабочим, на фактическую строительную площадку». Бармен зевнул.

Должен сказать, что поначалу я не хотел использовать Кинжал 2. Идея показалась мне действительно непонятной. Оказывается, Dagger 2 — замечательный инструмент:

  1. Это позволяет вам контролировать время жизни объектов в вашем коде.
  2. Это позволяет вам отделить создание экземпляра класса от реальных вариантов использования.
  3. Это заставляет вас писать гораздо более несвязанный код, что упрощает модульное тестирование.

Вы можете прочитать отличные статьи Евгения Мацюка о Dagger 2, чтобы понять его основы.

Вся идея сосредоточена на трех основных концепциях:

  • МОДУЛЬ — фабрика объектов из похожего домена. Помните бармена, говорящего о Гильдии каменотесов? Они предоставляют различные вещи, сделанные из камня. Это та же концепция.
  • КОМПОНЕНТ — группа модулей, которые совместно обеспечивают определенную функциональность. Вы можете думать об этом как о надсмотрщике, который говорит: «Гильдия столяров, гильдия каменщиков и гильдия кузнецов — теперь вы будете работать вместе, чтобы отремонтировать нашу ратушу. Мистер… эээ… Мистер. MainActivity будет следить за вашей работой. Какое странное имя!»
  • ОБЛАСТЬ — это аннотация, которую вы используете для определения срока действия любого конкретного объекта. Например, @MainActivityScope или @ProximityObserverScope.

Итак, в основном идея состоит в том, чтобы создавать компоненты Dagger 2 внутри основных фабрик вашего SDK — например, SensorDataProviderFactory. Взгляните на пример ниже:

Я действительно рекомендую вам попробовать использовать Dagger 2 — он выглядит сложным, поэтому мне потребовалось немало времени, чтобы понять его, но оно того стоит! Если у вас есть сомнения по поводу размера вашей кодовой базы — Dagger 2 выполняет большую часть своей работы во время компиляции. Есть несколько дополнительных сгенерированных классов, но:

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

2. Создайте гибкий API с интерфейсами, фабриками и свободными сборщиками.

«О, я вижу, ты только что допил свое пиво, незнакомец. Позволь мне налить тебе еще, потому что история продолжается, — сказал он, хватая твою пустую кружку. «Итак, строители наняли работника особого типа, называемого «беглым строителем». Вам, наверное, интересно, какова была его ответственность? Ну, он мог бегло перевести потребности клиентов на инженерный язык».

Разработка API для SDK сильно отличается от создания приложения. Другие разработчики будут использовать ваши классы и читать вашу документацию. Ваша задача — предоставить им максимально простой опыт. Вы должны задать себе эти три важных вопроса:

  1. Какова основная цель моего SDK?
  2. Как люди будут взаимодействовать с моим кодом?
  3. Что я хочу, чтобы они могли изменять?

А затем начните разрабатывать свой API. Я предпочитаю концепцию дизайна сверху вниз, когда вы сначала создаете API, а затем пишете реализацию. Хотя я бы сказал, что иногда лучше иметь гибридную версию, где нужно знать, как поведет себя система, чтобы спрогнозировать возможный API. В отличие от снизу вверх, вы не получите беспорядочный, единый класс для всех, связанный фасадный объект, и вы сохраните видимость и связность вашего API.

Пусть ваш API работает с интерфейсами, а не с конкретными классами.

Если вы не разрабатываете библиотеку, тесно связанную с Android Framework (например, настраиваемые представления или анимацию), вам следует использовать интерфейсы вместо необработанных классов. Не позволяйте пользователям создавать экземпляры классов с помощью конструкторов (например, оператора new в Java). Вместо этого предоставьте им объект factory или builder, и вот почему:

  1. Паттерн Фабрика не блокирует ваше дальнейшее развитие. При необходимости вы можете добавить дополнительные методы для новых типов объектов.
  2. Паттерн Factory скрывает ваши объекты за интерфейсами, поэтому вы можете изменить реализацию в будущем, не нарушая пользовательский код.
  3. Если вы решите сломать API ваших интерфейсов, вы можете добавить класс адаптера для старого интерфейса, чтобы ваш код был обратно совместим.

Используйте беглых конструкторов — они крутые!

Я большой поклонник беглых строителей. Они обеспечивают простой способ создания объектов, а благодаря поддержке автодополнения IntelliJ вы можете создавать великолепные контекстные области видимости для параметров. Посмотрите, какой классный опыт:

Хотя это не единственный шаблон проектирования, который люди используют. Некоторые люди могут захотеть использовать простые объекты с getters и setters , поэтому вы можете рассмотреть возможность добавления API-интерфейса Fluent Builder в качестве дополнительной функции, предоставив им самим решать, какой шаблон использовать.

3. Проверьте свой код!

«Вы должны знать, мой приятель-авантюрист, что конструкторы взялись за дело и сосредоточились на качестве своей работы. Никакой спешки. Никаких импровизаций. Каждая деталь, предоставленная гильдиями, должна была быть прочной и полностью протестированной».

Любой, кто отправил код в производство, не протестировав его должным образом,
поднимите руку.

*поднимает руку*

Да, я сделал это. И это научило меня больше так не делать. Интересно, почему?

Во-первых, это как заниматься сексом без предохранения (если, конечно, вы не хотите иметь ребенка). Если вам нравится жить на грани — дерзайте. Но если вам глубоко небезразлично качество вашего ночного сна, вам лучше начать писать юнит-тесты.

Во-вторых, всегда помните, что ваш код будет развиваться. Это почти никогда не бывает на одну ночь. Это долгие отношения, о которых вам нужно заботиться. Со временем вы начнете добавлять новый код и модифицировать существующий — и как вы будете уверены, что старый функционал останется прежним? Конечно, вы попадете в порочный круг, когда добавление нового кода приведет к рассеиванию ошибок в существующем коде. Вы будете отчаянно пытаться исправить их, в результате чего появятся новые. «И это продолжается и продолжается, и продолжается, и в раю, и в аду».

С другой стороны, очень сложно покрыть тестами 100 % кода. Иногда у вас есть классы, тесно связанные с внешней структурой, иногда это просто тип взаимодействия, который слишком сложно имитировать; но в большинстве случаев, если вы пишете свой код с учетом принципов SOLID, вы сможете разрабатывать элегантные модульные тесты с высоким охватом.

Независимо от того, насколько вы продвинуты, у меня есть пара советов, которые могут оказаться для вас полезными. Я использую их все в своей повседневной работе.

Да прибудет с вами ТДД

Вы когда-нибудь слышали о TDD? Это означает Разработка через тестирование и предназначена для людей, одержимых тестированием и качеством. Должен сказать, что я действительно хочу когда-нибудь достичь чистого TDD, но применить эту технику в динамической среде, такой как стартап, довольно сложно. Вот почему я предпочитаю более гибридную версию.

Итак, идея состоит в том, чтобы максимально использовать круг RED, GREEN, REFACTOR.
Это предполагает другой подход — сначала вам нужно написать тест, а затем реализовать свой класс, чтобы пройти этот тест. Давайте подробно рассмотрим:

Во-первых, этап RED. Вы начинаете с реализации необработанных тестов для проверки функциональности вашего класса. Потом запускаешь тесты — бум, ни один не прошел! Вот почему она называется КРАСНОЙ фазой.

Во-вторых, ЗЕЛЕНЫЙ этап. Вы реализуете фактический класс, чтобы он прошел ваши тесты. Не беспокойтесь об элегантном коде прямо сейчас. Нажмите кнопку. Все тесты пройдены? Вот почему она называется ЗЕЛЕНОЙ фазой.

В-третьих, этап РЕФАКТОРА. Теперь вы можете провести рефакторинг своего кода. Сделайте это красиво. Сделайте это удивительным. Гордитесь этим!

Наконец, вы возвращаетесь к КРАСНОЙ фазе. Попробуйте добавить больше тестов, чтобы сделать ваш код более надежным, и повторяйте круг, пока не будете довольны результатами.

Используйте обратные галочки Kotlin, чтобы сделать имена тестов более читабельными

Это забавно — Kotlin позволяет использовать обратные кавычки для объявления имени функции. Я использую это, чтобы сделать названия моих тестов более читабельными, и я могу написать там почти все, что захочу! Это намного лучше, чем использование стандартного верблюжьего регистра или подчеркивания:

@Test
fun `returns zero for negative input`(){
  ...
}

Отказ от ответственности: Android Studio, вероятно, выделит эти имена как ошибку. Он все равно скомпилируется, но если вы хотите, вы можете отключить эту ошибку в настройках.

Используйте шаблон ДАННЫЙ-КОГДА-ТО

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

@Test
fun `returns zero for negative input`(){
  // GIVEN  
  val operator = UsefulOperator()
  // WHEN
  val returnedValue = operator.compute(-1)
  // THEN
  assertThat(returnedValue).equals(0)
}

Рассмотрите возможность использования AssertJ для быстрых утверждений

AssertJ — замечательная библиотека, позволяющая свободно создавать утверждения. Он будет адаптироваться к типу данного объекта, предоставляя вам набор удобочитаемых утверждений. Посмотрите на пример из документов AssertJ:

// collection specific assertions (there are plenty more) 
// in the examples below fellowshipOfTheRing is a
// List<TolkienCharacter> 
assertThat(fellowshipOfTheRing)
                           .hasSize(9)
                           .contains(frodo, sam)
                           .doesNotContain(sauron);

Избегайте использования Robolectric

Robolectric — это фреймворк модульного тестирования, который дает вам набор фиктивных классов фреймворка Android для запуска ваших тестов без эмулятора. Ну, это в основном все равно, что сделать дыру в вашем корабле, а затем попытаться починить ее с помощью различных инструментов. Некоторые из них могут быть эффективными, но зачем вообще делать дыру?

Всякий раз, когда ваши модульные тесты требуют, чтобы вы имитировали какой-либо класс фреймворка Android, вы, вероятно, делаете что-то не так.

Вы хотите протестировать свой код, а не стороннюю платформу. Лучше изолируйте свою кодовую базу от любого внешнего кода/классов/объектов с помощью интерфейсов и абстракций. Таким образом, вам в конечном итоге придется издеваться над своими собственными интерфейсами, а не над внешними вещами.

Резюме

  1. Сделайте модуль ответственным за создание всех объектов, используемых в вашей системе — использование Dagger 2 может быть очень полезным! Это позволит вам контролировать время жизни ваших объектов и сделает вашу библиотеку менее подверженной утечкам памяти.
  2. Дважды подумайте о своем API. Это главная страница вашего SDK/библиотеки. Спрячьте столько, сколько вы можете за интерфейсами. Используйте фабрики для создания объектов
    и рассмотрите возможность использования быстрых компоновщиков для настраиваемых параметров.
  3. Всегда не забывайте тестировать свой код. Эта дополнительная работа сэкономит вам много работы в будущем — вы будете уверены, что ваши новые функции не сломают старый функционал. TDD может резко повысить качество вашего кода.

«О, мне нужно принести еще одну бочку пива, текущая пуста. Не могли бы вы немного подождать меня? Я скоро вернусь с новой историей!»

Надеюсь, вы узнали что-то полезное — Часть 3 ждет вас!
Мне было очень весело писать эту историю, надеюсь, вы испытаете те же чувства, читая ее :)

Если у вас есть какие-либо комментарии, отзывы или вопросы, не стесняйтесь спрашивать меня! Вы можете связаться со мной по электронной почте: [email protected],
подписывайтесь на меня в Twitter, Github или LinkedIn,
или просто задавайте вопросы прямо в комментариях :)