четверг, 31 октября 2013 г.

Немного о FM и потребляемой памяти

Значит так.

FM - мне ДИКО нравится. Ещё раз повторю - FM - мне ДИКО нравится.

За СТИЛИЗАЦИЮ и за "слоёный пирог из атомарных контролов".

Но! "Раздаются голоса", что там "не всё оптимально".

Внесу "свои 5 копеек".

Посмотрим на:
  TPosition = class(TPersistent)
  private
    FOnChange: TNotifyEvent;
    FY: Single;
    FX: Single;
    FDefaultValue: TPointF;

и:
  TBounds = class(TPersistent)
  private
    FRight: Single;
    FBottom: Single;
    FTop: Single;
    FLeft: Single;
    FOnChange: TNotifyEvent;
    FDefaultValue: TRectF;

-- вообще говоря - FDefaultValue - ЯВЛЯЮТСЯ "лишними".

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

Я у "себя" когда "дотачивал" VGScene - просто закомментировал FDefaultValue и места их использования.

И НИЧЕГО не сломалось.

О чём я? Опять же о HighLoad и "Экономии на спичках".

Но в ДАННОМ случае - это как раз пример "вырастания спичек в брёвна".

За счёт "слоёного пирога".

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

И тут "экономия на спичках" - начинает играть свою роль.

Ни в КОЕМ СЛУЧАЕ - не хочу "укусить Embarcadero".

Это просто - совет.

Я бы - пересмотрел бы подобные "шероховатости".

Если уж "хочется DefaultValue" - это "легко" решается полиморфизмом.

То же касается и использования интерфейса IControl. По моему скромному мнению - "излишний он". А затраты на получение его - "тоже спички", но тоже "вырастают в брёвна".

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

Ну и "возвращаясь к HighLoad".

Я когда "общался с VGScene" - Она "слегка подтормаживала". Я сделал все её объекты "кешируемыми" (рассказать как?). Говоря языком HighLoad - "пулил всё и вся". И "торможения" - прекратились.

Ну и уж "совсем спички" - использовать nil вместо IdentityMatrix - не эффективнее?

Это моё частное мнение. Вероятно - однобокое.И вероятно - "для частных случаев". Но лишь "частными случаями" - я и занимаюсь.

Никого не хотел обидеть.

Да и ещё. "дочерние атомарные контролы" из которых состоит "слоёный пирог" - ведь используют ТУ ЖЕ САМУЮ матрицу преобразования, что и родительский контрол. ВСЕГДА.

Или я ошибаюсь?

А если не ошибаюсь - тут тоже есть "поле для оптимизации".

А уж "пулить" (выражаясь словами HighLoader'ов) матрицы вот тут:
    FLocalMatrix: TMatrix;
    FAbsoluteMatrix: TMatrix;
    FInvAbsoluteMatrix: TMatrix;
...
    FInPaintToAbsMatrix, FInPaintToInvMatrix: TMatrix;

...
ну и там много мест ещё...

-- по-моему - сам бог велел. Хранить, не значения, а указатели. На конкретные экземпляры матриц. Из пула. Их вообще говоря в "обычных" приложениях - немного. Обычно - только IdentityMatrix и есть.

Или я опять ошибаюсь?

Да. И ещё немного расстраивает, что нет возможности "отложить вызов FOnChange" или я опять ошибаюсь?

Делаем черновик. Делаем тесты с замером времени. Делаем другой черновик. Делаем другие тесты

"Делаем черновик. Делаем тесты с замером времени. Делаем другой черновик. Делаем другие тесты"

- опять же - "рекомендует HighLoad".

ЛУЧШИЕ результаты - ОСТАВЛЯЕМ. Всё остальное - ВЫКИДЫВАЕМ.

ВСЁ ПРАВИЛЬНО.

Только МНЕ лично обычно говорят - "ну что ты блин, на тесты нет времени" :-)

(Не внутри того коллектива где я работаю).

Ещё одна рекомендация от HighLoad

"Развернуть case в цепочку if. Ну или - наоборот - свернуть". В зависимости от результатов профилирования :-)

Тоже - "баловался этим" :-)

Хорошая рекомендация. Но опять же - "экономия на спичках". Иногда эти спички "перерастают в брёвна".

"Пулить всякое"...

Наткнулся в материалах конференции HighLoad на рекомендацию "пулить всякое".

Долго думал - что же это может означать.

Раза с двадцатого - вроде понял.

"Помещать данные в пул повторного использования".

Открыли блин Америку....

Я сам лет 15-ть это делаю.

Почему надо говорить об этом "на нечеловеческом языке" - НЕ ПОНИМАЮ.

Наверное, чтобы "создать ореол таинственности"....

О да! Про supports.

Множество задач использования Supports (QueryInterface) сводятся примерно к следующему:
IMemoryPool = interface
end;//IMemoryPool

IString = interface(SomeAncestor1)
end;//IString

IMemoryStream = interface(SomeAncestor2)
end;//IMemoryStream

DoString(aString : IString);
begin
 if Supports(aString, IMemoryPool, l_Pool) then
 ... do something with l_Pool
end;

DoStream(aStream : IMemoryStream );
begin
 if Supports(aString, IMemoryPool, l_Pool) then
 ... do something with l_Pool
end;

хотя можно написать так:

IMemoryPool = interface
end;//IMemoryPool

IString = interface(SomeAncestor1{, implements IMemoryPool })
 function AsPool : IMemoryPool;
end;//IString

IMemoryStream = interface(SomeAncestor2{, implements IMemoryPool })
 function AsPool : IMemoryPool;
end;//IMemoryStream

DoString(aString : IString);
begin
 aString.AsPool
 ... do something with AsPool
end;

DoStream(aStream : IMemoryStream );
begin
 aStream.AsPool
 ... do something with AsPool
end;

-- второе - понятное дело - эффективнее.

Offtopic. Вышел Windows 8.1

Вышел Windows 8.1
Вернули кнопку "Пуск"
Это правда, что остались ещё люди, которые не знают про Ctrl-Esc?

Ещё HighLoader'ы ОЧЕНЬ любят функциональные языки, и их "плюшки" типа распараллеливания и кешируемости

Ещё HighLoader'ы ОЧЕНЬ любят функциональные языки, и их "плюшки" типа распараллеливания и кешируемости.

Но лишь ЕДИНИЦЫ могут рассказать - "как это устроено в байтах"....

Они "просто свято верят фреймворку"...

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

Про "статические примеси" с "call-back" к "реализатору" примеси

Есть примесь:

MyMixIn = class
 procedure MyProcedure;
 var
  someData : Boolean;
 begin
  someData := _Instance_R_(Self).getData;
 end;
end;

И есть ИНСТАНЦИРОВАНИЕ:

MyClass = class(MyMixIn)
 function getData : Boolean;
end;

_Instance_R_ = MyClass;

"Виртуальность" БЕЗ виртуальности. Идея понятна?

среда, 30 октября 2013 г.

Ссылка. "Объектный пул"

http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D1%8B%D0%B9_%D0%BF%D1%83%D0%BB

HighLoader'ы его "очень любят"...

Чтобы "давить аллокации".

Я САМ - его придумал... Когда-то... лет 15-ть назад...

"Объектный пул (англ. object pool) — порождающий шаблон проектирования, набор инициализированных и готовых к использованию объектов. Когда системе требуется объект, он не создаётся, а берётся из пула. Когда объект больше не нужен, он не уничтожается, а возвращается в пул."

"Протоколы" на "коленке"

Предыдущая "скажем серия" тут - http://18delphi.blogspot.ru/2013/10/blog-post_8453.html

Теперь как организовать "протоколы" без изменения компилятора.

Пусть есть "мета-код":
PmyProtocol = protocol
 procedure Method1;
 function Method2: Boolean;
end;//PmyProtocol

TmyObject1 = class(TSomeParent1, implements PmyProtocol)
 procedure Method1;
 function Method2: Boolean;
end;//TmyObject1 

TmyObject2 = class(TSomeParent2, implements PmyProtocol)
 procedure Method1;
 function Method2: Boolean;
end;//TmyObject2 

Теперь что "на реальном языке":
type
 TMethod1Prototype = procedure of object;
 TMethod2Prototype = function : Boolean of object;

PmyProtocol = object
 method1 : TMethod1Prototype;
 method2 : TMethod2Prototype;
 constructor Create(aMethod1 : TMethod1, aMethod2 : TMethod2);
end;//PmyProtocol

PmyProtocolFactory.Register(TmyObject1, PmyProtocol.Create(TmyObject1(nil).MyMethod1, TmyObject1(nil).MyMethod2));
PmyProtocolFactory.Register(TmyObject2, PmyProtocol.Create(TmyObject2(nil).MyMethod1, TmyObject2(nil).MyMethod2));

var
 someObject1 : TMyObject1;
PmyProtocolFactory.Call(someObject1).method1;
someBoolean := PmyProtocolFactory.Call(someObject1).method2;

var
 someObject2 : TMyObject2;
PmyProtocolFactory.Call(someObject2).method1;
someBoolean := PmyProtocolFactory.Call(someObject2).method2;

fuction PmyProtocolFactory.Call(anImplementor : TObject): PmyProtocol;
begin
 Result := Self.FindPrototypeByClass(anImplementor);
 TMethod(Result.method1).Data := anImplementor;
 TMethod(Result.method2).Data := anImplementor;
end;
Идея понятна? Понятное дело, что "руками" такое написать - задача "нудная", хотя и решаемая. А вот "с модели" - запросто.

Это если "на коленке". А вот компилятор мог бы это сделать и "сильно оптимальнее".

Про "экономию на спичках"

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

TmyObject = class(TInterfacedObject, ImyInterface)

а можно так:

TmyObject = class(Tl3DataContainerWithoutIUnknown, ImyInterface)

И съэкономить 4-ре байта на КАЖДЫЙ экземпляр TmyObject.

Рассказать как Tl3DataContainerWithoutIUnknown устроен? Или "неинтересно"?

В общем вот так:


interface

_UnknownImpl_Parent_ = TObject;

{$Include UnknownImpl.imp.pas}

Tl3DataContainerWithoutIUnknown = class(_UnknownImpl_) // - тут ОСОЗНАННО не пишем IUnknown
end;//Tl3DataContainerWithoutIUnknown
 
implementation

{$Include UnknownImpl.imp.pas}

end.

"Голый код" про Immutable-строки и фабрику строк

unit kwStringFactory;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "ScriptEngine"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Scripting::ScriptEngine::PrimitiveWords::TkwStringFactory
//
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

{$Include ..\ScriptEngine\seDefine.inc}

interface

{$If not defined(NoScripts)}
uses
  l3Interfaces,
  kwString,
  kwStringList
  ;
{$IfEnd} //not NoScripts

{$If not defined(NoScripts)}
type
 TkwStringFactory = class(TkwStringList)
 protected
 // overridden protected methods
   procedure InitFields; override;
 public
 // public methods
   function MakeKW(const aValue: Il3CString): TkwString;
 public
 // singleton factory method
   class function Instance: TkwStringFactory;
    {- возвращает экземпляр синглетона. }
 end;//TkwStringFactory
{$IfEnd} //not NoScripts

implementation

{$If not defined(NoScripts)}
uses
  l3Base {a},
  SysUtils,
  l3String
  ;
{$IfEnd} //not NoScripts

{$If not defined(NoScripts)}


// start class TkwStringFactory

var g_TkwStringFactory : TkwStringFactory = nil;

procedure TkwStringFactoryFree;
begin
 l3Free(g_TkwStringFactory);
end;

class function TkwStringFactory.Instance: TkwStringFactory;
begin
 if (g_TkwStringFactory = nil) then
 begin
  l3System.AddExitProc(TkwStringFactoryFree);
  g_TkwStringFactory := Create;
 end;
 Result := g_TkwStringFactory;
end;


function TkwStringFactory.MakeKW(const aValue: Il3CString): TkwString;
//#UC START# *4F3E41C603BC_4F3E416701E8_var*
const
 cLimit = 300;
var
 l_Len : Integer;
 l_Index : Integer;
 l_KW : TkwString;
//#UC END# *4F3E41C603BC_4F3E416701E8_var*
begin
//#UC START# *4F3E41C603BC_4F3E416701E8_impl*
 l_Len := l3Len(aValue);
 if (l_Len < cLimit) then
 begin
  if FindData(aValue, l_Index) then
   Result := Self.Items[l_Index].Use
  else
  begin
   l_KW := TkwString.Create(aValue);
   Result := l_KW;
   Self.DirectInsert(l_Index, l_KW);
  end;//FindData(aValue, l_Index)
 end//l_Len < cLimit
 else
  Result := TkwString.Create(aValue);
//#UC END# *4F3E41C603BC_4F3E416701E8_impl*
end;//TkwStringFactory.MakeKW

procedure TkwStringFactory.InitFields;
//#UC START# *47A042E100E2_4F3E416701E8_var*
//#UC END# *47A042E100E2_4F3E416701E8_var*
begin
//#UC START# *47A042E100E2_4F3E416701E8_impl*
 inherited;
 Sorted := true;
//#UC END# *47A042E100E2_4F3E416701E8_impl*
end;//TkwStringFactory.InitFields

{$IfEnd} //not NoScripts

end.

unit l3TwoByteCString;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "L3"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Low Level::L3::l3CoreObjects::Tl3TwoByteCString
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

{$Include ..\L3\l3Define.inc}

interface

uses
  l3Interfaces,
  l3CProtoObject
  ;

type
 Tl3TwoByteCString = class(Tl3CProtoObject, Il3CString)
 private
 // private fields
   f_Chars : Word;
 protected
 // realized methods
   function pm_GetAsWStr: Tl3WString;
 public
 // public methods
   constructor Create(aChars: Word); reintroduce;
   class function Make(aChars: Word): Il3CString; reintroduce;
     {* Сигнатура фабрики Tl3TwoByteCString.Make }
 end;//Tl3TwoByteCString

implementation

uses
  l3String,
  l3Chars
  ;

// start class Tl3TwoByteCString

constructor Tl3TwoByteCString.Create(aChars: Word);
//#UC START# *4F5CBCAF00B4_4F5CBBE60070_var*
//#UC END# *4F5CBCAF00B4_4F5CBBE60070_var*
begin
//#UC START# *4F5CBCAF00B4_4F5CBBE60070_impl*
 inherited Create;
 f_Chars := aChars;
//#UC END# *4F5CBCAF00B4_4F5CBBE60070_impl*
end;//Tl3TwoByteCString.Create

class function Tl3TwoByteCString.Make(aChars: Word): Il3CString;
var
 l_Inst : Tl3TwoByteCString;
begin
 l_Inst := Create(aChars);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;

function Tl3TwoByteCString.pm_GetAsWStr: Tl3WString;
//#UC START# *46780DEF03E5_4F5CBBE60070get_var*
//#UC END# *46780DEF03E5_4F5CBBE60070get_var*
begin
//#UC START# *46780DEF03E5_4F5CBBE60070get_impl*
 Result.S := @f_Chars;
 Result.SLen := 2;
 Result.SCodePage := CP_ANSI;
//#UC END# *46780DEF03E5_4F5CBBE60070get_impl*
end;//Tl3TwoByteCString.pm_GetAsWStr

end.

function l3CStr(const aStr: Tl3WString): Il3CString;
  //overload;
  {-}
var
 l_S : Tl3InterfacedString;
begin
 if l3IsNil(aStr) then
 begin
  Result := Tl3CEmptyString.Instance;
  Exit;
 end;//l3IsNil(aStr)
 if l3IsAnsi(aStr.SCodePage) then
 begin
  if (aStr.SLen = 1) then
  begin
   Result := Tl3OneByteCString.Make(aStr.S^);
   Exit;
  end//aStr.SLen = 1
  else
  if (aStr.SLen = 2) then
  begin
   Result := Tl3TwoByteCString.Make(PWord(aStr.S)^);
   Exit;
  end//aStr.SLen = 2
  else
  if (aStr.SLen = 4) then
  begin
   Result := Tl3FourByteCString.Make(PLong(aStr.S)^);
   Exit;
  end;//aStr.SLen = 4
 end;//l3IsAnsi(aStr.SCodePage)
 l_S := Tl3InterfacedString.Make(aStr);
 try
  Result := l_S;
 finally
  l3Free(l_S);
 end;//try..finally
end;

unit tfwCStringFactory;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Библиотека "ScriptEngine"
// Родные Delphi интерфейсы (.pas)
// Generated from UML model, root element: SimpleClass::Class Shared Delphi Scripting::ScriptEngine::CString::TtfwCStringFactory
//
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

{$Include ..\ScriptEngine\seDefine.inc}

interface

{$If not defined(NoScripts)}
uses
  l3Interfaces,
  l3PrimString,
  tfwCStringList
  ;
{$IfEnd} //not NoScripts

{$If not defined(NoScripts)}
type
 TtfwCStringFactory = class(TtfwCStringList)
 protected
 // overridden protected methods
   procedure InitFields; override;
 public
 // public methods
   class function C(const aString: AnsiString): Il3CString; overload; 
   class function C(const aString: Tl3WString): Il3CString; overload; 
   class function C(aString: Tl3PrimString): Il3CString; overload; 
   class function C(aChar: AnsiChar): Il3CString; overload; 
 public
 // singleton factory method
   class function Instance: TtfwCStringFactory;
    {- возвращает экземпляр синглетона. }
 end;//TtfwCStringFactory
{$IfEnd} //not NoScripts

implementation

{$If not defined(NoScripts)}
uses
  l3Base {a},
  l3String,
  tfwCStringArraySing,
  tfwCStringArraySing2,
  l3Types
  ;
{$IfEnd} //not NoScripts

{$If not defined(NoScripts)}


// start class TtfwCStringFactory

var g_TtfwCStringFactory : TtfwCStringFactory = nil;

procedure TtfwCStringFactoryFree;
begin
 l3Free(g_TtfwCStringFactory);
end;

class function TtfwCStringFactory.Instance: TtfwCStringFactory;
begin
 if (g_TtfwCStringFactory = nil) then
 begin
  l3System.AddExitProc(TtfwCStringFactoryFree);
  g_TtfwCStringFactory := Create;
 end;
 Result := g_TtfwCStringFactory;
end;

const
   { Local }
  cLimit = 200;

// start class TtfwCStringFactory

class function TtfwCStringFactory.C(const aString: AnsiString): Il3CString;
//#UC START# *4F47405B02FD_4F473F9402D8_var*
var
 i : Integer;
 l_Len : Integer;
 l_W   : Word;
//#UC END# *4F47405B02FD_4F473F9402D8_var*
begin
//#UC START# *4F47405B02FD_4F473F9402D8_impl*
 {.$IfNDef XE}
 l_Len := Length(aString);
 if (l_Len < cLimit) then
 begin
  if (l_Len = 1) then
   Result := C(aString[1])
  else
  if (l_Len = 2) then
  begin
   l_W := PWord(@(aString[1]))^;
   Result := TtfwCStringArraySing2.Instance.Items[l_W];
   if (Result = nil) then
   begin
    Result := l3CStr(aString);
    TtfwCStringArraySing2.Instance.Items[l_W] := Result;
   end;//Result = nil
  end//l_Len = 2
  else
   with Instance do
   begin
    if FindData(l3PCharLen(aString), i, SortIndex) then
     Result := Items[i]
    else
    begin
     Result := l3CStr(aString);
     DirectInsert(i, Result);
    end;//FindData(l3PCharLen(aString), i)
   end;//with Instance
 end//Length(aString) < cLimit
 else
 {.$EndIf XE}
  Result := l3CStr(aString);
//#UC END# *4F47405B02FD_4F473F9402D8_impl*
end;//TtfwCStringFactory.C

class function TtfwCStringFactory.C(const aString: Tl3WString): Il3CString;
//#UC START# *4F47407D0052_4F473F9402D8_var*
var
 i : Integer;
 l_W : Word;
//#UC END# *4F47407D0052_4F473F9402D8_var*
begin
//#UC START# *4F47407D0052_4F473F9402D8_impl*
 {.$IfNDef XE}
 if (aString.SLen < cLimit) then
 begin
  if (aString.SLen = 1) AND l3IsANSI(aString.SCodePage) then
  begin
   Assert(aString.S <> nil);
   Result := C(aString.S[0]);
  end//aString.SLen = 1
  else
  if (aString.SLen = 2) AND l3IsANSI(aString.SCodePage) then
  begin
   Assert(aString.S <> nil);
   l_W := PWord(aString.S)^;
   Result := TtfwCStringArraySing2.Instance.Items[l_W];
   if (Result = nil) then
   begin
    Result := l3CStr(aString);
    TtfwCStringArraySing2.Instance.Items[l_W] := Result;
   end;//Result = nil
  end//aString.SLen = 2
  else
   with Instance do
   begin
    if FindData(aString, i, SortIndex) then
     Result := Items[i]
    else
    begin
     Result := l3CStr(aString);
     DirectInsert(i, Result);
    end;//FindData(l3PCharLen(aString), i)
   end;//with Instance
 end//Length(aString) < cLimit
 else
 {.$EndIf XE}
  Result := l3CStr(aString);
//#UC END# *4F47407D0052_4F473F9402D8_impl*
end;//TtfwCStringFactory.C

class function TtfwCStringFactory.C(aString: Tl3PrimString): Il3CString;
//#UC START# *4F4740A802A8_4F473F9402D8_var*
var
 l_S : Tl3WString;
//#UC END# *4F4740A802A8_4F473F9402D8_var*
begin
//#UC START# *4F4740A802A8_4F473F9402D8_impl*
 if (aString = nil) then
  Result := l3CStr('')
 else
 begin
  l_S := aString.AsWStr;
  if (l_S.SLen < cLimit) then
   Result := C(l_S)
  else
   Result := l3CStr(aString);
 end;//aString = nil
//#UC END# *4F4740A802A8_4F473F9402D8_impl*
end;//TtfwCStringFactory.C

class function TtfwCStringFactory.C(aChar: AnsiChar): Il3CString;
//#UC START# *4F50782700AA_4F473F9402D8_var*
//#UC END# *4F50782700AA_4F473F9402D8_var*
begin
//#UC START# *4F50782700AA_4F473F9402D8_impl*
 Result := TtfwCStringArraySing.Instance.Items[Ord(aChar)];
 if (Result = nil) then
 begin
  Result := l3CStr(aChar);
  TtfwCStringArraySing.Instance.Items[Ord(aChar)] := Result;
 end;//Result = nil
//#UC END# *4F50782700AA_4F473F9402D8_impl*
end;//TtfwCStringFactory.C

procedure TtfwCStringFactory.InitFields;
//#UC START# *47A042E100E2_4F473F9402D8_var*
//#UC END# *47A042E100E2_4F473F9402D8_var*
begin
//#UC START# *47A042E100E2_4F473F9402D8_impl*
 inherited;
 Sorted := true;
//#UC END# *47A042E100E2_4F473F9402D8_impl*
end;//TtfwCStringFactory.InitFields

{$IfEnd} //not NoScripts

end.

Про блог №3

"Мои 5 копеек. Пару дней назад увидел впервый раз ваш блог. Первое впечатление, Wow! сколько всего интересного, оказалось обманчивым. Интересное оказалось в заголовках. Большинство тем ждут явно более полного описания и покрытия. Вот скажем есть пост "Почему мы используем GUI-тесты", открывая его ждал описания какого то workflow, инструментов, примеров GUI тестов, процесс организации. Безусловно, название поста не подразумевает этого, а то что подразумевает не дает никакого опыта. Так и не понятно что за GUI тесты вы используете и почему. 
Понимаю, что тут действует принцип "не нравится, не ешь", но раз вы спрашиваете...

На счет UML, тема интересна. Но я бы не стал фокусироваться только на ней. Она влияет на все аспекты разработки. От управлениям требованими, документирования до написания кода. Лет 7 назад активно пытался пользоваться кодогенерацией. Но не взлетело. Был период программирования на C# и VS2010. С удивлением обнаружил, что делфовая интеграция с Together на голову выше чем все что есть для VS2010, может поэтому делфисты как то особо ревностно относятся к UML? В VS понравилась фича, которую все еще ищу в Delphi. Контроль архитектуры: настраиваешь на диаграмме слои приложения, устанавливаешь связи, а на этапе компиляции производится проверка на нарушение архитектуры и нерадивому программисту бьет по рукам в случае надобности. Очень полезная вещь. 
Поток сознания тоже бывает интересен, но быстро утомляет, хочется погружения и умных статей."

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

Так, ДРУЗЬЯ МОИ! ЗАДАВАЙТЕ вопросы.

Я пишу ТО, что МНЕ - "очевидно". Если я написал "почему мы используем GUI-Тестирование", а потом написал текст - то РОВНО это я и имел в виду.

Я же - НЕ ТЕЛЕПАТ - чтобы "угадывать" - как вы прочитаете заголовок.

ЗАДАВАЙТЕ ВОПРОСЫ. По существу, а не "зачем вам это надо" или "а что будет если в бензпилу положить лом".

Я С РАДОСТЬЮ - ОТВЕЧУ.

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

"хочется погружения и умных статей." - у меня значит - "статьи - не умные" :-) ну да ладно :-)

Вот смотрите - написал я несколько "более менее удачных" постов (с моей точки зрения) про "контрольные точки", про "абстрактные контейнеры", про "шаблонизирование тестов", про "использование примесей". Про "VCM vs MVC" -наконец.

И что? Ну задал вопросы всё тот же NameRec и Николай Зверев. Какие-то вопросы остались неотвеченными? По-моему - нет.

В посте про "примеси" - мне написали - "это всё ерунда - используйте Haskell". Так мы и про монады - вроде продуктивно поговорили. Нет? Вроде бы выяснили - что такое - монады. Спасибо - Роману Янковскому в конечном итоге.

И где "непонятность" и "нераскрытая многообещающесть"?

Кладезь-то она - кладезь. Только нужно "уметь ею пользоваться".

Если конечно есть интерес.

Ещё раз повторю - ЗАДАВАЙТЕ ВОПРОСЫ. Тем - много.

Я - не gunsmoker - который "всё вдумчиво и детально разжёвывает". Я - ТАК НЕ УМЕЮ.

Мне ВАЖНА - "реакция аудитории". Ну что уж поделать. Если реакция НЕГАТИВНАЯ или её нет, то я считаю - "тема неинтересна - всё дальше проехали".

Про "процесс организации" - я бы с РАДОСТЬЮ бы написал. Тема - хорошая. ВАЖНАЯ. Но тут - есть одно опасение, что мне "не дадут по голове".

Ну что делать - у нас не Америка. Это там Джоэл радостно пишет о "подноготной Microsoft" и "не получает по голове". Менталитет другой видимо...

"связи, а на этапе компиляции производится проверка на нарушение архитектуры и нерадивому программисту бьет по рукам в случае надобности. Очень полезная вещь" - у нас кстати - ТОЖЕ ТАКОЕ есть. На "этапе кодогенерации" и создания модели. Только скажем так.. Не все любят - "когда бьют по рукам".. Это кстати - ВООБЩЕ отдельная тема. Когда бить -  и когда не бить. И зачем.

"Большинство тем ждут явно более полного описания и покрытия." - ЗАДАВАЙТЕ ВОПРОСЫ. Я МНОГО сил и ВРЕМЕНИ трачу на "хорошие статьи". Их - крайне немного. Но по "реакции публики" - обычно складывается ощущение, что "не попал куда нужно".

Вот написал я про CoreText. Мне коллега задал вопрос про Glyph'ы и "обтекание" - я этот вопрос - расшифровал. Коллега - удовлетворился написанным. "Публика" - ноль внимания. Значит интересно было - ОДНОМУ ЧЕЛОВЕКУ.

"После этого код я и сам написать могу." - вначале я писал СОВСЕМ БЕЗ кода - стали просить - "покажите код".

Выясняется - "куски объёмные". А как иначе? Не "hello world" пишем...

В общем - не хотел никого затронуть или обидеть. Возможные темы - НЕ РАЗ описаны.

Если что-то интересно - ЗАДАВАЙТЕ вопросы.

Ну а сам я буду писать - "по своему разумению".

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

Так что простите - на такие вопросы - далеко не всегда буду отвечать.

P.S. Понимаете - я сделал МНОГОЕ. Я могу "побайтно" расписать как "устроен редактор Эверест". Потратив силы и время. Но будет ОЧЕНЬ ОБИДНО если это окажется "фу это неинтересно" или более того "фу это очевидно".

Есть такая "конференция" или "сообщество" - HighLoad. Не знаю как назвать...

Есть такая "конференция" или "сообщество" - HighLoad. Не знаю как назвать...

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

Что сказать.

Язык конечно - УЖАСНЫЙ.

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

Ну да ладно...

Так вот...

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

Многое, что я писал или пытался писать - относится именно к тему HighLoad.

Всё это кеширование объектов, Immutable-строки, статический полиморфизм контейнеров (за счёт примесей) взамен динамического, собственные "хоккейные"- структуры. Экономия "на спичках" и "на копейках". Оптимизация AddRef/Release.

Это всё из "темы HighLoad". Привести вам выдержки из докладов?

В общем на вопрос "зачем всё это" - у меня есть  - ЖЕЛЕЗНЫЙ аргумент - "потому что HighLoad".

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

Про блог №2

"Александр, дорогой, а как же "Нести доброе светлое в люди" (с)?
Насколько помню при нашем общении, Вы говорили что хотели бы передать, хотя бы, часть знаний будущим поколениям. И вам нравится сама идея просветительского подхода для блога.
Лично я воспринимаю блог как некую песочницу, где встречаются люди по интересам (как в детском саду), где Вы сохраняете те самые знания, полезные с Вашей точки зрения... Или я не прав?
Понятно, что не все воспринимается, и не все будет услышано, но пользы от блога меньше не станет...
Наверное это Осень... В общем, Желаю Вам не унывать, а нам побольше интересных тем в Вашем блоге..."

Смотрел я тут с детьми серию Смешариков с названием "Психолог". Там было про то как Кар-Карыч "расстроился"... А потом сказал "друзья - я наговорил вам массы глупостей.. минутный срыв.. а сейчас - прошу - готов любого выслушать" :-)

"Голый код". Пример использования CoreText

- (void) render: (EVDRenderContext &) aCtx : (DoRenderedPara) aDoRendered : (DoPageEnded) aPageEnded
{
    EVDRenderedPara *l_Para = nil;
    @try {
        CFAttributedStringRef l_Text = self.Text;
        
        int vTextLength = 0;
        if (l_Text)
            vTextLength = CFAttributedStringGetLength(l_Text);
        
        if (vTextLength <= 0) {
            // - пустые строки надо как-то отдельно обрабатывать
            //aOffsetInParaRenderingNow = 0;
            if (aCtx.rParaAdded)
                aCtx.rRect.size.height -= 10;
            l_Para = [[EVDRenderedPara alloc] initWithFrame: nil andPara: self];
            aDoRendered(l_Para);
        }
        else {
            CTFramesetterRef l_Framesetter = CTFramesetterCreateWithAttributedString(l_Text);
            @try {
#ifdef UseHoles
                CGMutablePathRef l_Path = CGPathCreateMutable();
                CGPathAddRect(l_Path, NULL, aCtx.rRect);
                
                CGFloat vHeight = 30;
                vHeight = MIN(vHeight, aCtx.rRect.size.height - 10);
                vHeight = MAX(0, vHeight);
//                CGFloat vDelta = 20;
                CGFloat vDelta = 0;
                vDelta = -vDelta;
                
                {
//                    CGFloat vIndent = 40;
                    CGFloat vIndent = 0;
//                    CGFloat vWidth = 50;
                    CGFloat vWidth = 80;
                    CGRect vHole;
                    vHole.origin.x = aCtx.rRect.origin.x + vIndent;
                    vHole.origin.y = aCtx.rRect.size.height - vHeight + vDelta;
                    vHole.size.width = vWidth;
                    vHole.size.height = vHeight + 10/* - vDelta*/;
                    if (vHole.origin.y >= 0) {
//                        CGPathAddEllipseInRect(l_Path, NULL, vHole);
                        CGPathAddRect(l_Path, NULL, vHole);
                    }
                }
#else
                CGPathRef l_Path = CGPathCreateWithRect(aCtx.rRect, NULL);
#endif // UseHoles
                @try {
                    //        CGMutablePathRef l_Path = CGPathCreateMutable();
                    //        CGPathAddRect(l_Path, NULL, aRect);
                    
                    CTFrameRef l_Frame = CTFramesetterCreateFrame(l_Framesetter,
                                                                  CFRangeMake(aCtx.rCursor.rOffset, 0), l_Path, NULL);
                    @try {
                        l_Para = [[EVDRenderedPara alloc] initWithFrame: l_Frame andPara: self];
                    }
                    @finally {
                        if (l_Frame)
                            CFRelease(l_Frame);
                    }
                }
                @finally {
                    CFRelease(l_Path);
                }
            }
            @finally {
                CFRelease(l_Framesetter);
                l_Framesetter = NULL;
            }
            aDoRendered(l_Para);
            aCtx.rParaAdded = YES;
            if ([l_Para CheckPageIsEnded: vTextLength : aCtx.rRect : aCtx.rCursor]) {
                // - тут закончилась страница
                aPageEnded();
            }
        }
    }
    @finally {
        DESTROY(l_Para);
    }
    aCtx.rWasTableRow = NO;
}

- (void) render: (EVDRenderContext &) aCtx : (DoRenderedPara) aDoRendered : (DoPageEnded) aPageEnded
{
    [self renderParas: aCtx : ^(id<IevdPara> vPara) {
        [vPara render: aCtx : aDoRendered : ^() {
            if ([self canBreak])
                aPageEnded();
        }];
    }];
}

@implementation EVDHorzParaList

- (void) render: (EVDRenderContext &) aCtx : (DoRenderedPara) aDoRendered : (DoPageEnded) aPageEnded
{
    CGRect vPrevRect = aCtx.rRect;
    __block CGFloat vRowWidth = 0;
    {
        __block CGRect vRect = vPrevRect;
        __block EVDParaIndexEx vCursor;
        __block EVDRenderContext vCtx (vCursor, vRect, aCtx.rParaAdded);
        [self renderParas: vCtx : ^(id<IevdPara> vPara) {
            vRowWidth += [vPara width];
        }];
    }
    
    __block CGFloat vHeight = std::numeric_limits<int>::max();
    __block BOOL vPageEnded = NO;
    __block EVDParaIndexEx::InnerCursors vInner;
    __block EVDParaIndexEx::InnerCursors::const_iterator vIt = aCtx.rCursor.rInnerCursors.begin();
    {
        __block CGRect vRect = vPrevRect;
        __block EVDParaIndexEx vCursor;
        __block EVDRenderContext vCtx (vCursor, vRect, aCtx.rParaAdded);
        [self renderParas: vCtx : ^(id<IevdPara> vPara) {
            CGFloat vCellWidth = (vPrevRect.size.width * [vPara width]) / vRowWidth;
            CGRect vParaRect = vRect;
            vParaRect.size.width = vCellWidth - 8;
            if ([vPara hasFrame]) {
                vParaRect.origin.x += 3;
                vParaRect.size.width -= 3;
            }
            vParaRect.origin.y = aCtx.rRect.origin.y;
            vParaRect.size.height = aCtx.rRect.size.height;
            EVDParaIndexEx vParaCursor;
            
            if (vIt != aCtx.rCursor.rInnerCursors.end()) {
                vParaCursor = *vIt;
                vIt++;
            }
            
            EVDRenderContext vParaCtx (vParaCursor, vParaRect, vCtx.rParaAdded);
            [vPara render: vParaCtx : aDoRendered : ^() {
                vPageEnded = YES;
            }];
            vInner.push_back(vParaCursor);
            vRect.origin.x += vCellWidth;
            vHeight = MIN(vHeight, vParaRect.size.height);
        }];
    }
    
    EVDTableRowFrame *vRowFrame = [[EVDTableRowFrame alloc] initWithFrame: NULL andPara: self];
    @try {
        CGFloat vDH = vPrevRect.size.height - vHeight;
        __block CGRect vRect = vPrevRect;
        vRect.origin.x -= 4;
        __block EVDParaIndexEx vCursor;
        __block EVDRenderContext vCtx (vCursor, vPrevRect, aCtx.rParaAdded);
        __block BOOL vIsFirst = YES;
        [self renderParas: vCtx : ^(id<IevdPara> vPara) {
            CGFloat vCellWidth = (vPrevRect.size.width * [vPara width]) / vRowWidth;
            if ([vPara hasFrame]) {
                vRect.origin.y = vHeight + 23;
                vRect.size.height = vDH;
                vRect.size.width = vCellWidth;
                EVDRenderedFrame * vFrame = [[EVDRenderedFrame alloc] initWithRect: vRect andPara: vPara];
                @try{
                    if (vIsFirst) {
                        vIsFirst = NO;
                        vFrame->myIsFirst = YES;
                    }
                    if (!aCtx.rWasTableRow) {
                        vFrame->myIsFirstRow = YES;
                    }
                    [vRowFrame addRendered: vFrame];
                }
                @finally {
                    DESTROY(vFrame);
                }
            }
            vRect.origin.x += vCellWidth;
        }];
        if ([vRowFrame hasRendered])
            aDoRendered(vRowFrame);
    }
    @finally {
        DESTROY(vRowFrame);
    }
    
    if (vPageEnded) {
        aCtx.rCursor.rInnerCursors = vInner;
        aCtx.rCursor.rPara--;
        aPageEnded();
    }
    else {
        aCtx.rCursor.rInnerCursors.clear();
        aCtx.rRect.size.height = vHeight;
    }
    aCtx.rWasTableRow = YES;
}

- (BOOL) canBreak
{
    return NO;
}

@end

- (void) draw: (CGContextRef) aContext forDoc: (id<IevdDocument>) aDoc
{
    
    EVDSelection * vSelection = [aDoc selection];
    if (vSelection) {
        NodeKey vNodeKey = [myPara NodeKey];
        EVDParaCursorLite vCursor(vNodeKey, [myPara indexInParent], 0, [[aDoc blockByKey: vNodeKey] ZOrder]);
        if (vSelection->Has(vCursor))
            [self fillPara: aContext withBackColor: [UIColor colorWithRed: 0.0627451 green: 0.419608 blue: 0.745098 alpha: 0.3] : NO];
    }
    
    [self fillPara: aContext withBackColor: [myPara backColor] : YES];
    
    const EVDImage &vImage = [myPara headerImage: aDoc];
    if (vImage.Image())
    {
        CTFrameRef vFrame = myFrame;
        CGPathRef vFramePath = CTFrameGetPath(vFrame);
        CGRect vFrameRect = CGPathGetBoundingBox(vFramePath);
        NSArray *vLines = (NSArray *)CTFrameGetLines(vFrame);
        
        int vLineCount = [vLines count];
        
        if (vLineCount > 0) {
            CGPoint vLineOrigin;
            CTFrameGetLineOrigins(vFrame, CFRangeMake(0, 1), &vLineOrigin);
            //                CTFrameGetLineOrigins(aFrame, CFRangeMake(vLineCount - 1, 1), &vLineOrigin);
            
            vLineOrigin.y -= 6;
            //                vLineOrigin.y -= 5;
            //                    vFrameRect.size.height -= vLineOrigin.y;
            vFrameRect.size.width = vImage.Width();
            vFrameRect.size.height = vImage.Height();
            vFrameRect.origin.y += vLineOrigin.y;
            vFrameRect.origin.x += 10;
            CGContextDrawImage(aContext, vFrameRect, vImage.Image().CGImage);
        }
    }
    
    [aDoc fillStringToSearch: ^(const NSRange & aRange){
        [self fillSelected: aContext range: aRange];
    } forPara: myPara];
    
    CTFrameDraw(myFrame, aContext);
    
    CFAttributedStringRef vText = myPara.Text;
    if (vText) {
        [self checkSelected: NSMakeRange(0, CFAttributedStringGetLength(vText)) do: ^(const GlyphData & aData)
         {
             CFTypeRef vAttr = CFAttributedStringGetAttribute(vText, aData.rGlyphIndex, kCTRunDelegateAttributeName, NULL);
             if (vAttr) {
                 CTRunDelegateRef delegate = (CTRunDelegateRef)vAttr;
                 NSDictionary *vDict = (NSDictionary*)CTRunDelegateGetRefCon(delegate);
                 if (vDict) {
                     CGRect vContextRect = CGContextGetClipBoundingBox(aContext);
                     UIImage *vGlyphImage = [vDict objectForKey:@"image"];
                     CGFloat vWidth = MIN([(NSString*)[vDict objectForKey:@"width"] floatValue], vContextRect.size.width - 40);
                     CGFloat vHeight = [(NSString*)[vDict objectForKey:@"height"] floatValue];
                     CGFloat vGap = [(NSString*)[vDict objectForKey:@"gap"] floatValue];
                     vWidth -= vGap;
                     if (vGlyphImage) {
                         CGContextDrawImage(aContext, CGRectMake(aData.rGlyphOrigin.x/* - vBox[0].origin.x*/, aData.rGlyphOrigin.y - 5/*- vBox[0].origin.y*/, vWidth, vHeight), vGlyphImage.CGImage);
                     }
                 }
             }
         }]; // [self checkSelected
    } // vText
} // vNeedDrawPara

- (void) draw: (CGContextRef) aContext forDoc: (id<IevdDocument>) aDoc
{
    int vCount = [myFrames count];
    for (int i = 0; i < vCount; i++) {
        [(EVDRenderedPara *)[myFrames objectAtIndex: i] draw: aContext forDoc: aDoc];
    }
}

- (void) draw: (CGContextRef) aContext forDoc: (id<IevdDocument>) aDoc
{
    /* Stroke a sequence of line segments one after another in `context'. The
     line segments are specified by `points', an array of `count' CGPoints.
     This function is equivalent to
     
     CGContextBeginPath(context);
     for (k = 0; k < count; k += 2) {
     CGContextMoveToPoint(context, s[k].x, s[k].y);
     CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
     }
     CGContextStrokePath(context); */
    
    id<IevdPara> vPara = [self para];
    
    if (vPara) {
        CGContextSetLineWidth(aContext, 1.0);
        CGContextBeginPath(aContext);
        
        if ([vPara prev] == nil) {
            // левая рамка
            CGContextMoveToPoint(aContext, myRect.origin.x, myRect.origin.y);
            CGContextAddLineToPoint(aContext, myRect.origin.x, myRect.origin.y + myRect.size.height);
        }
        
        // правая рамка
        CGContextMoveToPoint(aContext, myRect.origin.x + myRect.size.width, myRect.origin.y);
        CGContextAddLineToPoint(aContext, myRect.origin.x + myRect.size.width, myRect.origin.y + myRect.size.height);
        
        if (([vPara mergeStatus] != EVD::ms_Head) || ([[myRow para] next] == nil)) {
            // нижняя рамка
            CGContextMoveToPoint(aContext, myRect.origin.x, myRect.origin.y);
            CGContextAddLineToPoint(aContext, myRect.origin.x + myRect.size.width, myRect.origin.y);
        }
        
        if (myIsFirstRow) {
            // верхняя рамка
            CGContextMoveToPoint(aContext, myRect.origin.x, myRect.origin.y + myRect.size.height);
            CGContextAddLineToPoint(aContext, myRect.origin.x + myRect.size.width, myRect.origin.y + myRect.size.height);
        }
        CGContextStrokePath(aContext);
    }
    
//    CGContextStrokeRect(aContext, myRect);
}