В первую голову проблема касается продуктовых софтостроителей, хотя и в проектном тоже не все гладко.

Начнем с недавней истории. Технология 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) имеет под собой веские основания. Как минимум, иметь под рукой исходники будет нелишним.

Комментарии (32)


  1. lair
    03.04.2015 11:08
    +2

    Скажите, пожалуйста, честно, что вы предлагаете? Отказаться от использования внешних зависимостей? Или вот скажите, вы про nuget не слышали?

    PS «Основное назначение шаблонов так называемого проектирования — создание костылей и подпорок.»
    Ну да, конечно, человек, за плечами которого две сотни проектов по всему миру, докторская по архитектуре и еще некоторое количество бумажек, строил здания на костылях и подпорках.


    1. ololoepepe
      04.04.2015 11:16
      -3

      Ну да, конечно, человек, за плечами которого две сотни проектов по всему миру, докторская по архитектуре и еще некоторое количество бумажек, строил здания на костылях и подпорках.

      Скрытый текст
      image


    1. cross_join Автор
      19.04.2015 01:43

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

      В общем случае проблема не решается. Подтверждается великим множеством эксплуатируемого ПО, которого поставщики уже не поддерживают. Далеко не всегда у такого ПО есть исходники, что вынуждает клиентов иметь в резерве уже давно не производящееся «железо». Далеко не всегда даже имеющиеся исходники могут быть перенесены в новую архитектуру.


      1. lair
        19.04.2015 01:47

        Что такое «разработка сторонних компонентов»? Как наличие исходных кодов избавляет от dependency hell?

        Чем вас в общем случае не устраивают решения с локально распространяемыми зависимостями?


        1. cross_join Автор
          19.04.2015 13:32

          Компонент программной системы — это минимальная единица развертывания. В данном случае речь идет еще и компоненте в понятии COM.

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

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

          Простой пример был приведен — экспорт в PDF. Поскольку разработчики PDF Creator безответственно относятся к разработке, то эта функция должна быть внедрена в основную систему, возможно уже на базе другой реализации.

          Другие часто встречающиеся примеры — архивация и распаковка, сканирование (без драйверов), доступ к БД, генерация отчетности, реализации сетевых протоколов разных уровней и т.д.

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


          1. lair
            19.04.2015 13:34

            Поскольку разработчики PDF Creator безответственно относятся к разработке, то эта функция должна быть внедрена в основную систему, возможно уже на базе другой реализации.

            Нет, должен быть выбран другой компонент.

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

            То есть кроме поглощения исходников вы других вариантов разрыва dependency hell не знаете?


            1. cross_join Автор
              19.04.2015 13:44

              Давайте вернемся к примеру. У вас есть система, развернутая на сотнях рабочих мест. Предположим, она использует упомянутый PDFCreator для печати. Разработчики PDFCreator выпустили новую версию. Несколько клиентов обновили PDFCreator и обнаружили, что печать больше не работает (технические причины описаны в тексте). Они начинают звонить в поддержку вендора.

              Вы предлагаете:
              — выбрать другой компонент (надо полагать, его разработчики более ответственны, такое тоже бывает). Извините, этот вариант нам не подходит.
              — какой-то другой вариант кроме (1) поглощения или (2) поддержки совместимости со всеми версиями (механизм также описан в тексте)

              Готов внимательно выслушать про другой вариант, только не теоретический, а подкрепленный реальной практикой в похожих условиях.


              1. lair
                19.04.2015 13:55

                Я предлагаю вам выбрать компонент, используемую версию которого вы сможете гарантированно зафиксировать (например, через bin-deploy).


                1. cross_join Автор
                  19.04.2015 14:32

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

                  Спасибо, как вариант иногда проходит. Например, в случае embedded СУБД, они развертываются примерно так.


                  1. lair
                    19.04.2015 14:56

                    Нет никакого поглощения — вы не несете ответственность за этот код, это просто используемый вами компонент. Когда он обновляется у производителя, и если вам нужна функциональность этого обновления — вы прогоняете тесты и включаете в свой следующий релиз.

                    Win-win.


                    1. cross_join Автор
                      19.04.2015 15:02

                      Совершенно верно, это называется поглощение бинарников. Развертывая их в составе своей системы, вы несете ответственность за них перед клиентами, не имея исходного кода, полагаясь исключительно на тесты типа «черный ящик».

                      В некоторых случаях это возможно, когда компонент достиг определенного уровня зрелости и риск обнаружения в нем критичных ошибок невелик. Иначе это не работает., к сожалению.


                      1. lair
                        19.04.2015 15:07

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


                        1. cross_join Автор
                          19.04.2015 15:11

                          Если вы не несете ответственность перед клиентом, скажем, за ошибку в SQL Server Compact (который поставляется вместе с вашей системой простым copy deployment), это означает, что вы можете клиенту предложить обратиться в Микрософт, а не к вам.

                          Поскольку это не так практически всегда, предлагаю не заниматься терминологическими изысками.

                          С сутью же ваших предложений понятно, спасибо.


                          1. lair
                            19.04.2015 15:13

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


                            1. cross_join Автор
                              19.04.2015 15:16

                              В случае развертывания SQL Server Express — может использовать. В случае PDFCreator — тоже. Зафиксировать их версии возможным не представляется. Поэтому предлагаемый подход в таких случаях не работает.


                              1. lair
                                19.04.2015 15:18

                                Значит, не используйте эти компоненты (собственно, SQL Server — это не компонент, это сервис). Это не означает, что сам по себе компонентный подход мертв или плох.


                                1. cross_join Автор
                                  19.04.2015 15:20

                                  Конечно не означает. Компонентный подход был серьезным шагом в индустриализации интеграции.


                                  1. lair
                                    19.04.2015 15:21

                                    Что возвращает нас к вопросу «о чем ваш пост, чем вас не устраивают существующие решения, и что вы предлагаете».


  1. Gorthauer87
    03.04.2015 12:14
    +1

    Для таких случаев придумали opensource.


    1. Deranged
      03.04.2015 14:10
      -1

      Нет, .NET.


      1. Gorthauer87
        03.04.2015 14:34
        +1

        Проблема тогда просто в другой фантик заворачивается.


        1. Deranged
          03.04.2015 14:55
          +1

          Почему это? Там все сборки версионируются, угробить другое приложение, просто разместив сборку в GAC уже сложнее.


          1. cross_join Автор
            19.04.2015 01:53

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

            В случае COM-DLL сделать это даже проще, чем для сборки в GAC: всего лишь установить DLL новой версии в другой директорий, и не надо никаких strong name и прочего.

            Однако… см. по тексту.


    1. mayorovp
      03.04.2015 16:21
      +2

      Для таких случаев придумали таскать зависимости вместе с приложением.


      1. Gorthauer87
        03.04.2015 20:12

        А если лицензия говорит нельзя? В случае общих библиотек опенсорс дохрена головной боли снимает.


        1. mayorovp
          03.04.2015 20:33

          Для таких случаев придумали redistributable packages… Разумеется, о портабельности библиотеки должен думать ее создатель.

          PS как там по-русски будет «портабельность»? «Таскаемость»? :)


          1. lair
            03.04.2015 20:40
            +2

            «Переносимость».


            1. mayorovp
              03.04.2015 20:40

              Да, точно. Спасибо.


        1. qw1
          04.04.2015 12:18
          +1

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


          1. Gorthauer87
            06.04.2015 13:36
            +1

            Почти все драйвера на видео в линуксе так распространяются, такая же хрень творится с oracle jvm.


          1. cross_join Автор
            19.04.2015 01:45

            Коммерческие CORBA-реализации, например.


  1. Deranged
    03.04.2015 14:10

    Не туда.