Правила разработки для лёгкого переноса и конвертации

19 11

Каждый знает, что процесс конвертации на новую версию DIRECTUM может быть не самым простым. Чаще всего он усложняется в системах с многочисленными доработками, интеграциями или при конвертации с большой разницей между текущей и новой версиями. Одной из причин такого усложнения является то, что большинство стандартных решений дорабатывается под конкретные нужды организации без учёта конвертации системы в будущем. Далее будут приведены рекомендации, следуя которым вы сможете упростить не только конвертацию, но и перенос разработки на другие системы.

Правильное наименование – первый шаг к успеху!

Хуторок Бухты-барахтыКак бы просто это ни звучало, но правильное наименование позволяет избежать ряда проблем в будущем. Основная суть данной рекомендации заключается в применении префиксов в названии элементов разработки. Такой подход позволяет сделать имя элемента/разработки уникальным.

Префиксы стоит использовать для вновь создаваемых элементов разработки.

Давайте рассмотрим подробнее, что именно, в каких местах и как стоит называть правильно:

  1. Разработка новых модулей системы. В случае если ведётся разработка модуля для системы DIRECTUM с целью его распространения, то для всей прикладной разработки стоит придумать свой префикс.
    Например, в стандартной поставке DIRECTUM разработка, связанная с модулем «Интеграция с системами обмена документами», имеет префикс DISI (DIRECTUM Interchange Systems Integration).

  2. Для модификаций внутри организации, дополняющих стандартную функциональность, также следует придумать и использовать уникальный префикс. В качестве префикса можно взять любое сочетание букв/цифр, главное, чтобы вся дополнительная разработка содержала его. Обычно, в качестве префикса предлагается использовать краткое наименование организации.
    Например, модификации для Very Fast Motor могут иметь логичный префикс VFM, а сценарий для отправки уведомлений, будет иметь наименование: VFMSendNotice.

Ниже приведён список элементов/компонент разработки, для которых данная рекомендация является актуальной:

  • наименования блоков, групп блоков и реквизиты Код типовых маршрутов;
  • названия (и имена полей) реквизитов электронных документов;
  • наименования типов карточек документов;
  • названия (и имена полей) реквизитов справочников;
  • наименования типов справочников;
  • наименования констант;
  • наименования отчётов;
  • наименования сценариев;
  • наименования функций;
  • наименования диалогов.

Используйте для идентификаторов объектов параметры и константы.

Очень часто разработчики стараются «вшить» в код конкретные ИД или Коды объектов. Для наглядности давайте рассмотрим процесс формирования комплекта договорных документов из макетов. В случае большого количества макетов, это может выглядеть так:

ListActsID = CreateList()
ListActsID.Add("Схема располсжения ЗУ"              ; 638051) 
ListActsID.Add("Технический план"                   ; 638056)
ListActsID.Add("Межевой план"                       ; 638048)
ListActsID.Add("Выбор трассы"                       ; 638048)
ListActsID.Add("Выкопи ровка"                       ; 637978)
ListActsID.Add("Вынос границ"                       ; 637985)
ListActsID.Add("Земляные работы"                    ; 638045)
ListActsID.Add("Акт обследования"                   ; 637855)
ListActsID.Add("ГКУ объекта кап. строительства"     ; 637897)
ListActsID.Add("ГКУ участка"                        ; 637910)
ListActsID.Add("Межевой план + ГКУ"                 ; 637916)
ListActsID.Add("Схема + МП"                         ; 637928)
ListActsID.Add("Технический план + ГКУ"             ; 637969)
ListActsID.Add("Схема + МП + ГКУ"                   ; 637930)
ListActsID.Add("Съемка"                             ; 637939)
ListActsID.Add("Съемка + МП"                        ; 637942)
ListActsID.Add("Съемка + схема"                     ; 637946)
ListActsID.Add("Съемка + схема + МП"                ; 637950)
ListActsID.Add("Съемка + схема + МП + ГКУ"          ; 637956)
ListActsID.Add("Изыскания"                          ; 638626)
ListActsID.Add("Схема на использование"             ; 699487)

Выше показан плохой подход к написанию кода. Представленный выше вариант не адаптирован для переноса между системами. Более правильным будет вынести ИД макетов из кода на уровень администрирования. Например, когда ИД документов не очень много, то для их хранения можно использовать константы или параметры ТМ, в зависимости от места использования данных значений. В данном примере было бы правильнее сделать ещё один справочник (т.к. макетов гораздо больше, чем показано тут) и уже в нем указать макеты документов и связываемый элемент. Такой подход обеспечил бы как лёгкость переноса разработки между системами, так и упрощение настройки данного решения, ведь её смог бы проводить администратор без участия разработчика.

Что стоит выносить в константы и параметры ТМ? Отдельно стоит выносить только идентификаторы объектов и данные, которые могут измениться при переносе на другую систему.
Пример данных, которые стоит выносить в параметры или константы:  

  • настройки подключения (систем интеграции, сервисов и т.д.);
  • ИД документов (макеты, шаблоны и т.д.);
  • Коды или ИД записей справочников;
  • списки групп пользователей;
  • списки пользователей;
  • пути для записи log-файлов и т.п.;
  • данные, которые могут быть изменены при переносе на другой сервер или систему.

Особенности при разработке типовых маршрутов

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

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

Это повысит удобство при настройке и позволит избежать ошибок из-за невнимательности.

Пишем SQL запросы правильно!

При составлении и использовании запросов к базе данных, стоит придерживаться ряда правил:

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

    Давайте рассмотрим два примера:

    // Пример 1
    AUTOTEXT = References.AUTOTEXT.GetComponent
    SQLText = Format('select count(*) from dbo.MBAnalit where Vid = %s'; AUTOTEXT.ComponentID)
    // Пример 2
    AUTOTEXT = References.AUTOTEXT.GetComponent
    SQLText = Format('select count(*) from dbo.%0:s where Vid = %1:s'; 
           ArrayOf(AUTOTEXT.SQLTableName; AUTOTEXT.ComponentID))

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

  2. Microsoft предлагает указывать в запросе обращения к объектам четырехсоставными именами:

    [server_name].[database_name].[schema_name].[object_name]

    Но также поддерживаются более короткие записи:

    [database_name].[schema_name].[object_name]
    [schema_name].[object_name]
    [object_name]

    Где:

    • server_name - Указывает имя связанного или удалённого сервера.
    • database_name - Указывает имя базы данных SQL Server, если объект хранится на локальном экземпляре SQL Server. Когда объект находится на связанном сервере, аргумент database_name указывает каталог OLE DB.
    • schema_name - Если объект находится в базе данных SQL Server, указывает имя схемы, которая содержит объект. Когда объект находится на связанном сервере, аргумент schema_name указывает имя схемы OLE DB.
    • object_name - Ссылается на имя объекта.

    Чтобы избежать ошибок разрешения имён, при указании объекта области схемы рекомендуется всегда указывать имя схемы:

    [schema_name].[object_name]

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

    • без указания схемы, SQL сервер начинает искать таблицу в схеме по умолчанию для пользователя. В случае если таблица не найдена в схеме пользователя, поиск таблицы будет продолжен в схеме по умолчанию для сервера. У такого подхода есть минус. В результате выполнения запроса могут вернуться данные из таблицы, расположенной в другой схеме;
    • каждый запрос будет компилироваться, а значит SQL сервер будет тратить время на его выполнение. Но только тратой серверных ресурсов тут дело не ограничивается, в дополнение ко всему есть вероятность, что кэш планов запросов разрастётся, что в дальнейшем может сказаться на производительности всей системы в целом.

    Ситуация в связи с переносом разработки может усугубиться, если дополнительно указать название сервера. Вариант с указанием названия сервера стоит использовать только в межбазовых запросах. В основном это интеграции с другими системами, когда интегрируемая система прилинкована к SQL серверу. В таких ситуациях название прилинкованного сервера необходимо указывать в константе и подставлять в SQL запрос перед выполнением.

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

    Получать в таких случаях название базы данных следует через константы и объектную модель:

    // Получение наименования базы данных Directum
    Application.Connection.DatabaseName
  3. При составлении SQL запросов, ориентируйтесь на совместимость синтаксиса с последними версиями SQL серверов.
    В зависимости от уровня совместимости базы данных, появляется поддержка определённых команд или становится недоступной часть SQL синтаксиса.
    Вспомним о новом режиме Modern, который появился в DIRECTUM 5.4.
    О нем уже достаточно написано, однако не вся прикладная разработка без модификаций может работать в новом режиме. Данный режим появился ввиду отказа Microsoft от поддержки старых параметров соединения, что делает SQL ещё быстрее и стабильнее, однако требует модификаций текущей разработки.
    Рассмотрим два примера:

    -- Пример 1
    Select ValuePar from dbo.XIni where NamePar = "PlatformVersion"
    -- Пример 2
    Select ValuePar from dbo.XIni where NamePar = 'PlatformVersion'

    Запросы отличаются только кавычками, однако первый вариант не будет работать в новых редакциях MS SQL, а второй продолжит возвращать ожидаемые значения без доработок во всех редакциях MS SQL.

    Давайте ещё рассмотрим вариант запроса, который считается устаревшим:

    -- Устаревший вариант соединения таблиц
    select * from dbo.Orders o, dbo.Customers c where o.cnum *= c.cnum

    Операторы *= and =* для внешнего соединения перестали поддерживаться в базах с уровнем совместимости 90 (SQL Server 2005) и выше.

    Вариант для замены:

    -- Работающий вариант запроса
    select * from dbo.Orders o left join dbo.Customers c on c.cnum = o.cnum
  4. Не рекомендуется вставлять и изменять данные в системе непосредственно через SQL запросы, т.к. эти манипуляции могут нарушить работу всей системы и вызвать проблемы.

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

По возможности не вносите изменения в стандартную разработку

Не рекомендуется изменять элементы стандартной разработки DIRECTUM.
Давайте рассмотрим на примере функций. Допустим, вы внесли изменения в функцию DayOfWeekName(). После обновления ваши изменения могут быть потеряны или несовместимы с новой версией функции. Для того чтобы этого избежать, следует создать вторую функцию на основе имеющейся, методом копирования с префиксом и необходимыми изменениями.

Пример наименования такой функции: VFMDayOfWeekName().

Изменение ISBL-кода в стандартной разработке необходимо выделять

Редко стандартное решение не дорабатывается под нужды организации. В таких случаях все свои доработки необходимо выделять и отмечать в коде. Хорошим правилом считается отмечать начало и конец изменений в коде хорошо видимым комментарием, а также единым обозначением.  Также не рекомендуется удалять стандартный код, для более понятного и лёгкого анализа в будущем.

Пример замены стандартной разработки:

// -----------------------------------------------
// Start Very Fast Motor 
// Мешало параллельно работать с задачами 
// -----------------------------------------------

// Object.Form.ShowModal
Object.Form.ShowNoModal

// -----------------------------------------------
// End Very Fast Motor 
// -----------------------------------------------

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

Добавляем реквизиты в стандартную разработку правильно!

Разберём ситуацию, когда Вам необходимо провести модернизацию стандартного модуля и добавить новый реквизит в разработку, ранее в ней не задействованный.

С одной стороны, это может показаться простым: берём незадействованный реквизит и используем его под свои нужды.
На самом деле беспорядочный подход способен вызвать проблемы при обновлении, т.к. в новой редакции модуля (разработки) данный реквизит может быть задействован. Для того чтобы избежать данных ситуаций, рекомендуется брать сгенерированный реквизит с максимально возможным порядковым номером или аналогичный сгенерированный реквизит с префиксом организации.

Пример: Вам необходимо добавить реквизит с типом Дата, в карточку документа. В списке незадействованных реквизитов существуют реквизиты от Дата3 до Дата7. Для уменьшения вероятности появления несовместимости в дальнейшем, более правильным будет выбрать реквизит с именем Дата7.

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

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

Документируйте как можно больше информации

Пожалуй, многим известна ситуация, когда необходимо перенести разработку с тестовой системы на рабочую. Вы выполняете перенос, но разработка не работает потому, что, например, забыли выдать права доступа или перенести изменённую функцию. Именно для того, чтобы избежать подобных ситуаций, и следует вести документирование.
Такой подход к разработке поможет в будущем сэкономить время и избежать непредвиденных проблем. Формат и полноту документируемой информации каждая организация может определить сама.
Альтернативным вариантом подготовки документации могут служить достаточно полные и структурированные комментарии в коде. Например, можно разместить большой комментарий в самом начале вычислений или в примечании к разработке. В тексте комментария можно описать все константы, необходимые для работы, выполненные модификации, минимально необходимые права доступа и т.д.

Дополнительные материалы:

Алексей Семакин

Можно, я сразу начну дискутировать?

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

Я согласен. Однако безоговорочное следование этому принципу приводит к появлению вот такого кода:

  Ref = References.SomeReference.GetComponent()
  DDS = Ref.DetailDataSet(6)   
  QueryTemplate =
  Format("EXECUTE sp_executesql N'
          select 
            %1:s.%6:s
          from   %1:s 
            join  %0:s  on %0:s.%4:s = %1:s.%9:s
               and %0:s.%3:s  = %1:s.%7:s
          where  %1:s.%7:s = %2:s
            and %0:s.%5:s = ''%10:s''
            and %1:s.%8:s = ''%11:s'''";
            
   ArrayOf(Ref.TableName;
           DDS.TableName;
           Ref.ComponentID; 
           Ref.Requisites(SYSREQ_REFERENCE_TYPE_ID).FieldName;
           Ref.Requisites(SYSREQ_ID).FieldName;
           Ref.Requisites(SYSREQ_NAME).FieldName;
           DDS.Requisites('ISBYesNoT6').FieldName;
           DDS.Requisites(SYSREQ_REFERENCE_TYPE_ID).FieldName;
           DDS.Requisites('ISBString3T6').FieldName;
           DDS.Requisites(SYSREQ_MAIN_RECORD_ID).FieldName;
           RecordName; 
           DDSRecordName))

Это реальный код, изменено только имя справочника. Такой код безумно сложно рецензировать и неудобно сопровождать. Им только детей пугать!

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

Может, читатели предложат другие решения?

Владимир Гарипов

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

Павел Леонов

Что мешает завести переменные с понятными именами для таблиц/полей? Можно и комментарии к ним добавить. Да - это увеличение количества кода, но облегчение в плане поддержки.

Михаил Тарасов

Павел, то, что переменные тут идут после запроса. Что бы сопоставить, чего у меня тут выводится второй колонкой в селекте и из какой таблицы это берется, нужно произвести не хилую такую (для простой задачи) умственную работу. Сопоставить номера подстановок и отсчитать нужную подстановку в массиве. Это кстати ещё не самый плохой пример приведен. Тут хоть форматирование более менее соблюдено...

Мы не паримся и пишем имена таблиц и колонок "как есть". Подстановке подвергаются ИД типов справочников и данные для фильтрации. И подстановки осуществляются не с помощью функции Format, а с помощью оператора конкатенации & прямо в текст запроса.

sql = "select a.Kod
form MBAnalit a
where a.IDSpr = '"& myValue &"'
  and a.vid = '"& References.__myRef.ID &"'"

result = sql(sql)

 

Владимир Гарипов

Для того, чтобы не считать можно делать так:

   ArrayOf(Ref.TableName;                                      // 0
           DDS.TableName;                                      // 1
           Ref.ComponentID;                                    // 2
           ... 
           DDS.Requisites(SYSREQ_MAIN_RECORD_ID).FieldName;    // 9
           RecordName;                                         // 10 
           DDSRecordName                                       // 11
           ))

 

Юлия Литвинюк

Я при сложных запросах пишу их в комментарии в коде

/*
    select cast(cast(t.TextT as binary) as varchar(1024)) as Comment
    from dbo.MBAnValR doc
    join dbo.XObj Obj on Obj.TblName = 'MBAnValR'
    join dbo.MBText t on t.SrcObjID = Obj.XRecID and t.SrcRecID = doc.XRecID and t.TextT is not null
    where
    doc.Vid = 3385 and doc.Analit = 2714282
    */
    SelectText = Format(
                    " select cast(cast(t.%3:s as varbinary(1024)) as varchar(1024))
                      from dbo.%2:s doc
                        join dbo.XObj Obj on Obj.TblName = ''%2:s''
                        join dbo.MBText t on t.SrcObjID = Obj.XRecID and t.SrcRecID = doc.XRecID and t.%3:s is not null
                      where
                        doc.Vid = %0:s and doc.%1:s = @MessageID" ;
                  ArrayOf(
                  MessagesRef.ComponentID; //0
                  MessagesRef.Requisites(SYSREQ_ID).SQLFieldName; //1
                  DocDS.SQLTableName; //2
                  DocDS.Requisites('TextT').SQLFieldName //3
                  ))

Времени занимает немного (просто копирую запрос на котором проверяю), но так намного удобнее рецензировать и в будущем сопровождать код.

Александр Чугунов

Статья отличная, очень нужная.
Поддержу Алексея и Михаила. Много полезных советов, но есть некоторые пункты, по которым советы слишком перфекционисткие что-ли, не дающие реальной пользы или не учитывающие всех возможных ситуаций, только усложняющие разработку и дальнейшее восприятие кода.
По пунктам:
1) Очень, очень маловероятно, что данные перекочуют в другие таблицы. Но даже если такое ВДРУГ КОГДА-НИБУДЬ случиться, то не стоит труда найти все упоминания таблицы в коде и заменить на нужные. Гораздо проще и удобнее сделать это один раз, чем годами/десятилетиями писать и читать излишне сложный код.
2) "В результате выполнения запроса могут вернуться данные из таблицы, расположенной в другой схеме" - проще следить за тем, чтобы не было таблиц с такими же именами. Опять же, все кастомные таблицы с префиксами.
3) "По возможности не вносите изменения в стандартную разработку" - тут всё зависит от ситуации. Если логика функции меняется, то надо создавать свою. А если мы исправляем баг в стандартной разработке или улучшаем ее, то стоит исправить стандартную функцию. Тогда при обновлении мы перенесем исправление бага или нужное нам улучшение в стандартную функцию и не надо будет везде, где используется эта функция, менять на свою (и в свою добавлять новую логику из обновления).
4) "Изменение ISBL-кода в стандартной разработке необходимо выделять" - довольно легко сравнить всю свою разработку со стандартной. И сразу станет понятно что меняли. Так что не надо менять использование хороших инструментов на костыли, которые при множественных изменениях сделают из небольшого куска кода простыню из "выделений". Комментарий тоже стоит писать только если с первого взгляда непонятно из-за чего поменяли этот кусок кода. Вообще надо допиливать уже работу с системой контроля версий, чтобы было понятно не только что, а еще и кто и когда что-то менял....
+ то, что выше изложили Алексей и Михаил.

Владимир Гарипов

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

п 2. Не соглашусь. Даже на уровне платформы часто создаются временные таблицы в схеме пользователя. А еще использование схемы может быть отличным вариантом для подготовки данных для отчета, когда все данные готовятся на стороне SQL, а чтобы не было пересечений в случае если пользователи одновременно запустили формирование отчета то можно использовать схему пользователя. Вообще считаю создавать  таблицы в схеме dbo для временного использования - зло.

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

п 4. Под рукой может и не быть изначальных вычислений, для проведения сравнения. А открыв один раз код можно понять что было до и что есть сейчас. Но соглашусь что стоит применять данный подход в гранях разумного.

Владимир Гарипов: обновлено 04.09.2017 в 14:51
Александр Чугунов

Владимир, 
1) Ну не будет никто переименовывать MBAnalit и выносить оттуда часть данных=) Вы посмотрите стандартную разработку - там сотни раз упоминается MBAnalit, MBAnalitSpr и тд. Вендор не станет менять названия таблиц, так как это сломает очень много всего. Ну не надо предусматривать вообще все варианты развития событий, только самые вероятные. Выключите перфекционизм и включите практичность.
2) Ну приведите пример, когда может получиться что у нас имена таблиц одинаковые если мы придерживемся озвученного мной правила "все кастомные таблицы с префиксами". Видать Вы это правило не прочитали или не поняли. 
3) Я согласен с дополнением, что надо учитывать то, что функция много где может использоваться. Но это не повод не менять стандартную функцию. Просто надо быть осторожнее. Тема в статье не раскрыта и воспринимается как "Не влезай! Убьет!".
4) "Изменение ISBL-кода в стандартной разработке необходимо выделять" - это в каком случае у Вас не будет стандартной разработки? Да и относится это только к стандартной разработке, так как может вызвать проблемы с конвертацией. Точнее только при конвертации.

Александр Чугунов

Юлия, а теперь представьте, что кто-то поменял запрос, но не поменял комментарий.
Еще несколько лет назад десяток опытных разработчиков дискутировало на эту тему и пришло к выводу, что так делать не надо.
И не надо защищать стандарты разработки своей организации, за которые Вы ответственны. Лучше выслушайте что Вам посоветует сообщество и улучшайте их!

Юлия Литвинюк

"Юлия, а теперь представьте, что кто-то поменял запрос, но не поменял комментарий."

Лично я такое на рецензировании не пропущу =) Я поделилась своим опытом (такого у нас в стандартах нет если чо), а уж прислушиваться к нему или нет решать вам.

"Очень, очень маловероятно, что данные перекочуют в другие таблицы. Но даже если такое ВДРУГ КОГДА-НИБУДЬ случиться, то не стоит труда найти все упоминания таблицы в коде и заменить на нужные" - если перекочуют только некоторые справочники в другие таблицы (что более вероятно), думали как искать будете?

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

 

Авторизуйтесь, чтобы написать комментарий