Inside refactoring with Delphi

In this article I will show you two common techniques that I use in my C# projects that they are quite relevant for any other programming language out there. In this case Delphi as I'm sure many developers out there can refer to the same principles. The following code has been tested under Delphi 10.2 Tokyo version.

The first technique is quite used in Functional Programming but it can be related to OOP and it's called Imperative Refactoring. The second technique helps reducing common code and eliminates inconsistencies and it's called Inline Refactoring. See the examples below for guidance.

Imperative Refactoring

This technique is quite easy to understand and I'm sure you've applied this many times in your projects.

In this case, we have a method or function that has some code that we would like to reuse. The principle says that this code needs to be extracted and placed externally onto another function and then add the call where the previous code was. This technique is very simple and very easy to embrace for code reusability.

Here you can see a typical example:

Before Refactoring:
// Copyright (c) 2017, Jordi Corbilla
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// - Neither the name of this library nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
function TMyQuery.Results(url: string): TList<string>;
var
response: string;
IdHTTP: TIdHTTP;
IdIOHandler: TIdSSLIOHandlerSocketOpenSSL;
document: OleVariant;
i : integer;
element: OleVariant;
value : string;
urlList : TList<string>;
begin
CoInitialize(nil);
IdIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
IdIOHandler.ReadTimeout := IdTimeoutInfinite;
IdIOHandler.ConnectTimeout := IdTimeoutInfinite;
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.IOHandler := IdIOHandler;
response := IdHTTP.Get(url);
document := coHTMLDocument.Create as IHTMLDocument2;
document.write(response);
document.close;
urlList := TList<string>.create();
for i := 0 to document.body.all.length - 1 do
begin
element := document.body.all.item(i);
if (element.tagName = 'A') then //the <a> anchors
urlList.Add(element.href); //get the url
end;
result := urlList;
finally
IdIOHandler.Free;
IdHTTP.Free;
end;
finally
CoUninitialize;
end;
end;
As you can see this is a very simple  example where I request a web page and then I do some parsing to get the list of urls that are part of the html document. Let's see how to refactor it to make it more reusable.

After Refactoring:
// Copyright (c) 2017, Jordi Corbilla
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// - Neither the name of this library nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
function TMyQuery.ResultsImperativeRefactoring(url: string): TList<string>;
var
response: string;
IdHTTP: TIdHTTP;
IdIOHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
CoInitialize(nil);
IdIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
IdIOHandler.ReadTimeout := IdTimeoutInfinite;
IdIOHandler.ConnectTimeout := IdTimeoutInfinite;
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.IOHandler := IdIOHandler;
response := IdHTTP.Get(url);
result := ParseHTML(response); //REFACTORING APPLIED
finally
IdIOHandler.Free;
IdHTTP.Free;
end;
finally
CoUninitialize;
end;
end;
function TMyQuery.ParseHTML(response: string): TList<string>;
var
document: OleVariant;
i : integer;
element: OleVariant;
value : string;
urlList : TList<string>;
begin
document := coHTMLDocument.Create as IHTMLDocument2;
document.write(response);
document.close;
urlList := TList<string>.create();
for i := 0 to document.body.all.length - 1 do
begin
element := document.body.all.item(i);
if (element.tagName = 'A') then //the <a> anchors
urlList.Add(element.href); //get the url
end;
result := urlList;
end;
Notice that I've extracted the parsing functionality and I've created a parseHTML function that gets the response and parses it and returns the list of urls. Now I can reuse my parsing functionality should I have any other page where this functionality is required. No-brainer here.

Inline Refactoring

This one is a bit different and it relates to the outer code as a reusable code. Imagine that we would like to refactor the inline functionality: In this example, I'm repeating quite a lot the functionality to fetch an item from the internet but I would like to reuse it so I can a) replace the http component at any time without impacting the rest of the code and b) replace the parsing part so it can return any kind of object:

The idea behind this refactoring is to be able to reuse the external call also using anonymous methods and generics.

Here is the after refactoring code:

After Refactoring:
// Copyright (c) 2017, Jordi Corbilla
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// - Neither the name of this library nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
type
TOperator<T, U> = reference to function(value : T): U;
TFactory<T> = reference to function(value : T) : T;
TRequest<T, U> = class(TObject)
function ResultsInlineRefactoring(value : T; factory : TFactory<T>; operation : TOperator<T, U>) : U;
end;
implementation
function TRequest<T, U>.ResultsInlineRefactoring(value : T; factory : TFactory<T>; operation : TOperator<T, U>) : U;
begin
result := operation(factory(value));
end;
function TMyQuery.ResultsFuncInlineRefactoring(url: string): TList<string>;
var
request : TRequest<String, TList<string>>;
return : TList<string>;
begin
request := TRequest<String, TList<string>>.create();
try
return := request.ResultsInlineRefactoring(url, GetContent, ParseHTML);
finally
request.Free;
end;
result := return;
end;
function TMyQuery.GetContent(url: string) : string;
var
IdHTTP: TIdHTTP;
IdIOHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
CoInitialize(nil);
IdIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
IdIOHandler.ReadTimeout := IdTimeoutInfinite;
IdIOHandler.ConnectTimeout := IdTimeoutInfinite;
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.IOHandler := IdIOHandler;
result := IdHTTP.Get(url);
finally
IdIOHandler.Free;
IdHTTP.Free;
end;
finally
CoUninitialize;
end;
end;
As you can see the idea is to use anonymous methods and generics heavily to be able to reuse most of the functionality and allow the developer to separate the concerns of downloading the page and parsing it. It also allows you to rebuild the component in a different way e.g. in this case I'm using Indy components to request the page but you might like to use another component. Using this approach everything is quite modular and it gives room for testing. Notice that no functionality has changed here.

You can find the full source code of this example in my personal repository on Github.

Jordi.
Embarcadero MVP.

Comments

Popular Posts