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

Offtopic. О "текущем международном положении"

О Ливии.. Ираке... Египте... Сирии... И много о чём....


Сатановский предрекает "развал ИГИЛ" мотивируя тем что "они между собой не договорятся".. И предрекает "тяжёлую гражданскую войну"... И мне кажется, что СТОИТ с ним согласиться... Почему Приведу простой пример - "попробуйте поставить домофон в подъезде и собрать деньги СО ВСЕХ"... Просто попробуйте..

Так что - "держитесь люди"... :-(

Хафтар, Халифа

Offtopic.О репутации

Я сегодня покупал машину.. Через мастера (точнее хозяина автосервиса).. Я у него лет 5-ть уже чинюсь.. Я ему честно сказал - "Юра, подкупает покупать машину через вас... потому что я всё равно приеду к вам чиниться... Посему - хотя никому нельзя верить, но я подставы от вас не жду"... И потом добавил - "хотя вряд ли я вас чем-то прижму".. Знаете что он мне сказал? "А моя репутация?"...
Ещё раз... "А моя репутация?"
Знаете - ОТРАДНО было это слышать.. есть люди, которые думают о РЕПУТАЦИИ...
О репутации...
Как будет дальше - "история покажет".. Я напишу - "если вдруг разочаруюсь"... Но пока - я удивлён...

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

Ссылка. Опять Тепляков. Open/Closed Principle. ФП vs. ООП

http://sergeyteplyakov.blogspot.ru/2014/09/openclosed-principle-fp-vs-oop.html

Если я ПРАВИЛЬНО понял, то там речь и о "анемичной модели" - ТОЖЕ.

Ссылка. RAD Studio XE7, Delphi XE7 и C++Builder XE7

Ссылка. Пара слов о Delphi XE7.

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

Коротко. О рефакторинге. "С колёс"

Совсем не стремлюсь открыть Америку и тем более чему-то "научить".

Скажем так - "меня попросили сделать review кода".

Поэтому просто дам "пример переписывания кода".

Был код:

procedure TraverseTreeItems(const aTree : TTreeViewItem; var ResultList : TList<TTreeViewItem>);
var
 i : Integer;
begin
 for i := 0 to Pred(aTree.Count)  do
 begin
  if aTree.Items[i].IsChecked
   then ResultList.Add(aTree.Items[i]);

  TraverseTreeItems(aTree.Items[i], ResultList);
 end;
end;

procedure TraverseTree(const aTree: TTreeView; var ResultList : TList<TTreeViewItem>);
var
 i : integer;
begin
 for i := 0 to Pred(aTree.Count) do
 begin
  if aTree.Items[i].IsChecked then
   ResultList.Add(aTree.Items[i]);

  TraverseTreeItems(aTree.Items[i], ResultList);
 end;
end;

Вообще говоря, можно написать так:

type
 TTreeViewItemList = TList<TTtreeViewItem>;

...

procedure TraverseTreeItem(anItem : TTreeViewItem; ResultList : TTreeViewItemList);
var
 i : Integer;
begin
 if anItem.IsChecked then
  ResultList.Add(anItem);
 
 for i := 0 to Pred(anItem.Count)  do
  TraverseTreeItems(anItem.Items[i], ResultList);
end;

procedure TraverseTree(aTree: TTreeView; ResultList : TTreeViewItemList);
var
 i : integer;
begin
 for i := 0 to Pred(aTree.Count) do
  TraverseTreeItem(aTree.Items[i], ResultList);
end;

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

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

Мы перенесли "ответственность" по проверке aTree.Items[i].IsChecked в anItem.IsChecked (из одного метода в другой).

Т.е. спустили часть одного метода в другой.

Ну и ввели "алиас" - TTreeViewItemList.

Ну и убрали лишние const и var. (Кстати в Delphi не хватает всё же спецификаторов параметров типа constref и varref или constobject и varobject и спецификатора const на метод, особенно getter, ну по аналогии с C++, но правда тогда и mutable может понадобиться)

Вот собственно и всё. Ничего "космического".

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

P.S. Можно ещё generic'и применить. Но надо ли? Для одной строчки.

Update.

P.P.S. кстати к TTreeView и TTreeViewItem можно enumerator'ы привесить через helper'ы
попробуете?

http://programmingmindstream.blogspot.ru/2014/08/for-in.html

Или:

for anItem in TTreeViewEnumerator.Get(aTree) do ...
for anItem in TTreeViewEnumerator.Get(aTree) do ...

Где - TTreeViewEnumerator.Get это class function: TEnumerator с overload

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

Тогда можно будет написать так:

type
 TTreeViewItemList = TList<TTtreeViewItem>;

...

procedure TraverseTreeItem(anItem : TTreeViewItem; ResultList : TTreeViewItemList);
var
 l_Item : TTreeViewItem;
begin
 if anItem.IsChecked then
  ResultList.Add(anItem);
 
 for l_Item in TTreeViewEnumerator.Get(anItem)  do
  TraverseTreeItems(anItem.Items[i], ResultList);
end;

procedure TraverseTree(aTree: TTreeView; ResultList : TTreeViewItemList);
var
 l_Item : TTreeViewItem;
begin
 for l_Item in TTreeViewEnumerator.Get(aTree) do
  TraverseTreeItem(l_Item, ResultList);
end;

- мысль понятна?

пятница, 29 августа 2014 г.

Ссылка. Получение ресурса есть инициализация (RAII). И "немного от себя"

https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D1%81%D1%83%D1%80%D1%81%D0%B0_%D0%B5%D1%81%D1%82%D1%8C_%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F

Хочется написать "что-то умное", поэтому попробую добавить "немного от себя".

Приведу "надуманный пример". Но надеюсь, что он будет понят.

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

Итак.

Пусть есть такой код:

type
 TA = class
  private
   fCS : TCriticalSection;
   procedure SomeInitCode;
   procedure SomeDoneCode;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TA
...
constructor TA.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := aCS;
 SomeInitCode;
 fCS.Enter;
end;

destructor TA.Destroy;
begin
 fCS.Leave;
 fCS := nil;
 SomeDoneCode;
 inherited;
end;

procedure TA.SomeInitCode;
begin
 raise Exception.Create('Some fake error');
end;

procedure TA.SomeDoneCode;
begin
 // - do nothing
end;
...
var
 A : TA;
begin
 A := TA.Create(SomeCriticalSection);
 ...
end;

Приведу ещё ссылки:

- http://www.delphimaster.net/view/14-1089121849/all
- http://www.rsdn.ru/forum/delphi/411835.flat
- http://objectmix.com/delphi/402814-exception-constructor.html

Из последней ссылки процитирую:

"No, the destructor is automatically called when an exception is raised
in the constructor."

Это написано и в документации по Delphi, но к сожалению ссылку в интернете - я не нашёл.

Поверьте мне "на слово".

Процитирую лишь место из help к Delphi XE6:

"When an exception is raised during the creation of an object, Destroy is automatically called to dispose of the unfinished object. This means that Destroy must be prepared to dispose of partially constructed objects. Because a constructor sets the fields of a new object to zero or empty values before performing other actions, class-type and pointer-type fields in a partially constructed object are always nil. A destructor should therefore check for nil values before operating on class-type or pointer-type fields. Calling the Free method (defined in TObject) rather than Destroy offers a convenient way to check for nil values before destroying an object."

- что мы тут получим?

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

И это вообще говоря - проблема. Подробности зависят от версии Windows.

Что можно сделать?

Можно например написать так:

type
 TA = class
  private
   fCS : TCriticalSection;
   procedure SomeInitCode;
   procedure SomeDoneCode;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TA
...
constructor TA.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := aCS;
 fCS.Enter;
 SomeInitCode;
end;

destructor TA.Destroy;
begin
 SomeDoneCode;
 fCS.Leave;
 fCS := nil;
 inherited;
end;

procedure TA.SomeInitCode;
begin
 raise Exception.Create('Some fake error');
end;

procedure TA.SomeDoneCode;
begin
 // - do nothing
end;
...
var
 A : TA;
begin
 A := TA.Create(SomeCriticalSection);
 ...
end;

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

Мы поменяли местами строчки:
SomeInitCode;
fCS.Enter;

- тут "всё срастётся".

Но повторю - "пример надуманный".

В реальном коде - "может и не срастись". Да и не факт, что "ресурс не захвачен" и SomeInitCode как раз и приведёт к его освобождению.

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

Тут тоже можно сказать - "присваивайте fCS непосредственно перед fCS.Enter".

Можно!

Но повторю - "пример надуманный".

Как "я считаю" было бы правильно?

А вот примерно так:
(Об "оверхеде" на время - забудем)

type
 TLocker = class
  private
   fCS : TCriticalSection;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TLocker

 TA = class
  private
   fCS : TLocker;
   procedure SomeInitCode;
   procedure SomeDoneCode;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TA
...
constructor TLocker.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := aCS;
 fCS.Enter;
end;

destructor TLocker.Destroy;
begin
 fCS.Leave;
 fCS := nil;
 inherited;
end;

constructor TA.Create(aCS: TCriticalSection);
begin
 inherited Create;
 SomeInitCode;
 fCS := TLocker.Create(aCS);
end;

destructor TA.Destroy;
begin
 FreeAndNil(fCS);
 SomeDoneCode;
 inherited;
end;

procedure TA.SomeInitCode;
begin
 raise Exception.Create('Some fake error');
end;

procedure TA.SomeDoneCode;
begin
 // - do nothing
end;
...
var
 A : TA;
begin
 A := TA.Create(SomeCriticalSection);
 ...
end;

- тут "всё срастается".

Всё?

Да нет - не всё.

Напишем так:

type
 TLocker = class
  private
   fCS : TCriticalSection;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TLocker

 TA = class
  private
   fCS : TLocker;
   procedure SomeInitCode;
   procedure SomeDoneCode;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TA
...
constructor TLocker.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := aCS;
 fCS.Enter;
end;

destructor TLocker.Destroy;
begin
 fCS.Leave;
 fCS := nil;
 inherited;
end;

constructor TA.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := TLocker.Create(aCS);
 SomeInitCode;
end;

destructor TA.Destroy;
begin
 SomeDoneCode;
 FreeAndNil(fCS);
 inherited;
end;

procedure TA.SomeInitCode;
begin
 // - do nothing
end;

procedure TA.SomeDoneCode;
begin
 raise Exception.Create('Some fake error');
end;
...
var
 A : TA;
begin
 A := TA.Create(SomeCriticalSection);
 ...
end;

- что мы сделали?

Мы перенесли исключение из SomeInitCode в SomeDoneCode.

И опять переставили местами: SomeInitCode; fCS := TLocker.Create(aCS);

Как было рассказано "о решении проблемы" ранее.

- в чём проблема?

А в том, что до строчки:
FreeAndNil(fCS); - мы не дойдём.

И критическую секцию - мы не освободим.

Это понятно? Или я что-то напутал?

Как можно "поправить ошибку"?

Можно написать так:

destructor TA.Destroy;
begin
 try
  SomeDoneCode;
 finally
  try
   FreeAndNil(fCS);
  finally
   inherited;
  end;
 end;
end;

- так опять - "все срастётся".

Всё? Вроде - да.

Но! "Эта ужасная лестница из try".

Что можно сделать?

Однозначного рецепта у меня - нет.

Но!

Могу предложить - "лишь беглый взгляд". Он - далеко не совершенный.

Но это путь - "куда думать" и отчасти ответ на вопрос - "почему Embarcadero так настойчиво продвигает ARC". Хотя я сам с этим и не согласен (Про ARC).

Но! Один из вариантов:

type
 ILocker = interface(IUnknown)
 end;//ILocker

 TLocker = class(TInterfacedObject, ILocker)
  private
   fCS : TCriticalSection;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TLocker

 TA = class
  private
   fCS : ILocker;
   procedure SomeInitCode;
   procedure SomeDoneCode;
  public
   constructor Create(aCS: TCriticalSection);
   destructor Destroy; overide;
 end;//TA
...
constructor TLocker.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := aCS;
 fCS.Enter;
end;

destructor TLocker.Destroy;
begin
 fCS.Leave;
 fCS := nil;
 inherited;
end;

constructor TA.Create(aCS: TCriticalSection);
begin
 inherited Create;
 fCS := TLocker.Create(aCS);
 SomeInitCode;
end;

destructor TA.Destroy;
begin
 SomeDoneCode;
 fCS := nil;
 inherited;
end;

procedure TA.SomeInitCode;
begin
 // - do nothing
end;

procedure TA.SomeDoneCode;
begin
 raise Exception.Create('Some fake error');
end;
...
var
 A : TA;
begin
 A := TA.Create(SomeCriticalSection);
 ...
end;

Что мы получили?

Мы получили тот факт, что fCS - будет однозначно освобождён.
И как следствие - мы попадём в fCS.Leave;

Зачем написана вот эта строчка:
fCS := nil;
?

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

Криво? Да - "местами криво".

Но - "лучше, чем ничего".

Какое решение является серебряной пулей?

Я пока - не знаю.

Есть один вариант.

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

На что стоит обратить внимание?

А вот на это:

constructor Tm3CustomHeaderStream.Create(const AStream: IStream;
                                         const AAccess: LongInt);
begin
 inherited;
 m2InitOperation(_Status,InitProc00000001($00000001));
 m2InitOperation(_Status,InitProc00000002($00000002));
 m2InitOperation(_Status,InitProc00000004($00000004));
 m2InitOperation(_Status,InitProc00000008($00000008));
 m2InitOperation(_Status,InitProc00000010($00000010));
end;
 
procedure Tm3CustomHeaderStream.Cleanup;
begin
 m2DoneOperation(_Status,$00000010,DoneProc00000010);
 m2DoneOperation(_Status,$00000008,DoneProc00000008);
 m2DoneOperation(_Status,$00000004,DoneProc00000004);
 m2DoneOperation(_Status,$00000002,DoneProc00000002);
 m2DoneOperation(_Status,$00000001,DoneProc00000001);
 inherited;
end;
...
procedure m2InitOperation(var   AStatus: LongWord;
                          const ABitMask: LongWord);
begin
 Assert((AStatus and ABitMask) = 0);
 AStatus:=AStatus or ABitMask;
end;

procedure m2DoneOperation(var   AStatus: LongWord;
                          const ABitMask: LongWord;
                          const AClassDoneProc: Tm2ClassDoneProc);
begin
 if ((AStatus and ABitMask) <> 0) then
 begin
  try
   AClassDoneProc();
  except
   m2ExcErrHandler();
  end;
  AStatus:=AStatus and not(ABitMask);
 end;
end;

Что тут написано?

А вот что:

В конструкторе вызываются процедуры вида InitProcXXX и они взводят в "маске состояния" биты означающие, что "этот метод был вызван".
А в деструкторе вызываются процедуры вида DoneProcXXX. И сбрасывают биты в "маске состояний".

Причём про DoneProcXXX есть два момента:

1. Они вызываются только если взведён соответствующий бит.
2. Они обрамлены блоком try..except. Т.е. они позволяют пройти следующим процедурам DoneProcXXX даже если в предыдущих произошло исключение.

Что сказать?

Этот вариант - железобетонный. И в нём - проблем нет.

И - не я его придумал. А другие умные люди.

Он реально - железобетонный.

Но!

Чем он мне не нравится?

А тем, что он не читабельный. Вообще.

Не знаю кому как, но от него мне лично - крышу рвёт. Но он - работает.

Когда подобный код "генерируется из UML" (Зачем UML) то это "куда ни шло.

А если это "руками писать" и потом читать - это - беда. Но зато - работает.

В общем - подведу итоги. Проблему я обозначил - "возбуждение исключений в конструкторах и деструкторах" и как следствие - неполная инициализация и деинициализация объектов. И как следствие - негарантированное получение/освобождение ресурсов.

Свои пути решения - я также перечислил. Они - не идеальны. Но! "Лучше чем ничего".

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

Спасибо за внимание. Надеюсь, что "что-то умное" написать таки - удалось.

P.S. Кстати есть "ещё два слова" - AfterConstruction и BeforeDestruction.

Процитирую документацию к Delphi:

"Responds after the last constructor has executed.
AfterConstruction is called automatically after the object's last constructor has executed. Do not call it explicitly in your applications.
The AfterConstruction method implemented in TObject does nothing. Override this method when creating a class that performs an action after the object is created. For example, TCustomForm overrides AfterConstruction to generate an OnCreate event."

"Responds before the first destructor executes.
BeforeDestruction is called automatically before the object's first destructor executes. Do not call it explicitly in your applications.
The BeforeDestruction method implemented in TObject does nothing. Override this method when creating a class that performs an action before the object is destroyed. For example, TCustomForm overrides BeforeDestruction to generate an OnDestroy event.
Note: BeforeDestruction is not called when the object is destroyed before it is fully constructed. That is, if the object's constructor raises an exception, the destructor is called to dispose of the object, but BeforeDestruction is not called. "
Внимание стоит обратить вот на что:

"Note: BeforeDestruction is not called when the object is destroyed before it is fully constructed. That is, if the object's constructor raises an exception, the destructor is called to dispose of the object, but BeforeDestruction is not called."

sic!

P.P.S. И вот ещё связанная вещь - BeforeRelease.

Ну и вот ещё ссылки:

http://18delphi.blogspot.ru/2013/04/3.html
http://18delphi.blogspot.ru/2013/04/iunknown.html

P.P.P.S. Есть ещё одна вещь - AutoPtr - они "сходны подсчёту ссылок", но имеют "особенное применение". Свои мысли об "умных указателях" для Delphi я попробую как-нибудь потом рассказать.