Данная работа является оформлением некоторых размышлений, появившихся при знакомстве с продуктом «1С:Предприятие 8.0» (далее V8). По своей направленности работа является в некотором роде продолжением статьи «От V7 к V8. Обсуждение концепций структур данных» [1] . Часть поднятых в той работе вопросов устарела с выходом V8, часть осталась, появились и новые.
Небольшое вступление (или отступление)
Как известно, в электронике существует всего два типа проблем – отсутствие контакта там, где он должен быть и его наличие там, где его быть не должно. Сходным образом можно сформулировать два типа проблем в программных концепциях:
- Смешение нескольких понятий там, где требуется их разделение;
- Использование различных понятий там, где можно обойтись одним.
Первая проблема обычно приводит к неполному описанию (функциональному покрытию) предметной области, а вторая – к сложности (запутанности) внутреннего устройства систем.
Процессы повышения «внутреннего качества» системы обычно называют рефакторингом и/или нормализацией. « Рефакторинг (Refactoring) (сущ.) представляет собой процесс такого изменения программной системы, при котором не меняется ее внешнее поведение, но улучшается внутренняя структура» [2]. Под нормализацией предметной области (системы) мы понимаем выделение минимального числа независимых понятий (сущностей, объектов), позволяющих описывать данную область (реализовывать требования системы) в полном объеме. В данном определении два ключевых слова «минимального» и «независимых» связаны между собой. Если, все понятия предметной области независимы, то их количество будет минимально, и, наоборот, при минимизации количества понятий их взаимосвязанность уменьшается.
Вообще, можно выделить два рода проблемных систем (подсистем). Плохо спроектированные системы первого рода слабо отвечают заявленной функциональности (например, ненадежны). Очевидно, что сравнивать качество систем по простоте (сложности) их внутреннего устройства можно лишь при их сравнимой внешней функциональности. Поэтому, первый коэффициент качества системы (подсистемы) можно представить в виде: КК = Функциональность/Сложность.
Подозрительные системы второго рода функциональность отрабатывают, но их трудно модифицировать. Оглядываясь на развитие программных систем, мы видим, что именно «простота модификации» является историческим судьей, выносящим приговор той или иной системе. Для оценки качества программных систем очень важен дифференциальный КК, показывающий насколько сложно изменить систему: dКК = (∆ функциональности)/(∆ сложности). Разработчик любого коммерческого продукта находится под давлением необходимости «расширения функциональности продукта в сжатые сроки». Возникает соблазн пожертвовать ясностью и простотой. Разумеется, такие жертвы допустимы только до определенного предела, после которого сложность системы не позволяет наращивать ее функциональность эволюционным путем. Кстати, тот факт, что прямая преемственность между V7 и V8 отсутствует (V8 не поддерживает конфигурации V7), говорит о низком значении dКК для V7. Одной из причин этого является то, что объектная модель V7 является плоской и замкнутой.
Удобство использования и простота модификации, минимизация сущностей и полнота покрытия потребностей разработчиков конфигураций являлись основными критериями для оценки «проблемности» того или иного решения, реализованного в V8.
Набор базовых объектов
Платформа «1С:Предприятие» является универсальной учетной платформой. В то же время согласно классическим учебникам [3], именно учет является одной из основных задач баз данных. Таким образом, платформу V8 можно отнести к классу объектно-ориентированных СУБД (ООСУБД), точнее к классу темпоральных (обеспечивающих работу с динамическими данными) ООСУБД.
Однако при такой классификации (и, соответственно, позиционировании) бросается в глаза чрезмерная детальность V8 в отношении нескольких частных прикладных задач. Речь идет, прежде всего, об объектах для решения бухгалтерских и расчетных задач. Не умаляя их важности, все же отметим, что данные объекты принадлежат другому уровню, прикладной предметной области. Они нарушают стройность и законченность фундаментального набора объектов. В каком-то смысле объектная модель V7 была более логичной: прикладные объекты были вынесены в отдельные компоненты платформы.
Итак, в фундаментальный (базовый) набор хранимых объектов входят: Константы, Справочники, Документы и Регистры. Все остальные объекты должны строиться на основе базовых. В идеале – через механизм наследования свойств, но поскольку V8 является плоской объектной средой, то, значит, через эмуляцию наследования.
Необходимость разделения объектов (фактически классов) по слоям (уровням) предметной направленности кажется настолько очевидной, что вряд ли здесь требуются какие-либо доказательства. То, что базового набора достаточно для успешного решения как бухгалтерских, так и расчетных задач, доказано на практике. А вообще, о негативных последствиях того, что различные классы объектов выполняют фактически одно и то же, много говорится, например, в [2]. Помимо неудобств для разработчиков (имеющих тенденцию возрастать со временем) отметим неудобства для пользователей в изучении и использовании похожих классов. Все ли их свойства тождественны? Нет ли каких-либо тонкостей, о которых необходимо постоянно помнить?
Чем различаются, например, накопительный и бухгалтерский регистр? Существует ли какая-то разница в поведении объекта «План счетов» и обычного справочника? А если и существует, то достаточна ли она для выделения плана счетов в отдельный класс? В V7 план счетов отличался тем, что, в отличие от обычного справочника для него существовала возможность ввода в режиме «Конфигуратор» так называемых предопределенных элементов (счетов). Но в V8 механизм предопределенных элементов реализован для любого справочника.
Обобщение предопределенности
Опыт разных команд разработчиков тиражных конфигураций на V7 привел к появлению понятия «предопределенных элементов справочников» (еще их называют регламентированными элементами). Это такие элементы, значения (и поведение) которых разработчику заранее известно (поскольку им же определено). Почему именно «тиражных конфигураций»? В «заказных» конфигурациях разработчик имеет дело с одной уникальной базой данных, поэтому для него в некотором смысле все элементы справочников являются предопределенными. Он имеет возможность произвольного управления свойствами того или иного элемента. В тиражных же конфигурациях разработчику заранее неизвестно, какой, например, код присвоят пользователи (или установит государство) бухгалтерскому счету учета взаиморасчетов с поставщиками. Прописывание в коде строковых констант («Если Счет.Код=«60» Тогда…») противоречит всем санитарным нормам создания программного кода. Зато при наличии механизма предопределенных элементов можно написать: «Если Счет=Справочники.ПланСчетов. СчетПоставщиков Тогда…». Красиво, удобно и читаемо.
Попробуем обобщить понятие предопределенности. Прежде всего, отметим, что теперь тип метаданных «Перечисление» становится излишним, поскольку Перечисление есть ни что иное, как Справочник, все элементы которого являются предопределенными. Соответственно, методы и свойства перечислений должны совпадать с методами и свойствами справочников. Удобство объединения понятий «перечисление» и «справочник» заключается также в простоте перехода от справочника с жестко определенным набором элементов к справочнику с расширяемым набором элементов. Такая модификация может быть востребована в сложных проектах, в которых на начальном этапе разработки конфигурации не всегда можно уверенно определить, должен ли быть набор элементов справочника/перечисления доступен для расширения пользователем или нет. Кстати, в текущей версии V8 отсутствует возможность задавать для предопределенных элементов справочников значения их реквизитов. Наверное, такая возможность была бы полезной.
Теперь рассмотрим проблему «предопределенных элементов» шире. В процессе разработки любой тиражной конфигурации разработчики стремятся достичь компромисса между жесткостью (предопределенностью) и гибкостью (настраиваемостью) ее свойств. Таким образом, понятие предопределенности можно (нужно) применить ко всем объектам платформы (конфигурации). Например, упоминаемый выше «План счетов» есть не что иное, как предопределенный справочник (элемент списка справочников). Другими кандидатами на предопределенные элементы (группы) данного списка являются справочники «План видов характеристик» и «План видов расчета». Пригодился бы также регламентированный справочник «Виды документов» с предопределенным перечнем элементов, совпадающим с видами документов метаданных. Набор прав (роли) также логично было бы реализовать в виде регламентированного справочника. Такая реализация даст возможность доопределять реквизиты, создавать подчиненные справочники и т.д.
Предопределенными также могут быть (точнее являются по умолчанию) модули (код) тиражной конфигурации, ее переменные, формы, макеты и пр. Перенастраивать предопределенные элементы (например, менять код) является плохим стилем (возникают проблемы обновления). Вместо этого необходимо использовать возможности расширения, которые обычно предоставляются разработчиками для конечных пользователей (к примеру, добавление внешних модулей, печатных форм и пр.).
Понятие предопределенности распространяется также на реквизиты хранимых объектов (справочников, документов, регистров). В V8 для возможности расширения предопределенного набора реквизитов ввели объект «План видов характеристик». Для конечного пользователя было бы проще (удобнее) работать с понятиями «предопределенные реквизиты» и «реквизиты, определенные пользователем». При обновлении конфигурации поставкой разработчика все объекты, которые были определены пользователем, не затрагиваются. Модификации могут быть подвержены только «предопределенные элементы».
Объектность и подчиненность
Хранимые данные V8 делятся на два типа: ссылочные данные (объекты) и наборы записей (коллекции). Объектные данные характерны тем, что на них могут ссылаться другие данные. На элемент набора записей возможность ссылки отсутствует. Объектными данными являются справочники и документы. Примером набора записей служат табличные части тех же справочников и документов, данные движения регистров. В V8 наборы записей всегда подчинены какому-либо объекту (имеют владельца).
Как известно, в V7 справочники не имели табличных частей, зато могли иметь подчиненные справочники. Табличную часть (правда, всего одну) мог иметь документ. В V8 возможности справочников расширили (добавили механизм табличных частей, множественность подчинения), и стала очевидной натянутость разделения сущностей подчиненный справочник и табличная часть. Во-первых, на начальном этапе разработки конфигурации не всегда очевидно, какой тип объекта для подчинения (справочник или табличную часть) следует использовать. Следовательно, при необходимости модификации подчинения (преобразования табличной части в справочник) могут возникнуть проблемы. Во-вторых, фактически подчиненный справочник отличается от табличной части лишь наличием скрытого реквизита (идентификатора объекта OID), обеспечивающего возможность ссылки на элемент. Во всем остальном их свойства совпадают (должны совпадать). Например, для табличных частей (как справочников, так и документов) также бы пригодилась возможность иметь несколько видов владельцев. В этом случае табличная часть многих видов документов (к примеру, СчетФактураПриход и СчетФактураРасход) могла быть одним объектом, что опять же удобно при модификации их свойств.
Таким образом, имеет смысл объединить подчиненные справочники и табличные части в единый класс (например, ПодчиненнаяКоллекция), а разработчику конфигурации дать возможность устанавливать/отключать (естественно, соблюдая при этом ссылочную целостность) свойство «ссылочности» (объектности) элементов коллекции. При объединении данных сущностей становится единообразным и синтаксис доступа к элементам подчиненных объектов. То есть свойство справочника (документа) естественным образом перешло бы в свойство , давая доступ к реквизитам ее элементов.
Свойство подчиненности следует распространить и на регистры, точнее на детерминанты регистров. Данная проблематика требует отдельного раздела.
Регистры и детерминанты
Регистры являются центральным объектом, на котором базируются учетные системы. Подавляющее большинство всех отчетов в конфигурациях строятся на основе данных регистров. В [1] показано, что регистры являются реализацией фундаментальной концепции Функциональной Зависимости. Регистр сведений, введенный в V8, закрыл определенную брешь в иерархии регистров V7, на которую было указано в [1]. Тем не менее, предложенная в V8 модель регистров не является законченной (недостаточно покрывает предметную область).
Перечислим несколько проблемных моментов, которые не решаются удовлетворительно в рамках концепции V8. С устоявшейся терминологией пока туговато, - для набора измерений регистров мы будем использовать термин детерминант .
Объединение различных типов ресурсов с общим детерминантом
Во многих учетных задачах было бы удобно хранить в одном регистре (для одного набора измерений) ресурсы различных типов. Часто требуется хранить «состояние чего-либо», подтвержденное значением накопительного ресурса. Например, в регистре с детерминантом {#Товар, #Склад} наряду с накопительным ресурсом «Остаток товара» можно было бы хранить простой ресурс «Планируемая дата поступления». Таким образом, правильно говорить не о разных типах регистров, а о различных типах ресурсов – простые, периодические, накопительные остаточные, накопительные оборотные.
Учет единиц измерения накопительных ресурсов.
Данная проблема уже поднималась в [1]. Проще всего ее пояснить на примере количественного учета товаров на складах в различных единицах измерения и/или стоимостного в различных валютах. Один товар может учитываться на складе одновременно в разных единицах измерения. Очевидно, что суммировать имеет смысл лишь те ресурсы, которые выражены в одинаковых единицах измерения – штуки со штуками, килограммы с килограммами, рубли с рублями. Решением представляется введение понятия подчиненности регистров регистрам (точнее детерминантам регистров). Тогда ресурсы, требующие единиц измерения, можно будет хранить в подчиненных регистрах. Для ресурса «Количество» подчиненный регистр будет состоять из измерения «Единица измерения товара» и непосредственно количественного ресурса. Для ресурса «Стоимость» подчиненный регистр будет состоять из измерения «Валюта» (ссылка на соответствующий справочник) и накопительного ресурса «Стоимость».
Справочник или регистр сведений?
Данная проблема отсутствовала в V7, поскольку регистров сведений не существовало, и выбора не было. V8 же стимулирует пересмотр многих привычных концепций. Простой вопрос, какая структура данных V8 (объект метаданных) лучше всего подходит для отражения такого привычного объекта как «Сотрудник»? Поясним, почему выбор справочника неочевиден. Вообще говоря, сотрудник является ролью, которую исполняет физ. лицо по отношению к компании, его нанявшей. Для отражения таких связей двух объектов удобен регистр сведений – в данном случае с детерминантом {#ФизЛицо, #Фирма} и ресурсами, в которых хранятся атрибуты данной связи. Однако регистр сведений не является ссылочной сущностью, на такого сотрудника отсутствует возможность ссылки. Если для учета сотрудников заводить отдельный справочник, то теряется выделенность реквизитов «ФизЛицо» и «Фирма».
Мы перечислили несколько ситуаций, указывающих на неполное функциональное покрытие модели регистров V8 существующих потребностей. Указанные проблемы можно было бы решить через придание детерминанту регистров свойства «ссылочности».
Итак, является ли детерминант регистра быть объектной сущностью? На наш взгляд, да. Возьмем, к примеру стандартный регистр складского учета с детерминантом {#Номенклатура, #Склад}. И Номенклатура, и Склад являются объектами (справочниками). В то же время присутствие данной номенклатуры товара на данном складе означает и существование нового независимого объекта – . Ресурсы регистра играют роль реквизитов этой сущности. Таким образом, мы предлагаем ввести новый объектный тип метаданных – Детерминант. В регистре в качестве измерения можно указывать ссылку на какой-либо детерминант. Фактически, мы добавляем дополнительный уровень косвенности в структуры данных. Теперь ссылка на измерение регистра будет не прямой (Регистр.СкладскойУчет.Товар), а косвенной (Регистр.СкладскойУчет.Детерминант.Товар). Очевидно, что структура и свойства детерминанта будут совпадать со свойствами статического регистра сведений без ресурсов с тем отличием, что будет присутствовать скрытый реквизит OID.
При выполнении движений по регистру, детерминант которого является ссылочным, система должна проверить существование в таблице детерминанта записи с нужным набором измерений. При отсутствии таковой соответствующее значение детерминанта создается. Возможно, что также потребуется хранить в детерминанте «Количество внешних ссылок» с тем, чтобы при уменьшении их количества до нуля автоматически удалять данный детерминант из таблицы. То есть провели приход номенклатуры на склад – количество внешних ссылок на данный детерминант инкрементируем, отменили проведение – декрементируем и сверяем с нулем.
Отметим также, что косвенность измерений регистра (измерение ссылается на детерминант) приводит к упрощению модификации их структуры. Добавление нового измерения в детерминант не требует пересчета итогов регистров, поскольку структура регистра не меняется.
Объектный (ссылочный) детерминант является удобной структурой для хранения адресов (указателей) чего-либо. Например, детерминантом могут быть адреса проживания физ. лиц, поскольку не может быть двух адресов с одними и теми же значениями набора измерений. Адресом является также дебетовая и/или кредитовая часть бухгалтерской проводки. Совокупность бухгалтерского счета и набора значений субконто и есть адрес, на котором необходимо изменить остаток. Следовательно, измерением регистра остатков, хранящего бухгалтерские итоги, должна быть просто ссылка на детерминант, в котором бы хранился набор значений счета и субконто. Кстати, одно из преимуществ такой организации – отсутствие ограничения на количество уровней аналитики. Набор допустимых видов субконто совпадает с набором измерений детерминанта. Сколько видов субконто присутствует, - столько и возможных уровней аналитики на счете.
К ссылочным детерминантам также применимо естественным образом понятие подчиненности. То есть регистр может иметь детерминант, подчиненный другому детерминанту (решает проблему учета накопительных ресурсов в разных единицах измерения).
Периодические реквизиты справочников
В V8 для хранения периодических реквизитов справочников предлагается использовать регистры сведений. Это разумно. Однако теперь обычные (статические) реквизиты справочника определяются в одном месте (в самом справочнике), а периодические – в другом (в регистре сведений). И никакой видимой связи между ними. Получается, что разработчик конфигурации должен помнить, какие реквизиты, где лежат. Очевидно, что это неудобно. Обычной также является ситуация, когда статический реквизит становится динамическим. Изменить периодичность реквизита в V8 далеко не так же просто, как в V7. Было бы неплохо вернуть понятие периодических реквизитов на уровне семантики. Это было одно из заметных удобств в v7. То, что способ хранения периодичности кардинально изменился, совсем не означает, что теперь надо отказаться от самого понятия.
Может быть, имеет смысл использовать для хранения периодических реквизитов справочника регистр сведений с детерминантом, подчиненным данному справочнику? Если в детерминанте отсутствуют измерения, а сам регистр – периодический, то получим обычные периодические реквизиты справочника. В то же время, если в детерминанте будут присутствовать какие-то дополнительные измерения, то это будет означать, что для определения значения данного реквизита справочника, помимо даты нужно что-то еще. Например, в таком регистре можно было бы хранить реквизит «Адрес» для справочника «Юр. (физ.) лица». А в качестве дополнительного измерения детерминанта использовать перечисление {«Фактический», «Юридический»}.
Ссылка, Объект, Выборка...
В таблице 1 приведены свойства трех различных объектов для работы с данными справочников V8: СправочникСсылка, СправочникОбъект, СправочникВыборка (то же для документов).
Свойство |
СправочникСсылка |
СправочникОбъект |
СправочникВыборка |
+ | + | + | |
+ | + | + | |
Владелец | + | + | + |
Код | + | + | + |
Наименование | + | + | + |
ПометкаУдаления | + | + | + |
Предопределенный | + | + | + |
Родитель | + | + | + |
Ссылка | + | + | + |
ЭтоГруппа | + | + | + |
ЭтотОбъект | + |
Таблица 1. Перечень свойств объектов для работы со справочниками («+» означает наличие свойства).
Мы видим, что у различных объектов практически одни и те же свойства. Правда, большинство свойств (но не все) «СправочникОбъект» доступны как для чтения, так и для записи, а свойства остальных объектов – только для чтения.
Теперь составим аналогичную таблицу для методов данных объектов.
Свойство |
СправочникСсылка |
СправочникОбъект |
СправочникВыборка |
Заблокирован | + | ||
Заблокировать | + | ||
Записать | + | ||
Заполнить | + | ||
Метаданные | + | + | |
Модифицированность | + | ||
ПолноеНаименование | + | + | |
ПолныйКод | + | + | |
ПолучитьМакет | + | ||
ПолучитьОбъект | + | + | |
ПолучитьФорму | + | + | |
ПринадлежитЭлементу | + | + | |
Прочитать | + | ||
Пустая | + | ||
Разблокировать | + | ||
Скопировать | + | + | |
Следующий | + | ||
Удалить | + | ||
Уровень | + | + | |
УровеньВВыборке | + | ||
УстановитьНовыйКод | + | ||
УстановитьПометкуУдаления | + | ||
ЭтоНовый | + |
Таблица 2. Перечень методов объектов для работы со справочниками («+» означает наличие метода).
При взгляде на таблицу 2 возникает много вопросов. Почему при совпадающих свойствах объектов не обеспечено подобное же совпадение их методов? Почему бы ни обеспечить трансляцию методов «СправочникОбъект» в «СправочникСсылка» и «СправочникВыборка»? Ведь и «Ссылка» и «Выборка» имеют метод ПолучитьОбъект(), то есть возможность добраться до методов объекта существует. Почему-то «Ссылка» может напрямую ПолучитьФорму(), но не может ПолучитьМакет(). А «Выборка» вообще ничего напрямую не может. Не просматривается единой логики.
Вообще, возможно, было бы правильней не создавать линейку схожих объектов, а ввести понятие «состояние объекта» (нормализация!). То есть заявить о том, что объект «Справочник» (на уровне работы с данными) может иметь несколько состояний – «Выборка», «Ссылка», «Объект». В соответствии с теорией каждое состояние характеризуется своим набором свойств, методов, событий. В нашем случае, поскольку набор свойств каждого состояния практически идентичен, описание объекта (а, следовательно, его понимание и освоение) значительно бы упростилось. Для каждого свойства (метода) объекта «Справочник» дополнительно потребовалось бы лишь указать его специфику для различных состояний объекта. Функция ТипЗначенияСтр(спрОбъект) возвращала бы всегда «Справочник», а функция СостояниеСтр(спрОбъект) возвращала бы одно из системных перечислений – «Выборка», «Ссылка», «Объект».
Все перечисленное выше относится также и к объекту метаданных «Документ».
Дата и время
Появление данного раздела вызвано неудовлетворенностью принятого в V8 правила измерять разницу дат в секундах, необходимостью определять состав даты и пр. На самом деле разве не странно, что разница между переменными, заданными с одной точностью (день) измеряется с другой (секунда)? Кого-нибудь интересует, чему равна разница в секундах между 12 июня и 1 мая? Разработчики V8 пошли на компромисс (SQL поддерживает формат дата+время), пожертвовав ясностью прикладного кода.
Что можно предложить в качестве альтернативы? Введение понятия точности для объектов дата+время (для избежания путаницы назовем такой объект «Отметка времени»). Причем не одной точности (как это принято для обычных чисел), а двух – левой и правой. Правая точность идентична точности чисел, - определяет точность объекта «Отметка времени» справа. То есть, если правая точность «Секунды», то отметка времени учитывается с точностью до секунды. Если «Минуты» - то до минуты, если «День» - до дня и т.д. «Левая точность» учитывает относительность временных привязок. Объект «Отметка времени» всегда задает временной отступ относительно другой более крупной временной шкалы. Таким образом, если в качестве левой точности временной отметки указан «Месяц», то такой объект указывает на возможность применения данной отметки к какому-либо году. Левая точность всегда грубее правой. Конкретный пример, отметка времени «Март, 8» имеет левую точность «Месяц», правую «День». Объекты такого типа позволяют хранить памятные даты. Отметим, что левая точность «Месяц» подразумевает существование временной шкалы «Год». Другой пример, «Пятница, 10:00». Левая точность – день недели, правая – минуты. В объектах данной точности можно хранить время еженедельных совещаний.
Итак, левая точность задает относительность временных отметок. Однако если ограничиться с левой стороны масштабом – годы, то отметку с такой левой точностью можно считать абсолютной. Для отметок времени с одинаковой точностью доступна операция образования временного интервала . Временной интервал является структурой данных с временными отметками {С, По}. Для временного интервала должна существовать функция вычисления его длины с заданной точностью. Данная точность, естественно, не может превышать точности временных отметок, составляющих интервал.
Заключение
Платформа V8 является замечательным и уникальным продуктом. Надеюсь, что перечисленные выше замечания и предложения будут способствовать дальнейшему развитию и улучшению линейки продуктов «1С:Предприятие».
Ссылки
1. «От V7 к W8. Обсуждение концепций структур данных», - Дмитрий Малюгин, 2001.
2. «Рефакторинг. Улучшение существующего кода», - Мартин Фаулер, «Символ», 2003.
3. «Теория и практика построения баз данных», - Дэвид Крёнке, «Питер», 2003.
Copyright © www.ITland.ru 2002-2003
Начать дискуссию