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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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:
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.
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
You can find the full source code of this example in my personal repository on Github.
Jordi.
Embarcadero MVP.
Comments
Post a Comment