уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

uBuild_203x285_outСобственно, как и говорил в прошлом посте, на данный момент при появлении свободного времени я изучаю возможности рефакторинга в Delphi XE. Этот пост, как и пост по Git, является чем-то вроде моей онлайн-шпаргалки и, думаю, что будет полезен тем, кто хочет узнать в общих чертах, что предлагает нам Delphi XE в плане рефакторинга наших с Вами проектов.

Прежде всего стоит определиться с тем, что такое рефакторинг в принципе. В принципе, на данный момент, я понимаю под словом “refactoring”  улучшение кода программы с целью его (кода) наилучшего понимания. Можно это определение корректировать, уточнять, но суть останется та же — улучшение.

Введение

Зачем нам с Вами “заморачиваться” над рефакторингом, улучшением понимания кода и т.д.? Конечно, если программа пишется “на раз” – написал и забыл, то да, не стоит даже и лезть в рефакторинг. Можно даже написать программу, собрать exe-шник, раздать/продать, а все исходники удалить, чтоб не дай боже руки не потянулись разбирать через год весь этот мусор. Однако, если предполагается, что Ваш проект будет жить и развиваться далее, то рефакторинг просто жизненно необходим. И вне зависимости от того знали ли Вы до сегодняшнего дня, что есть такое понятие или нет Вы использовали рефакторинг в своих проектах и не один раз.

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

Вы пишете программу, скажем,  простенький почтовый клиент. Первое с чего начинается обычно работа (исключая предварительную работу по продумыванию структуры, функций и т.д.)  в Delphi  – открыли IDE, создали проект, накидали на главную форму компонентов и начинаем писать обработчики при этом, естественно, большинство из нас забывает дать всем компонентам на форме осмысленные, понятные имена отличные от стандартных. К чему приведет такая работа, скажем, через месяц? Как минимум к тому, что будет очень затруднительно вспомнить для чего служит Memo1 или  как называется правильно label куда выводится сообщение. Придётся сначала искать где вообще происходит присвоение значений, потом присваивать новое имя компоненту, а это в свою очередь приведет к тому, что при следующей компиляции Delphi выведет нам несметное количество Error’ов, как бы намекая, что такого компонента-то нету и т.д. и т.п. И будем горевать над кодом пол дня. Знакомая ситуация? Так вот для избегания таких ситуаций в том числе и предназначен рефакторинг – сделать Ваш код понятным на столько, чтобы и через 3 года Вы открыли код и вспомнили, что и к чему.

Между прочим, некоторые IDE, например, тот же Lazarus (если мне не изменяет память) способны по ходу написания кода и переименования компонентов на форме корректировать исходник и при этом не требуется устанавливать какие-либо дополнительные плагины – в Delphi XE переименование компонента через Object Inspector затрагивает только то, что на виду, т.е. – объявление поля в классе формы, объявление обработчика, а имя переменной внутри метода остается без изменений и в результате приводит к ошибкам компиляции. Но это так, к слову. И раз уж я начал описание рефакторинга с примера использования в программах непонятных названий компонентов и переменных, то с этой функции я и начну свой небольшой обзор.

Если заглянуть в меню Refactor, то можно увидеть следующий набор разнообразных опций:

refactor

Собственно, если перед этим почитать небольшую, но достаточно познавательную статью в Вики, то станет более менее понятно, что каждая из этих опций реализует один из методов рефакторинга. Посмотрим как работают эти функции на практике.

Rename – переименование

Как я сказал выше, если по имени переменной нельзя сразу понять для чего она предназначена, то на чтение кода приходится затрачивать дополнительное время. Когда проект небольшой – в 1000-1500 строк кода, то времени уходит не много, но если проект большой, то, иногда без бутылки пива и не разберешься, что и к чему работает.

Если при просмотре кода своей программы Вы столкнулись с такой ситуацией, то опция Rename (Shift+Ctrl+E) – это то, что Вы должны применить сразу же (иначе через полчаса опять все забудется :)).

Для примера работы этой функции я создал небольшое приложение в котором “забыл” присвоить кнопке осмысленное имя и оставил его по умолчанию – Button1, затем я написал обработчик onClick для этой кнопки, а также ещё в нескольких местах программы использовал переменную Button1, скажем, что-то типа:

unit Unit2;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
 
type
  TForm2 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form2: TForm2;
 
implementation
 
{$R *.dfm}
 
procedure TForm2.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Add('Нажали кнопку Buton1');
end;
 
procedure TForm2.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add('Нажали кнопку Buton2');
  Memo1.Lines.Add('Присвоили кнопке Button1 новое значение Caption = "Кнопка"');
  Button1.Caption:='Кнопка';
end;
 
end.

 

Код выглядит жутко, но мы сейчас все поправим. Так как это всего лишь пример использования Rename, то зададим первой кнопке (Button1) новое имя, скажем «btnSimple«. Для этого ищем в любом месте нашего кода упоминание Button1, скажем в обработчике Button2Click выделяем имя переменной и жмем Shift+Ctrl+E. Появится новое окно с предложением задать новое имя переменной:

refactoring_rename

Заносим в поле “New name” новое имя переменной – btnSimple, жмем Ok и…в исходнике ничего не переименуется. Вместо этого Delphi покажет нам список тех действий, которые произойдут, если Вы примените изменения. В нашем случае окно Refactorings будет выглядеть следующим образом:

refactoring_rename_2

И такое поведение IDE вполне оправданно и логично. Вы можете сколько угодно долго выискивать непонятные места в коде, задавать новые операции для рефакторинга, а среда будет Ваши действия запоминать. В любой момент времени Вы можете пересмотреть все предлагаемые изменения, внести в них коррективы и потом одним кликом по кнопке Apply Refactoring (Ctrl+R) внести в исходник все изменения. Также у Вас появляется и возможность отката изменений.

Теперь после того, как мы применим операцию переименования к нашей кнопке в нашем исходнике все упоминания Button1 автоматически сменятся на btnSimple и не будет никаких ошибок при повторной компиляции проекта. При этом, обратите внимание на то, что название обработчика OnClick тоже сменилось и вместо Button1Click стало btnSimpleClick.

С Rename разобрались, двигаемся далее прямо по ходу листинга представленного выше. Что остается “не так” кроме имён компонентов? Естественно, использование строк внутри методов. Что, если через год мы решим, что наша программа должна выдавать сообщения не на русском, а на английском языке? Лопатить весь код, выискивая строки и, помещая их в секцию resourcestring? Не вариант, тем более, что есть замечательная функция рефакторинга – Extract resource string.

Extract resource string

Итак, для примера вынесем какую-нибудь строку в секцию resourcestring. Выделяем эту строку в редакторе исходного кода и жмем Shift+Ctl+L – снова новое окно, но уже по-меньше предыдущего в котором необходимо задать имя для нашей строки:

refactor_extract_res_string

Задаем любое понятное имя (я обычно для всех ресурсных строк использую префикс rs) и жмем “Ок“ и в этом случае изменения попадут в исходник сразу же. В данном случае, опять же никаких противоречий в работе рефакторинга в Delphi нет, т.к. использование “волшебных значений” в коде есть зло и врядли программисту в здравом уме придёт в голову откатить такое правильное изменение исходника – вынести строковое значение в секцию resourcestring.

Удобная функция? Несомненно, особенно, если модуль достаточно большой по количеству строк.

Двигаемся далее. Для следующего примера используем не используемую ранее кнопку Button3.

Extract Method

Для порядка я переименовал компонент и присвоил кнопке имя btnOpenFile. Пусть обработчик OnClick у неё будет такой:

procedure TForm2.btnOpenFileClick(Sender: TObject);
begin
if dlgOpenFile.Execute then
  begin
    XML:=TNativeXml.Create;
    try
      XML.LoadFromFile(dlgOpenFile.FileName);
      if not XML.IsEmpty then
        begin
          Memo1.Lines.Add(Format(rsRootNode,[XML.Root.Name]));
          {продолжаем парсинг XML ещё на строк эдак 100 :)}
        end;
    finally
      XML.Free;
    end;
  end;
end;

 

Вместо комментария в коде содержатся ещё по меньшей мере 100 строк разбора открытого XML-файла, проверок имен узлов, атрибутов и т.д. Что в данном случае противоречит здравому смыслу и может вогнать в ступор человека впервые увидевшего Ваш код? Во-первых, несоответствие имени метода и его содержимого. По логике вещей клик по кнопке должен открыть файл, но не парсить его. Чтобы избежать такой “подмены” мы можем:

  1. Переименовать метод и присвоить ему новое, более подходящее имя
  2. Разделить метод на два – первый метод так и будет открывать файл, а второй – парсить.

Как работает переименование мы уже знаем, поэтому воспользуемся вторым способом – выделим всё, что относится к парсингу XML в новый метод. Для этого воспользуемся функцией Refactor/Extract Method.

Выделяем код, относящийся непосредственно к парсингу XML и жмем Shit+Ctrl+M – в открывшемся окне даем осмысленно имя новому методу:

refactor_extract_method

Здесь следует обратить внимание на такой момент. Вы не сможете прямо в поле названия метода определить и список параметров – появится сообщение об ошибке:

refactor_extract_method_Error Поэтому пока довольствуемся тем, что выделяем участок кода и выносим его в новый метод ParseXML. После нажатия кнопки Ok выделенный код будет перенескн в новый метод, а Delphi корректно определит новый метод в классе и в правильном месте вставит его вызов в исходном методе, т.е. получим в итоге такой код:

type
  TForm2 = class(TForm)
    Memo1: TMemo;
    smpl: TButton;
    Button2: TButton;
    btnOpenFile: TButton;
    dlgOpenFile: TOpenDialog;
    procedure btnOpenFileClick(Sender: TObject);
    procedure smplClick(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    XML:TNativeXml;
    procedure ParseXML;//это объявление сделано автоматически
  public
    { Public declarations }
  end;
 
var
  Form2: TForm2;
 
implementation
 
....
 
{$R *.dfm}
 
procedure TForm2.btnOpenFileClick(Sender: TObject);
begin
if dlgOpenFile.Execute then
  begin
    XML:=TNativeXml.Create;
    try
      XML.LoadFromFile(dlgOpenFile.FileName);
    ParseXML; //правильно вставленный вызов метода
    finally
      XML.Free;
    end;
  end;
end;
 
procedure TForm2.ParseXML; //наш новый метод
begin
  if not XML.IsEmpty then
  begin
    Memo1.Lines.Add(Format(rsRootNode, [XML.Root.Name]));
      {продолжаем парсинг XML ещё на строк эдак 100 :)}
  end;
end;

 

Теперь, пока не отошли далеко от работы с методами, рассмотрим как изменять состав параметров. Чем хорош наш ParseXML? Этот метод выполняет свою узкую функцию – парсит XML-файл. В данном случае сама переменная, содержащая данные определена в классе Form2. Но вполне может возникнуть такая ситуация, когда нам захочется/потребуется сделать так, чтобы в ParseXML данные поступали в виде параметра. Для того, чтобы изменить список параметров метода в рамках нашего рефакторинга мы должны воспользоваться функцией Change Params…

Change Params – изменение списка параметров

Выделяем в любом месте кода имя метода – ParseXML и нажимаем сочетание клавиш Shift+Ctrl+X. Откроется окно, содержащее список параметров для метода. В нашем случае список будет пустым:

refactor_change_params

Для добавления нового параметра нажимаем кнопку Add… В открывшемся окне мы должны задать название параметра, тип данных и тип параметра (var, const и т.д.):

refactor_change_params_add

На этом окне остановимся чуть более подробно. Что касается полей “Parametr Name”, “Data Type” и списка “Parametr Type”, то тут, думаю, всё и так интуитивно понятно – заносится имя параметра, тип данных и выбирается тип самого параметра. При этом в момент набора с клавиатуры типа данных Delphi будет автоматически подыскивать по всем модулям, в т.ч. и не “родным”, таким как NativeXML, наиболее подходящий тип.

Остается поле “Literal Value”. Для чего оно используется? Чтобы понять его назначение, запишем в него такую строку – “XML”. Теперь жмем “Ок” и в окне Refactorings применяем изменения. А теперь обратите внимание на вызов метода ParseXML в обработчике btnOpenFileClick. Вызов стал таким:

ParseXML(XML);

 

А сам метод теперь имеет такое описание:

 
  procedure ParseXML(XMLFile: TNativeXml);

 

Если бы мы не задали значение в поле “Literal Value”, то в итоге при компиляции проекта Delphi справедливо бы выдало ошибку, сказав “[DCC Error] E2035 Not enough actual parameters”.

Вот те четыре функции рефакторинга, которые могут и будут требоваться практически всегда и везде. Оставшиеся функции не менее полезны и интересны в использовании, но о них немного позже, а пока попробую сформулировать несколько моментов для проведения успешного рефакторинга проектов с использованием представленных выше функций.

В части работы с переменными:

  • Имя переменной должно содержать какой-либо префикс, который поможет нам узнать тип данных, например btn – укажет на то, что это переменна TButton, int – integer и т.д.;
  • Имя переменной должно быть осмысленное — без лишних пояснений должно быть понятно, что за этим именем скрывается.
  • Одна и та же переменная не должна выполнять сразу два действия в одном и том же блоке кода. Если переменная сначала используется для записи в неё размера файла, а потом выступает в качестве счётчика цикла, то это усложнит понимание код, следовательно код необходимо будет подвергать рефакторингу.

 

В части работы с методами:

  • Как и при работе с переменными, метод должен иметь осмысленное название;
  • Метод, по возможности, должен быть узконаправленным и выполнять ту задачу,которая на него возлагается. При этом не стоит разбивать взаимосвязанные операции, например, открытие файла и проверка его существования.
  • В части работы с методами следует также помнить, что размер имеет значение – если метод громоздкий и не помещается хотя бы в экран, то стоит задуматься о рефакторинге и выделении из этого метода одного или нескольких самостоятельных.

 

И в качестве заключения немного слов о том, что даст рефакторинг Вам, как разработчику:

  1. Несмотря на кажущуюся головную боль из-за перебирания кода, выделения непонятных мест их исправления и т.д. правильно проведенный рефакторинг значительно увеличит читабельность и, соответственно, понимание Вашего кода не только Вами, но и другими разработчиками.
  2. Есть реальная возможность найти “проколы” в логике приложения, особенно, если дело касается крупных проектов.
  3. Рефакторинг позволяет повысить безопасность Ваших разработок, т.к. см. п.1-2.
  4. Мусор в проектах имеет тенденцию быстро расти и множится – рефакторинг как в процессе работы над проектом так и после позволяет избежать такого накопления.

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

Кстати, по части написания профессионального кода, говорят, очень полезная книга Макконнелла “Совершенный код”, сам пока полностью книгу не читал, но некоторые выдержки из книги встречал. Думаю, стоит такую книгу заиметь на своей книжной полке.

Книжная полка

Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
купить книгу delphi на ЛитРес
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
Описание: Книга рассчитана на подготовленного пользователя ПК, желающего самостоятельно научиться программировать и разрабатывать приложения и базы данных в среде Delphi. Опытные программисты смогут использовать издание как справочник. В тексте подробно описаны более 80 компонентов VCL, функции Object Pascal и Win32 API.
купить книгу delphi на ЛитРес
Автор: Юрий Магда
Описание: Описаны общие подходы к программированию приложений MS Office. Даны программные методы реализации функций MS Excel, MS Word, MS Access и MS Outlook в среде Delphi.
купить книгу delphi на ЛитРес
4 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
10 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Александр
14/05/2011 21:13

Кроме Совершенного кода рекомендую ещё Фаулера с его книгой по рефакторингу. Хорошо направляет в нужное русло.

BB
BB
15/05/2011 10:37

“Совершенный код” — а я б вот твою книжку с удовольствием заимел, с тем же самым что на сайте! подумай об этом, как у Гришковца есть несколько книжек «Год ЖЖизни» назывались, с его статьями за год в ЖивомЖурнале..
или хоть в пдф бы это все в кучку.. а то времени читать сайт очень мало((( я бы даже купил такой pdf..  но к сожалению в отличии от книжки pdfы быстро нагулят:) и про спасибо забудут… да и бумажная книжка удобнее. :)
про статью — полезно, давно на реф. посматривал да не понимал что и как.

BB
BB
15/05/2011 15:26

Ribbon… а я еще дежавю какое-то испытывал когда про pdf писал, в ленте видел это когда появилось, но не заинтересовало тогда :)  .. так что на не Ribbon pdfки я первый в очереди, с автографом ;)))

JayDi
15/05/2011 17:19

Переименование не работает в больших проектах, где библиотеки с кодом подключаются через uses. Т.е. если в списке файлов проекта нет нужного исходника, то он не будет проверяться на вносимые изменения.
 
Также переименование иногда страшно глючит в старых версиях делфи (2006-2010) — может похерить пол модуля с кодом, удалив или добавив непонятный текст. Поэтому рекомендуется сразу после изменений проверять, все ли в порядке.

JayDi
15/05/2011 17:25

Единственные полезные для себя команды, которые регулярно использую:
CTRL+SHIFT+E — переименование
CTRL+SHIFT+A — поиск недостающего модуля и его добавление в uses.
 
Например, хотим использовать функцию для нахождения минимального значения — пишем «min», жмем CTRL+SHIFT+A и в списке найденный модулей выбираем Math — он сразу добавится в uses, если его еще нет. Очень удобно. К сожалению, эта фича также не всегда работает из-за давно извесных ошибок с «не видением» модулей и их исходников для анализа/поиска.

Евгений
Евгений
05/09/2011 05:58

>>Имя переменной должно содержать какой-либо префикс
Не согласен. Зачастую и так понятно какого типа переменная (достаточно взглянуть на код или навести курсор мыши на имя переменной). У меня зачастую проблемы возникают с тем, что бы имя было коротким и  информативным. Частенько случается так, что имя информативное, но длинное и тратить драгоценное место на и идентификацию ее типа нет никакого желания.