среда, 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-ти штук.

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

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

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

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

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

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