Проблемы инкапсуляции

Недавно мне попалась на глаза интересная статья о проблемах в концепции инкапсуляции — почитайте, если есть время.
Для тех, у кого времени нет, я быстренько перескажу суть: инкапсуляция не выполняет одной из своих основных задач (дать «черный ящик» с описанными входами и выходами) по целому ряду причин:
  1. Программисты не доверяют чужим «черным ящикам».
  2. В чужих «чёрных ящиках» случаются ошибки, которые приходится фиксить, влезая в их внутренности (и ломая этим всю затею).
  3. Входы и выходы не всегда описаны понятно. Иногда бывает проще создать свой велосипед, чем разбираться в том, как поехать на чужом.

Всё это презренная реальность, которую теоретики программирования игнорируют. И как же с этим жить?

Без понятия. Вряд ли тут есть какая-то «серебряная пуля» (да и где она есть?) и в каждом случае нужно решать что-то своё. Рассказ будет об одном подобном случае и одном из вариантов решения.

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

Я немного подумал и решил сделать для каждого используемого класса «обертку», по примерно следующему принципу: вот есть ОГРОМНЫЙ класс для шифрования, который имеет массу опций и вообще монстр. (Кто видел серьёзные классы для шифрования, тот знает о чём я — всякие там инициализационные векторы, константы и прочее). Мне он нужен для одной лишь цели — зашифровать и расшифровать файл по симметричному ключу. И всё. Ну вот значит берём и пишем обёртку из двух методов:
  • ЗашифроватьФайл(что, куда, ключ)
  • РасшифроватФайл(что, куда, ключ)

Внутри методов дергаем сложные методы базового класса. Дальше по коду юзаем свою обёртку. В чём тут плюсы?
  1. Проблема недовоерия ЧУЖОМУ черному ящику решена — теперь везде по коду я использую СВОЙ черный ящик. Я ему верю. Начнёт выкобениваться — поменяем внутренности, чтобы использовал какие-то другие средства шифрования (без изменения интерфейса).
  2. На такую простую обертку я теперь моментально напишу тесты. Чего их писать-то: зашифровал — расшифровал — сверил файлы. 5 строк кода. Плюс проверка на ошибки файловых операций — еще 10. На тот большой и страшный базовый класс писать тесты я бы не взялся — долго и трудно (их вон даже его автор не написал). Более того, мои тесты будут лучше тестов автора класса, даже если бы он их написал. Потому что автор кода всегда имеет какое-то своё представление о том, как этот код будет использоваться. И это отражается не только на коде, но и на тестах. Ну вот верил бы автор, что файлы для шифрования всегда существуют и доступны для чтения — и не проверил бы это в тестах. А я-то знаю, что у меня это не так — и обязательно проверю эти ситуации. Ну потому, что мне это реально надо.
  3. Входы и выходы стали намного понятнее. Ну, мне по крайней мере. Да и другим, я так думаю. И плевать, что я создал еще одну сущность, старик Оккам меня простит.

Под конец статьи ко мне подкралась мысль, что я невольно пересказал какую-то главу книги Фаулера или Макконнелла, столь просто и банально всё звучит. Но вроде бы нет (поправьте, если ошибаюсь).
Update: мне тут из зала подсказывают, что я рассказываю о паттернах Фасад и Адаптер из книги Банды Четырёх — вполне может быть.

Короткая мораль


Если Вы используете чужой код, которому не доверяете — напишите для него обёртку, которая была бы удобна и для тестирования и для реального использования. Тестируйте эту обёртку так, как она будет использоваться реальным кодом.
+17
17 января 2012, 12:44
84
tangro 426,0

комментарии (36)

+3
bfDeveloper #
Хорошая статья по хорошему вопросу.
Оставлю это здесь. Это, имхо, одна из лучших теоретических статей про инкапсуляцию. Хорошо отражает то, что вы пишете про теоретиков.
+18
Neutral #
+3
johnnythekid #
да-да, я тоже «Дюну» вспомнил
0
Neutral #
это всё из-за картинки в топике =)
+5
lightcaster #
Залез проверить, есть ли что-то про Дюну в комментах :).
0
Vladson #
Играть в неё на сеге, мне лично было приятнее чем смотреть фильм…
0
johnnythekid #
а книге никто не?..
0
Colwin #
Читал все :-)
ИМХО, гораздо понятнее и интереснее фильма.
+1
prefrontalCortex #
404. Я пропустил всё веселье? :(
+1
Neutral #
Скриншот из х/ф «Дюна», Преподобная Мать говорит Полу Атрейдесу:
— Положи свою правую руку в коробку.
— Что в коробке?
— Боль.
+22
youlose #
Мне кажется что вы придумали то, что называется «adapter» паттерном программирования ООП.
+15
AlexanderYastrebov #
Только не адаптер, а фасад
0
youlose #
Да, согласен, но адаптер, тоже, в принципе, подходит.
+8
AlexanderYastrebov #
У автора не стояла задача адаптировать интерфейс, у него стояла задача его упростить — поэтому фасад.
+2
youlose #
Да, действительно, вы абсолютно правы.
0
d7k #
Обертка или Wrapper одно из его названий («банда четырех»). В свое время проверял.
+5
serf #
Шаблон адаптера, не ново, приходит на ум даже не читая умных книжек, черный ящик запакованный в белый (серый) ящик :) Если «оборачиваемая» библиотека обловляется, может вставть проблема синхронизации (хотя она встанет и без обертки).
+7
subecho #
Каким образом приведенный пример решают описанную вначале проблему? Он может послужить иллюстрацией к паттернам Фасад и Адаптер но не более
+1
vaniaPooh #
У GoF прямо так и написано: вы можете использовать шаблон Фасад не только для организации внешнего интерфейса ко всему продукту, но и для упрощения взаимодействия между отдельными его подсистемами.
0
mrjj #
Внешний, внутренний, какая нафиг разница с точки зрения теории.
На практике любой интерфейс лучше воспринимать как «внешний» с соответствующим уровнем доверия к входным и выходным данным и минимизацией всякой служебной гадости.
+3
VolCh #
Это хорошо, если у вас нормальный класс… А если просто кусок «потока сознания», задействующий глобальные переменные, инклудящий другие такие же куски и т. п…
+2
EugeneOZ #
Кто-то пишет плохой код, при чём тут инкапсуляция? Инкапсуляция это основа ООП. Если возникают с этим проблемы — читайте книжки. Изобретённый Вами подход, как уже заметили, является обычной практикой. Кстати, для таких обёрток ещё полезно писать «учебные тесты» — подробнее в книге "Чистый код".
+1
Alexnn #
en.wikipedia.org/wiki/Indirection

A famous aphorism of David Wheeler goes: «All problems in computer science can be solved by another level of indirection»;[1] this is often deliberately mis-quoted with «abstraction layer» substituted for «level of indirection». Kevlin Henney's corollary to this is, "...except for the problem of too many layers of indirection."

Вольно по-русски: все проблемы программирования можно решить, добавляя ещё одну обвертку… но только не проблему слишком большого количества обверток!
0
elw00d #
Уровень абстракции же, зачем придумывать странные слова :)
+11
freeAKK #
Раньше у вас был один черный ящик, понятный только его автору. Теперь их два.
0
CheatEx #
www.youtube.com/watch?feature=player_detailpage&v=VKQMWZHiQVQ#t=120s — иллюстрирует проблему в целой куче аспектов :)
0
elw00d #
Проблема недоверия к чужим черным ящикам решается двумя путями:
1. Документирование
2. Привлечение комьюнити
Поэтому проектам, выложенным на гитхабе, с хорошей документацией и многочисленным комьюнити, обычно доверяют. Если же нет возможности выложить код в опенсорс и потратить время на полноценное документирование, то можно хотя бы прокомментировать код и сделать маленькое описание рядом в текстовом файле, а потом дать коллегам посмотреть. Это не занимает много времени, зато ваш последователь 2 раза подумает, прежде чем выкинуть ваш ящик с тем, чтобы заменить его своим.
0
Mr_Floppy #
Чем обернуть своим ящиком.
+1
rumatavz #
В чем свзяь паттерна адаптер и тех проблем с черными ящиками, которые вы обозначили?
+1
youlose #
Адаптер, Adapter или Wrapper/Обёртка — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс

В данном случае «недоступный» в смысле — неохота разбираться с ним. Но выше мы уже выяснили что больше подходит определение паттерна «фасад».
+1
mrjj #
Уфф, смешелись в кучу кони, люди.

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

Вы, применив паттерн фасада увеличили степень инкапсуляции, так как бОльшее количество деталей реализации оказалось скрытыми и уменьшалась связанность. В итоге было продемонстрировано как инкапсуляция решает проблему, а не создает.
+1
Alexznadr #
в начале статьи «инкапсуляция это ууу плохо», и в конце — «инкапсуляция» как метод борьбы с инкапсуляцией. Вы правда не видите подвоха? Проблема с вашим кодом вовсе не в инкапсуляции
0
FB3 #
Если тут про юнит тесты имеется ввиду, то странно писать тесты для полученного класса при отсутствии тестов для класса, с которым он взаимодействует… Тот большой класс наверняка можно протестировать, а потом отрефакторить. Если есть код и есть возможность его менять, то нужно это делать, когда возникает необходимость. А так да, про фасад уже сказали.
В общем, тут в кучу смешались шаблоны проектирования и тестирование кода, поэтому возник такой комментарий. На самом деле нельзя написать тесты к тому, что получилось, потому что нет тестов к тому большому классу, а соответственно, неизвестно его поведение в различных ситуациях.
0
VolCh #
А в различных и не надо, надо в нужных для приложения. И тесты будут не юнит, а интеграционные или функциональные, пускай и с помощью того же инструментария, что и юнит. Если нужные тесты не проходят, например не выбрасывается исключение при отсутствии файла или его недоступности на запись, то пишем throw в своём фасаде, не трогая чужой код. Также поступаем со всеми остальными исключительными случаями, а что там в этом классе происходит нас мало интересует, если функциональный тест проходит. Правда не знаю, можно ли его в таком случае ещё называть фасадом.

0
B08AH #
тоже как то сам на это набрёл.

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

вобщем то мне кажется оно так и должно быть — исходные классы могут быть сколь угодно сложны и намудрены, и опять же каждый класс имеет свой вид «общения» с тем, кто его пользует.
а тот, кто задумал пользовать эти классы, причем они могут быть все от разных разработчиков, с разными интерфейсами, просто пишет для них обертки в нужном ему виде, униформирует интерфейс, упрощает и т.п.
0
FaNtAsY #
Представлено описание классического шаблона «Фасад».
Собственно, весь пост можно свести к фразе «Пользуйтесь шаблонами». :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.