Большинство проектов, в которых я участвую, используют несколько компонентов с открытым исходным кодом. Как общий принцип, всегда ли полезно избегать привязки всех компонентов кода к сторонним библиотекам и вместо этого проходить через инкапсулирующую оболочку, чтобы избежать проблем с изменениями?
В качестве примера, большинство наших проектов PHP напрямую используют log4php в качестве каркаса ведения журнала, то есть они создают с помощью \ Logger :: getLogger (), они используют методы -> info () или -> warn () и т. Д. В будущем однако может появиться гипотетическая структура ведения журнала, которая в некотором смысле лучше. В настоящее время все проекты, которые тесно связаны с сигнатурами метода log4php, должны были бы измениться в десятках мест, чтобы соответствовать новым сигнатурам. Это, очевидно, будет иметь большое влияние на кодовую базу, и любое изменение является потенциальной проблемой.
Для того, чтобы создавать перспективные новые кодовые базы из этого сценария, я часто рассматриваю (и иногда реализую) класс-оболочку для инкапсуляции функций ведения журналов и облегчения, хотя и не надежного, изменения способа ведения журналов в будущем с минимальными изменениями. ; код вызывает обертку, обертка передает вызов в структуру ведения журнала du jour .
Принимая во внимание, что есть более сложные примеры с другими библиотеками, я слишком перегружен или это мудрая мера предосторожности в большинстве случаев?
РЕДАКТИРОВАТЬ: Больше соображений - использование внедрения зависимостей и удвоения теста практически требует, чтобы мы в любом случае абстрагировали большинство API («Я хочу проверить, выполняет ли мой код и обновляет ли его состояние, но не писать комментарии в журнале / обращаться к реальной базе данных»). Разве это не решающий фактор?
источник
Ответы:
Если вы используете только небольшое подмножество стороннего API, имеет смысл написать обертку - это помогает с инкапсуляцией и сокрытием информации, гарантируя, что вы не предоставите потенциально огромный API для своего собственного кода. Это также может помочь убедиться, что все функции, которые вы не хотите использовать, «скрыты».
Еще одна веская причина для обертки, если вы ожидаете изменить стороннюю библиотеку. Если это часть инфраструктуры, которую, как вы знаете, вы не измените, не пишите обертку для нее.
источник
Не зная, какие супер-великолепные новые функции будет у этого предполагаемого будущего улучшенного регистратора, как бы вы написали обертку? Наиболее логичный выбор - чтобы ваша оболочка создавала какой-то класс logger и имела такие методы, как
->info()
или->warn()
. Другими словами, по сути идентичный вашему нынешнему API.Вместо того, чтобы писать код на будущее, который мне, возможно, никогда не понадобится изменить, или который в любом случае может потребовать неизбежного переписывания, я предпочитаю «код на будущее». То есть, в тех редких случаях , когда я существенно изменить компонент, это когда я пишу обертку , чтобы сделать его совместимым с прошлым кодом. Тем не менее, любой новый код использует новый API, и я реорганизую старый код, чтобы использовать его всякий раз, когда я вносю изменения в тот же файл в любом случае, или как позволяет расписание. Через несколько месяцев я могу снять обертку, и изменение было постепенным и надежным.
Иными словами, обертки действительно имеют смысл только тогда, когда вы уже знаете все API, которые вам нужно обернуть. Хорошие примеры: если ваше приложение должно поддерживать множество различных драйверов баз данных, операционных систем или версий PHP.
источник
Оборачивая стороннюю библиотеку, вы добавляете дополнительный слой абстракции поверх нее. Это имеет несколько преимуществ:
Ваша кодовая база становится более гибкой к изменениям
Если вам когда-нибудь понадобится заменить библиотеку другой, вам нужно всего лишь изменить реализацию в вашей обертке - в одном месте . Вы можете изменить реализацию оболочки и не нужно ничего менять ни о чем другом, другими словами, у вас есть слабо связанная система. В противном случае вам придется пройти через всю кодовую базу и везде вносить изменения - что явно не то, что вам нужно.
Вы можете определить API оболочки независимо от API библиотеки
Разные библиотеки могут иметь совершенно разные API, и в то же время ни одна из них не может быть именно тем, что вам нужно. Что если какой-то библиотеке нужен токен, который будет передаваться при каждом вызове? Вы можете передавать токен в своем приложении везде, где вам нужно использовать библиотеку, или вы можете хранить его где-то более централизованно, но в любом случае вам нужен токен. Ваш класс-оболочка снова делает все это простым - потому что вы можете просто хранить токен внутри своего класса-оболочки, никогда не подвергая его воздействию какого-либо компонента в вашем приложении, и полностью абстрагировать его от необходимости. Огромное преимущество, если вы когда-либо использовали библиотеку, которая не подчеркивает хороший дизайн API.
Модульное тестирование намного проще
Модульные тесты должны проверять только одну вещь. Если вы хотите выполнить модульное тестирование класса, вы должны смоделировать его зависимости. Это становится еще более важным, если этот класс выполняет сетевые вызовы или получает доступ к другому ресурсу вне вашего программного обеспечения. Оборачивая стороннюю библиотеку, легко смоделировать эти вызовы и вернуть тестовые данные или все, что требуется для этого модульного теста. Если у вас нет такого уровня абстракции, это становится гораздо труднее сделать - и в большинстве случаев это приводит к большому количеству уродливого кода.
Вы создаете слабосвязанную систему
Изменения в вашей оболочке не влияют на другие части вашего программного обеспечения - по крайней мере, до тех пор, пока вы не измените поведение своей оболочки. Вводя слой абстракции, такой как эта обертка, вы можете упростить вызовы в библиотеку и почти полностью удалить зависимость вашего приложения от этой библиотеки. Ваше программное обеспечение будет просто использовать оболочку, и не будет иметь значения, как реализована оболочка или как она делает то, что делает.
Практический пример
Будем честны. Люди могут спорить о преимуществах и недостатках чего-то подобного часами - вот почему я просто хочу показать вам пример.
Допустим, у вас есть какое-то приложение для Android, и вам нужно загрузить изображения. Существует множество библиотек, которые делают загрузку и кэширование изображений быстрым, например, Picasso или Universal Image Loader .
Теперь мы можем определить интерфейс, который мы собираемся использовать, чтобы обернуть любую используемую нами библиотеку:
Это интерфейс, который мы теперь можем использовать во всем приложении, когда нам нужно загрузить изображение. Мы можем создать реализацию этого интерфейса и использовать внедрение зависимостей, чтобы внедрить экземпляр этой реализации везде, где мы используем
ImageService
.Допустим, мы изначально решили использовать Пикассо. Теперь мы можем написать реализацию, для
ImageService
которой внутри используется Пикассо:Довольно прямо, если вы спросите меня. Обертывание вокруг библиотек не должно быть сложным, чтобы быть полезным. Интерфейс и реализация содержат менее 25 комбинированных строк кода, поэтому создать его было практически невозможно, но мы уже кое-что добились благодаря этому. Видите
Context
поле в реализации? Платформа для внедрения зависимостей по вашему выбору уже позаботится о внедрении этой зависимости, прежде чем мы когда-либо будем использовать нашеImageService
, теперь вашему приложению не нужно заботиться о том, как загружаются изображения и какие зависимости у этой библиотеки могут быть. Все, что видит ваше приложение, - этоImageService
когда ему нужно изображение, к которому он обращаетсяload()
с помощью URL - просто и понятно.Однако реальная выгода приходит, когда мы начинаем что-то менять. Представьте, что теперь нам нужно заменить Picasso на Universal Image Loader, потому что Picasso не поддерживает некоторые функции, которые нам сейчас абсолютно необходимы. Должны ли мы теперь прочесывать нашу кодовую базу и утомительно заменять все вызовы Picasso, а затем иметь дело с десятками ошибок компиляции, потому что мы забыли несколько вызовов Picasso? Нет. Все, что нам нужно сделать, - это создать новую реализацию
ImageService
и сообщить нашей инфраструктуре внедрения зависимостей, чтобы с этого момента использовать эту реализацию:Как видите, реализация может сильно отличаться, но это не имеет значения. Нам не нужно было менять ни одной строки кода где-либо еще в нашем приложении. Мы используем совершенно другую библиотеку, которая может иметь совершенно разные функции или может использоваться совсем по-другому, но нашему приложению все равно. Как и прежде, остальная часть нашего приложения просто видит
ImageService
интерфейс с егоload()
методом, и, тем не менее, этот метод реализован, уже не имеет значения.По крайней мере, для меня все это уже звучит довольно мило, но подождите! Там еще больше. Представьте, что вы пишете модульные тесты для класса, над которым вы работаете, и этот класс использует
ImageService
. Конечно, вы не можете позволить своим модульным тестам выполнять сетевые вызовы на некотором ресурсе, расположенном на каком-либо другом сервере, но, поскольку вы сейчас используете,ImageService
вы можете легко позволитьload()
вернуть статическое значение,Bitmap
используемое для модульных тестов, реализовав mockedImageService
:Подводя итог, оборачивая сторонние библиотеки, ваша кодовая база становится более гибкой к изменениям, в целом проще, легче тестируется, и вы уменьшаете связывание различных компонентов в вашем программном обеспечении - все вещи, которые становятся все более и более важными, чем дольше вы поддерживаете программное обеспечение.
источник
Я думаю, что обертывание сторонних библиотек сегодня, если завтра произойдет что-то лучшее, является очень расточительным нарушением YAGNI. Если вы неоднократно вызываете сторонний код способом, характерным для вашего приложения, вы должны (должны) преобразовать эти вызовы в класс переноса, чтобы исключить повторение. В противном случае вы полностью используете библиотечный API, и любая оболочка будет выглядеть так же, как сама библиотека.
Теперь предположим, что появляется новая библиотека с превосходной производительностью или чем-то еще. В первом случае вы просто переписываете оболочку для нового API. Нет проблем.
Во втором случае вы создаете оболочку, адаптирующую старый интерфейс для управления новой библиотекой. Немного больше работы, но без проблем, и не больше работы, чем вы бы сделали, если бы написали обертку ранее.
источник
Основная причина написания оболочки вокруг сторонней библиотеки состоит в том, что вы можете обмениваться этой сторонней библиотекой, не изменяя код, который ее использует. Вы не можете избежать связывания с чем-то, поэтому аргумент гласит, что лучше соединиться с API, который вы написали.
Стоит ли это усилий - другая история. Эта дискуссия, вероятно, продолжится еще долго.
Для небольших проектов, где вероятность того, что такое изменение будет необходимо, мала, это, вероятно, ненужные усилия. Для более крупных проектов такая гибкость вполне может перевесить дополнительные усилия по переносу библиотеки. Однако трудно заранее знать, так ли это на самом деле.
Другой способ взглянуть на это - основной принцип абстрагирования того, что может измениться. Таким образом, если сторонняя библиотека хорошо создана и вряд ли будет изменена, то, возможно, не стоит ее оборачивать. Однако, если сторонняя библиотека является относительно новой, существует большая вероятность, что ее потребуется заменить. Тем не менее, развитие существующих библиотек было прекращено много раз. Таким образом, это не простой вопрос.
источник
В дополнение к тому, что уже сказал @Oded , я просто хотел бы добавить этот ответ для специальной цели регистрации.
У меня всегда есть интерфейс для регистрации, но мне еще не приходилось заменять
log4foo
фреймворк.На создание интерфейса и написание оболочки требуется всего полчаса, поэтому, я полагаю, вы не тратите слишком много времени, если это окажется ненужным.
Это особый случай ЯГНИ. Хотя мне это не нужно, это не займет много времени, и я чувствую себя в большей безопасности. Если день обмена логгером действительно наступит, я буду рад, что потратил полчаса, потому что это сэкономит мне больше, чем день на обмен звонками в реальном проекте. И я никогда не писал и не видел модульный тест для регистрации (кроме тестов для самой реализации регистратора), так что ожидайте дефектов без оболочки.
источник
Я имею дело именно с этим вопросом в проекте, над которым я сейчас работаю. Но в моем случае библиотека предназначена для графики, и поэтому я могу ограничить ее использование небольшим количеством классов, которые работают с графикой, вместо того, чтобы разбрасывать ее по всему проекту. Таким образом, довольно легко переключать API позже, если мне нужно; в случае с регистратором все становится намного сложнее.
Таким образом, я бы сказал, что решение во многом связано с тем, что именно делает сторонняя библиотека, и сколько боли будет связано с ее изменением. Если изменить все вызовы API будет легко независимо от этого, то, вероятно, это не стоит делать. Если, однако, позднее изменить библиотеку будет очень сложно, то, вероятно, я бы обернул ее сейчас.
Кроме того, другие ответы очень хорошо охватили основной вопрос, поэтому я просто хочу сосредоточиться на этом последнем добавлении, на внедрении зависимостей и макете объектов. Конечно, это зависит от того, как именно работает ваша структура журналирования, но в большинстве случаев нет, что для этого не требуется оболочка (хотя она, вероятно, выиграет от одной). Просто сделайте API для вашего фиктивного объекта точно таким же, как и в сторонней библиотеке, и тогда вы легко сможете заменить фиктивный объект для тестирования.
Основным фактором здесь является то, реализована ли сторонняя библиотека через внедрение зависимостей (или локатор службы, или какой-то такой слабо связанный шаблон). Если к функциям библиотеки обращаются через одноэлементные или статические методы или что-то еще, вам нужно будет обернуть это в объект, с которым вы можете работать при внедрении зависимостей.
источник
Я нахожусь в лагере оберток и не могу заменить стороннюю библиотеку самым большим приоритетом (хотя это и бонус). Мое главное объяснение, которое одобряет упаковку, простое
И это проявляется, как правило, в форме большого количества дублирования кода, например, разработчики пишут 8 строк кода просто для того, чтобы создать
QButton
и стилизовать его так, как должно выглядеть приложение, только для дизайнера, который хочет не только внешний вид но также и функциональность кнопок для полного изменения для всего программного обеспечения, которое в конечном итоге требует возврата и переписывания тысяч строк кода, или обнаруживает, что модернизация конвейера рендеринга требует эпического переписывания, потому что кодовая база разбрызгивала низкоуровневые специальные фиксированные Разрабатывать код OpenGL повсеместно, вместо того, чтобы централизовать проект рендеринга в реальном времени и оставить использование OGL строго для его реализации.Эти проекты не приспособлены к нашим конкретным потребностям. Они, как правило, предлагают огромный набор того, что действительно необходимо (и то, что не является частью дизайна, так же важно, если не больше, чем то, что есть), и их интерфейсы не предназначены для специального обслуживания наших потребностей на высоком уровне. Мысль = один запрос », который лишает нас всех центральных элементов управления проектами, если мы используем их напрямую. Если разработчики заканчивают тем, что пишут гораздо более низкоуровневый код, чем это требуется для выражения того, что им нужно, они могут иногда сами заключить его в обертку, что делает вас так, что вы получите в итоге десятки наскоро написанных и грубо Разработанные и документированные обертки вместо одной хорошо разработанной, хорошо документированной.
Конечно, я бы применил строгие исключения к библиотекам, в которых обертки представляют собой почти один к одному перевод того, что могут предложить сторонние API. В этом случае может не потребоваться поиск проекта более высокого уровня, который более непосредственно выражал бы требования бизнеса и дизайна (это могло бы иметь место для чего-то, напоминающего скорее «служебную» библиотеку). Но если есть гораздо более приспособленный дизайн, который намного более прямо отражает наши потребности, то я решительно в лагере обёрток, так же как я категорически за использование функции более высокого уровня и ее повторное использование вместо встраивания кода сборки повсюду.
Как ни странно, я сталкивался с разработчиками так, что они казались настолько недоверчивыми и пессимистичными в отношении нашей способности разрабатывать, скажем, функцию для создания кнопки и возвращать ее, что они предпочли бы написать 8 строк низкоуровневого кода, ориентированного на микроскопию. подробности создания кнопок (которые в будущем должны были неоднократно изменяться) по поводу разработки и использования указанной функции. Я даже не вижу цели в том, чтобы мы пытались спроектировать что-либо в первую очередь, если мы не можем доверять себе, чтобы спроектировать эти виды оберток разумным способом.
Другими словами, я вижу сторонние библиотеки как способы потенциально сэкономить огромное время при реализации, а не как заменитель проектирования систем.
источник
Моя идея о сторонних библиотеках:
В последнее время в сообществе iOS обсуждались плюсы и минусы (хорошо, в основном минусы) использования сторонних зависимостей. Многие аргументы, которые я видел, были довольно общими - группировка всех сторонних библиотек в одну корзину. Как и в большинстве случаев, все не так просто. Итак, давайте попробуем сосредоточиться на одном случае
Следует ли нам избегать использования сторонних библиотек пользовательского интерфейса?
Причины рассмотрения сторонних библиотек:
Кажется, есть две основные причины, по которым разработчики рассматривают использование сторонней библиотеки:
Большинство библиотек пользовательского интерфейса ( не все! ) Обычно попадают во вторую категорию. Это не ракетостроение, но нужно время, чтобы его правильно построить.
Существует почти два типа элементов управления / представлений:
UICollectionView
изUIKit
.UIPickerView
. Большинство сторонних библиотек, как правило, попадают во вторую категорию. Более того, они часто извлекаются из существующей кодовой базы, для которой они были оптимизированы.Неизвестные ранние предположения
Многие из разработчиков делают обзоры кода своего внутреннего кода, но могут воспринимать качество стороннего исходного кода как должное. Стоит потратить немного времени на просмотр кода библиотеки. Вы можете быть удивлены, увидев некоторые красные флаги, например, метание, когда оно не нужно.
Вы не можете это скрыть
Из-за способа разработки UIKit вы, скорее всего, не сможете скрыть стороннюю библиотеку пользовательского интерфейса, например, за адаптером. Библиотека будет переплетаться с вашим кодом пользовательского интерфейса, становясь де-факто вашего проекта.
Стоимость будущего времени
UIKit меняется с каждым выпуском iOS. Вещи сломаются. Ваша сторонняя зависимость не будет такой необслуживаемой, как вы можете ожидать.
Заключение :
Исходя из моего личного опыта, большинство применений стороннего кода пользовательского интерфейса сводятся к обмену меньшей гибкостью на некоторое время.
Мы используем готовый код для быстрой доставки нашей текущей версии. Однако рано или поздно мы достигаем пределов библиотеки и предстаем перед трудным решением: что делать дальше?
источник
Использование библиотеки напрямую более удобно для команды разработчиков. Когда новый разработчик присоединяется, он может иметь полный опыт работы со всеми используемыми средами, но не сможет внести продуктивный вклад до изучения вашего собственного API. Когда более молодой разработчик пытается продвинуться в вашей группе, он будет вынужден изучить ваш конкретный API, которого нет нигде, вместо того, чтобы приобретать более полезную общую компетенцию. Если кто-то знает полезные функции или возможности исходного API, возможно, он не сможет достичь уровня, написанного кем-то, кто о них не знал. Если кто-то получит задание на программирование во время поиска работы, возможно, он не сможет продемонстрировать основные вещи, которые он использовал много раз, просто потому, что все эти времена он получал доступ к необходимым функциям через вашу оболочку.
Я думаю, что эти вопросы могут быть важнее, чем возможность удаленного использования совершенно другой библиотеки позже. Единственный случай, когда я использовал бы обертку, - это когда переход на другую реализацию определенно запланирован или обернутый API недостаточно заморожен и постоянно меняется.
источник