Показаны сообщения с ярлыком UML. Показать все сообщения
Показаны сообщения с ярлыком UML. Показать все сообщения

вторник, 6 августа 2013 г.

"Синтез" логики форм приложения на тему "MVC". Или "анализ прецедентов сверху вниз". А также - "рефакторинг при проектировании". Пока БЕЗ UML

Предварительная вводная: Говоря о классах "реализации" - будем придерживаться нотации Delphi и будем иметь в виду архитектуру библиотеки VCL. Это необязательно.  Но это - для определённости.

Пусть есть несколько "похожих" прецедентов (http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D1%86%D0%B5%D0%B4%D0%B5%D0%BD%D1%82_(UML)):

1. Работа с документом
2. Работа со словарной статьёй
3. Работа со статьёй энциклопедии
4. Обзор изменений документа
5. Сравнение соседних редакций документа

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

Надеюсь, что с одной стороны - никто не узрит в этом "выдачу технологических секретов", а с другой - "скрытую рекламу". Если бы не эти два обстоятельства - писать было бы сильно проще.

Этап сбора и описания требований - опускаю. Этой теме можно посвятить отдельную статью (если интересно). Пока предположим, что требования собраны и описаны. И частично - детализированы.

Прецеденты - РАЗНЫЕ, но по функциональности - ПЕРЕСЕКАЮТСЯ.

Что у них общего? Это - "работа с документом" в том или ином виде.

Понятное дело, что в реализации этих прецедентов - есть много общего, но и есть различия.

Понятное дело, все эти прецеденты визуализуются РАЗЛИЧНЫМИ формами отображения.

Понятно, что эти формы - родственны, но при этом - они и различаются.

Что делать? Как будем проектировать функционал этих прецедентов?

Наследование? Визуальное наследование? Агрегация? Выделение бизнес-логики в отдельный слой/слои?

Хочется немножко рассказать на эту тему.

Для начала надо наверное выделить ОБЩИЕ ответственности для данных прецедентов:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.

пока - ограничимся...

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

Для начала поймём - как эти прецеденты - визуализируются.

На самом деле каждый прецедент (отображаемый для определённости "формой" - наследником от TForm) распадается на несколько "зон активностей" (или "зон пользовательского фокуса"). На самом деле эти "зоны" (забегая вперёд) это отдельные вложенные прецеденты. На эту тему я немножко "закинул удочку" тут - http://18delphi.blogspot.com/2013/08/usecase.html.

Каждая из зон характеризуется тем, что у них у каждой может быть СОБСТВЕННЫЙ набор Toolbar'ов, каждая может принимать ОТДЕЛЬНО фокус ввода, каждая имеет СОБСТВЕННОЕ контекстное меню, каждая может ВЛИЯТЬ на главное (командное) меню приложения.

Каждая из зон может обладать СОБСТВЕННЫМ состоянием, которое может записываться в историю работы приложения.

Предположим, что распадаются они так (в скобках буду указывать имена "реализующих классов"):

1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).

2. Работа со словарной статьёй (TDictionEntryForm):
а. Русский текст статьи (TRussianTextViewArea).
б. Английский текст статьи (TEnglishTextViewArea).
в. Список словарных статей (TDictionEntriesViewArea).

3. Работа со статьёй энциклопедии (TEncyclopediaForm):
а. Собственно "текст статьи" (TArcticleViewArea).
б. Оглавление энциклопедии (TEncyclopediaContentsViewArea).

4. Обзор изменений документа (TDocumentReviewForm):
а. Собственно "текст обзора документа" (TDocumentReviewTextViewArea).

5. Сравнение соседних редакций документа (TEditionsCompareForm):
а. Текст предыдущей редакции документа (TPrevEditionTextViewArea).
б. Текст "актуальной" редакции документа (TActualEditionTextViewArea).

Что это за классы TXXXViewArea и каково их место в иерархии того же VCL - мы поговорим - чуть позже.

Если вы хотите аналогию из "классического" Delphi и VCL, то скажем так, что TXXXViewArea - "похожи" на TFrame. Именно - ПОХОЖИ. Но НЕ ЯВЛЯЮТСЯ таковыми. Но ПОКА - так думать будет понятно (надеюсь).

Далее надо отметить, что каждый прецедент имеет "точку входа". Это нечто, что принимает "данные для конструирования прецедента" и возвращает собственно ЭКЗЕМПЛЯР прецедента.

В терминах "классики" это - конструктор класса (формы). Но если забегать вперёд и думать об "инъекции зависимостей" (http://ru.wikipedia.org/wiki/Dependency_Injection), то это - фабрика. Фабрика прецедента.

Но! ПОКА забудем о фабриках. До поры до времени.

ПОКА будем считать, что "точка входа" в прецедент это конструктор объекта в терминах "классики".

Также ПОКА забудем о сигнатуре конструктора TForm (и TComponent) и что на самом деле он выглядит так - Create(anOwner : TComponent).

Также ПОКА забудем о "геометрии" и размещении форм (визуализующих прецеденты) в пространстве, а также о свойстве Parent.

Об этом мы поговорим чуть позже. Обещаю.

ПОКА будем считать, что "точка входа" выглядит примерно так CreateUseCase(aData : TUseCaseData).

Где TUseCaseData - некоторый базовый (в пределе - шаблонный) класс, обеспечивающий связь данных прецедента и собственно логики и визуализации прецедента.

Забегая вперёд - отметим, что это скорее всего не класс, а - интерфейс. Но на данном этапе - нам ПОКА это не важно.

Почему ИНТЕРФЕЙС? Потому, что - ПОДСЧЁТ ССЫЛОК. Пока - это - не важно. Но позже - я остановлюсь и на этом моменте.

(Для тех немногих, кто знаком с фреймворком VCM скажу, что TUseCaseData - это собственно и есть IsdsXXX).

Пока дело обстоит так. Есть "визизуализация прецедента" - TXXXForm и "данные прецедента" - TXXXUseCaseData.

TXXXForm обладает конструктором CreateUseCase, с сигнатурой - CreateUseCase(aData : TXXXUseCaseData).

Давайте теперь посмотрим - какие "данные прецедентов" нам понадобятся для визуализации описанных выше прецедентов.

Тут ПОКА - всё очень просто. Отображение тут - 1:1. Т.е. "визуализация прецедента" TXXXForm требует "данных прецедента" TXXXUseCaseData.

Хочется отметить тот факт, что в описываемой "модели" - "данные прецедента" - являются КОНСТАНТНЫМИ. Т.е. они не меняют своё состояние с момента своего рождения до момента своей смерти. Это означает, что собственно прецедент (и его визуализация) могут рассматривать эти данные как инвариант на всём времени своей жизни. Если "данные прецедента" надо поменять (например переходим из одного документа в другой), то это означает, что надо СМЕНИТЬ текущий прецедент. Создав НОВЫЙ экземпляр, подав в "точку входа" (конструктор/фабрику) новые "данные прецедента". Этот ВАЖНЫЙ факт - мы тоже - обсудим. Чуть позже.

Тут бы уже можно было бы нарисовать модель прецедентов и их данных в терминах UML, но к сожалению - инструмент у меня недоступен - посему "буду писать текстом". Пока. А модель - нарисую чуть позже.

Итак - посмотрим на описанные выше прецеденты и их данные.
Пусть запись вида - TXXXForm <=| TXXXUseCaseData - означает "данные TXXXUseCaseData - могут порождать экземпляр прецедента TXXXForm". Т.е. что данные совместимы с входным параметром соответствующего прецедента.

Итак - прецеденты и их данные:

1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData

Теперь собственно рассмотрим - "что же такое эти TXXXUseCaseData":
Как мы уже "договорились" - они все наследуются от TUseCaseData.

Т.е. как-то так (ПОКА):
1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData = class(TUseCaseData)
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData = class(TUseCaseData)
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData = class(TUseCaseData)
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData = class(TUseCaseData)
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData = class(TUseCaseData)

Это - "крупными мазками".

Теперь поговорим об их наполнении.

Мы уже говорили о TXXXViewArea. "Активных зонах".
И "по индукции" было бы логично раз TXXXUseCaseData порождает TXXXForm, то должен найтись TXXXViewAreaData, который порождает TXXXViewArea.

Про "геометрию", Owner'ов и Parent'ов - опять же - ПОКА - не говорим.

Считаем, что TXXXViewArea имеет "конструктор" CreateViewArea(aData : TXXXViewAreaData).

Откуда берутся эти самые TXXXViewAreaData?

В описываемой модели - они "висят" в виде read-only-property на TXXXUseCaseData. И наследуются (забегая вперёд) от TViewAreaData.

(Для тех немногих, кто знаком с VCM скажу TXXXViewAreaData это ни что иное как IdsXXX)

И тогда картина описываемых прецедентов приобретает следующий вид:

1. Работа с документом - TDocumentForm <=|
TDocumentUseCaseData = class(TUseCaseData)
 property DocumentText: TDocumentTextViewAreaData;
 property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData)
  property DocumentStructure: TDocumentStructureViewAreaData;
  property DocumentPictures: TDocumentPicturesViewAreaData;
 end;//TDocumentContentsViewAreaData
 property DocumentIncomingLinks: TDocumentIncomingLinksViewAreaData;
 property DocumentOutgoingLinks: TDocumentOutgoingLinksViewAreaData;
 property DocumentAnnotation: TDocumentAnnotationViewAreaData;
end;//TDocumentUseCaseData

2. Работа со словарной статьёй - TDictionEntryForm <=|
TDictionEntryUseCaseData = class(TUseCaseData)
 property RussianText: TRussianTextViewAreaData;
 property EnglishText: TEnglishTextViewAreaData;
 property DictionEntries: TDictionEntriesViewAreaData;
end;//TDictionEntry 

3. Работа со статьёй энциклопедии - TEncyclopediaForm <=|
TEncyclopediaUseCaseData = class(TUseCaseData)
 property Arcticle: TArcticleViewAreaData;
 property EncyclopediaContents: TEncyclopediaContentsViewAreaData;
end;//TEncyclopediaUseCaseData

4. Обзор изменений документа - TDocumentReviewForm <=|
TDocumentReviewUseCaseData = class(TUseCaseData)
 property DocumentReviewText: TDocumentReviewTextViewAreaData;
end;//TDocumentReviewUseCaseData 

5. Сравнение соседних редакций документа - TEditionsCompareForm <=|
TEditionsCompareUseCaseData = class(TUseCaseData)
 property PrevEditionText: TPrevEditionTextViewAreaData;
 property ActualEditionText: TActualEditionTextViewAreaData;
end;//TEditionsCompareUseCaseData

Итак - мы ПОКА имеем ЧЕТЫРЕ основных понятия - TXXXForm (прецедент), TXXXViewArea (активная зона/вложенный прецедент), TXXXUseCaseData (данные прецедента) и TXXXViewAreaData (данные активной зоны).

Теперь давайте более детально спроектируем один прецедент. Самый "большой". Первый. "Работа с документом":

Ранее мы ПОКА договорились, что TXXXViewArea это "нечто вроде TFrame". Пока этих знаний - нам достаточно.

Но кто же реально визуализирует данные и осуществляет интерактив с пользователем?

Этот момент - мы сейчас более-менее подробно - разберём.

Итак - наш прецедент "работа с документом" с точки зрения представления пользователю ПОКА выглядит вот так:

1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).

Давайте разберёмся - какие конечные компоненты визуализируют те или иные "активные зоны".

Пусть из требований следует что:
а. Собственно "текст документа" (TDocumentTextViewArea) - отображается в виде "текста с гиперссылками и прочим оформлением".
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea) - отображается в виде "древесной структуры".
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea) - отображается в виде "одноуровневого списка".
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea) - отображается в виде "одноуровневого списка".
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea) - отображается в виде "одноуровневого списка".
д. Аннотация к документу (TDocumentAnnotationViewArea)- отображается в виде "текста с гиперссылками и прочим оформлением" (как и сам текст документа).

Итак мы ПОКА имеем три вида отображения данных:
1. "текст с гиперссылками и прочим оформлением".
2. "древесная структура".
3. "список".

(Не могу не отметить, что для БОЛЬШИНСТВА бизнес-приложений этих способов представления данных - БОЛЕЕ чем достаточно)

Пусть у нас УЖЕ ЕСТЬ компоненты, которые умеет отображать структуры данных в заданном виде:
1. "текст с гиперссылками и прочим оформлением" - TDocumentView.
2. "древесная структура" - TTreeView.
3. "список" - TListView.

Тогда - будем ПОКА считать, что в каждую TXXXViewArea (которая ПОКА по договорённости является TFrame) - вставлен - соответствующий компонент. Например как alClient (это для любителе Delphi). Больше про "геометрию", Owner'а и Parent'а - опять же - ПОКА не говорим.

Теперь как выглядит описание прецедента с точки зрения представления?

А вот так:

type
 TDocumentForm = class(TForm)
 
  DocumentTextViewArea : TDocumentTextViewArea = class(TFrame)
   Text : TDocumentView;
  end;//TDocumentTextViewArea 
 
  DocumentContentsViewArea: TDocumentContentsViewArea = class(TFrame)
 
   DocumentStructureViewArea: TDocumentStructureViewArea = class(TFrame)
    Tree : TTreeView;
   end;//TDocumentStructureViewArea 
 
   DocumentPicturesViewArea: TDocumentPicturesViewArea = class(TFrame)
    List : TListView;
   end;//TDocumentPicturesViewArea
 
  end;//TDocumentContentsViewArea 
 
  DocumentIncomingLinksViewArea: TDocumentIncomingLinksViewArea = class(TFrame)
   List : TListView;
  end;//TDocumentIncomingLinksViewArea
 
  DocumentOutgoingLinksViewArea: TDocumentOutgoingLinksViewArea = class(TFrame)
   List : TListView;
  end;//TDocumentOutgoingLinksViewArea
 
  DocumentAnnotationViewArea: TDocumentAnnotationViewArea = class(TFrame)
   Text : TDocumentView;
  end;//TDocumentAnnotationViewArea
 
end;//TDocumentForm

Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:

type
 TDocumentForm = class(TForm)
 
  TDocumentTextViewArea = class(TFrame)
   Text : TDocumentView;
  end;//TDocumentTextViewArea
 
  DocumentTextViewArea : TDocumentTextViewArea; 
 
  TListViewArea = class(TFrame)
   List : TListView;
  end;//TListViewArea 
 
  DocumentContentsViewArea: TDocumentContentsViewArea = class(TFrame)
 
   DocumentStructureViewArea: TDocumentStructureViewArea = class(TFrame)
    Tree : TTreeView;
   end;//TDocumentStructureViewArea 
 
   DocumentPicturesViewArea: TListViewArea;
 
  end;//TDocumentContentsViewArea 
 
  DocumentIncomingLinksViewArea: TListViewArea;
 
  DocumentOutgoingLinksViewArea: TListViewArea;
 
  DocumentAnnotationViewArea: TDocumentTextViewArea;
 
end;//TDocumentForm

Но теперь встаёт вопрос - "откуда же берутся данные для соответствующих компонент"? Как TXXXUseCaseData и TXXXViewAreaData коррелируют с данными для конечных компонентов?

Это вопрос - мы сейчас и разберём.

В общем - всё - по индукции.
TXXXUseCaseData - содержит в себе TXXXViewAreaData в виде read-only-property, значит - логично предположить, что TXXXViewAreaData содержит в себе TXXXViewData в виде всё тех же read-only-property.

В общем - так оно и есть.

Картина приобретает следующий вид:
type
 TDocumentUseCaseData = class(TUseCaseData)
 
  property DocumentText: TDocumentTextViewAreaData = class(TViewAreaData)
   property Text: TDocument;
  end;//TDocumentTextViewAreaData
 
  property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData)
 
   property DocumentStructure: TDocumentStructureViewAreaData = class(TViewAreaData)
    property DocumentStructure : TTree;
   end;//TDocumentStructureViewAreaData
 
   property DocumentPictures: TDocumentPicturesViewAreaData = class(TViewAreaData)
    property Pictures : TList;
   end;//TDocumentPicturesViewAreaData
 
  end;//TDocumentContentsViewAreaData
 
  property DocumentIncomingLinks: TDocumentIncomingLinksViewAreaData = class(TViewAreaData)
   property IncomningLinks : TList;
  end;//TDocumentIncomingLinksViewAreaData
 
  property DocumentOutgoingLinks: TDocumentOutgoingLinksViewAreaData = class(TViewAreaData)
   property OutgoingLinks : TList;
  end;//TDocumentOutgoingLinksViewAreaData
 
  property DocumentAnnotation: TDocumentAnnotationViewAreaData = class(TViewAreaData)
   property Annotation: TDocument;
  end;//TDocumentAnnotationViewAreaData
 
 end;//TDocumentUseCaseData

(Для тех кто в курсе про VCM: TDocument это и есть TXXXDocumentContainer)

И ОПЯТЬ! Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:

type
 TDocumentUseCaseData = class(TUseCaseData)
 
  TDocumentTextViewAreaData = class(TViewAreaData)
   property Text: TDocument;
  end;//TDocumentTextViewAreaData
 
  property DocumentText: TDocumentTextViewAreaData;
 
  TListViewAreaData = class(TViewAreaData)
   property List : TList;
  end;//TListViewAreaData
 
  property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData)
 
   property DocumentStructure: TDocumentStructureViewAreaData = class(TViewAreaData)
    property DocumentStructure : TTree;
   end;//TDocumentStructureViewAreaData
 
   property DocumentPictures: TListViewAreaData;
 
  end;//TDocumentContentsViewAreaData
 
  property DocumentIncomingLinks: TListViewAreaData;
 
  property DocumentOutgoingLinks: TListViewAreaData;
 
  property DocumentAnnotation: TDocumentTextViewAreaData;
 
 end;//TDocumentUseCaseData

Идея понятна?

Вот тут наверное подходящий момент, чтобы остановиться и спросить - "а зачем так сложно?"

Я сейчас постараюсь это пояснить.

Во-первых - мне кажется, что в общем - НИЧУТЬ не сложно. Процедура - ФОРМАЛЬНАЯ. И устроена - как "матрёшка", что само по себе (меня лично - радует).

Выделяем прецеденты, потом описываем вложенные прецеденты, потом описываем данные прецедентов и вложенных прецедентов. Потом - детализируем прецеденты и получаем компоненты, которые взаимодействуют с пользователем, потом - описываем - данные для компонентов. Процедура - ФОРМАЛЬНАЯ и рекурсивная.

А во-вторых - вся эта "сложность" во-первых направлена на взаимозаменяемость и гибкость архитектуры, а во вторых - на упрощение рефакторинга и повышение ПОВТОРНОГО использования.

Как? Поясню - чуть позже.

Собственно это станет видно - когда мы перейдём к оставшимся прецедентам.

(Тут "синергия" видна ИМЕННО для СЛОЖНЫХ систем и для систем в которых есть МНОЖЕСТВО "похожих", но в то же время "не похожих" прецедентов)
.....

Теперь вернёмся к списку ответственностей (требований) наших прецедентов.

ПРЕДПОЛОЖИМ, что указанные ответственности:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
-- ПОЛНОСТЬЮ реализуются компонентом TDocumentView (ну - ПОВЕЗЛО нам так с указанным компонентом).

Соответственно эти ответственности - мы смело можем считать реализованными (ну по крайней мере - пока требования не поменяются). И мы СМЕЛО можем их вычеркнуть из списка ответственностей, которые нам надо реализовать. Считаем их - УЖЕ реализованными.

Как быть с ОСТАЛЬНЫМИ ответственностями? А именно:
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.

На этот вопрос - мы сейчас попробуем ответить. Реализуя далее наш "большой" прецедент - "работа с документом":

Делать мы это будем тут - http://18delphi.blogspot.com/2013/08/mvc_8.html

to be continued...

пятница, 2 августа 2013 г.

Заметки про наш кодогенератор. Надеюсь - Макс - будет НЕ против

Отдельно про "собственный кодогенератор".

1. Он скорее "декларативный" нежели "императивный".
2. Построен на "продвинутых regexp". Очень продвинутых. С включениями
"императивного кода".
3. Использует понятие user-section. Это места в "скелете кода" куда
программист "вписывает логику" не описанную на модели.
4. Базируется на понятии <<стереотипа>>. Стереотип это - "единица
генерации". SimpleClass, Enum, Form, Project, Library, Algorythm etc. И
каждый стереотип может генерировать "свой код".  И в СВОЙ файл - если нужно.
5. Экземпляры <<стереотипов>> нарисованные программистом могут
"предварительно генерировать" ДРУГИЕ стереотипы.
Например <<property>>
от <<SimpleClass>> могут генерировать <<method>> (методы доступа к
свойству). Или <<LocalizeableConst>> генерирует <<Const>> И массу
других. Уже без вмешательства "пользователя". А потом вся эта
конструкция генерируется в целевой язык.
6. Настраивается на практически любой "текстовый язык".

P.S. многие решения настолько "банальны", что мне иногда хочется
поверить в "теорию заговора". Что "киты" типа  MS или IBM - сознательно
не публикуют своих разработок в плане UML. Потому, что я иногда не
понимаю - "как мои коллеги до этого додумались", а "киты" - нет. Просто
не верится в это.

7. ОТДЕЛЬНО умеет генерировать в wiki-подобную базу знаний. Это скажем
так - отдельный "целевой язык" кодогенерации.
8. С элементами кода в этой базе умеют связываться
требования/задачи/ошибки. "Сверху". А также - изменения кода - "снизу".
Взяв отдельно взятый "элемент кода" (экземпляр <<стереотипа>>). Мы
"сверху" можем видеть - "какие запросы его меняли", а "снизу" - можем
видеть - "какие строчки кода менялись в результате этих запросов".

P.P.S. Я в его написании - участия не принимал. Я только шаблоны
кодогенерации потом писал. 

Ссылка. Ещё про UML

Ссылки. UML "по верхам"

четверг, 1 августа 2013 г.

Про UseCase'ы

хотите подкину пищу для раздумий? чем наследование прецедентов отличается от <<include>> и <<extends>>?

а ещё бывают "вложенные" прецеденты :)

т.е. ЧЕТЫРЕ нотации для ОЧЕНЬ похожих вещей

что такое "абстрактные" UseCase - я тоже - только недавно понял

смотрите - бывают "абстрактный" документ, юридический документ, статья словаря, пользовательская консультация :) и т.п.

вот тут и появляются связи между UseCase
и возможность "конструирования" системы на уровне UseCase
именно "конструирования".. архитектуры....

а эти "общие слова" - рождают собою - вполне конкретные проектные классы...

и Dependency Injection (http://ru.wikipedia.org/wiki/Dependency_Injection)

а вложенность UseCase трактую так - работа с ДОКУМЕНТОМ распадается на два ВЛОЖЕННЫХ UseCase - "работа с текстом" и "работа с оглавлением" - они существуют параллельно в рамках объемлющего UseCase

вложенные UseCase - это по сути - "разные области пользовательского фокуса", но существующие одновременно и взаимодействующие друг с другом

совсем грубо - Object Inspector, Project Inspector, CodeEditor в delphi ;) - это вложенные прецеденты в основном UseCase "написание программы на delphi"

пятница, 12 июля 2013 г.

Обобщаем тесты списков

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/07/blog-post_8789.html

Теперь обобщим приведённые там тесты.

Как обычно.

Модель:

Тут добавился "примесный тест" - ListTest, а все остальные тесты от него - наследуются и определяют тип конкретного списка, который надо тестировать. Этот тест нам сильно пригодится, когда мы будем расширять функциональность контейнеров. Вместе с функциональностью абстрактных контейнеров будем расширять и функциональность примесного теста. Таким образом - КОНКРЕТНЫЕ тесты - "сами" будут расширяться.

Код:

ListTest.imp.pas:
{$IfNDef ListTest_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "ListTest.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCaseMixIn::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::ListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define ListTest_imp}
{$If defined(nsTest)}
 _ListTest_ = class(TTestCase)
 published
 // published methods
   procedure DoIt;
   procedure TestTwoLists;
 end;//_ListTest_
{$IfEnd} //nsTest

{$Else ListTest_imp}

{$If defined(nsTest)}

// start class _ListTest_

procedure _ListTest_.DoIt;
//#UC START# *51DEB319037C_51E03FC80111_var*
const
 cCount = 1000;
var
 l_List : _ListType_;
 l_Count : IndexType;
 l_Index : IndexType;
//#UC END# *51DEB319037C_51E03FC80111_var*
begin
//#UC START# *51DEB319037C_51E03FC80111_impl*
 l_List := _ListType_.Create;
 try
  l_List.Count := cCount;
  Check(l_List.Count = cCount);
  Check(l_List.Capacity >= cCount);

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);

  l_Count := Random(cCount);
  l_List.Count := l_Count;
  Check(l_List.Count = l_Count, Format('Выделяли %d элементов. Count = %d', [l_Count, l_List.Count]));
  Check(l_List.Capacity >= l_Count, Format('Выделяли %d элементов. Capacity = %d', [l_Count, l_List.Capacity]));

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);
   
 finally
  FreeAndNil(l_List);
 end;//try..finally
//#UC END# *51DEB319037C_51E03FC80111_impl*
end;//_ListTest_.DoIt

procedure _ListTest_.TestTwoLists;
//#UC START# *51DED6FC03C1_51E03FC80111_var*
const
 cCount = 1000;
var
 l_A : _ListType_;
 l_B : _ListType_;
 l_Index : IndexType;
 l_Value : _ItemType_;
 l_Max   : _ItemType_;
//#UC END# *51DED6FC03C1_51E03FC80111_var*
begin
//#UC START# *51DED6FC03C1_51E03FC80111_impl*
 l_A := _ListType_.Create;
 try
  l_B := _ListType_.Create;
  try
   l_A.Count := cCount;
   for l_Index := 0 to l_A.Count - 1 do
   begin
    l_Value := Random(1000);
    l_A[l_Index] := l_Value;
    Check(l_A[l_Index] = l_Value);
   end;//for l_Index

   l_B.Count := l_A.Count;
   for l_Index := 0 to l_A.Count - 1 do
   begin
    l_B[l_Index] := l_A[l_Index];
   end;//for l_Index

   for l_Index := 0 to l_A.Count - 1 do
   begin
    Check(l_B[l_Index] = l_A[l_Index]);
   end;//for l_Index
  finally
   FreeAndNil(l_B);
  end;//try..finally
 finally
  FreeAndNil(l_A);
 end;//try..finally
//#UC END# *51DED6FC03C1_51E03FC80111_impl*
end;//_ListTest_.TestTwoLists

{$IfEnd} //nsTest

{$EndIf ListTest_imp}

IntegerListTest.pas:
unit IntegerListTest;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "IntegerListTest.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCase::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::IntegerListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Include .\sbtDefine.inc}

interface

{$If defined(nsTest)}
uses
  TestFrameWork,
  IntegerList
  ;
{$IfEnd} //nsTest

{$If defined(nsTest)}
type
 _ListType_ = TIntegerList;
 {$Include .\ListTest.imp.pas}
 TIntegerListTest = class(_ListTest_)
 end;//TIntegerListTest
{$IfEnd} //nsTest

implementation

{$If defined(nsTest)}
uses
  SysUtils
  ;
{$IfEnd} //nsTest

{$If defined(nsTest)}

type _Instance_R_ = TIntegerListTest;
type _ListTest_R_ = TIntegerListTest;

{$Include .\ListTest.imp.pas}


{$IfEnd} //nsTest

initialization
 TestFramework.RegisterTest(TIntegerListTest.Suite);
end.

ByteListTest.pas:
unit ByteList;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "ByteList.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Sand Box::SandBox::FinalContainers::TByteList
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Include .\sbDefine.inc}

interface

uses
  Refcounted,
  Classes,
  l3PtrLoc
  ;

type
 _ItemType_ = Byte;
 _AtomicList_Parent_ = TRefcounted;
 {$Include .\AtomicList.imp.pas}
 TByteList = class(_AtomicList_)
 end;//TByteList

implementation

uses
  RTLConsts,
  l3MemorySizeUtils
  ;

type _Instance_R_ = TByteList;
type _AtomicList_R_ = TByteList;

{$Include .\AtomicList.imp.pas}

end.

Int64ListTest.pas:
unit Int64ListTest;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "Int64ListTest.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCase::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::Int64ListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Include .\sbtDefine.inc}

interface

{$If defined(nsTest)}
uses
  TestFrameWork,
  Int64List
  ;
{$IfEnd} //nsTest

{$If defined(nsTest)}
type
 _ListType_ = TInt64List;
 {$Include .\ListTest.imp.pas}
 TInt64ListTest = class(_ListTest_)
 end;//TInt64ListTest
{$IfEnd} //nsTest

implementation

{$If defined(nsTest)}
uses
  SysUtils
  ;
{$IfEnd} //nsTest

{$If defined(nsTest)}

type _Instance_R_ = TInt64ListTest;
type _ListTest_R_ = TInt64ListTest;

{$Include .\ListTest.imp.pas}


{$IfEnd} //nsTest

initialization
 TestFramework.RegisterTest(TInt64ListTest.Suite);
end.

Код тут - http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBox и http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBoxTest

четверг, 11 июля 2013 г.

Дальше будем делать конкретные "неатомарные" контейнеры

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/07/blog-post_8789.html

Дальше будем делать конкретные "неатомарные" контейнеры.

Будем делать контейнеры интерфейсов. Строк. Записей.

Потом перейдём к сортированным контейнерам.

Потом будем обобщать тесты контейнеров через примеси.

Интересно?

Выводим конкретные атомарные контейнеры из абстрактных

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/07/blog-post_3683.html

Теперь выведем из абстрактных контейнеров предыдущей серии - конкретные. Пока - атомарные.

Итак. Как обычно.

Модель:


Код:
IntegerList.pas:
unit IntegerList;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "IntegerList.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Sand Box::SandBox::FinalContainers::TIntegerList
//
// Список Integer'ов
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  Refcounted,
  Classes,
  l3PtrLoc
  ;

type
 _ItemType_ = Integer;
 _AtomicList_Parent_ = TRefcounted;
 {$Include AtomicList.imp.pas}
 TIntegerList = class(_AtomicList_)
  {* Список Integer'ов }
 end;//TIntegerList

implementation

uses
  RTLConsts,
  l3MemorySizeUtils
  ;

type _Instance_R_ = TIntegerList;
type _AtomicList_R_ = TIntegerList;

{$Include AtomicList.imp.pas}

end.
ByteList.pas:
unit ByteList;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "ByteList.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Sand Box::SandBox::FinalContainers::TByteList
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  Refcounted,
  Classes,
  l3PtrLoc
  ;

type
 _ItemType_ = Byte;
 _AtomicList_Parent_ = TRefcounted;
 {$Include AtomicList.imp.pas}
 TByteList = class(_AtomicList_)
 end;//TByteList

implementation

uses
  RTLConsts,
  l3MemorySizeUtils
  ;

type _Instance_R_ = TByteList;
type _AtomicList_R_ = TByteList;

{$Include AtomicList.imp.pas}

end.
Int64List.pas:
unit Int64List;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "Int64List.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Sand Box::SandBox::FinalContainers::TInt64List
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  Refcounted,
  Classes,
  l3PtrLoc
  ;

type
 _ItemType_ = Int64;
 _AtomicList_Parent_ = TRefcounted;
 {$Include AtomicList.imp.pas}
 TInt64List = class(_AtomicList_)
 end;//TInt64List

implementation

uses
  RTLConsts,
  l3MemorySizeUtils
  ;

type _Instance_R_ = TInt64List;
type _AtomicList_R_ = TInt64List;

{$Include AtomicList.imp.pas}

end.

Тесты (взятые "от фонаря", но тем не менее они имеют право на жизнь).

Модель тестов:


Код тестов:
IntegerListTest.pas:
unit IntegerListTest;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "IntegerListTest.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCase::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::IntegerListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  TestFrameWork
  ;

type
 TIntegerListTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
   procedure TestTwoLists;
 end;//TIntegerListTest

implementation

uses
  IntegerList,
  SysUtils
  ;

// start class TIntegerListTest

procedure TIntegerListTest.DoIt;
//#UC START# *51DEB319037C_51DEB2FA00B0_var*
const
 cCount = 1000;
var
 l_List : TIntegerList;
 l_Count : IndexType;
 l_Index : IndexType;
//#UC END# *51DEB319037C_51DEB2FA00B0_var*
begin
//#UC START# *51DEB319037C_51DEB2FA00B0_impl*
 l_List := TIntegerList.Create;
 try
  l_List.Count := cCount;
  Check(l_List.Count = cCount);
  Check(l_List.Capacity >= cCount);

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);

  l_Count := Random(cCount);
  l_List.Count := l_Count;
  Check(l_List.Count = l_Count, Format('Выделяли %d элементов. Count = %d', [l_Count, l_List.Count]));
  Check(l_List.Capacity >= l_Count, Format('Выделяли %d элементов. Capacity = %d', [l_Count, l_List.Capacity]));

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);
   
 finally
  FreeAndNil(l_List);
 end;//try..finally
//#UC END# *51DEB319037C_51DEB2FA00B0_impl*
end;//TIntegerListTest.DoIt

procedure TIntegerListTest.TestTwoLists;
//#UC START# *51DED6FC03C1_51DEB2FA00B0_var*
const
 cCount = 1000;
var
 l_A : TIntegerList;
 l_B : TIntegerList;
 l_Index : IndexType;
 l_Value : Integer;
//#UC END# *51DED6FC03C1_51DEB2FA00B0_var*
begin
//#UC START# *51DED6FC03C1_51DEB2FA00B0_impl*
 l_A := TIntegerList.Create;
 try
  l_B := TIntegerList.Create;
  try
   l_A.Count := cCount;
   for l_Index := 0 to l_A.Count - 1 do
   begin
    l_Value := Random(1000);
    l_A[l_Index] := l_Value;
    Check(l_A[l_Index] = l_Value);
   end;//for l_Index

   l_B.Count := l_A.Count;
   for l_Index := 0 to l_A.Count - 1 do
   begin
    l_B[l_Index] := l_A[l_Index];
   end;//for l_Index

   for l_Index := 0 to l_A.Count - 1 do
   begin
    Check(l_B[l_Index] = l_A[l_Index]);
   end;//for l_Index
  finally
   FreeAndNil(l_B);
  end;//try..finally
 finally
  FreeAndNil(l_A);
 end;//try..finally
//#UC END# *51DED6FC03C1_51DEB2FA00B0_impl*
end;//TIntegerListTest.TestTwoLists

initialization
 TestFramework.RegisterTest(TIntegerListTest.Suite);

end.
ByteListTest.pas:
unit ByteListTest;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "ByteListTest.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCase::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::ByteListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  TestFrameWork
  ;

type
 TByteListTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TByteListTest

implementation

uses
  ByteList,
  SysUtils
  ;

// start class TByteListTest

procedure TByteListTest.DoIt;
//#UC START# *51DEE6960378_51DEE67C003A_var*
const
 cCount = 1000;
var
 l_List : TByteList;
 l_Count : IndexType;
 l_Index : IndexType;
//#UC END# *51DEE6960378_51DEE67C003A_var*
begin
//#UC START# *51DEE6960378_51DEE67C003A_impl*
 l_List := TByteList.Create;
 try
  l_List.Count := cCount;
  Check(l_List.Count = cCount);
  Check(l_List.Capacity >= cCount);

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);

  l_Count := Random(cCount);
  l_List.Count := l_Count;
  Check(l_List.Count = l_Count, Format('Выделяли %d элементов. Count = %d', [l_Count, l_List.Count]));
  Check(l_List.Capacity >= l_Count, Format('Выделяли %d элементов. Capacity = %d', [l_Count, l_List.Capacity]));

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);
   
 finally
  FreeAndNil(l_List);
 end;//try..finally
//#UC END# *51DEE6960378_51DEE67C003A_impl*
end;//TByteListTest.DoIt

initialization
 TestFramework.RegisterTest(TByteListTest.Suite);

end.
Int64ListTest.pas:
unit Int64ListTest;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBoxTest"
// Модуль: "Int64ListTest.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: TestCase::Class Shared Delphi Sand Box::SandBoxTest::FinalContainersTests::Int64ListTest
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ! Полностью генерируется с модели. Править руками - нельзя. !

interface

uses
  TestFrameWork
  ;

type
 TInt64ListTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TInt64ListTest

implementation

uses
  Int64List,
  SysUtils
  ;

// start class TInt64ListTest

procedure TInt64ListTest.DoIt;
//#UC START# *51DEE90202A8_51DEE8E9025A_var*
const
 cCount = 1000;
var
 l_List : TInt64List;
 l_Count : IndexType;
 l_Index : IndexType;
//#UC END# *51DEE90202A8_51DEE8E9025A_var*
begin
//#UC START# *51DEE90202A8_51DEE8E9025A_impl*
 l_List := TInt64List.Create;
 try
  l_List.Count := cCount;
  Check(l_List.Count = cCount);
  Check(l_List.Capacity >= cCount);

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);

  l_Count := Random(cCount);
  l_List.Count := l_Count;
  Check(l_List.Count = l_Count, Format('Выделяли %d элементов. Count = %d', [l_Count, l_List.Count]));
  Check(l_List.Capacity >= l_Count, Format('Выделяли %d элементов. Capacity = %d', [l_Count, l_List.Capacity]));

  for l_Index := 0 to l_List.Count - 1 do
   Check(l_List[l_Index] = 0);
   
 finally
  FreeAndNil(l_List);
 end;//try..finally
//#UC END# *51DEE90202A8_51DEE8E9025A_impl*
end;//TInt64ListTest.DoIt

initialization
 TestFramework.RegisterTest(TInt64ListTest.Suite);

end.

Код тут - http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBox и http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBoxTest

Абстрактные контейнеры

Подготовительные серии были тут - http://18delphi.blogspot.com/2013/07/1.html

Теперь хочется рассказать о практике создания "шаблонных" контейнеров в "стиле STL".

ПОКА - БЕЗ "настоящих" Generic'ов (http://18delphi.blogspot.ru/2013/03/generic-generic.html). Но и их - ТОЖЕ можно использовать. Но ПОКА - есть МНОЖЕСТВО НЕЗАКРЫТЫХ ошибок, которые Embarcadero почему-то не спешит закрывать. Минус ребятам. Что сказать. Даже мою ошибку (http://18delphi.blogspot.com/2013/05/resolved.html http://18delphi.blogspot.com/2013/05/xe4.html) - "типа исправили", но не закрыли. Хотя там - "копейки". Если я своим умом - всё правильно понимаю...

Одна ремарка. Несмотря на то, что в STL подобные контейнеры называются vector - я решил сохранить преемственность с Delphi и назвал их List.

Итак. Как обычно.

Модель:

Код:

List.imp.pas:

{$IfNDef List_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "List.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::List
//
// Абстрактный список значений
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define List_imp}
 PItemType = ^_ItemType_;

const
  { Sizes }
 cItemSize = SizeOf(_ItemType_);

type
 IndexType = System.Integer;

 _List_ = {mixin} class(_List_Parent_)
  {* Абстрактный список значений }
 private
 // private fields
   f_Data : Tl3PtrLoc;
   f_Count : IndexType;
    {* Поле для свойства Count}
 private
 // private methods
   procedure ReAllocList(aNewCapacity: IndexType);
   procedure CheckIndex(anIndex: IndexType); // can raise EListError
     {* проверяет валидность индекса и поднимает исключение, если он неправильный }
   function ItemSlot(anIndex: IndexType): PItemType;
   function ExpandSize(aTargetSize: IndexType): Cardinal;
   procedure CheckSetItem(anIndex: IndexType); // can raise EListError
     {* Проверяет валидность индекса при вставке }
 protected
 // property methods
   procedure pm_SetCount(aValue: IndexType);
   function pm_GetCapacity: IndexType;
   procedure pm_SetCapacity(aValue: IndexType);
   function pm_GetItems(anIndex: IndexType): _ItemType_;
   procedure pm_SetItems(anIndex: IndexType; const aValue: _ItemType_);
 protected
 // overridden protected methods
   procedure Cleanup; override;
     {* Функция очистки полей объекта. }
 public
 // public properties
   property Count: IndexType
     read f_Count
     write pm_SetCount;
   property Capacity: IndexType
     read pm_GetCapacity
     write pm_SetCapacity;
   property Items[anIndex: IndexType]: _ItemType_
     read pm_GetItems
     write pm_SetItems;
     default;
     {* Элементы списка. }
 end;//_List_

{$Else List_imp}

// start class _List_

procedure _List_.ReAllocList(aNewCapacity: IndexType);
//#UC START# *51DEB8770017_51DEB07E03E4_var*
var
 l_Cap : Integer;
 l_Cnt : Integer;
//#UC END# *51DEB8770017_51DEB07E03E4_var*
begin
//#UC START# *51DEB8770017_51DEB07E03E4_impl*
 f_Data.SetSize(aNewCapacity * cItemSize);
 l_Cap := Self.Capacity;
 Assert(l_Cap >= aNewCapacity);
 l_Cnt := f_Count;
 if (l_Cap > l_Cnt) then
  System.FillChar(ItemSlot(l_Cnt)^, (l_Cap - l_Cnt) * cItemSize, 0);
//#UC END# *51DEB8770017_51DEB07E03E4_impl*
end;//_List_.ReAllocList

procedure _List_.CheckIndex(anIndex: IndexType); // can raise EListError
//#UC START# *51DEB95E00BD_51DEB07E03E4_var*

 procedure _Error;
 begin
  raise EListError.CreateFmt(SListIndexError + ' from (%d)', [anIndex, f_Count])
 end;

//#UC END# *51DEB95E00BD_51DEB07E03E4_var*
begin
//#UC START# *51DEB95E00BD_51DEB07E03E4_impl*
 if (anIndex < 0) or (anIndex >= f_Count) then
  _Error;
//#UC END# *51DEB95E00BD_51DEB07E03E4_impl*
end;//_List_.CheckIndex

function _List_.ItemSlot(anIndex: IndexType): PItemType;
//#UC START# *51DEBE2D008A_51DEB07E03E4_var*
//#UC END# *51DEBE2D008A_51DEB07E03E4_var*
begin
//#UC START# *51DEBE2D008A_51DEB07E03E4_impl*
 Result := PItemType(f_Data.AsPointer + anIndex * cItemSize);
 assert(Result <> nil);
//#UC END# *51DEBE2D008A_51DEB07E03E4_impl*
end;//_List_.ItemSlot

function _List_.ExpandSize(aTargetSize: IndexType): Cardinal;
//#UC START# *51DEC11F0058_51DEB07E03E4_var*
const
 cIncrArray : array [0..3] of Integer = (64 * 1024, 1024, 128, 4);
 cMaxForTwice : Integer = 1 * 1024 * 1024;
var
 I : Integer;
//#UC END# *51DEC11F0058_51DEB07E03E4_var*
begin
//#UC START# *51DEC11F0058_51DEB07E03E4_impl*
 Assert(aTargetSize > 0);

 Result := aTargetSize;
 if (Result > cMaxForTwice) then
 // большие массивы не удваиваем а подравниваем под 1мб
  Result := (aTargetSize div cMaxForTwice + 1) * cMaxForTwice
 else
 begin
  for I := 0 to High(cIncrArray) do
   if (aTargetSize > cIncrArray[I]) then
   begin
    Result := (aTargetSize div cIncrArray[I]) * cIncrArray[I] * 2;
    Break;
   end;//aTargetSize > cIncrArray[I]
 end;//Result > cMaxForTwic
//#UC END# *51DEC11F0058_51DEB07E03E4_impl*
end;//_List_.ExpandSize

procedure _List_.CheckSetItem(anIndex: IndexType); // can raise EListError
//#UC START# *51DECAA8035E_51DEB07E03E4_var*
//#UC END# *51DECAA8035E_51DEB07E03E4_var*
begin
//#UC START# *51DECAA8035E_51DEB07E03E4_impl*
 CheckIndex(anIndex);
//#UC END# *51DECAA8035E_51DEB07E03E4_impl*
end;//_List_.CheckSetItem

procedure _List_.pm_SetCount(aValue: IndexType);
//#UC START# *51DEB1ED0017_51DEB07E03E4set_var*

 procedure SayBadCount(aNewCount: LongInt);
 begin
  raise EListError.CreateFmt(sListIndexError, [aNewCount]);
 end;

var
 l_Ptr   : PItemType;
 {$IfNDef l3Items_IsUnrefcounted}
 l_Index : Integer;
 {$EndIf  l3Items_IsUnrefcounted}
//#UC END# *51DEB1ED0017_51DEB07E03E4set_var*
begin
//#UC START# *51DEB1ED0017_51DEB07E03E4set_impl*
 if (aValue < 0) then
  SayBadCount(aValue);
 if (aValue < f_Count) then
 begin
  l_Ptr := ItemSlot(aValue);
  {$IfDef l3Items_IsUnrefcounted}
  System.FillChar(l_Ptr^, (f_Count - 1 - aValue) * cItemSize, 0);
  {$Else  l3Items_IsUnrefcounted}
  for l_Index := aValue to f_Count - 1 do
  begin
   FreeItem(l_Ptr^);
   Inc(PMem(l_Ptr), cItemSize);
  end;//for i
  {$EndIf  l3Items_IsUnrefcounted}
 end//aValue < f_Count
 else
 if (aValue > Self.Capacity) then
  ReAllocList(ExpandSize(aValue));
 if (f_Count < aValue) then
  System.FillChar(ItemSlot(f_Count)^, (aValue - f_Count) * cItemSize, 0);
 f_Count := aValue;
//#UC END# *51DEB1ED0017_51DEB07E03E4set_impl*
end;//_List_.pm_SetCount

function _List_.pm_GetCapacity: IndexType;
//#UC START# *51DEB20E0130_51DEB07E03E4get_var*
//#UC END# *51DEB20E0130_51DEB07E03E4get_var*
begin
//#UC START# *51DEB20E0130_51DEB07E03E4get_impl*
 Result := f_Data.GetSize div cItemSize;
//#UC END# *51DEB20E0130_51DEB07E03E4get_impl*
end;//_List_.pm_GetCapacity

procedure _List_.pm_SetCapacity(aValue: IndexType);
//#UC START# *51DEB20E0130_51DEB07E03E4set_var*

 procedure SayBadCap(aNewCapacity: IndexType);
 begin
  raise EListError.CreateFmt(sListIndexError, [aNewCapacity]);
 end;

//#UC END# *51DEB20E0130_51DEB07E03E4set_var*
begin
//#UC START# *51DEB20E0130_51DEB07E03E4set_impl*
 if (aValue < 0) then
  SayBadCap(aValue);
 if (pm_GetCapacity <> aValue) then
 begin
  { If the list is shrinking, then update _Count for the smaller size. }
  if (aValue < f_Count) then
   Count := aValue;
  ReAllocList(aValue);
 end;//GetCapacity(Self) <> aValue
//#UC END# *51DEB20E0130_51DEB07E03E4set_impl*
end;//_List_.pm_SetCapacity

function _List_.pm_GetItems(anIndex: IndexType): _ItemType_;
//#UC START# *51DECA1202C5_51DEB07E03E4get_var*
//#UC END# *51DECA1202C5_51DEB07E03E4get_var*
begin
//#UC START# *51DECA1202C5_51DEB07E03E4get_impl*
 CheckIndex(anIndex);
 Result := ItemSlot(anIndex)^;
//#UC END# *51DECA1202C5_51DEB07E03E4get_impl*
end;//_List_.pm_GetItems

procedure _List_.pm_SetItems(anIndex: IndexType; const aValue: _ItemType_);
//#UC START# *51DECA1202C5_51DEB07E03E4set_var*
{$IfNDef l3Items_IsAtomic}
var
 l_P : PItemType;
{$EndIf  l3Items_IsAtomic}
//#UC END# *51DECA1202C5_51DEB07E03E4set_var*
begin
//#UC START# *51DECA1202C5_51DEB07E03E4set_impl*
 CheckSetItem(anIndex);
 {$IfDef l3Items_IsAtomic}
 PItemType(ItemSlot(anIndex))^ := aValue;
 {$Else  l3Items_IsAtomic}
 l_P := PItemType(ItemSlot(anIndex));
 if not IsSame(l_P^, aValue) then
 begin
  FreeItem(l_P^);
  FillItem(l_P^, aValue);
 end;//not IsSame(l_P^, anItem)
 {$EndIf l3Items_IsAtomic}
//#UC END# *51DECA1202C5_51DEB07E03E4set_impl*
end;//_List_.pm_SetItems

procedure _List_.Cleanup;
//#UC START# *479731C50290_51DEB07E03E4_var*
//#UC END# *479731C50290_51DEB07E03E4_var*
begin
//#UC START# *479731C50290_51DEB07E03E4_impl*
 Count := 0;
 f_Data.SetSize(0);
 inherited;
//#UC END# *479731C50290_51DEB07E03E4_impl*
end;//_List_.Cleanup

{$EndIf List_imp}

UnrefcountedListPrim.imp.pas:

{$IfNDef UnrefcountedListPrim_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "UnrefcountedListPrim.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::UnrefcountedListPrim
//
// Список значений без какого то бы ни было подсчёта ссылок
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define UnrefcountedListPrim_imp}
 {$Define l3Items_IsUnrefcounted}

 _List_Parent_ = _UnrefcountedListPrim_Parent_;
 {$Include List.imp.pas}
 _UnrefcountedListPrim_ = {mixin} class(_List_)
  {* Список значений без какого то бы ни было подсчёта ссылок }
 end;//_UnrefcountedListPrim_

{$Else UnrefcountedListPrim_imp}

// start class _UnrefcountedListPrim_

function IsSame(const A: _ItemType_;
  const B: _ItemType_): Boolean;
//#UC START# *51DECB820261_51DED02E0163_var*
//#UC END# *51DECB820261_51DED02E0163_var*
begin
//#UC START# *51DECB820261_51DED02E0163_impl*
 Result := (A = B);
//#UC END# *51DECB820261_51DED02E0163_impl*
end;//IsSame

type _List_R_ = _UnrefcountedListPrim_;

{$Include List.imp.pas}


{$EndIf UnrefcountedListPrim_imp}

AtomicList.imp.pas:

{$IfNDef AtomicList_imp}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "SandBox"
// Модуль: "AtomicList.imp.pas"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: Impurity::Class Shared Delphi Sand Box::SandBox::STLLike::AtomicList
//
// Список атомарных значений
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

{$Define AtomicList_imp}
 {$Define l3Items_IsAtomic}

 _UnrefcountedListPrim_Parent_ = _AtomicList_Parent_;
 {$Include UnrefcountedListPrim.imp.pas}
 _AtomicList_ = {mixin} class(_UnrefcountedListPrim_)
  {* Список атомарных значений }
 end;//_AtomicList_

{$Else AtomicList_imp}

// start class _AtomicList_

procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_); forward;

procedure FreeItem(var thePlace: _ItemType_);
//#UC START# *51DEC20B01D7_51DED48301D9_var*
//#UC END# *51DEC20B01D7_51DED48301D9_var*
begin
//#UC START# *51DEC20B01D7_51DED48301D9_impl*
 thePlace := _ItemType_(0);
//#UC END# *51DEC20B01D7_51DED48301D9_impl*
end;//FreeItem

procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_);
//#UC START# *51DECB440087_51DED48301D9_var*
//#UC END# *51DECB440087_51DED48301D9_var*
begin
//#UC START# *51DECB440087_51DED48301D9_impl*
 thePlace := aFrom;
//#UC END# *51DECB440087_51DED48301D9_impl*
end;//FillItem

type _UnrefcountedListPrim_R_ = _AtomicList_;

{$Include UnrefcountedListPrim.imp.pas}


{$EndIf AtomicList_imp}

Код тут - http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBox и http://sourceforge.net/p/rumtmarc/code-0/19/tree/trunk/Blogger/SandBoxTest

пятница, 17 мая 2013 г.

О кодогенерации

Вот код, он генерируется с ОДНОГО "квадрата на модели":


program SearchTest;
 
uses
...
  ;
 
{$R SearchTest.res}
 
begin
 {$IfDef nsTest}
 g_CVSPath := 'w:common\components\SearchTest';
 {$EndIf nsTest}
 //#UC START# *511B4CA100B2CVSPath*
 //#UC END# *511B4CA100B2CVSPath*
 TAutoTestsSuite.Register;
 try
  if KTestRunner.NeedKTestRunner([TToK]) then
   KTestRunner.RunRegisteredTests
  else
  if System.IsConsole then
   TextTestRunner.RunRegisteredTests
  else
   GUITestRunner.RunRegisteredTests;
 except
  on E: Exception do
  begin
   {$If defined(MTDORB) AND defined(NoKPageTool)}
   if TKBridge.Exists then
    TKBridge.Instance.Logout;
   {$IfEnd}
   l3System.Exception2Log(E);
   Halt(2);
  end;//Exception
 end;//try..except
 if (TestsExitCode <> 0) then
  Halt(TestsExitCode);
end.


А генерируется он так:

// реализация абстрактного стереотипа Delphi интерфейсы и реализация::MDAGenerator
// Реализация на Delphi(.pas)
+ impl.pas
R  
//#UC START# *470F15B800CBfor470382290251*
//#UC END# *470F15B800CBfor470382290251*
 
// реализация абстрактного стереотипа Delphi интерфейсы и реализация::MDAGenerator
// Родные Delphi интерфейсы (.pas)
+ intf.pas
R  
//#UC START# *470F1571031Cfor470382290251*
O [{"%f_pas_UnitFileName(%S)"!=""}[{%S{finished}!=true}NOT_FINISHED_]%f_pas_UnitFileName(%S)]
C %S%f_pas_UnitPath()
 %f_clear_list(TOTAL_USES)
 %f_pas_OpenUnitInterface(%S)
 
 [{%S{console}=true}
 
 {$APPTYPE CONSOLE}
 ]
 
 [{%SS=VCMTestTarget}
 <{}{%D#f_IsVCMGUI()=true}
 %D%f_OutExtIntfUses()
 %f_cycle_break(%S)
 >
 ]
 
 %S%f_pas_TotalUses()
 [{%S{need UC in project}=true}
 n%U[{manualuses}n]
 ]
 %f_pas_CloseUses(%S)
 %f_clear_TotalUses(%S)
 
 [{%S{console}!=true}
 {$R [{%S{finished}!=true}NOT_FINISHED_]%f_pas_UnitName(%S).res}
 [{%S{needs second icon}=true}n{$R main_icon2.res} // вторая иконка приложения]
 
 ]
 [{%S{need UC in project}=true}
 %U[{manualres}n]n
 ]
 
 begin
 [{%S%f_IsTest()=true}{
  %U[{manualcode}n ]
 }
  {$IfDef nsTest}
  g_CVSPath := '%f_str_replace(%SR%Sd,/,\)';
  {$EndIf nsTest}
  %U[{CVSPath}n ]
 [{%S{need UC in project}=true}{
 [{%SS=VCMTestTarget}{
 [{%S{no scripts}!=true}
  TAutoTestsSuite.Register;\n
 ]
  try
   if KTestRunner.NeedKTestRunner
 ([<{, }{%C#f_IsTestResults()=true}%f_pas_TypeName(%C)>])
  then
    KTestRunner.RunRegisteredTests
   else
   if System.IsConsole then
    TextTestRunner.RunRegisteredTests
   else
    GUITestRunner.RunRegisteredTests;
  except
   on E: Exception do
   begin
    {$If defined(MTDORB) AND defined(NoKPageTool)}
    if TKBridge.Exists then
     TKBridge.Instance.Logout;
    {$IfEnd}
    l3System.Exception2Log(E);
    Halt(2);
   end;//Exception
  end;//try..except
  if (TestsExitCode <> 0) then
   Halt(TestsExitCode);
 }
  TF1AutoTestSuite.Register;
 <{}{%D#f_IsVCMGUI()=true}
 %D%f_OutApplicationBody()
 %f_cycle_break(%S)
 >
 ]
 }
  %U[{manualcode}n ]
 ]
 ]
 %f_pas_CloseUnit(%S)
 <{}{%CV!=PrivateAccess&%C#f_IsClassInner()!=true&"%CO"!=""}%CX>
//#UC END# *470F1571031Cfor470382290251*

-- по-моему - всё понятно.

четверг, 16 мая 2013 г.

Банальное об UML и моделях вообще

Первое что хочу озвучить - НИЧТО НЕ ДОГМА. Ни TDD, ни UML, ни вообще "хорошие практики программирования". Всё - ЭВОЛЮЦИОНИРУЕТ. Это надо ПОНЯТЬ. То что считалось хорошим - завтра может быть уже "устаревшей практикой" и наоборот.

Хочу рассказать СВОЙ взгляд на проектирование проектов на "модели".

Так как я для СЕБЯ это понимаю. И так - как я это пытаюсь презентовать это вновь прибывшим коллегам устно.

Я не буду пытаться рассказать о "космических технологиях" или <<стереотипах>> или устройстве нашей кодогенерации. Тем боле, что ПОКА - что-то не получается.

Хочется сказать БАНАЛЬНЫЕ вещи, которые на самом деле - являются основой и из которых вытекает всё остальное.

"САМЫЙ НАУЧНЫЙ СПОР - это СПОР О ТЕРМИНАХ"...

Повторю ещё раз:

"САМЫЙ НАУЧНЫЙ СПОР - это СПОР О ТЕРМИНАХ"...

Очень много копий ломается именно в спорах, дискуссиях, раздумьях - "как нам организовать процесс и как навести порядок".

Например:
Эта библиотека может зависеть от этой? А эта от этой? А циклические зависимости тут возможны?

В какую папку поместить компонент X - в common\rtl или в rtl\common?

Инструкция программирования? Как оформлять входные параметры? А выходные? А члены класса? А приватные? А методы доступа к свойствам?

Где граница между подсистемами? Это ещё данные? Или УЖЕ бизнес-объект? Или КОНТРОЛЛЕР? Или может ПРЕДСТАВЛЕНИЕ? Как они должны зависеть друг от друга?

Как проекты могут зависеть друг от друга?

Фабрики? Dependency Injection? Как? Зачем и почему?

Шаблоны GoF? Где и как?

И пишутся ИНСТРУКЦИИ. И ведутся СПОРЫ. И все "друг друга пинают" за несоблюдение инструкций. Аппелируют к регламенту. Шучу конечно. Но в каждой шутке... Но - "тыкать в инструкцию" - это ведь некоструктив. И это - НИКАК не улучшает производительность труда.  А что иначе делать? Или забить на инструкцию?

И главное - ВСЕ МЫ ЛЮДИ. Мы НЕ УМЕЕМ тупо соблюдать инструкции. Особенно многостраничные. Даже ЕСЛИ ЗАХОТИМ. А мы ведь - ХОТИМ. Но не МОЖЕМ.

А как требования трассируются на код? Какой участок кода выполняет это требование? А это требование протестировано? Как?

Вопросы.. Вопросы..

А как вообще должны называться проектные папки? "Самый научный спор - это спор о терминах".

Рут проекта где должен находится? В стандартизованном месте или как захочется разработчику?

А вот этот код это уже реализация прецедента? Или ещё представление? Или что-то ещё? И где вообще собственно реализация прецедента?

И через ВСЁ это - я прошёл. И не раз.

И могу сказать, что модель - это конечно не "серебряная пуля". Но это ОТВЕТ на МНОГИЕ вопросы.

Модель - снимает многое из этой "пены на волне". Она - ВЕДЁТ разработчика в ПРАВИЛЬНОМ направлении. Она де даст поставить циклические зависимости - ТАМ ГДЕ ЭТО НЕВОЗМОЖНО. И даст - там где возможно.

Она не даст нарисовать бизнес-объект на уровне данных. Просто физически. И она не даст нарисовать зависимость от данных к представлению. Что компилятор Delphi - НИКАК не контролирует.

Повторюсь - "модель и её шаблоны - не ДОГМА". Но они служат "ремнями безопасности" или даже скорее "тьюторами" для тех раз разработчиков, которые ХОТЯТ СОБЛЮДАТЬ ПРАВИЛА, но которым тяжко каждый раз сверяться с инструкцией.

Вот как-то так. Это - вершина айсберга. Далее конечно есть "шаблоны проектирования" или "параметризация классов" или "устойчивые проектные решения", но это - ДАЛЬНЕ. Но в первую очередь - это то, что проектирование на модели умеет вести разработчика нужной колеёй.

Например (опять же - это не догма) - модель не даст провести связь к данным миную контроллер или бизнес-объект. Просто ФИЗИЧЕСКИ не даст. Ну если правила игры (шаблоны) такие.

Да! модель (на первый взгляд) - "бьёт по рукам" разработчикам (Embarcadero кстати тоже не прочь "дать по рукам" - http://18delphi.blogspot.com/2013/04/delphi-language.html, и это кстати, на мой взгляд - "правильно"). Она ограждает их от "неверных" решений ( в данной конкретной среде). Но! Надо не забывать только одну вещь - среда тоже - НАСТРАИВАЕМА. Ничто - не догма. Всё МЕНЯЕТСЯ.

Нужны циклические зависимости? На этом уровне? Окей - давайте - введём. Если они ОБОСНОВАННЫ. Но! Das Ordnung muss sein. Если решили так - то - решили. До следующего ревью мета-модели. Которое конечно надо "делать на лету, но всё же дискретно и ВСЕМИ заинтересованными участниками процесса. А не "тихо гундеть в углу". И "в минуты передышки".

НО! При этом. В каждый конкретный момент времени есть - "ощущение порядка и автоматического соблюдения договорённостей". Мне лично - это нравится. Не нравятся договорённости - давайте их менять. Но они - ДОЛЖНЫ БЫТЬ! Какие-то. Должны быть "общие правила игры". В общем - не суть важно - какие. Хотим "стоять на голове" - давайте стоять на голове. Но - ВСЕ вместе, а не КАЖДЫЙ в свою сторону. Дурацкие договорённости? Дурацкие! Но - должны быть. Хотя бы какие-то. И АВТОМАТИЧЕСКИ валидируемые. Чтобы не углубляться "в самый научный спор" на этапе разработки.

Вот как-то так я всё это вижу. Рассказывать дальше про UML и модели? Или я не найду отклика?

P.S. UML тут кстати не совсем причём. Даже может "совсем не причём". Он лишь одна из реализаций. А вообще говоря - надо бы иметь средства мета-мета-программирования (проектирования).