Another Delphi Programming Blog

Delphi seen through the eyes of a corporate software developer.

Tuesday, August 03, 2004

Software idea of the day

As I was compiling our several-100K-loc Delphi project today, I was thinking how nice it would be if Delphi would make use of some of the idle cycles to compile in the background when I'm just sitting there and staring at the screen or typing in the editor.

I wonder if it would be possible to write a tool that monitors the source files involved in a project and invokes dcc32.exe whenever a source file is changed, so that when I press F9 in the IDE, most of the work is already done?

Tuesday, July 27, 2004

Refactoring Day

I just spent a day refactoring a service application written in Delphi 5. The original plan was to simply port it to Delphi 7 - which would involve switching from using the TNMFTP component to the Indy equivalent. That part of the job didn't take long, but I also wanted to try to fix some odd behaviours that the service exhibited. For example, it appeared to sometimes wipe out the data in the table it was supposed to fill with data, without warning.

In order to better understand the code, I went through a number of refactoring steps.

A symmetry between the acquisition and the release of resources is one of those things that really help make code bug resistant. In place of the word symmetry, you could also call it predictable release of resources. For lightweight resources (e.g. TStringLists) that must have a lifetime beyond a single routine (where a simple Create/try/finally/Free/end pattern would work), why not just allocate them in the constructor of the owning object, and free them in the destructor (or the respective events of a form or data module.)

Error handling code is often overlooked in testing, especially when programmers do their own testing. Looking over the error handling, I found something similar to this:

try
...
except
on E: Exception do
Exception.Create('Operation Failed:', E.Message);
end;

I'm pretty sure this code wasn't ever tested. How about getting rid of the whole try/except block?

I'm a big fan of debugging the simplest possible way. That means debugging a regular exe file, rather than try to be fancy and attach the debugger to a host executable that loads up my code. So I created a data module to separate the business processing out of the TService data module. I also introduced an include file with a $DEFINE to indicate whether to build the executable as a service or as a regular application. With a decent separation of service-management code and business logic, there were only a handful of places where I needed to use conditional compilation. As an added bonus, the application is now more modular, and can now run as a regular application, without being installed as a service.

Duplicate code is one of my favourite things to get rid of. I love to take any sets of 2 or more lines of code that look almost the same and turn them into a shared routine. Sometimes that might require that I create a new shared unit, and I honestly think that some programmers just copy and paste because they are too lazy to add a new unit.

The larger the scope of a variable is, the more difficult it is to ensure that it is only being used when it contains the expected value. Class members variables can often be reduced to local variables and/or method parameters.

Accurate variable and method names are very important. If a boolean variable is called DoneDidIt, what do you think that means? What if it was called ScheduledProcessCompleted instead? This goes hand in hand with the idea that a method should do exactly what is says it does, no more, no less. If a method is called LoadSettings, it should load the settings. Should it open a database connection? No.

Figuring out the simplest way to design an application in a modular way where a picture of all the dependencies point in the same direction (higher-level code depends on lower-level code, and never the other way around), is an important challenge early on in a development project. Properly partitioned code is much easier to maintain for several reasons.

By following the above ideas, you can read the code at any level and not need to know what is going on at the levels below the one you're at in order to find the place where you need to fix that bug, or add that new functionality.

Comments? As long as the code is self-describing, as simple as possible, and contains no technical workarounds, they aren't really necessary. Sometimes however it is easier to read a quick explanation than read through several lines of code, but there is always the danger of comments getting out of sync with the code, and no amount of testing will verify that it doesn't happen.

Wednesday, July 21, 2004

Dynamically created DataSnap server modules

If you're creating a DataSnap application server for a large client application, the default TRemoteDataModule descendant will soon run out of room for dataset providers. By default, only providers on the remote data module are exported (made available to the client application.) The first thing you'll want to do is enable providers to be stored on additional data modules in the server application to make room for growth and proper partitioning. Fortunately, the TRemoteDataModule class provides a RegisterProvider method that can easily be used to register a providers on any data module.

Eventually, you'll end up with a large number of data modules, each of which is probably serving a different feature area of the client application. The way DataSnap is implemented, when a client requests some kind of service from a provider, the provider must already be exported. This makes it a little tricky to delay creating server data modules until they are needed, but there is a way.

The TRemoteDataModule implements the GetProvider method, which can be overridden, allowing you to create the data module before looking up the provider if required. I have implemented this by prefixing the ProviderName property on the client side with the class name of the server data module.

To implement this, you'll need to register the server module class (using RegisterClass) with the streaming system when the server application starts, implement the code to extract the class name and provider name parts from the ProviderName parameter inside the overridden GetProvider function, and write a small procedure that creates a data module from the class name (using FindClass) and exports its providers, if it hasn't already been created.

This technique increases the scalability of large stateful DataSnap servers considerably. It also happens to make maintenance easier, because the ProviderName property on my TClientDataSet now indicates which module its corresponding provider resides on, so I don't have to hunt around for the provider among my 50 server modules.

Sunday, July 04, 2004

Language interop with .net

As part of the work I'm currently doing, our software needs to talk to a webservice that is hosted by USDA. Unfortunately, Delphi 7's web service importer doesn't generate the correct data types for unbounded complex types, and Delphi 8's code generator generates code that doesn't compile because types in the generated unit are declared in the wrong order.

I suspect the web service was developed in Visual Studio, because Visual C# imports the WSDL perfectly. A great thing about .net is the language interoperability that makes it trouble free to use a C# assembly from a Delphi application, so it was easy to work around that little problem.

This all got me wondering if it would be possible to generate a Delphi unit using the wsdl.exe utility. wsdl.exe supports a /language parameter, although its usage isn't perfectly clear, and I was unable to google the answer as to how to specify Delphi as the language. So I fired up the .NET reflector (my first time) and located the routine that uses the /language parameter, and found that this is how the parameter is used:







...

else
if (((string.Compare(language, 'VJ#', true, CultureInfo.InvariantCulture) = 0) or
(string.Compare(language, 'vjs', true, CultureInfo.InvariantCulture) = 0)) or

(string.Compare(language, 'VJSharp', true, CultureInfo.InvariantCulture) = 0)) then
begin
type2 := Type.GetType('Microsoft.VJSharp.VJSharpCodeProvider, VJSharpCodeProvider,
Version=7.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
);
if (type2 = nil) then

begin
objArray1 := new TObject[1];
objArray1[0] := language;
raise CommandLineArgumentException.Create(Res.GetString('ErrNoVJSharp', objArray1))
end;
provider1 := (Activator.CreateInstance(type2) as CodeDomProvider)

end
else
begin
type3 := Type.GetType(language, false, true);
if (type3 = nil) then

begin
objArray1 := new TObject[1];
objArray1[0] := language;
raise CommandLineArgumentException.Create(Res.GetString('ErrLanguage', objArray1))
end;
obj1 := Activator.CreateInstance(type3);
...



As the Reflector output shows, if the value of language isn't one of the recognized, built in values, the value is simply passed directly to Type.GetType to retrieve the type of the CodeDomProvider or ICodeGenerator. The documentation for Type.GetType explains that unless an assembly name is specified, GetType will look for the type in mscorlib.dll. In our case, we want to look for the type in the assembly DelphiProvider.dll, so our /language parameter needs to specify the assembly name. Apparently, it is also necessary to specify the version, culture and public key token for the assembly. The whole language parameter string for using the DelphiCodeProvider looks like this:

"/l:Borland.Delphi.DelphiCodeProvider, DelphiProvider, Version=7.1.1523.17956, Culture=neutral, PublicKeyToken=91d62ebb5b0d1b1b"

And wsdl.exe emits Delphi code that almost compiles - apart from a few missing clauses at the top of the unit - and in my case, the order of the declarations is wrong, just like it was when I used the Delphi 8 webservice importer. So apparently I'm stuck with using a VC# assembly for now. Which is cool, because playing with the interop is fun...

Friday, July 02, 2004

Brainbench testing

I received an email the other day from www.brainbench.com, to let me know that since yesterday and for two weeks, all certifications are free, so last night when I had nothing better to do, I went over and took a couple of tests. I have taken a few of their free tests before just for the hell of it, but this time I decided to actually take them seriously and see how high I could score, and I did pretty good.

Lots of the Delphi 5 questions were very specific and technical and of the sort where you wouldn't know the answer unless you'd been doing it recently. Fortunately, I had Delphi handy and could answer almost every question within the time limit. You might call it cheating, but I honestly think that in a world where technology changes faster than anyone can keep up with, being able to find answers quickly is a much more valuable skill than having the specialized knowledge itself.

My transcript ID is 3377028.

Thursday, July 01, 2004

Writing code with the keyboard

I'm finally getting started on a project involving the use of Delphi 8 at work, and I have to say that Borland got a lot of things right with this release. I'm getting to work with some very cool interop stuff that also happens to be justified from a business perspective, and I'd like to talk about that in more detail a bit later on.

But first I want to comment on a trend I've noticed getting worse over the years. In the good old 3.1 days, it was actually possible to operate most Windows applications by just using the keyboard. Once you got to know all the secret keystrokes, you could be incredibly productive. I find that there are fewer and fewer applications that work well without using the mouse, but up until version 8, I think Delphi has been pretty good in that respect. Unfortunately it is no longer so. For example, the F11 key used to switch between the code/form and the object inspector. It only works one way now. After that you're stuck. F12 also doesn't do anything from there. Bringing up the project window would set input focus on the first item in the project tree (if it wasn't docked.) It no longer does that. Ctrl+Tab used to switch between the tabs in the editor. This still works, except when you get to a page that displays in design mode. Then the input focus is lost.

Today, I made a serious effort to explore how to get work done in Delphi 8 without using the mouse. I actually decided to put the mouse away and keep trying until I figured it out. As a result, I have a couple of tricks to share.

Object Inspector: When pressing F11, the object inspector gets focus. However before you can use the keyboard to navigate the lists of properties and events, you have to press Tab first.

To return input focus to the code editor at any time, you can press F11 and hit Tab twice.

To set focus in the actual items inside the project window, press Alt+V P, then Tab twice.

To avoid losing focus when Ctrl+Tabbing through the pages in the editor, switch all pages to code view (F12.) Unfortunately when an existing project is first opened, all files open in design view by default, so the first ritual I go through every time I open the IDE is to switch all editor tabs to code view.

I am hoping that Borland will put some effort into getting the concept of programming with the keyboard to work a little better in the next release.