tag:blogger.com,1999:blog-13097646496089499512024-02-08T09:26:05.179-08:00Дневник программистаРассуждения, идеи, реализации...Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.comBlogger8125tag:blogger.com,1999:blog-1309764649608949951.post-73634953083686662552011-01-18T02:59:00.000-08:002011-01-18T02:59:36.974-08:00Оптимизация может быть зломДавно не обновлял свой блог, пора бы это сделать. В этом сообщении хотел бы поделиться своими взглядами на оптимизацию программ. Скажу сразу, для опытного программиста тут едва ли будет что-либо новое.<br />
<br />
Мне порой кажется, что существует определенная категория программистов, которые считают что самое главное в программе - это максимальная степень оптимизации. Склонен с этим мнением не согласится. Отвечая на вопрос, что самое главное, нужно смотреть с двух разных точек зрения и искать компромисс между ними. И во многих случаях получится так, что оптимизации места здесь не найдется. Две точки зрения - это прикладная программа с точки зрения конечного пользователя, и программный код с точки зрения разработчика(-ов).<br />
<br />
Конечного пользователя интересует программный продукт, набор его функций и возможностей, ему все равно выполняется ли определенный участок кода за 1 мс или за 10 мс, если он все равно не заметит разницы. Но погоня за выйгрышем этих 9 мс может обойтись разработчику достаточно дорого. Вопрос - в чем смысл? Единственный возможный - это в целях самообразования, но не более. Поймите, что качество программы от этого не вырастет ни на йоту, а может даже и упадет.<br />
<br />
Программный код же далеко не в последнюю очередь пишется для людей. И требования к нему - соответствующие. Он должен быть понятен, читаем, гибок и расширяем. Разработка почти никогда не заканчивается в момент выпуска первой версии программы, и очень важно чтобы усовершенствование программного продукта обходилась как можно дешевле. Оптимизация же может принести вред качеству кода с этой точки зрения (особенно низкоуровневая оптимизация) - мы пытаемся сделать код более оптимальным для машины, за счет уменьшения его оптимальности для человека. Выигрыш от ненужной оптимизации сомнителен, в то время как проигрыш - ощутим. ПРЕЖДЕВРЕМЕННАЯ ОПТИМИЗАЦИЯ - ЗЛО! Не чешите, пока не чешется. Но с другой стороны, обратное - преждевременная пессимизация - тоже зло. Ищите серединку опираясь на обозначенные приоритеты.<br />
<br />
Поэтому очень важно думая об оптимизации правильно расставлять приоритеты. А приоритеты эти - это юзабилити - для пользователя, и читаемость, гибкость, расширяемость кода - для разработчика. Оптимизировать нужно тогда и только тогда, когда итоговые показатели не устраивают пользователей. И то, делать это нужно - тоже правильно. Не стоит сразу же бросаться переписывать участки кода на ассемблере - это глупо. В первую очередь нужно выявить узкое место путем тестирования и замеров. Делать это нужно обязательно, даже если вам кажется что вы знаете почему ваша программа тормозит. Поверьте опыту, достаточно часто ваши догадки не оправдываются и причина кроется где-то еще. И только выявив это узкое место, найдя причину, нужно пытаться найти более оптимальный вариант, решающий ту же задачу. В большинстве случаев этот оптимальный вариант заключается в высокоуровневой оптимизации - нужно изменить сам подход к решению задачи. Я не являюсь заядлым любителем алгоритмов и при оптимизации использую скорее не математический, а инженерный подход, о котором расскажу в следующий раз.Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com13tag:blogger.com,1999:blog-1309764649608949951.post-43850940003409108422008-12-10T03:57:00.000-08:002009-04-01T04:47:57.502-07:00Реализация паттерна "Singleton" в DelphiДавно интересовала тема реализации шаблона дизайна "Singleton" в Delphi, которая бы мне просто позволила бы мне получить соответствующее поведение путем простого наследования от некоторого базового класса. В результате нескольких различных вариантов реализаций остановился на таком:<br /><pre>type<br /> // Базовый класс для объектов, реализующих паттерн <br /> // "Singleton". Для получения доступа к экземпляру<br /> // необходимо вызвать GetInstance. Если экземпляр <br /> // еще не существует, то он будет создан. Иначе - <br /> // возвращена ссылка на ранее созданный экземпляр.<br /> // Уничтожить экземпляр можно вручную, вызвав Free, <br /> // иначе он будет уничтожен автоматически перед <br /> // завершением приложения<br /> TSingleton = class(TObject)<br /> private<br /> class procedure RegisterInstance(Instance: <br /> TSingletone);<br /> procedure UnRegisterInstance;<br /> class function FindInstance: TSingletone;<br /> protected<br /> // Инициализацию производить только в этом <br /> // конструкторе, а не в GetInstance.<br /> // Не рекомендуется выносить этот конструктор <br /> // из секции protected<br /> constructor Create; virtual;<br /> public<br /> class function NewInstance: TObject; override;<br /> procedure BeforeDestruction; override;<br /> // Точка доступа к экземпляру<br /> constructor GetInstance;<br /> end;<br />...<br /><br />implementation<br /><br />uses Contnrs;<br /><br />var<br /> SingletonList: TObjectList;<br /><br />{ TSingleton }<br /><br />procedure TSingleton.BeforeDestruction;<br />begin<br /> UnregisterInstance;<br /> inherited BeforeDestruction;<br />end;<br /><br />constructor TSingleton.Create;<br />begin<br /> inherited Create;<br />end;<br /><br />class function TSingleton.FindInstance: <br /> TSingletone;<br />var<br /> i: Integer;<br />begin<br /> Result := nil;<br /> for i := 0 to SingletonList.Count - 1 do<br /> if SingletonList[i].ClassType = Self <br /> then begin<br /> Result := TSingleton(SingletonList[i]);<br /> Break;<br /> end;<br />end;<br /><br />constructor TSingleton.GetInstance;<br />begin<br /> inherited Create;<br />end;<br /><br />class function TSingleton.NewInstance: TObject;<br />begin<br /> Result := FindInstance;<br /> if Result = nil then begin<br /> Result := inherited NewInstance;<br /> TSingleton(Result).Create;<br /> RegisterInstance(TSingleton(Result));<br /> end;<br />end;<br /><br />class procedure TSingleton.RegisterInstance(Instance: <br /> TSingleton);<br />begin<br /> SingletonList.Add(Instance);<br />end;<br /><br />procedure TSingletone.UnRegisterInstance;<br />begin<br /> SingletonList.Extract(Self);<br />end;<br /><br />initialization<br /> SingletonList := TObjectList.Create(True);<br /><br />finalization<br /> SingletonList.Free;</pre>Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com35tag:blogger.com,1999:blog-1309764649608949951.post-9163718492298765362008-09-14T06:53:00.000-07:002008-09-14T10:06:33.499-07:00Ошибка в Delphi RTL - функция GetPropValue неверно работает с со свойствами типа CardinalОбнаружил неприятную ошибку в Delphi RTL. Казалось бы, такой совсем безобидный код вызывает Range check error:<br /><pre>type<br /> TFoo = class(TPersistent)<br /> private<br /> FBar: Cardinal;<br /> published<br /> property Bar: Cardinal read FBar write FBar;<br /> end;<br />...<br />var<br /> Obj: TFoo;<br /> Value: Variant;<br />begin<br /> Obj := TFoo.Create;<br /> try<br /> // Устанавливаем любое значение, выходящее за рамки <br /> // типа Integer<br /> Obj.Bar := 4294967295; {$FFFFFFFF}<br /> Value := GetPropValue(Obj, 'Bar');<br /> SetPropValue(Obj, 'Bar', Value); // Error<br /> finally<br /> Obj.Free;<br /> end;<br />end;</pre><br />Почему так происходит? Заглянув в исходный код модуля TypInfo.pas, обнаружим такие строки:<br /><pre> case PropInfo^.PropType^^.Kind of<br /> tkInteger, tkChar, tkWChar, tkClass:<br /> Result := GetOrdProp(Instance, PropInfo);</pre><br />Очевидно, наше свойство имеет Kind = tkInteger, следовательно, Result будет присвоен результат вызова функции GetOrdProp. Казалось бы, все логично, но посмотрев на прототип функции GetOrdProp обнаружим, что возвращаемое значение имеет тип Longint - т.е. 4 байтное целое <strong>со знаком</strong>. Следовательно, вариантной переменной Result изначально установится неверное значение поля VType. Оно станет равным varInteger ($0003), вместо varLongWord ($0013). Ошибка имеет место быть именно здесь. Значение Value будет интерпретироваться как -1, а не как 4294967295.<br />Следующая строка в нашем примере вызовет проявление этой ошибки. Заглянув в реализацию функции SetPropValue увидим, что перед установкой свойству значения, происходит проверка, не выходит ли оно за диапазон возможных для данного типа:<br /><pre>function RangedValue(const AMin, AMax: Int64): Int64;<br />begin<br /> Result := Trunc(Value);<br /> if (Result < AMin) or (Result > AMax) then<br /> RangeError;<br />end;</pre><br />Функция вызывается с верными параметрами - AMin = 0; AMax = 4294967295. Но Result в первой строке будет присвоено значение -1, т.е. наше $FFFFFFFF, которое мы установили свойству, но интерпретируемое как Integer, а не как Cardinal. И, разумеется, это значение не пройдет проверку на вхождение в диапазон.<br />Обходной путь для решения данной проблемы - не использовать Get(Set)PropValue для свойств, которые имеют или могут иметь тип 4-байтного целого без знака. Предусмотрите этот случай, и используйте вместо этого функции Get(Set)OrdProp.Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com6tag:blogger.com,1999:blog-1309764649608949951.post-77982945638399445122008-06-16T04:43:00.000-07:002009-03-30T00:36:27.541-07:00Ручное управление временем жизни для объектов, реализующих интерфейсыУ Алексея Михайличенко, есть статья, в которой показано, что при работе с интерфейсами, которые реализуют объекты не ведущие учет ссылок, может возникнуть AV на казалось бы пустом месте. Исключение возникает в функции _IntfClear, которая вызывается автоматически для интерфейсных ссылок при выходе их за область видимости. Однако если объект до этого был уничтожен, интерфейсная ссылка перестает быть валидной и попытка вызова метода этого интерфейса собственно и приводит к AV. Вот эта статья:<br />http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1312<br /><br />В процессе обсуждения, как мне кажется, родилось решение, которое может быть использовано для своих классов, которые должны реализовать интерфейс, однако управление временем жизни объектов должно быть ручное. Вот класс, от которого в таком случае можно унаследоваться:<br /><pre><br />TMyInterfacedObject = class(TObject, IInterface)<br />protected<br /> FRefCount: Integer;<br /> FDestroyed: Boolean;<br /> function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;<br /> function _AddRef: Integer; stdcall;<br /> function _Release: Integer; stdcall;<br />public<br /> procedure FreeInstance; override;<br />end;<br /><br />procedure TMyInterfacedObject.FreeInstance;<br />begin<br /> FDestroyed := True;<br /> if RefCount = 0 then<br /> inherited FreeInstance;<br />end; <br /><br />function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;<br />begin<br /> if GetInterface(IID, Obj) then<br /> Result := 0<br /> else<br /> Result := E_NOINTERFACE;<br />end;<br /><br />function TMyInterfacedObject._AddRef: Integer;<br />begin<br /> Result := InterlockedIncrement(FRefCount);<br />end;<br /><br />function TMyInterfacedObject._Release: Integer;<br />begin<br /> Result := InterlockedDecrement(FRefCount);<br /> if (Result = 0) and FDestroyed then<br /> FreeInstance;<br />end;<br /></pre><br /><br />При вызове Free для таких объектов освобождение памяти под экземпляр произойдет только в том случае, если равен нулю счетчик ссылок. Иначе - просто будет выполнен код деструктора, а освобождение памяти произойдет только после обнуления счетчика. С другой стороны, обнуление счетчика не приведет к уничтожению экземпляра до тех пор, пока явно не вызвать для данного объекта Free.Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com5tag:blogger.com,1999:blog-1309764649608949951.post-52719451589051242282008-05-25T12:28:00.000-07:002008-05-25T12:38:31.189-07:00class constructorsХотелось бы иметь в Delphi for Win32 методы, объявляемые как class constructor, т.е. выполняющую некоторую инициализацию для класса в целом, а не для его экземпляров. И чтобы их код гарантировано выполнялся при старте приложения (подобно секции initialization юнита). И чтобы потомки класса наследовали конструкторы класса предка, а при желании - могли переопределить его поведение. Зачем это нужно? Скажем, у меня есть классы с общим предком, которые я должен регистрировать с помощью RegisterClass. Мне приходится помнить об этом и прописывать код регистрации руками для каждого класса в секции инициализации юнита. А если бы была описанная выше возможность, я мог бы в коде классового конструктора базового класса написать<br />RegisterClass(Self);<br />и просто объявлять от класса потомков - они бы регистрировались автоматически. Можно было бы регистрировать даже более сложным образом: <br />RegisterClassAlias(Self, GetClassAlias);<br />где GetClassAlias - виртуальный классовый метод. Эх, мечты-мечты...Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com3tag:blogger.com,1999:blog-1309764649608949951.post-84067139871674224682008-05-13T09:05:00.000-07:002008-08-02T12:27:00.911-07:00Порождение разнотипных объектовОчень часто встречаю в различных проектах код, подобный представленному ниже:<br /><pre><br />сase SomeValue of<br /> 1: Obj := TFirstClass.Create;<br /> 2: Obj := TSecondClass.Create;<br />...<br />end;<br /></pre><br />И это еще при том, что классы родственны между собой и их конструкторы имеют одинаковый прототип. Не нужно заниматься подобной ерундой, язык поддерживает виртуальные конструкторы! Все, что нужно, это объявить тип "классовая ссылка" (он же метакласс) таким образом:<br />TMyClass = class of TMyObject;<br />где TMyObject - общий предок для всех классов, экземпляры которых необходимо создавать, а также у класса TMyObject объявить виртуальный конструктор, который в потомках следует перекрывать. Например:<br /><pre><br />TMyObject = class(...)<br />public<br /> constructor Create(SomeParam: SomeType); virtual;<br />...<br />end;<br />TFirstClass = class(TMyObject)<br />public<br /> constructor Create(SomeParam: SomeType); override;<br />...<br />end;<br /></pre><br />Теперь создать экземпляр неизвестного на этапе компиляции класса можно например так:<br /><pre><br />function Factory(ClassType: TMyClass; Param: SomeType): TMyObject;<br />begin<br /> Result := ClassType.Create(Param);<br />end;<br /></pre><br />Вызывать можно так:<br />Obj := Factory(TFirstClass, 10);<br />или<br />Obj := Factory(TSecondClass, 20);<br /><br />Можно пойти дальше и реализовать фабрику, которая принимает на входе некое значение типа String, Integer или другого типа, находит в неком реестре сопоставленный этому значению класс и создает его экземпляр:<br /><pre><br />RegisterClassAlias(TFirstClass, '1');<br />RegisterClassAlias(TSecondClass, '2');<br /><br />function Factory(ClassId: Integer; Param: SomeType): TMyObject;<br />var<br /> ClassType: TMyClass;<br />begin<br /> ClassType := TMyClass(FindClass(IntToStr(ClassId)));<br /> Result := ClassType.Create(Param);<br />end;<br /></pre><br />Тут мы воспользовались стандартным реестром классов, занеся в него элементы с помощью вызовов RegisterClassAlias. Ограничение этого реестра в том, что регистрировать в нем можно только потомки TPersistent. Если ваши классы не являются его потомками, то можно реализовать свой простенький реестр, на основе, скажем, TStringListInshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com2tag:blogger.com,1999:blog-1309764649608949951.post-29185459787113892682008-05-08T03:03:00.000-07:002008-05-13T00:35:33.711-07:00Delphi - виды контрактов у классовГрубо говоря, видов контрактов у классов в Delphi столько, сколько и директив области видимости - private, protected, public определяют контракты класса с самим собой, с потомками и с клиентским кодом. А как быть, если нам нужна более гибкая политика разграничения доступа к членам класса? Скажем, доступ к определенным членам класса при обычных условиях клиентскому коду должен быть запрещен, однако по специальной просьбе клиента, этот доступ необходимо предоставить. Такое поведение можно реализовать с помощью интерфейсов: класс определяет нужные приватные методы как реализацию некого интерфейса. Таким образом, получить доступ к этим методам клиент сможет только явно запросив интерфейс. Этим действием клиент берет на себя ответственность, как бы подтверждая "мне это действительно нужно и я знаю, что делаю". А можно пойти еще дальше - давать доступ к интерфейсу не всем кто попросит, а делать определенную проверку и только в случае успеха возвращать интерфейс. Таким образом можно, например, сэмитировать "дружественные классы" C++. В общем, простор для творчества большой, только нужно учесть, что подобное поведение не допускается в рамках технологии COM.Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com0tag:blogger.com,1999:blog-1309764649608949951.post-64283114526106030292008-05-08T01:23:00.000-07:002008-08-02T12:27:15.433-07:00Множественное наследование реализаций в DelphiМножественное наследование реализаций - безусловно мощная, но не всегда безопасная возможность некоторых ОО-языков программирования. Есть ли в Delphi множественное наследование? На этот вопрос можно ответить просто и категорично - "Нет". Однако в качестве альтернативы, в Delphi есть множественное наследование деклараций (или интерфейсов), что также позволяет реализовать полиморфный клиентский код, работающий с неродственными объектами - т.н. "горизонтальный полиморфизм". Но как быть, когда у неродственных классов кроме общего интерфейса нужна общая реализация методов этого интерфейса? Дублировать код? Не самый лучший вариант, к тому же - есть альтернатива. В Delphi на уровне языка поддерживается делегирование реализации интерфейса стороннему классу. Таким образом, мы можем создать два или более неродственных класса, реализующих общий интерфейс, и назначить один и тот же "третий" класс ответственным за его реализацию. Как результат - наши классы имеют и общий интерфейс, и общую реализацию, причем - без дублирования кода. Вот простой пример:<br /><pre><br />// Общий интерфейс<br />ISomeIntf = interface<br /> procedure SomeProc;<br />end;<br /><br />// Общая реализация интерфейса<br />TSomeIntfImpl = class(TAggregatedObject, ISomeIntf)<br />public<br /> procedure SomeProc;<br />end;<br /><br />// Класс, наследующий интерфейс ISomeIntf с его реализацией в классе TSomeIntfImpl<br />TBar = class(TInterfacedObject, ISomeIntf)<br />private<br /> FSomeIntfImpl: TSomeIntfImpl;<br /> function GetSomeIntfImpl: ISomeIntf;<br />protected<br /> property SomeIntfImpl: ISomeIntf read GetSomeIntfImpl implements ISomeIntf;<br />public<br /> constructor Create;<br /> destructor Destroy; override;<br />end;<br /><br />...<br /><br />constructor TBar.Create;<br />begin<br /> inherited Create;<br /> FSomeIntfImpl := TSomeIntfImpl.Create(Self);<br />end;<br /><br />destructor TBar.Destroy;<br />begin<br /> FSomeIntfImpl.Free;<br /> inherited Destroy;<br />end;<br /><br />function TBar.GetSomeIntfImpl: ISomeIntf;<br />begin<br /> Result := FSomeIntfImpl;<br />end;<br /></pre><br />Таким образом, класс TBar наследует интерфейс ISomeIntf и делегирует его реализацию классу TSomeIntfImpl. Таким же образом, мы можем создать и другие классы, кроме TBar, и писать для них полиморфный код, имея на руках ссылку на интерфейс ISomeIntf.<br />TSomeIntfImpl объявлен как потомок TAggregatedObject. Это важно, так как в этом классе методы интерфейса IInterface реализованы таким образом, что просто передают вызовы внешнему объекту. Если бы TSomeIntfImpl сам реализовал эти методы, то клиент не смог бы, имея на руках ссылку на ISomeIntf получить другой интерфейс, даже если класс TBar его и поддерживал. Кроме того, при запросе ISomeIntf счетчик ссылок увеличивался бы не у экземпляра TBar, как следовало бы, а у внутреннего объекта класса TSomeIntfImpl.Inshttp://www.blogger.com/profile/00713778854677687334noreply@blogger.com4