21 ноября 2011 г.

Code Templates

В 7-ой версии Delphi настраивать шаблоны очень просто: Tools -> Editor Options….
 На закладке Source Options жмем кнопку Edit Code Templates… и открывается окно редактора шаблонов,
где доступно изменение существующих, создание новых, импорт и экспорт шаблонов.

Разберемся, как сделать свой шаблон в Delphi 2007.

Если почитать справку (иногда полезно :) ), то вот что можно выяснить:
To add a template:
  1. Choose Tools -> Options -> Editor Options.
  2. Click the Source Options tab and then the Edit Code Templates button.
  3. In the Templates section, click Add.
  4. Type a name for the template after Shortcut name, enter a brief description of the new template, and click OK.
  5. Add the template code to the Code text box.
  6. Click OK.
но если пройти на указанную закладку диалога настройки, то никакой кнопки найти там не удастся.
Исследование главного меню подсказывает, что нужный нам список можно найти тут: View -> Templates.
Это окно не так функционально, как в предыдущих версиях Delphi, но здесь можно добавлять новые шаблоны и редактировать имеющиеся. То же самое можно сделать и прямым доступом к файлам, файлы шаблонов лежат в папке $(BDS)ObjRepos\Code_Templates.

Например, стандартный шаблон tryf я исправил таким образом:
<?xml version="1.0" encoding="utf-8" ?>

<codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
    version="1.0.0">
 <template name="tryf" surround="true" invoke="manual">
  <description>
   try finally
  </description>
  <author>
   Embarcadero
  </author>
  <code language="Delphi" context="methodbody" delimiter="|"><![CDATA[try

finally
|selected||*||end|
end;
]]>
  </code>
 </template>
</codetemplate>
Теперь курсор после вызова становится между finally и end. А вот пример нового шаблона mess, я использую его для вывода отладочных сообщений (вместо ShowMessage):
<?xml version="1.0" encoding="utf-8" ?>
 
<codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
    version="1.0.0">
 <template name="mess" surround="true" invoke="manual">
  <point name="message">
   <hint>
    Сообщение для показа
   </hint>
   <text>
    сообщение
   </text>
  </point>
  <description>
   MessageBox
  </description>
  <author>
   mínich
  </author>
  <code language="Delphi" context="methodbody" delimiter="|">
    <![CDATA[MessageBox(0, PChar(|selected||message||end|), nil, MB_OK);]]>
  </code>
 </template>
</codetemplate>
В Delphi XE всё то же самое, что и в более старой Delphi 2007, разве что справка не содержит досадной неточности.
При редактировании файлов шаблонов не через среду, а сторонними редакторами, обратите внимание на кодировку: если шаблон содержит кириллицу, то сохраняйте файл в кодировке UTF-8, а не ANSI.

24 октября 2011 г.

VCS: одна и только одна

Многие сочтут это аксиомой, не требующей упоминания, но я озвучу:
Разрабатываемый проект единовременно должен находиться в единственной системе контроля версий (VCS).
Не должно быть никаких «локальных» версий, «для личного пользования», «для тестов» и т.д. Только одна версия. Казалось бы, ну что такого, если я возьму совместно разрабатываемый код и самостоятельно поведу собственную ветку. Она лично моя, никому больше не нужна, на ней я экспериментирую и отрабатываю идеи. При всей логичности этого подхода, он опасен. Дело, опять же, в человеческом факторе. Не под силу нормальному человеку помнить обо всём.
Исповедуя такой подход, вы рискуете попасть в самостоятельно расставленную ловушку.

Варианты:
  1. Вопрос: Я это сделал. Почему же этого нет в основной ветке? Ответ: Решение забыли синхронизировать из собственной базы в общую. Надо синхронизировать, а это трудоёмко, поскольку утекло немало воды.
  2. Вопрос: Я это сделал. Почему этого нет ни в основной ветке, ни в моей локальной базе? Ответ: Решение забыли перенести в общую базу, а локальную убили. Переделывать.
  3. Вопрос: Что за херня тут понаписана, я совсем не так делал? Ответ: Решение не забыли синхронизировать, но сделали это криво.

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

Но всё же, как поступить, если есть желание замутить очень серьёзный эксперимент, а выкладывать творения рук своих в общий источник нельзя? Да очень просто: в общей базе требуется стандартными средствами создать ветку для экспериментов, которую по завершении удалить, чтобы не работать с ней даже случайно. Тогда код будет можно достать для исследования, а если он пойдёт в основную ветку, то сливаться будет в автоматическом режиме, что значительно снизит вероятность ошибки.
Потому не заводите на своей машине базы VCS. Не храните архив исходников, их вы всегда сможете взять из базы. А сохранение базы в рабочем состоянии уже не ваша головная боль.

20 октября 2011 г.

Notepad++

Давно и прочно прикипел к этому текстовому редактору. Поистине универсальная вещь. Знаю, что некоторые умудряются даже код в нём писать, но, как по мне, так специально для этого разработанная среда программирования всё же лучше.
Более всего использую Notepad++ для приведения текстовых данных к структурированному виду. Расскажу, как я это делаю.

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

Имя

Дата
1

Иванов И.П.

22.09.2010
2

Петров В.В.

17.08.2010
3

Сидоров П.В.

17.12.2010
Видно, что данные имеют верный порядок и их, в принципе, несложно привести к табличному виду вручную. Хорошо, это несложно для трёх строк, а что делать, когда таких строк тысячи? Вот тут на помощь и приходят макросы редактора. Становимся в начало первой строки, жмём кнопку «Старт записи» и аккуратно приводим первую строку в благовидное состояние, пользуясь только кнопками клавиатуры. Завершив обработку первой строки, ставим курсор в начало второй строки и жмём «Стоп записи». После этого пользуемся чудесами автоматизации, нажав кнопку «Запустить многократно…» — остальные строчки редактор приведёт в норму самостоятельно.

А ещё он замечательно умеет подсвечивать синтаксис десятков языков программирования, то есть Notepad++ идеален в качестве универсального просмотрщика текстовых документов.

Ещё я с помощью этого редактора производил множественные замены в файлах.

Сегодня устанавливал обновление, улыбнулся на последнем этапе мастера установки (нижний чекбокс):

14 октября 2011 г.

Комментарии излишни

Нас учат, что нужно снабжать свой код комментариями, мол, с кодом гораздо проще работать, если он хорошо комментирован. Ничего подобного. Код должен быть понятен и легко читаем и безо всяких комментариев.
Банальные комментарии
// Увеличиваем счетчик.
Inc(FCount);
только замусоривают код.
От менее банальных комментариев
// Увеличиваем счетчик.
Inc(a);
избавляемся рефакторингом: переименовываем идентификаторы, упрощаем уловия, выделяем методы и т.д. Комментарий нужен тогда, когда он действительно нужен. Причем писать его нужно так, чтобы он был понятен постороннему, а не только вам.
// Не убирать!!!
Inc(FCount);
Поверьте, если вам не повезло, и у вас нет абсолютной памяти, через очень непродолжительное время вы не сможете вспомнить, что же вы имели в виду, оставляя такой идиотский комментарий. И вам придётся разбираться в коде. В своём коде!
// Увеличиваем счетчик,
// так как произошло неявное увеличение числа элементов.
Inc(FCount);
Но, повторяю, необходимость такого комментария свидетельствует о том, что код требуется переработать.
Не стоит также оставлять автографов в случае коллективной работы над проектом. Для исследования хронологии и авторства изменений кода есть куда-лучшие средства.
// Добавил КулХацка.
Inc(FCount);

Отдельно хотелось бы отметить комментирование кода вместо удаления.
// Закомментировано, это не требуется.
// Inc(FCount);
Согласен, рука с трудом поднимается на результаты собственных усилий. Душу терзает мысль, что код наверняка пригодится, зачем же его удалять, достаточно будет раскомментировать. Уверяю: не пригодится. Такой подход только замусоривает код. Отвлекает и мешает. Запустите поиск — он найдет искомое в закомментированном участке. Начнете читать метод, а в нём вместо пяти рабочих строчек все сорок-пятьдесят, большая часть из которых закомментированы (доводилось видеть и такое).
То же самое относится и к хорошему, но не используемому коду. Обнаружился модуль, класс, метод, который в данный момент никак не используется — в корзину.
Не стесняйтесь. Надо убрать код — удаляйте. Система контроля версий (вы ведь ею пользуетесь?) сохранит всё гораздо лучше вас.

Форматирование кода

Всем известно, что нельзя пренебрегать форматированием, потому что … (тут идёт перечисление множества плюсов применения аккуратного форматирования исходного кода). Просто примите как аксиому, что код нужно писать так, как то велят делать спецификации. Нужны примеры — их много в исходниках, идущих со средой разработки, обычно они очень хорошо оформлены.
С правильно отформатированным кодом и разбираться проще, приведу совсем свежий пример (из RX Library):
procedure TRxLookupControl.SetValue(const Value: string);
begin
  if (Value <> FValue) then begin // Polaris // begin added
    if CanModify and (FDataLink.DataSource <> nil) and FDataLink.Edit then
    begin
//      if FMasterField <> nil then FMasterField.AsString := Value
//      else FDataField.AsString := Value;
      if FMasterField <> nil then SetFieldValue(FMasterField,Value) // Polaris
      else SetFieldValue(FDataField,Value); // Polaris
    end
    else // begin  // Polaris
      SetValueKey(Value);
      Change;
    end;
end;
Не знаю, как у вас, а у меня произошел вынос мозга, когда я пытался понять, что тут происходит. Однако после нескольких лёгких пассов происходит волшебное преображение:
procedure TRxLookupControl.SetValue(const Value: string);
begin
  if (Value <> FValue) then
  begin
    if CanModify and Assigned(FDataLink.DataSource) and FDataLink.Edit then
      if Assigned(FMasterField) then
        SetFieldValue(FMasterField, Value)
      else
        SetFieldValue(FDataField, Value)
    else
      SetValueKey(Value);
    Change;
  end;
end;
Как говорится, комментарии излишни.

Эта проблема отсутствует как класс в некоторых языках программирования, где форматирование имеет значение не только для разработчика, который пишет или читает этот код, но и для компилятора (например, Python). Неправильно форматированный код будет неправильно работать.

13 октября 2011 г.

Строки. PChar и PAnsiChar

Строки… Как много в этом слове собралось…
Очень часто нам приходится работать со строками, ни одна программа не обходится без этого. А уж если со строками работа ведется активно, то начинаются танцы с бубном в целях оптимизации работы программы.
Указатели — одна из вех в достижении оптимальной обработки строковых данных. В эпоху однобайтовых строчек в Delphi позволялись вольности в работе с указателями. Теперь же, когда типов строк стало много, возникли сложности. Уже не напишешь
pStr + 1
для получения указателя на следующий символ, приходится брать во внимание тип строки.
Есть такой вариант:
pStr + SizeOf(Char)
или
pStr + SizeOf(AnsiChar)
в зависимости от типа указателя.
Есть универсальный вариант:
pStr + N * SizeOf(pStr^)

Однако в Delphi есть замечательные системные фунции для простого получения указателя. Это Inc и Dec. Компилятор самостоятельно разберется с типом указателя и точнёхонько переместит указатель на заданное количество символов:
Inc(pStr, N)
Dec(pStr, N)
Надо учесть, что эти функции изменяют значение параметра pStr. Если необходимо его сохранить, то вводится дополнительная переменная. Если необходимо получить указатель на следующий символ, то на помощь придёт функция StrNextChar, которая так же умеет принимать параметр указателя любого типа:
StrNextChar(pStr)
К сожалению, нет функции для движения в обратном направлении и получения указателя через произвольное количество символов. Однако на основании изложенного материала реализовать такие функции самостоятельно не составит труда.

11 октября 2011 г.

Fucking Great Adviser

Неожиданно занялся разработкой нового приложения: Windows-клиента для сервиса Охуенный блять совет. С сервисом можете ознакомиться самостоятельно.
Разработку начал с использованием SourceForge.net, проект прописался тут: FGreatAdviser. Надеюсь, что затея будет доведена мной до логического конца, и в результате появится продукт, по первоначальному замыслу это скринсейвер и генератор обоев рабочего стола.
Разработку веду на привычной мне Delphi, версия выбрана XE. Не потому, что предыдущие, более распространённые версии плохо подходят для решения этой задачи, а просто чтобы лучше освоить эту версию.
На данный момент прорабатываются основные рабочие процедуры будущего приложения, чего-то цельного нет и в помине.

4 октября 2011 г.

Одиночка: доработка

Здесь описывается ход доработки моего шаблона Одиночка. Первоначальный вариант кода шаблона был таким:
unit Singleton;

interface

uses
  SysUtils;

type
  TSingleton = class(TObject)
  strict private class var
    FInstance: TSingleton;
  private
    constructor CreateInstance;
  public
    constructor Create;
    destructor Destroy; override;
    class function Instance: TSingleton;
    class procedure DestroyInstance;
  end;

implementation

resourcestring
  // Сообщение об ошибке прямого создания экземпляра класса,
  // минуя классовый метод получения.
  E_SINGLETON_DIRECT_INSTANCE_CREATION =
    'Нельзя создавать экземпляр класса %s напрямую. ' +
    'Используйте функцию Instance';

{ TSingleton }

constructor TSingleton.Create;
begin
  raise Exception.CreateFmt(E_SINGLETON_DIRECT_INSTANCE_CREATION,
    [ClassName]);
end;

constructor TSingleton.CreateInstance;
begin
  inherited Create;

end;

destructor TSingleton.Destroy;
begin

  inherited;
end;

class procedure TSingleton.DestroyInstance;
begin
  FreeAndNil(TSingleton.FInstance);
end;

class function TSingleton.Instance: TSingleton;
begin
  if not Assigned(FInstance) then
    FInstance := CreateInstance;
  Result := FInstance;
end;

function DestroyInstances: Boolean;
begin
  TSingleton.DestroyInstance;
  Result := True;
end;

initialization
  AddTerminateProc(DestroyInstances);

end.
Этот код корректен, но имеет серьёзный недостаток: он вынуждает очень много дублировать. Каждый раз, когда необходимо создать одиночку, мы вынуждены полностью реализовывать всю логику класса, пусть вся работа и заключается в автоматической замене имени класса. Нет методу Copy-Paste!
Доработаем код шаблона, создадим базовый класс, позволяющий просто наследоваться от него.
Функции базового класса:
  1. Слежение за единственностью экземпляра каждого класса наследника.
  2. Уничтожение всех экземпляров наследников при завершении.
С учетом этих требований новый код получился таким:
unit Singleton;

interface

uses
  Classes, Contnrs, SysUtils;

type
  // Базовый класс одиночки.
  TSingleton = class(TObject)
  strict private class var
    FInstanceList: TObjectList;
  strict private
    class function GetInstanceList: TObjectList;
    class function GetInstance: TSingleton;
  private
    class procedure DestroyInstances;
  strict protected
    constructor CreateInstance; virtual;
  public
    constructor Create;
    procedure AfterConstruction; override;
    class function Instance: TSingleton;
  end;

implementation

resourcestring
  // Сообщение об ошибке прямого создания экземпляра класса,
  // минуя классовый метод получения.
  E_SINGLETON_DIRECT_INSTANCE_CREATION =
    'Нельзя создавать экземпляр класса %s напрямую. ' +
    'Используйте классовый метод Instance';

{ TSingleton }

procedure TSingleton.AfterConstruction;
begin
  inherited AfterConstruction;
  GetInstanceList.Add(Self);
end;

constructor TSingleton.Create;
begin
  raise Exception.CreateFmt(E_SINGLETON_DIRECT_INSTANCE_CREATION,
    [ClassName]);
end;

constructor TSingleton.CreateInstance;
begin
  inherited Create;
end;

class procedure TSingleton.DestroyInstances;
var
  InstanceList: TObjectList;
begin
  InstanceList := GetInstanceList;
  // Очищаем с конца списка.
  while InstanceList.Count > 0 do
    InstanceList.Delete(InstanceList.Count - 1);
  FreeAndNil(FInstanceList);
end;

class function TSingleton.GetInstance: TSingleton;
var
  I: Integer;
  InstanceList: TObjectList;
  Instance: TObject;
begin
  Result := nil;
  InstanceList := GetInstanceList;
  for I := 0 to InstanceList.Count - 1 do
  begin
    Instance := InstanceList.Items[I];
    if Instance.ClassType = Self then
    begin
      Result := TSingleton(Instance);
      Break;
    end;
  end;
  if not Assigned(Result) then
    Result := CreateInstance;
end;

class function TSingleton.GetInstanceList: TObjectList;
begin
  if not Assigned(FInstanceList) then
    FInstanceList := TObjectList.Create;
  Result := FInstanceList;
end;

class function TSingleton.Instance: TSingleton;
begin
  Result := GetInstance;
end;

function DestroyInstances: Boolean;
begin
  Result := True;
  TSingleton.DestroyInstances;
end;

initialization
  AddTerminateProc(DestroyInstances);

end.
Теперь для получения одиночки достаточно просто унаследоваться от базового класса:
TMySingleton = class(TSingleton);
и по необходимости доработать виртуальный конструктор CreateInstance. Также можно изменить классовый метод получения экземпляра одиночки, чтобы не приводить тип результата:
TMySingleton= class(TSingleton)
  strict protected
    constructor CreateInstance; override;
  public
    class function Instance: TMySingleton;
  end;

constructor TMySingleton.CreateInstance;
begin
  inherited CreateInstance;
end;

class function TMySingleton.Instance: TMySingleton;
begin
  Result := TMySingleton(inherited Instance);
end;

3 октября 2011 г.

Одиночка

Всё лучшее придумано до нас, но мы стремимся всё если не улучшить, то переделать под себя. Вот и у меня дошли руки до создания шаблона класса-одиночки (Singleton). Интересующихся подробностями отправляю в википедию: Singleton pattern (английский) и Одиночка (шаблон проектирования).
Собственно, код:
unit Singleton;

interface

uses
  Classes, Contnrs, SysUtils;

type
  // Базовый класс одиночки.
  TSingleton = class(TObject)
  strict private class var
    FInstanceList: TObjectList;
  strict private
    class function GetInstanceList: TObjectList;
    class function GetInstance: TSingleton;
  private
    class procedure DestroyInstances;
  strict protected
    constructor CreateInstance; virtual;
  public
    constructor Create;
    procedure AfterConstruction; override;
    class function Instance: TSingleton;
  end;

implementation

resourcestring
  // Сообщение об ошибке прямого создания экземпляра класса,
  // минуя классовый метод получения.
  E_SINGLETON_DIRECT_INSTANCE_CREATION =
    'Нельзя создавать экземпляр класса %s напрямую. ' +
    'Используйте классовый метод Instance';

{ TSingleton }

procedure TSingleton.AfterConstruction;
begin
  inherited AfterConstruction;
  GetInstanceList.Add(Self);
end;

constructor TSingleton.Create;
begin
  raise Exception.CreateFmt(E_SINGLETON_DIRECT_INSTANCE_CREATION,
    [ClassName]);
end;

constructor TSingleton.CreateInstance;
begin
  inherited Create;
end;

class procedure TSingleton.DestroyInstances;
var
  InstanceList: TObjectList;
begin
  InstanceList := GetInstanceList;
  // Очищаем с конца списка.
  while InstanceList.Count > 0 do
    InstanceList.Delete(InstanceList.Count - 1);
  FreeAndNil(FInstanceList);
end;

class function TSingleton.GetInstance: TSingleton;
var
  I: Integer;
  InstanceList: TObjectList;
  Instance: TObject;
begin
  Result := nil;
  InstanceList := GetInstanceList;
  for I := 0 to InstanceList.Count - 1 do
  begin
    Instance := InstanceList.Items[I];
    if Instance.ClassType = Self then
    begin
      Result := TSingleton(Instance);
      Break;
    end;
  end;
  if not Assigned(Result) then
    Result := CreateInstance;
end;

class function TSingleton.GetInstanceList: TObjectList;
begin
  if not Assigned(FInstanceList) then
    FInstanceList := TObjectList.Create;
  Result := FInstanceList;
end;

class function TSingleton.Instance: TSingleton;
begin
  Result := GetInstance;
end;

function DestroyInstances: Boolean;
begin
  Result := True;
  TSingleton.DestroyInstances;
end;

initialization
  AddTerminateProc(DestroyInstances);

end.
Функции базового класса:
  1. Слежение за единственностью экземпляра каждого класса наследника;
  2. Уничтожение всех созданных экземпляров наследников при завершении.
Для получения одиночки достаточно просто унаследоваться от базового класса:
TMySingleton = class(TSingleton);
и по необходимости доработать виртуальный конструктор CreateInstance. Также можно изменить классовый метод получения экземпляра одиночки, чтобы не приводить тип результата:
TMySingleton= class(TSingleton)
  strict protected
    constructor CreateInstance; override;
  public
    class function Instance: TMySingleton;
  end;

constructor TMySingleton.CreateInstance;
begin
  inherited CreateInstance;
end;

class function TMySingleton.Instance: TMySingleton;
begin
  Result := TMySingleton(inherited Instance);
end;
Отмечу, что этот код написан для Delphi XE и в Delphi 7, например, компилироваться не будет, потребуется немного доработать его.

История создания шаблона: шаг 1.