Недавно мне попалась на глаза интересная статья о проблемах в концепции инкапсуляции — почитайте, если есть время.
Для тех, у кого времени нет, я быстренько перескажу суть: инкапсуляция не выполняет одной из своих основных задач (дать «черный ящик» с описанными входами и выходами) по целому ряду причин:
Всё это презренная реальность, которую теоретики программирования игнорируют. И как же с этим жить?
Без понятия. Вряд ли тут есть какая-то «серебряная пуля» (да и где она есть?) и в каждом случае нужно решать что-то своё. Рассказ будет об одном подобном случае и одном из вариантов решения.
Попался мне как-то раз один большой проект — его писали уже много лет и много людей. Соответственно, на каждую задачу в духе шифрования\парсинга\архивирования\и т.д. давно были написаны свои модули. Казалось бы — лафа, бери и пользуйся. Вот только беда в том, что модулей этих было так много и написаны они были уже так давно, что никто не помнил ни их авторов, ни деталей их реализации. Классические «черные ящики». Иногда понятные, иногда нет. Иногда работающие, а иногда глючные. Ну как этим пользоваться?
Я немного подумал и решил сделать для каждого используемого класса «обертку», по примерно следующему принципу: вот есть ОГРОМНЫЙ класс для шифрования, который имеет массу опций и вообще монстр. (Кто видел серьёзные классы для шифрования, тот знает о чём я — всякие там инициализационные векторы, константы и прочее). Мне он нужен для одной лишь цели — зашифровать и расшифровать файл по симметричному ключу. И всё. Ну вот значит берём и пишем обёртку из двух методов:
Внутри методов дергаем сложные методы базового класса. Дальше по коду юзаем свою обёртку. В чём тут плюсы?
Под конец статьи ко мне подкралась мысль, что я невольно пересказал какую-то главу книги Фаулера или Макконнелла, столь просто и банально всё звучит. Но вроде бы нет (поправьте, если ошибаюсь).
Update: мне тут из зала подсказывают, что я рассказываю о паттернах Фасад и Адаптер из книги Банды Четырёх — вполне может быть.
Если Вы используете чужой код, которому не доверяете — напишите для него обёртку, которая была бы удобна и для тестирования и для реального использования. Тестируйте эту обёртку так, как она будет использоваться реальным кодом.
Для тех, у кого времени нет, я быстренько перескажу суть: инкапсуляция не выполняет одной из своих основных задач (дать «черный ящик» с описанными входами и выходами) по целому ряду причин:
- Программисты не доверяют чужим «черным ящикам».
- В чужих «чёрных ящиках» случаются ошибки, которые приходится фиксить, влезая в их внутренности (и ломая этим всю затею).
- Входы и выходы не всегда описаны понятно. Иногда бывает проще создать свой велосипед, чем разбираться в том, как поехать на чужом.
Всё это презренная реальность, которую теоретики программирования игнорируют. И как же с этим жить?
Без понятия. Вряд ли тут есть какая-то «серебряная пуля» (да и где она есть?) и в каждом случае нужно решать что-то своё. Рассказ будет об одном подобном случае и одном из вариантов решения.
Попался мне как-то раз один большой проект — его писали уже много лет и много людей. Соответственно, на каждую задачу в духе шифрования\парсинга\архивирования\и т.д. давно были написаны свои модули. Казалось бы — лафа, бери и пользуйся. Вот только беда в том, что модулей этих было так много и написаны они были уже так давно, что никто не помнил ни их авторов, ни деталей их реализации. Классические «черные ящики». Иногда понятные, иногда нет. Иногда работающие, а иногда глючные. Ну как этим пользоваться?
Я немного подумал и решил сделать для каждого используемого класса «обертку», по примерно следующему принципу: вот есть ОГРОМНЫЙ класс для шифрования, который имеет массу опций и вообще монстр. (Кто видел серьёзные классы для шифрования, тот знает о чём я — всякие там инициализационные векторы, константы и прочее). Мне он нужен для одной лишь цели — зашифровать и расшифровать файл по симметричному ключу. И всё. Ну вот значит берём и пишем обёртку из двух методов:
- ЗашифроватьФайл(что, куда, ключ)
- РасшифроватФайл(что, куда, ключ)
Внутри методов дергаем сложные методы базового класса. Дальше по коду юзаем свою обёртку. В чём тут плюсы?
- Проблема недовоерия ЧУЖОМУ черному ящику решена — теперь везде по коду я использую СВОЙ черный ящик. Я ему верю. Начнёт выкобениваться — поменяем внутренности, чтобы использовал какие-то другие средства шифрования (без изменения интерфейса).
- На такую простую обертку я теперь моментально напишу тесты. Чего их писать-то: зашифровал — расшифровал — сверил файлы. 5 строк кода. Плюс проверка на ошибки файловых операций — еще 10. На тот большой и страшный базовый класс писать тесты я бы не взялся — долго и трудно (их вон даже его автор не написал). Более того, мои тесты будут лучше тестов автора класса, даже если бы он их написал. Потому что автор кода всегда имеет какое-то своё представление о том, как этот код будет использоваться. И это отражается не только на коде, но и на тестах. Ну вот верил бы автор, что файлы для шифрования всегда существуют и доступны для чтения — и не проверил бы это в тестах. А я-то знаю, что у меня это не так — и обязательно проверю эти ситуации. Ну потому, что мне это реально надо.
- Входы и выходы стали намного понятнее. Ну, мне по крайней мере. Да и другим, я так думаю. И плевать, что я создал еще одну сущность, старик Оккам меня простит.
Под конец статьи ко мне подкралась мысль, что я невольно пересказал какую-то главу книги Фаулера или Макконнелла, столь просто и банально всё звучит. Но вроде бы нет (поправьте, если ошибаюсь).
Update: мне тут из зала подсказывают, что я рассказываю о паттернах Фасад и Адаптер из книги Банды Четырёх — вполне может быть.
Короткая мораль
Если Вы используете чужой код, которому не доверяете — напишите для него обёртку, которая была бы удобна и для тестирования и для реального использования. Тестируйте эту обёртку так, как она будет использоваться реальным кодом.