Category: архитектура

Category was added automatically. Read all entries about "архитектура".

flow

Польза из сочетания случайностей

Живя в городах мы привыкли к сквозной нумерации домов, "улица такая-то, дом такой-то". Мы знаем, что с большой вероятностью найдём дом 6 между домами 4 и 8, при этом дом 7 будет на противоположной стороне улицы, а дом 32 где-то вдали, в том же направлении, что от 6 к 8. Это удобно.

Однако, такой подход работает только при сочетании нескольких факторов:
1. Квартальная городская застройка (а не "микрорайонная с пустырями", например).
2. Грамотность пользователя, который должен уметь читать и считать. Начиная с двадцатого века мы воспринимаем это как данность, но так было не всегда. Да и сейчас трудности с распознаванием латиницы или кириллицы могут быть, например, у туриста из Китая.
3. Традиция и практика размещения хорошо читаемых табличек с номерами домов.

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

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

Не пишите код

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

При обмене информацией к такому разбиению добавляются новые требования - не только вы должны понимать разбиение и критерии, по которым оно осуществляется (назовём их базисом), но и ваши собеседники или пользователи вашей информационной системы. Подробнее о вопросах, которые при этом возникают: части в пространстве и времени - http://justy-tylor.livejournal.com/171551.html

Для качественного человекопонимаемого разбиения данных внутри информационной системы нужно не так много. Элементарщина (числа, строки, ...), product types, sum types. Базовые коллекции/интерфейсы: Vector, Set, Map (включая атрибутный Map, где identity ключа типизирует значения атрибута). Не-базовые представления для иерархий, графов, etc. Но чем дальше перебирать ситуации и предметные области, тем меньше будет находиться новых представлений, области разные, а человеческое мышление одно.

Так вот, есть у вас какое-то разбиение: доходы_за_прошлый_месяц=X, доходы_за_этот_месяц=Y. Эту информацию можно держать в голове, записать на бумажку или записать в Map. А ещё можно сменить базис разбиения и обозначить ключи как "2013-02" и "2013-03", или вообще записать значения в Vector. Или сменить базис разбиения и... записать значение сумма_доходов. То есть, операционно у нас происходит сложение, а в когнитивном смысле оно просто переход от "количества яблок у Маши" и "количества яблок у Кати" к "количеству яблок у Маши и Кати", другому базису разбиения.

Главное - если базис не меняется, то не нужен и код. На всё про всё хватит обычных интерфейсов коллекций. И основная задача разработчика не строчение KLoC, а выбор такого базиса разбиения информации, при котором количестве кода/переходов будет минимально.

Более того, _любые_ низкочастотные API вида Create/Get/Set/Destroy могут быть переведены на интерфейсы коллекций и решения вида mythread sys.affinity newvalue, со значительным повышением удобства, доступности данных и обучаемости работе с ними. Ибо необходимость каких-либо трансляций остаётся только со стороны низкоуровневых структур библиотеки, но не высокоуровневого и пользовательского кода.

В сегодняшнем мире мы можем наблюдать, как непостижимые сознанию гадюшники вроде DOM оборачиваются в более практичные полу-коллекции jQuery и подобных решений. Огромные усилия тратятся сначала на создание проблемы, затем на превозмогание. Хотя достаточно одного атрибутного мапа, в который прекрасно поместились бы и "href", и "border-style", и ":hover", и создающий иерархию вектор "items".

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

Вредные привычки типизации

Одна из проблем современных языков программирования - неуправляемые системы типов. В каждом языке проведены границы между:
1. Значениями. Примеры: 42, "xyz".
2. Типами. Примеры: int, float, Maybe a, List[T].
3. Типами времени трансляции. Примеры: тип "локальная переменная", тип "свойство объекта".

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

Например, в языках со статической типизацией типы вычисляются в compile-time, константны в пределах сеанса работы приложения/сервиса. Хочется подгрузить из сторонней схемы во время работы программы - упс, нельзя. Придётся дописывать уровень косвенности со своей системой типов или использовать готовый язык с динамической типизацией.

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

Значения и типы при статической типизации распадаются на разные миры. Вычисления на типах - ад, коровники и Александреску в любом языке. Уходим на динамическую типизацию - получаем более внятный код, но менее эффективный и предсказуемый результат.

А что "типы времени трансляции"? Они есть, но вам недоступны.
log_value(y);
В большинстве языков подобный кусок кода может вывести "15.0" или "float 15.0", использование сишного препроцессора позволит получить "y = 15.0", но никто не даст вам identity "локальной переменной y, которая определена в строке [...] версии [...] файла [...]" или identity "значения этой переменной для данного фрейма".

Принято заранее делить на разные identity "это значение переменной, оно может меняться", "это её тип, он всегда неизменен" и "это информация времени компиляции, хрен знает что с ней там, но мы её просто никому не дадим". Или как-то иначе, если pure functional, например. Но любое из таких решений, как в случае использования одного основного языка проекта, так и в случае пары системный+скриптовой, приводит к расхождению с предметными областями в разных частях проекта. С коим приходится бороться.

Пока есть разница между типом и значением, классом и экземпляром - порядка не будет.

Надо работать с прототипами, где этой разницы нет. И тогда прототип "float" и прототип "аргумент функции" будут лишь константными кусочками прототипа для identity "distance", вычисленными во время компиляции. А работа с dependent types становится простой как 2+2.
flow

Части в пространстве и времени

Так подумал, что для дальнейшего продвижения в тему стандартов желательно сначала продемонстрировать, что такое загадочные термины "3D+1" и "4D", которые там по ходу встретятся. Начнём.

Представьте себе каталог моделей мобильных телефонов. У каждого телефона есть некие атрибуты: "разрешение дисплея", "тип дисплея", etc. Это важные потребительские характеристики самого телефона. Однако, мы можем выделить "дисплей" как отдельную сущность (отдельным атрибутом), и уже к нему назначить атрибуты "разрешение", ...; при этом соблюдая их вывод inplace в характеристиках телефона (а не "параметры дисплея QQQ123 вы можете посмотреть отдельно по ссылке"). Так цепочка работы с данными становится длиннее, что изначально приводит к затратам времени разработчика.

Однако, такое решение сразу себя оправдывает, как только в каталоге появляются телефоны с вторым (внешним) дисплеем. Ведь вместо дублирования всех атрибутов телефона, имеющих отношение к дисплею, с прибавкой "внешний", будет достаточно ввести один атрибут "внешний дисплей". Или "дополнительный дисплей", ведь бывают и другие двухэкранные мобилки. Или вообще список "дисплеи", вдруг их когда-то будет три, ведь мобильные телефоны с двумя камерами тоже было сложно предсказать когда-то. Но это уже слишком (да и с потребительской точки зрения слаборазличимо - что такое первый дисплей, а что третий). Надо на чём-то остановиться сейчас. Когда позже условия изменятся, тогда и у модели данных появится новая версия.

Выше был пример нарезки частей в пространстве. Можно дробить атрибуты ("разрешение дисплея" вместо "разрешение"), а можно сами сущности - сначала выделяя identity "дисплей мобильного телефона модели N900" из identity "мобильный телефон модели N900".

Причём, глубина разбиений в обе стороны не ограничена. Где проводить границу в вашей модели данных - этот вопрос придётся каждый раз решать отдельно. Может сложиться ситуация с очень похожими данными, когда для потребительских характеристик выгоднее создавать новые атрибуты, а для инженерных параметров выделять новые identity.

Однако, кроме пространства есть ещё и время, этим добавляются новые возможности/необходимости разбиения.

Основных парадигм при работе со временем три:

1) 3D.
Есть некий константный срез рассматриваемого мира. В пределах среза существует отношение R, связывающее сущности a и b. Или, если сказать иначе, есть предикат R, и утверждение R(a, b) is True. Значение неизменно. Для описания модели мобильника в каталоге этого достаточно. Но при появлении темпоральных характеристик придётся строить новые виды отношений: Rt(a, b, tx), Rtt(a, b, tstart, tend), etc.
Например:
к
Есть_соединение(клиент, сервер)
придётся добавлять по мере необходимости
Есть_соединение_в_момент_времени(клиент, сервер, tx)
Есть_соединение_с_момента_времени(клиент, сервер, tstart)
Есть_соединение_в_период_времени(клиент, сервер, tstart, tend)

2) 3D+1.
Здесь появляется возможность использования темпоральных характеристик поверх существующих отношений, R(a, b)@t.
Есть_соединение(клиент, сервер)@tx
Есть_соединение(клиент, сервер)@(tstart..)
Есть_соединение(клиент, сервер)@(tstart..tend)

Как 3D+1 встречается в обычной речи: "мы занимаемся этим проектом @ с 1 апреля 2012 года".

3) 4D.
Здесь появляется возможность использования темпоральных характеристик поверх cущностей, R(a@t, b@t).
Есть_соединение(клиент@tx, сервер@tx)
Есть_соединение(клиент@(tstart..), сервер@(tstart..))
Есть_соединение(клиент@(tstart..tend), сервер@(tstart..tend))

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

Как 4D встречается в обычной речи: : "я @сегодня не такой, как @вчера...".

В естественных языках это есть, но до философов доходило традиционно долго, так что некоторые логики и философы считают такой подход относительно недавним прорывом, а другие ересью. Рекомендую почитать http://plato.stanford.edu/entries/temporal-parts/ Для философов выбор между 3D, 3D+1 и 4D является отдельной темой срача специальной олимпиады. Для модельеров данных - осознанным выбором для конкретной ситуации или сразу для стандарта (4D в ISO 15926).

Как и в случае с пространством, нельзя однозначно заявить, что один из подходов лучше. Для отношений, атемпорально связывающих темпоральные части, например, причина_следствие(активность1@t1.start..t1.end), активность2@t2.start..t2.end)), в 3D+1 приходится возвращаться к 3D. Темпоральные n-ary отношения R(a, b, c, d, ...)@t в 4D превращаются в кашу R(a@t, b@t, c@t, d@t, ...). А 3D прекрасно подходит там, где все необходимые реификации заранее включены в отношение, т.е. не бывает R1(a, b, c) со временем или R2t(a, b, c, d, t) без времени, что в свою очередь оптимальнее для хранения в реляционных СУБД и табличного ввода/вывода в пользовательских интерфейсах.
flow

Микротеории и срезы

Продолжая http://justy-tylor.livejournal.com/170049.html

Атрибуты имеют смысл лишь для чего-то имеющего identity. Рассмотрим структуру (класс) Vector3f с тремя полями (x, y, z) типа float, в каком-либо языке со статической типизацией. Vector3f (x=1, y=2, z=3) абсолютно то же самое, что и любой другой Vector3f (x=1, y=2, z=3). Это уже некий абстрактный срез информации о чём-то в трёхмерном пространстве (позиции, размерах, ...), оптимизированный под определённую микротеорию и определённое железо. Другая микротеория может потребовать точности double, другое железо - наличия дополнительного (зануляемого) четвёртого поля. Vector3f можно использовать как тип для атрибута, но добавление атрибутов непосредственно к (x=1, y=2, z=3) невозможно.

Однако, на низком уровне, для "двенадцати байт по адресу 0x87654321, представляющих экземпляр Vector3f" адрес в памяти можно применять как identity, например, для оптимизации доступа к какому-нибудь box123.position в графе вычислений.

Теперь от значений типа Vector3f перейдём к самому типу. Данные оптимизированы под одну микротеорию (вычисления), а код как реализует эту микротеорию, так и обеспечивает связь с другими микротеориями и срезами. В монолитном описании типа жёстко прописаны свойства и методы. Если в классе не прописан соответствующий метод toString (т.к. не был нужен автору кода в реализации его микротеории), то вы не сможете дописать его извне. Тогда в точке связи вычислительных и текстовых (отчётных) микротеорий придётся разбирать эту проблему вручную, через цепочку if. Однако, в более продвинутых языках для этого есть свои аналоги атрибутов. Вы можете написать темплейтную функцию to_string в C++ или использовать тайпкласс Show в Haskell. Или напрямую указать какие-то значения через трейты.

Атрибуты обеспечивают реюз модели данных для разного кода, template metaprogramming и type classes обеспечивают реюз кода для разных данных. Кода меньше, качество выше, казалось бы счастье.

Однако, посмотрим теперь на мэйнстримовые динамические языки. Классы как объекты, duck typing, monkey patching... Реюз там проще, синтаксического оверхеда меньше. Даже встроить REPL консоль в работающее приложение можно почти нахаляву. Счастье?...

Ещё нет. Статическая типизация лучше работает в пределах чётко заданных микротеорий. По всем параметрам: оптимизация, верификация, читаемость/самодокументируемость кода.

С другой стороны, в динамике проще exploratory programming и склейка микротеорий.

Объединить эти положительные качества - задача следующих поколений языков. Но уже сейчас можно правильно идентифицировать кейсы и применять соответствующие возможности существующих языков для их реализации.
flow

Многоликие данные

Программисты (особенно начинающие) любят с размахом писать код. А я люблю этому препятствовать. Ибо каждая тысяча строк ведёт к дополнительным расходам на отладку, поддержку и рефакторинг, особенно если может быть выкинута или заменена чем-то более простым и компактным. Основными источниками мусорного кода являются конвертеры между разными моделями данных и адаптеры между разными API. Очевидное решение этой проблемы - обеспечить общую модель данных и общие средства доступа из разных подсистем. Но делать это надо хорошо, иначе получится трэш вроде windows registry.

Делать общие модели данных умеют разработчики САПРов и таких милых сердцу продуктов как 3DS Max и Maya. Именно оттуда можно взять атрибуты (обычно "attributes" или "custom attributes") в качестве основного механизма работы с произвольной информацией. Технически похожее можно встретить и в других местах (аннотационные атрибуты в XML/DOM, "attached properties" в WPF, ...), но основная идеология там другая.

От обычных "свойств объекта" такие атрибуты отличаются глобальностью. Т.е. вот в списке деталей есть entry123, где entry123.label хранит информацию о том, какой ярлык печатать/клеить, а label это свойство, которое описано в классе Entry со своим хитрым типом. А у кнопочки в пользовательском интерфейсе есть mybutton.label="OK", где label это свойство строкового типа в классе Button. Одинаковые названия, разный смысл, разное применение. А для тех мест, где смысл одинаковый, приходится подбирать иерархии наследования, где-то выносить label в UIElement, а где-то не выносить, потому что вроде label такой-же, а другие критерии UIElement не соблюдаются. Постепенно приходя к оверинжинирингу и ужасам вроде INamed, ILabelled, etc. Потому что свойства малопригодны для таких задач. Надо использовать атрибуты.

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

В коде проекта .15926 Editor (на данный момент) vis_label распознаётся для:
команд
меню
окон редактирования
элементов деревьев
диалогов
и даже для самого приложения

Причём, часть является экземплярами, а часть просто классами, благо в систему типов Python это прекрасно помещается.
в классах пишем vis_label = 'Save as...'
в экземплярах self.vis_label = label
получаем как getattr(obj, 'vis_label')
или obj.vis_label (в этом случае не забывая про исключение при отсутствии)

В языках со статической типизацией пришлось бы чуть иначе:
obj.set(vis_label, "value")
или vis_label.Bind(obj, "value")
...
С потерей некоторых фишек, но приобретением других полезных качеств за счёт систем типов.

У атрибута всегда должен быть тип, который указывается в коде и/или документации разработчика. Строка, целое число, "значение в килограммах от 0 до 150 в виде числа с плавающей точкой" или расплывчатое "любая другая сущность с атрибутами". Но нет смысла ограничивать сферу применения атрибута, заявляя что physical_mass может быть только у сущностей типа SomethingPhysical и используя его как обычное свойство. Потому что тогда сразу теряется возможность гибкой работы с идентичностями. Об этом далее.

В .15926 Editor кроме vis_label используются и другие атрибуты, например, vis_icon (тоже текстовая строка) - идентификатор иконки для команды меню, элемента дерева, etc. Можно продолжить и ввести какой-нибудь vis_color, если вдруг на дереве понадобятся разноцветные элементы.

И вот на базе этого кода появляется... например, какая-то презентация по крупным городам России. Справочные данные, статистика, динамика населённости, динамика застройки.

spb.vis_label = "Санкт-Петербург" # имя
spb.vis_icon = "saint_petersburg_flag" # однако, у города есть иконка... на самом деле флаг
spb.vis_color = "#FF00FF" # и цвет
В этот момент появляется стадо нервных онтологов с возгласом что "у города не может быть цвета, иначе у них религиозный траур и Аристотель с Абсолютом не сходятся". На что отвечается, что идентичность, выражаемая в указанном фрагменте как spb, используется и как "город Санкт-Петербург в его географическом значении", и как "город Санкт-Петербург в его административном значении", и как "город Санкт-Петербург для рисования его статистики на графиках фиолетовым цветом", и ещё много в каких других. Ведь в жизни, в текстах и прямой речи происходит то же самое. А если дробить идентичности без веской причины, то выползают неисчислимые MyFactoryAdapterInterfaceContextProxyLauncher.

Разумеется, тут возникает вопрос, что делать, если для визуализации трендов хочется один vis_color, а для отрисовки имени на дереве другой. Или разные vis_label для разных языков (i18n). Другая важная тема - как выбирать между простыми структурами, свойствами и глобальными атрибутами в разных случаях.

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