Thursday, February 14, 2013

Legacy Code: Abstract to Interface

Say you where given the following legacy code snippet, and where told that you needed five new  packet types that need to be written.

   TDataObject = class(TObject)
     // Some Shared Data here.
   end;

   TPacketWriter = class(TObject)
   private
      function CreateObjectX : TDataObject;
      procedure WriteFileZ(Data : TDataObject);
   protected
      procedure WriteFileX(Data : TDataObject); virtual; abstract;
      procedure WriteFileY(Data : TDataObject); virtual; abstract;
   public
      procedure Execute;
   end;

   TPacketWriterTypeA = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   TPacketWriterTypeB = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   ...
   
procedure TPacketWriter.Execute;
var
 lObj : TDataObject;
begin
  lObj := CreateObjectX;
  try
    WriteFileX(lObj);
    WriteFileY(lObj);
    WriteFileZ(lObj);
  finally
    lObj.Free;
  end;
end;

  ...
  // where it was used
var
 PWA : TPacketWriterTypeA;
 PWB : TPacketWriterTypeB;
begin
 PWA := TPacketWriterTypeA.create;
 try
   PWA.Execute;
 finally
   PWA.Free;
 end;

 PWB := TPacketWriterTypeA.create;
 try
   PWB.Execute;
 finally
   PWB.Free;
 end;
end;


Well you might say that is easy I can quickly add the following.


   TPacketWriterNewType1 = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   TPacketWriterNewType2 = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   TPacketWriterNewType3 = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   TPacketWriterNewType4 = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

   TPacketWriterNewType5 = class(TPacketWriter)
   protected
      procedure WriteFileX(Data : TDataObject); override;
      procedure WriteFileY(Data : TDataObject); override;
   end;

But I have goal that all new code should be unit tested. Let just say that WriteFileX and WriteFileY are unique in each case and they are complex pieces of code. In the real world example The Execute Method was even more complex. Writing a test on just the execute method would involve a lot of cut and paste of test code for each instance. Not something that smelled correct to me.

The key here that I want to point out is that when you see virtual; abstract; you should ask if an interface makes sense. In this case the answer I determined that an interface made sense. I refactored the TPacketWriter to implement an interface with the resulting code looking like this:

   TDataObject = class(TObject)
     // Some Shared Data here.
   end;

   IPacketFileWriter = interface
     ['{9BE43D5A-5566-4218-A83A-A54D567AD986}']
     procedure WriteFileX(Data : TDataObject);
     procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriter = class(TObject)
   private
      function CreateObjectX : TDataObject;
      procedure WriteFileZ(Data : TDataObject);
   public
      procedure Execute(aFileWriter : IPacketFileWriter);
   end;

   TPacketWriterTypeA = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterTypeB = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterNewType1 = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterNewType2 = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterNewType3 = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterNewType4 = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

   TPacketWriterNewType5 = class(TInterfacedObject,IPacketFileWriter)
   protected
      procedure WriteFileX(Data : TDataObject);
      procedure WriteFileY(Data : TDataObject);
   end;

...

procedure TPacketWriter.Execute(aFileWriter : IPacketFileWriter);
var
 lObj : TDataObject;
begin
  lObj := CreateObjectX;
  try
    aFileWriter.WriteFileX(lObj);
    aFileWriter.WriteFileY(lObj);
    WriteFileZ(lObj);
  finally
    lObj.Free;
  end;
end;

...

// Where it was used
var
 PW : TPacketWriter;
begin
  PW := TPacketWriter.Create;
  try
    PW.Execute(TPacketWriterTypeA.Create);
    PW.Execute(TPacketWriterTypeB.Create);
    PW.Execute(TPacketWriterNewType1.Create);
    PW.Execute(TPacketWriterNewType2.Create);
    PW.Execute(TPacketWriterNewType3.Create);
    PW.Execute(TPacketWriterNewType4.Create);
    PW.Execute(TPacketWriterNewType5.Create);
  finally
    PW.Free;
  end;
end;

So why do this?

  1. I was able to write a test for TPacketWriter.execute using a Mock Object that implemented the  Interface
  2. I was able to test both WriteFilesX and WriteFileY for each class independently 
  3. Implementing new supporting types is easy and I don't have to worry about TPacketWriter.Execute when I am writing my test.
I am sure there are other reasons, but writing testable code was my primary motivation.

There are many other things I can change with this code.  But the point I want to repeat.
When you see virtualabstract; you should ask if an interface makes sense.


Legacy Code

Today, It has been 18 years since Delphi was released.      Some Delphi projects have been around for quite some time now.     I think we all look back on code we wrote a year or two ago that we would write in a completely different way today.   Sometimes is because a new feature of the language or a new library is available, and sometimes it is just that you have learned a better way to do something.

Where I work....
In 1998 there was a mainframe application, that was not going to make it through the year 2000 switch.   A decision was made to rewrite the application and Delphi was chosen.     We have had several other applications that were started after the year 2000 that were written in other technologies.   Many of the these have had to be completely rewritten.     However, our not so little Delphi application not only survives it thrives.  Business is always changing we are constantly updating our application to meet these ever changing needs.   

Much of the original code written in 1998 still exists in some form, with many more lines of code after that.   The team has evolved over that time as well, we only have one developer left who started the project.  I estimate that we have had 40+ unique developers who have worked on this code base.   Each unique,
each had there own coding style and skill sets.    

Some of the past code is very procedural with tons of global variables and is written in a such a way that it's very difficult to test and maintain.   Where new code is written using better practices, and with good unit test coverage.     But it's not uncommon to be asked to change an area of the application that has not been significantly changed since it was first written in 1998.   When faced with such a change, how do I do that in a safe way and not continue to keep the code looking like it was written in 1998.

I have been collecting various snippets of code and how I changed them for the better.   Today I will be starting a series of blog posts on dealing with legacy code.   I suspect this may generate a bit of debate and that conversation is welcome.  I have learned that there is always a better way.     I have also learned that what works from problem X may be the complete wrong solution for problem Y.      

My hope is that what I write will help others to think in new and different ways when dealing with code.


Thursday, February 7, 2013

AnyDAC - worries with some real excitement.

When I read Marco Cantu's Post titled "Embarcadero Buys AnyDAC"  I began to worry.

Where I work we spent a great deal of time converting from the BDE to dbExpress.    Granted we did it faster than we thought it would take, it's not something I am ready to do again.    Having to switch from dbExpress to another Database Layer was really not going to be an option for us.    So I spent some time really reading the documentation on AnyDAC.

From what I have read this has turned from worry to exciting news, with a one thing that may need to be done.

First off let me address my worry about dbExpress.
What is lacking that would need to developed.  (Maybe already done since I have no code to look at yet, and I am just reading docs)
  • Ability to have a TSQLConnection use an existing TADConnection class or vice versa.
  • I basically think it would be great if AnyDAC existing connection could become an dbExpress Driver.
  • I know enough about the dbExpress framework to know this is something that would be very possible to do.   
  • In the long run this would also allow Embarcadero to only have to support one set of drivers and not one for dbExpress and one for AnyDAC.       
  • If Embarcadero does something stupid and don't do this and just stop maintaining dbExpress Drivers without providing a bridge then I would have to stop upgrading Delphi versions, but I really don't see that happening.    
This would allow existing dbExpress applications to start using AnyDAC without having to have two connections to the database.   

Then I started looking at the features of AnyDAC and found several that are really appealing.  
In addition I noticed that AnyDAC really has a good cross platform story.   Which might be a huge thing for the Mobile editions of Delphi.   Granted I think that connecting directly to a remote database via a mobile application is a security risk.

Overall I think this will be a good thing, depending on the decisions made by Embarcadero that have yet to be made.   



Wednesday, December 26, 2012

Oracle DBX Driver

Today I was looking my draft posts in blogger.   I found this one, I actually ran into this a several months ago...

Today, I ran into the following:

Access violation at address 00368099 in module 'dbxora.dll'. Write of address 00000000

It occurs when calling TSqlQuery.Next after retrieving several thousand rows.     I stepped through the Delphi code and found the error actually occurs inside the  dbxora.dll.

I ran the same query in Toad, which works until you scroll to the bottom of the query.   
Then it raises the following message: 
  ORA-01427: single-row subquery returns more than one row

So when I found out the real error was, it was easy to see my mistake and fix the query.

Besides the problem that I created I see the following problems with this story.

  1. received an A/V instead of a valid error message.    
  2. The source code to dbxora.dll is not present which means I don't have a way to change this behavior.

Note: This project was written with Delphi XE Update 1



Thursday, September 20, 2012

CodeRage 7 - Dates and Call for Papers Announced

Today the Call for Papers for Code Rage 7 was just announced.  

This is my favorite event every year.

This year the conference will be 2 weeks.  

  • CodeRage 7 - The Online Delphi Developer Conference -- November 6-8, 2012
  • CodeRage 7 - The Online C++ Developer Conference -- November 13-15, 2012
We typically scheduled a conference room and have my whole team participate in a single room, allowing 
dialog and training for the whole team.

Wednesday, August 15, 2012

FinalBuilder Server replacement Continua CI

For quite some time VSoft Technologies the makers of FinalBuilder have been working on a replacement for FinalBuilder Server, that they had called FindBuilder Server vNext +1.    They just announced a public beta of this new product called Continua CI.

I have played with this software and if your build is not automated it's required to take a look.   If your build is automated it worth taking a look.  For those who have used FinalBuilder Server it will take you by surprise as they are really not alike.    But many things that I had to build special scripts to do with FinalBuilder server are now just taken care of for you.

For example:
The basic architecture of FinalBuilder Server required a dependency on the FinalBuilder desktop product and typically builds would run on a single server.   With Continua CI you now have a server that does nothing more than provide the web interface, and store information about the builds and trigger agents to do the build.    The server and agent can be on the same machine if desired or you could have multiple agents configured with different stages of your build running on each.  

Where I work we have a 25 minute build, test, deploy process that occurs with each commit.    As we look at Continua CI we are rethinking our current build process to span multiple machines with the goal to get this down to the 5-10 minute range, something I think is very possible with this product without a huge effort.

Feel free to read the documentation on the product to get a better understanding of what it offers.  Remember that the product is in beta so the documentation is still a work in process, but it's more than enough to get you going.

But the biggest news that they announced was how it was licensed.   

Out of the box, Free of charge, Continua supports :

Unlimited Users
Unlimited Projects
Unlimited Configurations
1 Local Agent(on the same machine as the server)
1 Concurrent Build. 

After that you can purchase concurrent build licenses. Once you have at least 1 license installed, you can use as many remote agents as you like, agents do not take up licenses, concurrent builds do.

If we use Continua the way we used FinalBuilder Server before it will now cost us $0.  Granted we current have user 12 licenses that we have maintenance on,  so they will convert to 6 concurrent builds licenses.   
Which may be more than we need but I suspect we could use at least 4 and if other teams here use it then we might need quite a few more.

The pricing is right to try, it was fairly easy to setup, and get a CI server going.



Monday, July 9, 2012

Embarcadero - Clang and LLVM

Recently Tim Anderson reported that Embarcadero was going to be using the Clang Compiler in the Rad Studio.   When word broke about Embarcadero using LLVM with Delphi I was quite pleased.   I had been playing around with LLVM learning its IR for quite some time and it is a great tool for most any development language.    However, knowing that they have based there C++ work on the the Clang Compiler is very pleasing to me.    Clang is a very nice C/C++/Objective-C Compiler.

Benefits of the Clang Compiler  
Some of the Benefits of LLVM (I really don't do it justice with my small list)
All of this makes me wonder how much of other things from LLVM that they may end up using all or part of  as  LLDB, KLEE, or VMKit

Now which features of clang and llvm will be exposed in the Embarcadero tool chain is not know yet.   But it's nice to know that there is a good foundation they are building on.    

I guess it might be time to sign up for Priority Preview Access to see this in action.