Про интерфейсы или как не надо делать программы
В первую голову проблема касается продуктовых софтостроителей, хотя и в проектном тоже не все гладко.
Начнем с недавней истории. Технология COM (позднее, другие, но суть та же) дала возможность разработчикам компонентов отделить интерфейсы от реализаций. Для прикладных разработчиков это означало, например, что при обновлении компонентов старые интерфейсы продолжали бы работать. В теории, конечно. Но и на практике это выглядело гораздо лучше, чем "ад динамических библиотек", имеющих всегда версию "текущая".
Каков механизм? Он очень прост. Объявляет программист компонента интерфейс
[uuid(a03d1421-b1ec-11d0-8c3a-00c04fc31d2f)]
interface ICoolPrinting
{
void Print(const string fileName);
}
Потом он этот интерфейс реализует. Скомпилированная программа устанавливается на устройстве пользователя, где в некотором реестре означенному UUID ставится в соответствие исполняемый модуль. Например, в Program Files\CoolPrinting1_0\cool.exe.
В сторонней прикладной программе, использующей этот интерфейс, у того же "реестра" по UUID запрашивается интерфейс и вызывается нужная функция.
ICoolPrinting printing = GetInterface(...);
printing.Print("c:\myfile.pdf");
Что происходит, если разработчик компонента меняет функционал?
В простом варианте расширяется интерфейс. Это означает что разработчик добровольно обязан (ощутите это прекрасное сочетание слов):
- создать новый интерфейс ICoolPrinting2 с новым UUID, возможно, унаследовав его от прежнего;
- реализовать его, не ломая старую реализацию.
[uuid(d26d1421-g1ec-12d0-8c3a-12c84ef31d2f)]
interface ICoolPrinting2 : ICoolPrinting
{
bool SetupPrinter();
}
Теперь даже установленная вместо старой, новая версия продолжает работать по-прежнему. Прикладному программисту, использующему компонент, ничего переделывать не надо. То есть, совсем ничего. Круто, да?
В более сложном варианте разработчик выпускает новую версию, не поддерживающую старый интерфейс. Но при этом он опять же добровольно обязан предусмотреть возможность одновременной установки новой версии рядом со старой, куда-нибудь в Program Files\\CoolPrinting2_0
.
И в этом случае прикладному программисту тоже ничего переделывать не надо.
Однако вышеописанное происходит только в мире, где программисты хотя бы примерно представляют, что они производят. А их приказчики знают дело, которым руководят. Такое тоже бывает.
Чаще происходит наоборот.
В наиболее гуманном варианте программист не создает новый интерфейс, а просто правит старый, меняя, заодно, его UUID. Менять UUID надо обязательно, иначе невозможно будет отличить новый интерфейс от старого. Новая версия устанавливается вместо старой.
Программа, использующая компонент, сразу перестает работать. Потому что ссылка по прежнему UUID ведет в пустоту.
Но обойти эту проблему достаточно просто, поскольку интерфейс можно:
- запросить по имени, а не по UUID;
- использовать динамические вызовы функций (на стадии исполнения).
OleVariant printing = GetInterface("ICoolPrinting");
printing.Print("c:\myfile.pdf");
Такая "хитрая" техника будет работать до тех пор, пока программист компонента не удалит старые методы и интерфейсы. Тогда при попытке использовать функционал в вашей прикладной программе произойдет ошибка.
Положение усугубляется тем, что установить две версии компонента на одном и том же устройстве чаще всего оказывается невозможно. Потому что если разработчик не сумел обеспечить совместимость на уровне интерфейсов, то чтобы обеспечить бесконфликтную работу разных версий компонента, мозгов у него тем более не хватит.
Шайба переходит к прикладному программисту. Гуру с форума шепчут: "Паттерн адаптер". А то и "стратегия". Ну, а чему удивляться? На практике основное назначение шаблонов так называемого проектирования - создание костылей и подпорок в максимально единообразном виде.
И вот простая и ясная прикладная программа из двух строчек - получение ссылки на компонент и вызов метода, превращается в кашу из сотен строк "шаблонного" кода и нескольких классов, сообразно числу версий компонента. Разумеется, я не буду их тут приводить.
Примеры в студию? Пожалуйста. Компонент PdfCreator, развиваясь с версии 0.х до нынешней 2.х, разрабатывался именно так. Вначале правкой старых интерфейсов с заменой UUID, а потом созданием новых, несовместимых со старыми, но уже удалёнными. Две разные версии на одном компьютере устанавливать нельзя.
Мне могут возразить, что PdfCreator - бесплатная утилита с открытым кодом. А что это меняет? Разве нерадивость разработчиков можно компенсировать пословицей "Дареному коню в зубы не смотрят"? Есть и другие примеры вполне коммерческих компонентов, не обеспечивающих совместимость между версиями.
Мораль. Правило "Сделано не здесь" (not invented here) имеет под собой веские основания.
blog comments powered by Disqus