понедельник, 18 июля 2016 г.

Разделяемость (модульность)

Организующей программной единицей является раздел. В ряде языков их называют модулями [0].

section name {
   /* Это именованное место для упорядоченных объявлений:
      типов, констант, переменных, функций. */
}.
Oberon
MODULE name;
   /* Это именованное место для упорядоченных объявлений: 
      типов, констант, переменных, процедур. */
END name.

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

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

section m1 {
  const (+a = 0;/* экспорт обозначен «+» перед именем */
          b = a + 1)
  ...
}.

section m2 {
  import (m1) /* доступ к элементам раздела через двоеточие */
  const (a = m1:a;/* a = 0 */
         b = m1:b)/* ошибка - константа "b" не экспортирована в "m1" */
  ...
}.

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

section m1 { import (m2) }.

section m2 {
  import (m1)/* ошибка - m1 уже ссылается на m2 */
}.

Назначение разделов

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

  • Раздел может использоваться исключительно как вспомогательный, не влияя на интерфейс импортирующего раздела. Использование такого раздела может быть заменёно другим кодом, выполняющим те же задачи.
  • Раздел, являющийся частью интерфейса импортирующего его раздела. Его объявления могут становиться видимой частью экспортируемых объявлений импортирующего.
  • Раздел, чьё определение отчасти задаётся импортированными разделами-параметрами, и при создании которого учитывалось это.
  • Опциональные разделы, наличие которых можно проверить в коде. Могут использоваться либо для создания более ограниченной версии раздела, либо для задействования более эффективных средств, не приводя к жёсткой зависимости от них.
  • Декоративные разделы, каким-либо образом перестраивающие взаимодействие с основным функционалом, практически не меняя его, например, объединяя интерфейсы разных разделов. Служат для удобства использования.

Поскольку разделов, особенно с учётом сторонних библиотек, может быть много, то возникает необходимость в создании иерархий:

section a.m  { const (+c1 = 1) }.
section a.m2 { const (+c2 = 2) }.
section b.m  { const (+c3 = 3) }.

section m {
  import (
    d.m;  /* в качестве идентификатора раздела после импорта     */
    d.m2; /* используется только вторая часть имени после точки */
    bm = b.m/* переименование, чтобы избежать ошибки совпадения имён */
  )
  const (c4 = m.c1 + m2.c2 + bm.c3)
}.

Разграничение

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

section path+content+full {
  
  type (+t)
  
  section content+full {

    type (
     +t = {
       +name string;
       +next *t
      }
    )

    section full {
      proc +new(name string, subpath *t) (path *t) { ... }
    } full;
 
  } content+full;

} path+content+full.

section editor {
  import (
    /* path+full; */ /* после импорта должен быть доступен по имени path, 
                        но если у editor нет доступа к полному разделу, 
                        то здесь была бы ошибка трансляции */
                        
    path /* эта часть позволяет получать доступ к ресурсам, пути к
            которым явно переданы параметрами, но не позволяет 
            самостоятельно указывать, с какими ресурсами можно работать, 
            как и не даёт доступа к самому имени */
  )
  proc +do(file path:t) { ... }
}.

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

Трансляция

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

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

В операционных системах, где были бы воплощены идеи хотя бы 80-х, а не мусолились идеи 60-70-х, раздел может служить главной и единственной единицей загрузки вместо россыпи таких понятий как программа, статическая и динамическая библиотеки. В морально устаревших же операционных системах нужно будет выполнить наподобие этого:

$ sc build util.do

Что приведёт к созданию исполнимого файла util, полученного из одноименного раздела с точкой входа do - экспортированной процедуры без параметров.


Дополнительные сведения:

[0]Почему раздел - это не модуль или unit?

Чтобы понять почему так, нужно ответить на вопрос:

«Если в разделе несовместимо изменилось экспортированное объявление, должен ли он потерять совместимость с разделами, использующими другие его неизменившиеся объявления?»

Если ответ - «не должен», значит, истинными модулями являются отдельные объявления раздела, а не сам раздел. Объявления пронизаны неявным импортом внутренних объявлений, необходимого для избежания чрезмерного перечисления используемых связей, что было бы нужно в случае раздельного оформления отдельных сущностей раздела.

[1] Оберон умер, да здравствует Оберон! Часть 2. Модули
[2] Михаэль Франц Динамическая кодогенерация: ключ к разработке переносимого программного обеспечения
[3] Webassembly: Design Rationale

Комментариев нет:

Отправить комментарий