понедельник, 29 сентября 2014 г.

Совсем коротко. Про Assert'ы

Покажусь "снобом и ментором", но напишу:

Возьмите себе за правило - "никаких пустых begin/end".

Если "хотим написать пустой begin/end", то напишите:

begin 
 Assert(false, 'Пока не реализовано'); 
end;

И "наблюдайте за кодом" - когда Assert "всплывёт".

Ну и - О ТЗ, цейтноте и "позабывании"

Ну и пару слов из переписки:

XXX: Если Вы написали функцию которая ничего не делает, то так и напишите "ничего не делать", Ищу цитату из программиста прагматика, добавить в комментарий.


Александр: а лучше написать Assert, если она "не просто ничего не делает", а "мы не знаем - что она должна делать".

Особенно это касается методов интерфейсов, которые "пока непонятно как реализовывать".

Хуже нету "пустых методов" интерфейсов. А вот метод с Assert - "сам за себя говорит".

Ну и "из классиков":

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

Ну и "не по теме", но "вдогонку":

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

Assert'ы - это как раз "способ диагностировать" те самые "разбитые окна".

Ну и:

Я уже писал - нельзя писать так:

Assert(Supports(X, Y, X));

, а нужно:

if not Supports(X, Y, Z) then 
 Assert(false);

Т.е. не надо "пытаться съэкономить" пустой begin/end.

Ну и всецело подход описан тут:

Ссылка. Пишем простой интерпретатор на C++ с помощью TDD
Тестируем калькулятор №6.2.1. Применяем "классическое TDD"

Коротко. Сделали нагрузочные тесты

По мотивам - Депрессия. Или превратности hResult и прочих ErrorCode

Сделали нагрузочные тесты. С нескольких клиентов.

Вот кстати код теста:

USES
 QuickTestsUtils.script
;

Тест TK565491886

 ARRAY VAR "Список документов"
 [ Конституция ГК НК ТК ТНВЭД ОКОФ ОКОНХ ] >>> "Список документов"         

 BOOLEAN VAR Вечность
 ДА >>> Вечность 
 ПОКА Вечность (
          INTEGER VAR "Случайный документ"
          ( "Список документов" array:Count Random "Список документов" [i] ) >>> "Случайный документ" 
     Параметры: ( "Документ из базы {("Случайный документ")}" )
     Выполнить ( 
  "Набить текст {('Вносим изменения в текст и сохраняем документ!!!')}"
  "Нажать Enter" // - чтобы отделить параграфы документа, иначе параграфы могут быть "слишком длинными" для восприятия
  "Сохранить документ"
  "Обработать сообщения" // - чтобы приложение перерисовывалось
   )
        )
;

TK565491886

Молотят уже пятый день.

Ошибок - не выявлено.

Выявлены лишь ошибки функций ReadFile и WriteFile. В основном - LOCK_VIOLATION. Мы их теперь обрабатываем в цикле и протоколируем.

Будем увеличивать число клиентов.

P.S. Ну и разрулил "бутылочное горлышко". Стало всё сильно быстрее. И сильно меньше "конфликтов с залочками".

Я писал ранее - Про "рефакторинг":

"И ещё сегодня я нашёл два "бутылочных горлышка" - AllocNewFATAlement и AllocNewCluster. Они в свою очередь ведут к LockFile, Лочим заголовок. Где записана информация о структуре хранилища. И все пользователи при записи "бъются от эти залочки".

И знаю уже как это разрулить.

Надо упреждающе аллоцировать не один FATElement и Cluster, а сразу несколько (пять, десять, двадцать). Одним куском. Учитывая, что кластеров в файле обычно не один и не два и даже не десять, то это - эффективно. И держать список свободных локально у клиента. А потом когда клиент закрывается - возвращать их в список свободных. Т.е. "не пригодившихся". Чтобы из потом могли использовать другие клиенты.

Есть конечно вероятность - потерять элементы, если клиент завершится аварийно.

Но зато - мы реже попадаем в "бутылочное горлышко"."

-- ну вот это и "разрулил".

И нашёл ещё "пару подобных мест". Но не таких критичных.

пятница, 26 сентября 2014 г.

Коротко. О конструкторах

Депрессия - "частично" побеждена.

Много ошибок найдено.

В частности - СПАСИБО NameRec'у - http://programmingmindstream.blogspot.ru/2014/09/blog-post_23.html?showComment=1411514362019#c1699121675114974966

Нагрузочные тесты на основе GUI-тестов - написаны.

Эмулируют работу реальных пользователей в распределённой системе.

С нескольких машин.

Примерно так:

1. Случайным образом открывают документ (из заранее определённого списка).
2. Вводят "два три слова".
3. Сохраняют.
4. Закрывают документ.

Вот кстати код теста:

USES
 QuickTestsUtils.script
;

Тест TK565491886

 ARRAY VAR "Список документов"
 [ Конституция ГК НК ТК ТНВЭД ОКОФ ОКОНХ ] >>> "Список документов"         

 BOOLEAN VAR Вечность
 ДА >>> Вечность 
 ПОКА Вечность (
          INTEGER VAR "Случайный документ"
          ( "Список документов" array:Count Random "Список документов" [i] ) >>> "Случайный документ" 
     Параметры: ( "Документ из базы {("Случайный документ")}" )
     Выполнить ( 
  "Набить текст {('Вносим изменения в текст и сохраняем документ!!!')}"
  "Нажать Enter" // - чтобы отделить параграфы документа, иначе параграфы могут быть "слишком длинными" для восприятия
  "Сохранить документ"
  "Обработать сообщения" // - чтобы приложение перерисовывалось
   )
        )
;

TK565491886

Молотят уже третий день.

Намолотили порядка 9 Гб.

Оставил на выходные. Посмотрим, что будет.

По результатам - напишу глубокий анализ.

Одно пока могу сказать - даже если функция ReadFile "считала что-то" и вернула ReadSize = SizeToRead - "это не повод успокаиваться". Даже если "оно считало" то "что нужно". И даже если считанные результаты совпадают с данными из файла.

Надо проверять Result функции ReadFile, который BOOL. Ну и GetLastError.

Например оно может вернуть LockViolation или NetworkBusy.

Банально. Да.

Но я про это "ещё потом напишу".

А теперь я хотел написать про конструкторы.

По мотивам:

Ссылка. Получение ресурса есть инициализация (RAII). И "немного от себя"
Коротко. И ещё о фабриках
Коротко. Ещё немного "рассуждений о RAII"
Коротко. Ещё о фабриках
Коротко. О фабриках
Собственная реализация IUnknown и подсчёт ссылок. И примеси
Почему всегда нужно использовать FreeAndNil вместо Free - это надо перечитать особенно и внимательно потому, что моя мысль проистекает из "такой же парадигмы". Виртуальность. И классы-потомки.
Коротко. "Нелюбителям" FreeAndNil
Сегодня получил неожиданное пятикратное подтверждение тому, почему надо писать FreeAndNil, а не Free
Правило 9: Никогда не вызывайте виртуальные функции в конструкторе или деструкторе
Виртуальные функции в конструкторе и деструкторе

У gunsmoker'а написано про деструкторы, а я хочу написать про конструкторы.

Теперь собственно то, что я хотел написать о конструкторах:

Обычно конструкторы пишутся так:

type
 TObject1 = class
 end;//TObject1

 TObject2 = class
 end;//TObject2

 TA = class
  private
   f_SomeObject1 : TObject1;
  public
   constructor Create;
 end;//TA

 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  public
   constructor Create;
 end;//TB

...

constructor TA.Create;
begin
 inherited Create;
 f_SomeObject1 := TObject1.Create;
end;

...

constructor TB.Create;
begin
 inherited Create;
 f_SomeObject2 := TObject2.Create;
end;

Этот "стиль" нам "завещал ещё Борланд"... Почивший в бозе.

И все мы к нему "привыкли".

Что в нём не так?

Вообще говоря "правильнее" написать так:

type
 TObject1 = class
 end;//TObject1

 TObject2 = class
 end;//TObject2

 TA = class
  private
   f_SomeObject1 : TObject1;
  public
   constructor Create;
 end;//TA

 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  public
   constructor Create;
 end;//TB

...

constructor TA.Create;
begin
 f_SomeObject1 := TObject1.Create;
 inherited Create;
end;

...

constructor TB.Create;
begin
 f_SomeObject2 := TObject2.Create;
 inherited Create;
end;

Что мы тут сделали?

Мы ПОМЕНЯЛИ местами инициализацию "агрегированных объектов" и "вызов унаследованных конструкторов".

Это - ВАЖНО.

Почему?

Ключевое слово - виртуальность.

Что я имею в виду?

Давайте напишем "пока" так:

type
 TObject1 = class
  public
   SomeField : SomeDataType;
 end;//TObject1

 TObject2 = class
  public
   SomeField : SomeDataType;
 end;//TObject2

 TA = class
  private
   f_SomeObject1 : TObject1;
   f_SomeData : Integer;
  protected
   function CalcSomeData: Integer; virtual;
  public
   constructor Create;
 end;//TA

 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  protected
   function CalcSomeData: Integer; override;
  public
   constructor Create;
 end;//TB

...

constructor TA.Create;
begin
 inherited Create;
 f_SomeObject1 := TObject1.Create;
 f_SomeData := CalcSomeData;
end;

function TA.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm1(f_SomeObject1.SomeField);
 // - тут всё хорошо
end;

...

constructor TB.Create;
begin
 inherited Create;
 f_SomeObject2 := TObject2.Create;
end;

function TB.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm2(f_SomeObject2.SomeField);
 // - тут получаем AV, потому, что при вызове CalcSomeData из конструктора TA.Create - поле f_SomeObject2  - не инициализировано
end;

Как быть?

Теперь напишем так:

type
 TObject1 = class
  public
   SomeField : SomeDataType;
 end;//TObject1

 TObject2 = class
  public
   SomeField : SomeDataType;
 end;//TObject2

 TA = class
  private
   f_SomeObject1 : TObject1;
   f_SomeData : Integer;
  protected
   function CalcSomeData: Integer; virtual;
  public
   constructor Create;
 end;//TA

 TB = class(TA)
  private
   f_SomeObject2 : TObject2;
  protected
   function CalcSomeData: Integer; override;
  public
   constructor Create;
 end;//TB

...

constructor TA.Create;
begin
 f_SomeObject1 := TObject1.Create;
 inherited Create;
 f_SomeData := CalcSomeData;
end;

function TA.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm1(f_SomeObject1.SomeField);
 // - тут всё хорошо
end;

...

constructor TB.Create;
begin
 f_SomeObject2 := TObject2.Create;
 inherited Create;
end;

function TB.CalcSomeData: Integer;
begin
 Result := SomeAlgorythm2(f_SomeObject2.SomeField);
 // - тут всё хорошо, потому, что поле f_SomeObject2 инициализровано, потому, что конструктор TA зовётся ПОЗЖЕ
end;

Проблема исчезла.

Мысль понятна?

Сразу оговорюсь - "не говорите мне про C++ и другие языки". Там "по-другому" устроено.

Процитирую:

"В этом нет никакого секрета, а просто есть правило: виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора.

Правило надо заучивать, что неудобно. Проще понять принцип. А принцип тут в краеугольном камне реализации наследования в C++: при создании объекта конструкторы в иерархии вызываются от базового класса к самому последнему унаследованному. Для деструкторов все наоборот.

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

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

Итак, виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора."

Источник цитаты - Виртуальные функции в конструкторе и деструкторе

Это если про C++ и "другие языки".

НО!

Не про Delphi.

Теперь "пару слов" - на тему "откуда пошёл такой стиль Борладна".

А "пошло всё" из Turbo Pascal и Turbo Vision.

Там были конструкторы Init. И базовый объект TObject.

Но там была совсем ДРУГАЯ ПАРАДИГМА. Повторю - ПАРАДИГМА. И я не зря Caps Lock использовал.

Ещё раз повторю - ДРУГАЯ ПАРАДИГМА.

Что там было?

А вот что:

constructor TObject.Init;
begin
 FillChar(@Self, InstanceSize, 0);
end;

InstanceSize - конечно "выглядел иначе".

Но думаю, что "смысл понятен".

А теперь что будет с объектом наследником?

Напишем так:

type
 TA = object(TObject)
  public
   SomeField : Integer;
  public
   constructor Init;
 end;//TA

...

constructor TA.Init;
begin
 TObject.Init;
 SomeField := 123;
end;

...
var
 A : TA;
begin
 A := TA.Init;
 WriteLn(A.SomeField);
 // - Тут получаем 123
end;

А в "другой парадигме"?

type
 TA = object(TObject)
  public
   SomeField : Integer;
  public
   constructor Init;
 end;//TA

...

constructor TA.Init;
begin
 SomeField := 123;
 TObject.Init;
end;

...
var
 A : TA;
begin
 A := TA.Init;
 WriteLn(A.SomeField);
 // - Тут получаем 0
end;

Мысль понятна?

И "многие люди" до сих пор держат "в уме" эту ПАРАДИГМУ. Но при этом кстати почему-то "забывают" вызывать конструктор от TObject.

Я такое видел НЕ В ОДНОЙ сторонней библиотеке.

Например в том же ImageEn.

"Тема" конструкторов/деструкторов, фабрик и "RAII" - ещё совсем не закрыта. Она только открыта.

Если будет интерес - я напишу далее.

Но "пока" - вот "и всё что я хотел сказать о конструкторах".

Ну и "откуда всё взялось" - Коротко. О возбуждении исключений

P.S. Это всё конечно - "из-за кривой архитектуры", но это - "повод для отдельного разговора".

P.P.S. Собственно - "мысль поста" - была - БАНАЛЬНА - либо "использование фабрик", либо "слом парадигмы".

Хотя "для некоторых" и "использование фабрик" это - "слом парадигмы". К сожалению.

четверг, 25 сентября 2014 г.

Семь смертных грехов программных систем

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

Семь грехов:
  1. Медлительность
  2. Блокирующее взаимодействие
  3. Неуместные ограничения
  4. Ненастроенность
  5. Несамостоятельность
  6. Забывчивость
  7. Гордыня

http://tonsky.livejournal.com/236225.html

Про "рефакторинг"

По мотивам - Депрессия. Или превратности hResult и прочих ErrorCode

И ещё сегодня я нашёл два "бутылочных горлышка" - AllocNewFATAlement и AllocNewCluster.

Они в свою очередь ведут к LockFile.

Потому, что лочим заголовок.

Где записана информация о структуре хранилища.

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

Иначе - получится "каша", а не "хранилище".

И все пользователи при записи "бъются от эти залочки".

Я сегодня эту картину наблюдал на "синтетических" нагрузочных тестах.

Реально под отладчиком.

Когда другие машины - "молотили", а я одну из них - "отлаживал".

И УВИДЕЛ. Реально увидел. В коде. И на break-point'ах.

Как - "один другого ждёт".

И знаю уже как это разрулить.

Надо упреждающе аллоцировать не один FATElement и Cluster, а сразу несколько (пять, десять, двадцать, тридцать).

Одним куском.

Учитывая, что кластеров в файле обычно не один и не два и даже не десять, то это - эффективно.

И держать список свободных локально у клиента. А потом когда клиент закрывается - возвращать их в список свободных. Т.е. "не пригодившихся". Чтобы из потом могли использовать другие клиенты.

Есть конечно вероятность - потерять элементы, если клиент завершится аварийно.

Но зато - мы реже попадаем в "бутылочное горлышко".

Потому, что мы можем написать.

if AllocatedFatElements.Empty then
begin
 // - тут есть межПРОЦЕССНОЕ "бутылочное горлышко"
 Lock;
 try
  Result := AllocNewFatElement;
  for l_Index := 0 to 10 do
   AllocatedFatElements.Add(AllocNewFatElement);
 finally
  Unlock;
 end//try..finally
end
else
 // - тут ТОЛЬКО межПОТОЧНОЕ "бутылочное горлышко" (потому что AllocatedFatElements - естественно - многопоточно-защищён)
 Result := AllocatedFatElements.GetLastAndDeleteIt;

Вместо:

Lock;
// - а тут - ВСЕГДА - многоПРОЦЕССОРНОЕ "бутылочное горлышко"
try
 Result := AllocNewFatElement;
finally
 Unlock;
end;//try..finally

Ну и аналогично для кластеров.

А даже если и элементы "провиснут", то сильно хуже не будет, хранилище - не разрушится. Просто в нём будут "дырки".

Но учитывая тот факт, что у нас ночью происходит "ночной Update", если он возможен конечно, то хранилище - всё равно - перепаковывается. Т.е. "дырки" к "утру" - всё равно исчезнут.

И будет перепакованная постоянная часть без дырок. И "пустая" переменная часть.

Куда клиенты в течении дня пишут свои версии документов.

А следующей ночью - процесс опять повторяется.

Если конечно к базе не подключены работающие пользователи.

вторник, 23 сентября 2014 г.

Депрессия. Или превратности hResult и прочих ErrorCode

В общем - "в третий раз закинул старик невод"...

Точнее в третий раз не удался выпуск внутреннего продукта.

По моей вине.

Хотя и "со всеми пирогами", тестами, фабриками и прочим и прочим.

Тесты всякие разные - проходят.

Реальный софт в боевых условиях - не работает.

То READ_ERROR, то WRITE_ERROR. При распределённом доступе.

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

Ибо - незачем.

Ибо - "теория слаба без практики".

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

Пока - сваяли "нагрузочный тест".

Оставил его "молотить" на ночь.

Завтра с утра - погляжу.

Может быть будем писать "другой нагрузочный тест".

Давненько таких epic fail'ов не случалось.

Одно только могу сказать "не про себя" - hResult и прочие "ErrorCode" скажем так - "не очень хорошая придумка". Ну как использовать конечно... Я не всегда правильно использовал...

Было:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue));
Assert(SomeValue = SomeOtherValue);

Стало:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
// - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
Assert(SomeValue = SomeOtherValue);

- что удивительно - без SysCheck - проверка проходила.

Т.е. ошибку таки возвращали, но ПРОВЕРКА проходила.

Т.е. вот так работает:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
try
 SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
 // - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
finally
 Assert(SomeValue = SomeOtherValue);
 // - тут НЕ ПАДАЕМ, хотя и "падаем" выше, на SysCheck
end;

Т.е. "иногда читается то, что нужно", но "с ошибкой.

И БЕЗ проверки на ошибки - всё работало, но "подглючивало", с "проверкой на ошибки" - стало "чаще падать".

В условиях распределённой гетерогенной среды. С "дохлыми" и "полудохлыми" компьютерами.

И разными версиями Windows. Вплоть до "доисторических".

Почему? Пока - непонятно.

Грешу всё же на "собственные кривые руки", а не на "парней из Microsoft".

Ибо проще всего - "свалить на других парней".

Проще, но неконструктивно.

И кстати если написать:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
l_TryCount := 0;
while (l_TryCount < 100) do
begin
 Inc(l_TryCount);
 SetFilePos(hFile, aPos);
 try
  SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
  // - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
 except
  if (l_TryCount < 100) then
   continue
  else
   raise;
 end;//try..except
 break;
end;
 Assert(SomeValue = SomeOtherValue);

То опять же - "падает гораздо реже".

Повод "для раздумий".

К сожалению - на "синтетических тестах" это - не повторяется.

Ну и "до кучи" - там ещё участвуют LockRegion/UnlockRegion.

Естественно - "правильно расставленные" и "правильным образом" обрамлённые SysCheck etc.

НО - похоже - всё дело всё же в их присутствии.

Без них - "типа работает", но с "конкурентным доступом" - плохо. Что и понятно.

Будем переходить на новый уровень тестирования.

P.S. весь приведённый код выше - это конечно же - "псевдокод". Лишь для иллюстрации проблем. "Запятые" там скорее всего - стоят неправильно. Ну и SysCheck при FileWrite - преднамеренно - опущен. Поверьте - он там есть.

И кроме того там ещё WrittenSize и ReadSize - проверяются.

Но эти детали - тоже специально - опущены.

P.P.S. Код без SysCheck и OleCheck уже лет 15-ть как работает, но "подглючивает" (время от времени, именно время от времени - получаются некорректные данные, "неприятно", но "жить типа можно"), собственно почему я и "полез разбираться" и писать SysCheck и OleCheck.

И "огрёб по полной программе".

Чего именно "огрёб"? Пока - не понял.

Ещё раз повторю - "далеко не на всех клиентских станциях" это повторяется. Возможно - "руки кривые".

В общем - "не забывайте про коды ошибок", но их "обработка" - тоже "не всегда однозначна".

Когда уличу себя в "кривых руках" - тогда обязательно напишу.

P.P.P.S. Ну и ещё я "вкрутил логирование" проблемных операций. И.. И.. Получил "кошку Шрёдингера". Логирование стало влиять на "бизнес-логику". Хотя бы "в части временн'ых задержек". Ну, что и понятно.

Тоже - "отдельная тема".

P.P.P.P.S. Да. И код выше приведён для одного клиента. А не то, что один пишет, а другой читает. Для разных клиентов - я всё понимаю. А для одного - пока не понимаю. Но скоро - надеюсь пойму.

P.P.P.P.S. И ещё.

Версию вида:

function DoRead(...): LongBool;
begin
 FileRead(hFile ...);
 // - тут "забыли" вернуть результат
end;

...

SysCheck(DoRead(...));
// - тут проверяем "мусор"

-- я уже проверил.

"Навскидку" - нету "неинициализированных переменных".

P.P.P.P.P.S. Тесты кстати пока "ещё молотят"... Без ошибок :-( Что "навевает грусть". Буду далее - утром смотреть.

P.P.P.P.P.S. Как выяснилось - чтобы были проблемы надо две вещи:

1. Доступ по UNC-путям, то есть по путям вида - \\server\resource\path\filename.
2. Обязательное использование LockFile.

P.P.P.P.P.P.S. Вчера тесты отработали без ошибок. Намолотили порядка 3 Гб данных.

Сегодня запустил тесты с двух машин. Завтра буду смотреть на результаты.

Потом буду запускать с 3-х, 4-х, 5-ти и т.д. и т.п.

P.P.P.P.P.P.P.S. И ещё сегодня я нашёл два "бутылочных горлышка" - AllocNewFATAlement и AllocNewCluster. Они в свою очередь ведут к LockFile, Лочим заголовок. Где записана информация о структуре хранилища. И все пользователи при записи "бъются от эти залочки".

И знаю уже как это разрулить.

Надо упреждающе аллоцировать не один FATElement и Cluster, а сразу несколько (пять, десять, двадцать). Одним куском. Учитывая, что кластеров в файле обычно не один и не два и даже не десять, то это - эффективно. И держать список свободных локально у клиента. А потом когда клиент закрывается - возвращать их в список свободных. Т.е. "не пригодившихся". Чтобы из потом могли использовать другие клиенты.

Есть конечно вероятность - потерять элементы, если клиент завершится аварийно.

Но зато - мы реже попадаем в "бутылочное горлышко".

Потому, что мы можем написать.

if AllocatedFatElements.Empty then
begin
 // - тут есть межПРОЦЕССНОЕ "бутылочное горлышко"
 Lock;
 try
  Result := AllocNewFatElement;
  for l_Index := 0 to 10 do
   AllocatedFatElements.Add(AllocNewFatElement);
 finally
  Unlock;
 end//try..finally
end
else
 // - тут ТОЛЬКО межПОТОЧНОЕ "бутылочное горлышко" (потому что AllocatedFatElements - естественно - многопоточно-защищён)
 Result := AllocatedFatElements.GetLastAndDeleteIt;

Вместо:

Lock;
// - а тут - ВСЕГДА - многоПРОЦЕССОРНОЕ "бутылочное горлышко"
try
 Result := AllocNewFatElement;
finally
 Unlock;
end;//try..finally

Ну и аналогично для кластеров.

А даже если и элементы "провиснут", то сильно хуже не будет, хранилище - не разрушится. Просто в нём будут "дырки".

Но учитывая тот факт, что у нас ночью происходит "ночной Update", если он возможен конечно, то хранилище - всё равно - перепаковывается. Т.е. "дырки" к "утру" - всё равно исчезнут.

И будет перепакованная постоянная часть без дырок. И "пустая" переменная часть.

Куда клиенты в течении дня пишут свои версии документов.

А следующей ночью - процесс опять повторяется.

Если конечно к базе не подключены работающие пользователи.

Отдельный пост - тут.

Оговорюсь - "это всё про внутренние продукты". Про внешние продукты - рассказывать не буду.

Ну и "для тех кто дочитал", вот так вот выглядит задача:


"Дефицит" :-(

суббота, 20 сентября 2014 г.

Ссылка. DelphiEventBus

https://github.com/BitecSPB/DelphiEventBus

"Implementation of event bus pattern for Delphi XE
EventBus предназначен для обеспечения взаимодействия между различными компонентами, без повышения связанности."

Вдогонку - Разрабатываем. Тестируем. Наблюдаем

пятница, 19 сентября 2014 г.

Ссылка. "Rust: как код может быть одновременно быстрым и безопасным. Рассказ Степана Кольцова в Яндексе"

"Rust: как код может быть одновременно быстрым и безопасным. Рассказ Степана Кольцова в Яндексе"

"Безопасный C++. В частности, нельзя передать переменную наверх в вызывающую функцию."

"Для начала пара слов о том, что такое Rust. Последние 15 лет между разработчиками на Java и на C++ ведётся спор о том, какой язык программирования хуже — Java или C++. Программы на C++ глючат, падают, и в них утекает память. Программы на Java тормозят и требуют слишком много памяти."

четверг, 18 сентября 2014 г.

Offtopic. Про "манипуляцию сознанием"

Не могу сказать, что я "до конца согласен или до конца не согласен", но не могу не поделиться - Россию и Украину разделила кровь

Не дай бог кому-то воспринять это как "разжигание".

Просто ВСЕМ надо "посмотреть в зеркало". И РУССКИМ - тоже.

Спасибо всем, кто поймёт правильно.

P.S. Особенно нельзя бросаться словами типа "Украинцам (Евреям.. Грузинам.. Полякам.. подставьте по выбору) верить нельзя".

НЕЛЬЗЯ такими словами бросаться.

В ЭФИРЕ.

Хотя я лично "на кухне" иногда "подобными словами" и бросаюсь.

Но это - "я на кухне".

Но НЕ В ЭФИРЕ.

Хотя "многое другое" - и правильно было сказано.

P.P.S. Кстати процитирую один из комментариев:

"1. Украинцы отличаются от русских. По факту. Даже хохлы в Сибири - больше хохлы, чем сибиряки. Это - вне зависимости от того, как их, таких отличающихся, создавали, и кто создавал. Создали, всё! И нечего теперь воздух перемалывать и украинцев дальше стравливать с русскими. Контрпродуктивно!

2. У украинского проекта кроме русофобии нет идеологического стержня. Это - правильно. И что?!! Доказывать украинцам, что они - ничто, пустое место? А они - не пустое место. И они об этом знают. И не хотят этих бредней слышать. И за их украинскую идентичность любой чмошный бандеровец потянет и за сорок минут сделает их правосектантами. Контрпродуктивно!

3. Значит проект "Великая Украина" нужно наполнить конструктивным содержанием. Типа, альтернативный России проект развития. Не "будэм вбываты москалей", а построим такую Украину... такую! И поведём ЗА СОБОЙ растерянных русских по пути прогресса и гуманизма. Примем эстафету лидерства всего прогрессивного человечества. Выход? Нет?..

И сразу фашисты становятся врагами будущего Великой Украины, Вскрывается их "истинное лицо" и далее по тексту. И сразу Россия, оказывается, воюет ЗА Украину, против еврофашистской оккупации.

Слава Украине! Бандера капут!.. типа

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

Повторю:

"Значит проект "Великая Украина" нужно наполнить конструктивным содержанием. Типа, альтернативный России проект развития. Не "будэм вбываты москалей", а построим такую Украину... такую! И поведём ЗА СОБОЙ растерянных русских по пути прогресса и гуманизма. Примем эстафету лидерства всего прогрессивного человечества. Выход? Нет?..

И сразу фашисты становятся врагами будущего Великой Украины, Вскрывается их "истинное лицо" и далее по тексту. И сразу Россия, оказывается, воюет ЗА Украину, против еврофашистской оккупации. "

Коротко. Про IStorage

По мотивам - Коротко. "О торопливости"

Разбирался тут с ошибками нашего хранилища.

Написал массу разных "утилит" типа распаковки, запаковки, сравнения одного с другим.

Неожиданно понял, что "почти написал" реализацию IStorage над файловой системой Windows.

Осталось только "собрать всё в кучу".

Скоро - соберу. Ибо - полезная штука. Особенно в свете того, что многий наш код работает именно с IStorage.

Как соберу - выложу исходники. Их немного намечается.

P.S. Процитирую часть документации :-)

"

When to implement

Generally, you would not implement this interface unless you were defining a new storage scheme for your system. COM provides a compound file implementation of the IStorage interface that supports transacted access. COM provides a set of helper APIs to facilitate using the compound file implementation of storage objects. For more information, see IStorage - Compound File Implementation."

среда, 17 сентября 2014 г.

Offtopic. "Хочу в деревню"

Хочу в деревню.. С интернетом и достойной зарплатой.. Чтобы так "в духе Пришвина", прогуляться с собакой, раскочегарить самовар, сесть на крыльце.. И написать "хороший код"..

Московский "муравейник" - подзадолбал.

Но.. "Утопия"...

Коротко. "О торопливости"

Мы тут с моей подачи сильно переделали наше хранилище и выложили это дело нашим "внутренним пользователям".

И облажались. Точнее - я облажался.

При всех наших "тестах и всё остальном".

Поспешил. Сразу "боевое хранилище" перевёл на "новые рельсы".

А надо было конечно сначала "перевести зеркало" и потестировать "на реальных пользователях". А потом только - "боевое".

Поторопился.

Пришлось всё откатывать и "разбираться  с проблемами".

А если бы не пропустил шаг "тестирования зеркала" - было бы всё нормально.

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

Как говорилось в известном фильме - "торопиться не надо".

P.S. А так всё "красиво было". С фабриками, тестами, примесями и "прочими пирогами".

Но многопоточность - "она с..ка такая". Всех поставила на место. Особенно меня.

Но ничего. Поймаю. "За хвост".

Дайте время.

"Торопиться не надо".

Коротко. "Запуск Delphi XE7"

Вчера состоялся "Запуск Delphi XE7" в Москве.

Я к сожалению его посетить не смог.

Заехал буквально на 5 мин.

"Поручкался" с Всеволодом Леоновым, который был в роли "приглашённого партнёра".

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

(А с Леоновым мы собирались писать книгу, но ему теперь некогда, а один я - не потяну)

Но зато это мероприятие посетил мой товарищ с Украины (@Ingword), Которому мероприятие понравилось.

Он обещал написать "впечатления о мероприятии", когда доедет до дома.

С нетерпением жду.

P.S, Товарищу с Украины я провёл "презентацию" наших технологий. В частности - кодогенерации и тестов. Всяких разных и GUI и не-GUI. "Презентация" получилась "скомканной", ну как обычно, что-то не запустилось, что-то упало, что-то я забыл, но вроде ему понравилось.

P.P.S. Я товарища встречал на вокзале. Выглядело почти как в "Брат 2". Вокруг беженцы. Много беженцев. И с "территории АТО" и с "не территории АТО". Странный поезд. "Образца средней Азии начала и середины 90-х". С перепутанной нумерацией вагонов. Да и вообще - вагоны "отовсюду". Бердянск, Мариуполь, Кривой рог, ещё много чего. Опоздал минут на 30-ть.

И тут мой коллега - "на конференцию по компьютерным технологиям"...

Брат 2.

Сегодня он поехал домой. Надеюсь, что доедет удачно. Дай бог мира Украине. Да и всем остальным.

P.P.P.S. Другому коллеге (из Москвы) не очень мероприятие понравилось. Особенно он "критиковал" класс TParallel,

P.P.P.P.S. А ещё коллега из Кривого Рога обещал "подумать над статьёй о примесях". Может он напишет доходчивее чем я.

А коллега неплохо пишет. Вот например - "MindStream. Как мы пишем ПО под FireMonkey. Часть 2".

P.P.P.P.P.S. Мы с коллегой за "полтора часа презентации" сделали класс, который наследуется от другого, реализует абстрактные методы, является синглетоном, потокозащищённым, реализует собой "список целых" и умеет сериализоваться.

И всё - "за полтора часа". Коллега - по-моему - "впечатлился".

И всё благодаря "кодогенерации" и "примесям".

пятница, 12 сентября 2014 г.

Коротко. О "шаманстве" и инициализации переменных

Я сегодня "не в духе".

Поэтому - напишу ЖЁСТКО.

Люди когда же МЫ ВСЕ начнём читать исходники, а не полагать на "эти парни в Embarcadero что-то понаделали".

Вот пример про FMX.

Вот некоторые жалуются, что вот так работает:

procedure SomeLocalProcedure;
var
 l_Form : TSomeForm;
begin
 l_Form := TSomeForm.Create(nil);
 l_Form.SomeProperty := SomeData;
end;

А вот так не работает:

procedure SomeLocalProcedure;
var
 l_Form : TSomeForm;
begin
 Application.CreateForm(TSomeForm, l_Form);
 l_Form.SomeProperty := SomeData; // - тут получаем AV
end;

Заглянем в исходники FMX.

Там конечно - "трансректально":

procedure TApplication.CreateForm(const InstanceClass: TComponentClass; var Reference);
var
  Instance: TComponent;
  RegistryItems : TFormRegistryItems;
  RegItem : TFormRegistryItem;
begin
  if FRealCreateFormsCalled then
  begin
    Instance := TComponent(InstanceClass.NewInstance);
    TComponent(Reference) := Instance;
    try
      Instance.Create(Self);
      for RegItem in FCreateForms do
        if RegItem.InstanceClass = InstanceClass then
        begin
          RegItem.Instance := Instance;
          RegItem.Reference := @Reference;
        end;
    except
      TComponent(Reference) := nil;
      raise;
    end;
  end
  else
  begin
    SetLength(FCreateForms, Length(FCreateForms) + 1);
    FCreateForms[High(FCreateForms)] := TFormRegistryItem.Create;
    FCreateForms[High(FCreateForms)].InstanceClass := InstanceClass;
    FCreateForms[High(FCreateForms)].Reference := @Reference;

    // Add the form to form registry in case RegisterFormFamily will not be called
    if FFormRegistry.ContainsKey(EmptyStr) then
    begin
      RegistryItems := FFormRegistry[EmptyStr];
    end
    else begin
      RegistryItems := TFormRegistryItems.Create;
      FFormRegistry.Add(EmptyStr, RegistryItems);
    end;

    RegistryItems.Add(FCreateForms[High(FCreateForms)]);
  end;
end;

Я бы своего коллегу, за подобное - УБИЛ бы на месте.

Хотя у меня есть "пара коллег", которые такой "трансректальный" код пишут, но зато считают в шестнадцатиричной системе в УМЕ и склеивают бинарники неизвестного формата. Они - ГЕНИИ. Но их - ЕДИНИЦЫ.

На них - "не надо равняться".

Видим, что Reference - в "ветке else" - нигде не инициализируется. Что уже вызывает ВОЗГЛАС - "дебилы".

Как я это понял? Банально - "Ctrl-F".

Ну и ещё вот эта строчка:

FCreateForms[High(FCreateForms)].Reference := @Reference;

Это же - 3.14-ц... Простите за грубость...

А если Reference - это локальная переменная? Которая на СТЕКЕ!

Простите - это - 3.14-ц...

Я бы на месте Embarcadero положил бы туда Proxy, который бы делал форму при ПЕРВОМ же обращении.

Или хотя бы "кидало вменяемое исключение".

Как? Это - "тема отдельного поста".

В общем - "ребятам из Embarcadero" - двойка. С ДВУМЯ жирными минусами.

Или я - чего-то не понимаю.

Но я не об этом.

Но!

Люди "почему-то" начинают лезть "в кишки" вместо того, чтобы сделать вот что:

procedure SomeLocalProcedure;
var
 l_Form : TSomeForm;
begin
 l_Form := nil; // - ДА ДА - НЕ ЗАБЫВАЙТЕ, что там VAR
 Application.CreateForm(TSomeForm, l_Form);
 Assert(l_Form <> nil);
 Assert(l_Form Is TSomeForm);
 Assert(l_Form.InheritsFrom(TSomeForm));
 l_Form.SomeProperty := SomeData; // - тут получаем AV
end;

О чём я?

НАДО "читать исходники" и "писать Assert'ы". (Коротко. Ещё немного "рассуждений о RAII")

Это в ПЕРВУЮ очередь.

А во ВТОРУЮ очередь - ИЗБАВИТЬСЯ от ХОККЕЯ -  О термине "хоккей" и Портирование на Delphi XE4 идёт вполне успешно

И Embarcadero - ТОЖЕ.

Простите за ГРУБОСТЬ.

P.S. А "простым программерам" скажу - "ну забудьте уже про эту глупость - CreateForm" - пользуйтесь конструкторами.

Опять же. Простите за ГРУБОСТЬ.

P.P.S. Пишите Assert'ы.

четверг, 11 сентября 2014 г.

Коротко. Ещё немного "рассуждений о RAII"

По мотивам - RAII.

Хочется написать об "интегральных объектах".

Т.е. о тех, которые внутри себя создают другие объекты.

Обычно делается так:

type
 TSomeClass1 = class
  public
   constructor Create(aSomeData1 : TSomeType1);
 end;//TSomeClass1

 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  public
   constructor Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
   destructor Destroy; override;
 end;//TSomeClass

...

constructor TSomeClass1.Create(aSomeData1 : TSomeType1);
begin
 Assert(IsValid(aSomeData1));
 inherited Create;
 ...
end;

...

constructor TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
begin
 inherited Create;
 f_SomeClass1 := TSomeClass1.Create(aSomeData1);
 SomeInitCode;
 f_SomeClass2 := TSomeClass2.Create(aSomeData2);
end;

destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

Но иногда можно сделать так:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass

constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;

class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 Assert(IsValid(aSomeData1));
 Assert(IsValid(aSomeData2));
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;

destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

В чём цимес?

А в том, что экземпляр TSomeClass не создастся до того как создадутся экземпляры TSomeClass1 и TSomeClass2.

А значит и деструктор не вызовется.

И соответственно - SomeInitCode - тоже не вызовется.

А значит и проблем с уничтожением частично созданного объекта - не будет.

С SomeDoneCode - "есть вопросы". Оно вызовется (читаем документацию).

Но!

Если оно зависит только от объектов инициализированных выше, то проблем тоже не будет.

При этом оно вызовется только если строки:
...
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
...

- выполнятся.

А строки:

...
 inherited Create;
 f_SomeClass1 := aSomeClass1;
...

- почему-то не пройдут.

Но там - "вероятность стремится к нулю".

Ну можно написать как-то так:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass

constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;

class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;

destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - проверяем, что f_SomeClass1 - инициализирован
 //   Почему такая проверка? А ответ такой - "а иначе зачем этот SomeDoneCode именно в ЭТОМ МЕСТЕ?
  SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

Или даже так (совсем для параноиков как я):

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass

constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode(f_SomeClass1);
 f_SomeClass2 := aSomeClass2;
end;

class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;

destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - проверяем, что f_SomeClass1 - инициализирован
 //   Почему такая проверка? А ответ такой - "а иначе зачем этот SomeDoneCode именно в ЭТОМ МЕСТЕ?
  SomeDoneCode(f_SomeClass1);
 FreeAndNil(f_SomeClass1);
 inherited;
end;

- так наверное - понятнее?

Ну и TSomeClass1.Create и TSomeClass2.Create - можно сделать так же "по индукции", если они тоже - интегральные.

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

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

Если конструктор кидает исключение, то обязательно вызывается деструктор. Пусть даже и на "частично инициализированном объекте".

Это - надо иметь в виду.

Именно поэтому у меня был такой пост - Коротко. Заметки о рефакторинге

К чему я?

А к тому:

Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "частично инициализированном объекте".

Повторю:

Чем более "мелкие" у нас объекты, тем "меньше вероятность" получить вызов деструктора на "частично инициализированном объекте".

Т.е. чем меньше делается именно в конструкторе, а не в "фабрике". То - "тем спокойнее".

И тем "меньше вероятность" получить AccessViolation и/или неосвобождённые ресурсы.

Буду рад, если эти мысли кому-то окажутся полезны.

Я у себя их применил. И не раз. Но я же - не критерий.

Ну а если "вдруг интересно", то могу попробовать продолжить на реальном примере.

P.S. Что мы имеем при таком подходе, а то, что с одной стороны мы вроде инкапсулируем "внутреннюю логику" в классе. А с другой стороны - не даём создавать "частично инициализированный экземпляр класса".

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

вторник, 9 сентября 2014 г.

Коротко. Заметки о рефакторинге

Реализую "дорожную карту" - Черновик. Коротко. Для себя.

Попилил три ГИГАНТСКИХ класса типа "швейцарский нож" на множество мелких классов и примесей. Порядка 20-ти штук.

По принципу - "одна ответственность - одна сущность (класс или примесь).

Не нарадуюсь.

Код отдельно взятого класса умещается примерно на два-три экрана.

А результат их совместной работы - не хуже, чем  у "швейцарских ножей".

Попутно прикрутил логирование "проблемных мест".

Буду теперь вдумчиво изучать логи.

понедельник, 8 сентября 2014 г.

Offtopic. Offtopic. О "пропаганде"

Киселёв тут "брякнул" безответственное "Украинской армии больше нет"... Как когда-то он "брякнул", не менее безответственное "Майдана больше нет"...
И там же он "брякнул" - "у украинской армии ничего не ездит и ничего не стреляет"...
А потом дал сюжет о "ковровых бомбардировках и артиллерийских обстрелах"...
Хочется спросить - "если ничего не ездит и не стреляет, то что наносило бомбарировки и артобстрелы"....
Нет.. Я конечно - "патриот".. Если не "ура-патриот", то "эхблин-патриот" - уж точно, но против логики - сложно идти...
Так "стреляет" или "не стреляет"?

Журнализды блин...

P.S. Скажете - "а Путин мол".. Отвечу - вы слушали Пескова? Он ведь ПРЯМО сказал - "Россия не сторона конфликта и НЕ МОЖЕТ давать ОБЕЩАНИЯ". ЧЁТКО и ЯСНО.И грамотно.
А вот журнализды...
И ещё.. "Наёмничество"... Оно ведь в УК прописано "в соответствии с международными нормами"... И там про"добровольцев" и "деньги" - слов нету... Есть только про то, что "не являются гражданами одной из противоборствующих сторон"...
Т.е. всех добровольцев Россия теоретически может потом "посадить"?
Или как?
Или воспринимать тот факт, что "статью за наёмничество" не отменили в спешном порядке - как "доказательство того", что Россия к участию в конфликте не готовилась?

Хотя нет - http://www.zakonrf.info/uk/359/ - "1. Вербовка, обучение, финансирование или иное материальное обеспечение наемника, а равно его использование в вооруженном конфликте или военных действиях -

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

2. Те же деяния, совершенные лицом с использованием своего служебного положения или в отношении несовершеннолетнего, -

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

3. Участие наемника в вооруженном конфликте или военных действиях -

наказывается лишением свободы на срок от трех до семи лет с ограничением свободы на срок до одного года либо без такового.

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


И в родном гаранте посмотрел (http://ivo.garant.ru/SESSION/PILOT/main.htm) - там - ТАКОЙ ЖЕ текст.

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


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

И ещё вопрос - если Киселёв говорит, что "у украинцев ничего не ездит и не стреляет", а "у ополченцев всё ездит и стреляет" - это означает, что у "украинцев руки из жопы, а у ополченцев - из правильного места"? Или это означает, что "ополченцам кто-то помогает"? У меня вот лично - только такие варианты.. А у вас? Журнализды.. такие журнализды.. они думают "что говорят"? Или это такая "игра"?

Нет.. я конечно - ватник... и "эхблин-патриот"... но всё же.. Где логика?

Так "ездит и стреляет" или НЕ "ездит и НЕ стреляет"?

P.P.S. https://www.facebook.com/vladimir.pereverzin/posts/543942309068644?fref=nf - вот так и делается "пропаганда" - человек "забыл" или "не захотел" опубликовать ПРИМЕЧАНИЕ. Ну или я что-то не так понял...

Черновик. Коротко. Для себя

Черновик. Коротко. Для себя.

Про переделку IStorage

1. (done) Перенести Lock/Unlock в HeaderData.

2. Лочить HeaderData в конструкторе/деструкторе ТОЛЬКО если открываем НА ЗАПИСЬ.

3. Во всех остальных Read/Write лочить только непосредственно при "доступе к диску". Вложенно. Со счётчиком f_Lock.

4. Избавиться от aNeedLock. Заменить его на фабрику HeaderData, где параметром является aPosition. С владением HeaderData->HeaderDataFactory и Subscibe/Unsubscribe. Чтобы не держать РОДИТЕЛЬСКИЙ IStorage ЛИШНЕЕ ВРЕМЯ.

5. Проверить стратегию MainStorage и GetVersionsStorage. Если УЖЕ что-то открыто на ЗАПИСЬ,а мы хотим на ЧТЕНИЕ, то и это нам подходит.

6. Проверить Tm3StorageIndexStream на предмет Access. Если Storage требует только Read (а не Create/Delete), то открывать Tm3StorageIndexStream на ЧТЕНИЕ и не более того.

7. Проверять в Tm3Storage.Create данные Access vs. FRootStream.Access. Чтобы не открыли на ReadWrite поверх FRootStream. который открыт на ТОЛЬКО Read.

8. (done) Писать Lock/Unlock в log-файлы рядом с хранилищем - *.stg.log, *.sav.log, *_bkp.sav.log. С именем станции (ComputerName) и именем приложения (ParamStr(0)). Чтобы можно было "читать глазами", когда вдруг получаем Lock Violation.

9. Писать данные HeaderData НЕПОСРЕДСТВЕННО в методах pm_SetXXX. Чтобы не было рассинхронизации данных.

10. (done) Унаследовать RootStreamManager и HeaderData от "примеси" с Losk/Unlock. И защищать FRootStream и Data.

11. НУ и конечно же -ДОУБРАТЬ ВСЕ with из m3StgCla.

Я вот перечитываю то, что написал выше и ОДИН только вопрос меня тревожит - "как это всё работало 15 лет +". Наверное "вероятность попадания в ошибочную ветку - очень маленькая". Эх. Посчитать бы вероятность.

пятница, 5 сентября 2014 г.

Коротко. О солидных и кусочных решениях

По мотивам:

О расширенном делегировании
Расширенное делегирование: Протокол освобождения объекта и контроль ссылок

Не считайте критикой. Считайте "заметками на полях".

Это мне напоминает вот что:

О собственных фреймворках

"Я тоже когда-то шёл по пути универсальных решений".

Я пытался использовать ОДНИ "кошерные" строки. Один "кошерный" класс с итератором и т.д. и т.п.

Один "кошерный" базовый класс.

А потом в какой-то момент понял, что универсальных решений не бывает.

А бывает - "множество мелких решений" собранных по"Це из Эн по Ка".

И тогда я начал "делить код на мелкие кирпичики", а потом "собирать из кирпичиков нужное".

Вот ссылки:

Коротко. И ещё о фабриках
Собственная реализация IUnknown и подсчёт ссылок. И примеси
Generic'и без Generic'ов....
Абстрактные контейнеры

Т.е. я не делаю "расширенное делегирование" для ВСЕХ классов или "не задумываюсь о протоколе освобождения ссылок" вообще.

Я делаю множество "решений" в духе АОП.

Делаю всякие разные реализации Publisher/Subscriber, Changing/Changed, Refcounted, FactoryMethod, Visitor, Facade и т.д. и т.п.

А потом из "этих кирпичиков" собираю то, что нужно.

Я стараюсь делать классы и примеси - максимально короткими и "непохожими на швейцарский нож".

"Швейцарские ножи" я делаю уже как "комбинацию классов и примесей".

Я не использую "универсальные" решения, а использую, конкретные, собранные из "кирпичиков".

Возможно кому-то это будет интересно.

А вообще - "на вкус и цвет" - конечно "все фломастеры разные".

четверг, 4 сентября 2014 г.

Коротко. И ещё о фабриках

По мотивам:

Коротко. Ещё о фабриках

Я там писал - "фабрики, скажем так - это "полиморфизм в квадрате"".

Так вот.

Я сегодня сделал "полиморфизм в кубе".

Как?

Сделал "фабрику фабрик".

Примерно так:

type
 TSomeDataToDecide = record
  ...
 end;//TSomeDataToDecide

 TUsedBaseClass = class abstract (TObject)
  public
   constructor Create(const aSomeDataToDecide: TSomeDataToDecide);
 end;//TUsedBaseClass

 RUsedBaseClass = class of TUsedBaseClass;

 TUsedImpl1 = class(TBaseClass)
 end;//TUsedImpl1

 TUsedImpl2 = class(TBaseClass)
 end;//TUsedImpl2

 TBaseClass = class abstract (TObject)
  private
   f_Used : TUsedBaseClass;
  public
   constructor Create(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide);
 end;//TBaseClass

 TImpl1 = class(TBaseClass)
 end;//TImpl1

 TImpl2 = class(TBaseClass)
 end;//TImpl2

 TBaseClassFactory = class abstract (TObject)
  public
   class function Make(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide): TBaseClass; virtual; abstract;
 end;//TBaseClassFactory

 RBaseClassFactory = class of TBaseClassFactory;

 TFactory1 = class(TBaseClassFactory)
  public
   class function Make(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide): TBaseClass; override;
 end;//TFactory1

 TFactory2 = class(TBaseClassFactory)
  public
   class function Make(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide): TBaseClass; override;
 end;//TFactory2

 TBaseClassFactoryFactory = class abstract (TObject)
  public
   class function Make(const aSomeDataToDecide: TSomeDataToDecide): RBaseClassFactory; virtual; abstract;
   class function Used(const aSomeDataToDecide: TSomeDataToDecide): RUsedBaseClass; virtual; abstract;
 end;//TBaseClassFactoryFactory

 RBaseClassFactoryFactory = class of TBaseClassFactoryFactory;

 TFactoryFactory1 = class (TObject)
  public
   class function Make(const aSomeDataToDecide: TSomeDataToDecide): RBaseClassFactory; override;
 end;//T1FactoryFactory1

 TFactoryFactory2 = class (TObject)
  public
   class function Make(const aSomeDataToDecide: TSomeDataToDecide): RBaseClassFactory; override;
 end;//T1FactoryFactory2

...

constructor TBaseClass.Create(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide);
begin
 f_Used := aUsed.Create(aSomeDataToDecide);
 ...
end;

...
 
class function TFactory1.Make(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide): TBaseClass;
begin
 if SomeCondition(aSomeDataToDecide) then
  Result := TImpl1.Create(aUsed, aSomeDataToDecide)
 else
 if SomeOtherCondition(aSomeDataToDecide) then
  Result := TImpl2.Create(aUsed, aSomeDataToDecide)
 else
 begin
  Assert(false);
  Result := nil;
 end;
end;

class function TFactory2.Make(aUsed: RUsedBaseClass; const aSomeDataToDecide: TSomeDataToDecide): TBaseClass;
begin
 if SomeCondition(aSomeDataToDecide) then
  Result := TImpl2.Create(aUsed, aSomeDataToDecide)
 else
 if SomeOtherCondition(aSomeDataToDecide) then
  Result := TImpl1.Create(aUsed, aSomeDataToDecide)
 else
 begin
  Assert(false);
  Result := nil;
 end;
end;

...

class function TFactoryFactory1.Make(const aSomeDataToDecide: TSomeDataToDecide): RBaseClassFactory;
begin
 if SomeCondition(aSomeDataToDecide) then
  Result := TFactory1
 else
 if SomeOtherCondition(aSomeDataToDecide) then
  Result := TFactory2
 else
 begin
  Assert(false);
  Result := nil;
 end;
end;

...

class function TFactoryFactory2.Make(const aSomeDataToDecide: TSomeDataToDecide): RBaseClassFactory;
begin
 if SomeCondition(aSomeDataToDecide) then
  Result := TFactory2
 else
 if SomeOtherCondition(aSomeDataToDecide) then
  Result := TFactory1
 else
 begin
  Assert(false);
  Result := nil;
 end;
end;

...
var
 l_FF : RBaseClassFactoryFactory;
 l_BC : TBaseClass;
 l_SD : TSomeDataToDecide;
begin
 ...
 l_SD := TSomeDataToDecide.Create(SomeParams);
 ...
 if SomeCondition(l_SD) then
  l_FF := TFactoryFactory1
 else
 if SomeOtherCondition(l_SD) then
  l_FF := TFactoryFactory2
 else
 begin
  Assert(false);
  l_FF := nil;
 end;
 l_BC := l_FF.Make(l_SD).Make(l_FF.Used(l_SD), l_SD);
 try
  ...
 finally
  FreeAndNil(l_BC):
 end;//try..finally
end;

Зачем всё это? Чтобы охватить "Це из Эн по Ка".

Для меня лично - всё вокруг реализации IStorage вертится.

Там есть блоки "такой системы" и блоки "другой системы". А ещё есть "блоки в заголовком" и "блоки без заголовка". А ещё есть блоки "такого размера" и есть "блоки другого размера".

Вот там и возникает "Це из Эн по Ка". Которая решается "фабриками фабрик".

Может быть кому-нибудь мысль понравится.

P.S. Понятно, что тут всё "не так линейно". Конечно в реале появляются "циклы" и "списки фабрик". Особенно там где написано SomeConditionXXX.

Могу и про это детально написать. Если вдруг интересно.

Реальный пример (а не "коня в вакууме") - я тоже постараюсь описать.

Но пока может быть кому-то и "конь в вакууме" понравится.

Offtopic. Про нас про всех...

Бывал я в 97-м, 98-м, 99-м годах в киргизско-узбекском городе Ош... И там мне ВСЕ, что "киргизы", что "узбеки" рассказывали про "резню конца восьмидесятых"... И ВСЕ с ПЕНОЙ у рта говорили, что "резня больше не повторится"... Потому что мол "нахлебались"... ВСЕ... И хотелось верить в это.. Что БОЛЬШЕ НЕ ПОВТОРИТСЯ...

Но! Прошло без малого 10-ть лет... И? Резня в Оше повторилась... С НЕМЕНЬШЕЙ жестокостью..

Я звонил тогда знакомым в Ош.. Спрашивал - "как вы там".. Мне отвечали - "беда.. катастрофа.. как мы их не удержали"...

Всего 10-ть лет прошло...

И резали другие люди...

Которые "молодые"...

Другое поколение...

Что хочу сказать?

"История учит тому, что ничему не учит"..

Вот как-то так...

Выросло "поколение" и резня началась по-новой...

Что хочется сказать?

"Люди, будьте бдительны"

http://ru.wikipedia.org/wiki/%D0%A4%D1%83%D1%87%D0%B8%D0%BA,_%D0%AE%D0%BB%D0%B8%D1%83%D1%81

"Люди, будьте доверчивы"?

http://www.bards.ru/archives/part.php?id=4082

https://ru.wikipedia.org/wiki/%D0%9E%D1%88%D1%81%D0%BA%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%8B%D1%82%D0%B8%D1%8F_1990_%D0%B3%D0%BE%D0%B4%D0%B0

https://ru.wikipedia.org/wiki/%D0%91%D0%B5%D1%81%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B8_%D0%BD%D0%B0_%D1%8E%D0%B3%D0%B5_%D0%9A%D0%B8%D1%80%D0%B3%D0%B8%D0%B7%D0%B8%D0%B8_(2010)

Коротко. Delphi XE7

Прислали ключ. Буду пробовать.

Update.

При запуске инсталлятора "зависание" это НОРМАЛЬНО. Попереключайте Alt-Tab И найдите окно выбора языка.

среда, 3 сентября 2014 г.

Коротко. Ещё о фабриках

В некотором смысле по мотивам:

Коротко. О фабриках
Маниловщина. Пишем реализацию IStorage применяя TDD

О чём хочу повести речь?

У нас своя есть реализация IStorage (ну и IStream соответственно).

Она - хороша. В некотором смысле. Хотя бы потому, что достаточно стабильно работает уже лет 15-ть.

Но там есть "некоторые проблемы".

И я с этими проблемами сейчас скурпулёзно разбираюсь.

Ну там "проблемы" сложного порядка в гетерогенной сетевой среде.

Я не о них.

Но есть и "локальные проблемы".

Например то, что там всё построено на "бинарной сериализации".

Т.е. сделано примерно так:

type
 TStoreHeader = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeader

...

procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Read(l_H, SizeOf(l_H);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Write(l_H, SizeOf(l_H);
 ...
end;

"Реальный код" можно посмотреть тут.

В чём проблема? А в том, что "формат" TStoreHeader "просто так" не изменишь. Потому, что "всё поедет".

Что делать?

Для начала вот примерно так:

type
 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream);
   procedure Save(aStream: TStream);
 end;//TStoreHeader

...

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Save(Stream);
 ...
end;

Какой следующий шаг?

Вот как-то так:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract

...

 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader

 TStoreHeaderFactory = class
  public
   class function Make: TStoreHeaderAbstract;
 end;//TStoreHeaderFactory

...

class function TStoreHeaderFactory.Make: TStoreHeaderAbstract;
begin
 Result := TStoreHeader.Create;
end;

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Save(Stream);
 ...
end;

Что мы тут сделали?

Мы ввели базовый абстрактный класс - TStoreHeaderAbstract и фабрику -TStoreHeaderFactory.

Теперь как нам поменять формат данных?

А вот примерно так:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract

...

 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec

 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader

 TStoreHeaderRecNew = record
  rNextPosition : Int64;
  rRealSize : Int64;
  rSomeOtherData : SomeOtherType;
  ...
 end;//TStoreHeaderRecNew

 TStoreHeaderNew = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRecNew;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeaderNew

 TStoreHeaderFactory = class
  public
   class function Make(aVersion : TGUID): TStoreHeaderAbstract;
 end;//TStoreHeaderFactory

...

class function TStoreHeaderFactory.Make(aVersion : TGUID): TStoreHeaderAbstract;
begin
 if EqualGUID(aVersion, OldFormatGUID) then
  Result := TStoreHeader.Create
 else
 if EqualGUID(aVersion, NewFormatGUID) then
  Result := TStoreHeaderNew.Create
 else
  Assert(false, 'Неверный заголовок');
end;

procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;

...

procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Load(Stream);
 ...
end;

...

procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Save(Stream);
 ...
end;

Более того можно тогда написать и так:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.ReadBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.WriteBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;

Что мы тут сделали?

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

А во-вторых - мы поменяли часть полей местами, чтобы продемонстрировать "суть подхода".

За рамками повествования конечно осталось много вопросов.

Например - "откуда берётся GetVersionGUID"?

Или - "что делать, если запись версии изначально не предусмотрены"?

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

Но пока - оставлю их "за рамками повествования".

Что в итоге?

В  итоге - по-моему - было показано как фабрики являются весомым дополнением к инкапсуляции и полиморфизму.

Мы сначала воспользовались полиморфизмом - введя тип TStoreHeaderAbstract.
А потом воспользовались инкапсуляцией - разделив TStoreHeader.Data и TStoreHeaderNew.Data.

Ну и за счёт полиморфиза и инкапсуляции мы в некотором роде ушли от "бинарной сериализации".

Почему?

Потому, что следующий шаг может быть таким:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Load(aStream);
end;

procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Save(aStream);
end;

- т.е. тут уже пишем/читаем не "бинарно", а так как написано в SomeOtherDataType.Load/SomeOtherDataType.Save.

Итак.

Что я хотел показать?

Я повторю.

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

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

Как уж это у меня получилось - судить вам.

Думаю - "Америку не открыл", но надеюсь, что написал что-то полезное.