Wednesday 17 October 2007

Tools for the Development Team - SourceForge

SourceForge

SourceForgeSourceForge (http://sf.net/powerbar/sfee/) was the real contender against Jira and we took a good hard look at it, putting it through its paces. The price (free for less than 15 users) was a compelling argument, as was its document storage and version control of those documents. We installed it on a server here and the team had a play. The following are our findings.

Initially we found SourceForge (SF) very slow. This was perhaps related to the fact it was set up as a virtual machine. This also meant that it was many gigs to download.

There was a small learning curve, same as any new product I suppose. The one thing that threw me immediately was that Tasks is not the area you place issues. Within a project, you can "create" new Trackers, and its in that new tracker area off the Main Menu where you can now track issues, or "Artifacts" as SF calls them. I'm not sure that I like the term 'artifact' as I don't want issues buried for centuries for some scientist to dig up to find out how we performed our work.

You create fields or use the defaults, you can categorise tracker items and the items are also colour coded to show their priority. You can even ask tracker to auto assign different categories to different people. While the auto assignments worked very well, I wanted to auto assign depending on the development process - e.g. auto assign to me all new issues and I can then distribute them to the available team member, then auto assign to QA when the issue is resolved. However, this is another system and what it offers is good.

SourceForge has several areas of concentration:

  • Tracker - an issues register
  • Documents - a document control system where company and technical documents can be stored, and a sort of version control is applied (more about that shortly)
  • Tasks - These are project tasks as opposed to software issues.
  • Source Code - links to Subversion or other source/version control systems
  • Discussion - a nice discussion forum with threaded messages
  • Reports
  • Release Packages
  • Wiki
  • Project Admin

foldersDocuments

The document control was a really nice feature that we gave high marks to. I can add folders and sub folders and place documents in them, just like Windows. The main difference here is that when I go back and add updated files into the same folders, instead of overwriting the files as Windows does, SF treats the newer files as new versions of the older files. I can then review previous versions, almost like I was in a Version Control program.

Discussions

The SF discussion forums were a nice touch. You can associate a discussion with a tracker artifact, people can reply to discussions and you

can select to "watch" a particular discussion to get an email when someone adds to the discussion. You can also have it automatically sending out to a mailing list.

There were a couple of downsides to the discussions. When replying to a comment, you can not see the discussion you are replying to. This will surely cause a lot of mis-quoting. Also, it seems that the user is unable to edit their own discussion after posting so even if they find they have misquoted, they can't edit that to change it. While I can understand not being able to change a comment if another has replied, surely you can edit to change that spelling mistake or typo you noticed in that frozen moment after you press save and before it was redisplayed.

File Releases

It is a convenient tool to package files in a patch set. You could upload all the files needed for a patch and when pressing the downloading release button, it saves all files into a zip file. Good for patches but I'm uncertain for major releases.

Reports

Reports can be defined to report on any searchable criteria. A standard report of a graphical summary and the report detail will then be generated. Reports can be on Tasks (project) or Tracker Artifacts.

Wiki

The Wiki was a disappointment. It was a simple and very basic Wiki without even the ability to add graphics other than as a URL. It has a different command set than I was used to but as it was so limiting, it wouldn't take long to learn.

General

It had a nice interface and some nice additions when compared with Jira, however when even after 2 weeks the company still could not reply to even a standard sales enquiry (sent by two of us separately), we were left wondering about the likelihood of any response to a real support query. That was the real clincher as far as we were concerned. Sadly, we dropped SourceForge from our list.

Tuesday 16 October 2007

Tools for the Development Team (2)

coffeeprocessTo continue where I left off, the management team, of which I am a member, looked at Jira in a totally different manner. Each person had their own needs for a tool ever so slightly different to Jira. This caused some problems as I was then asked to evaluate a number of other tools that, perhaps with some compromise, everyone can use. Some suggestions were forthcoming and I dutifully took at look at each one.

The problem with the approach that I made to the management team was that I had obviously failed to impress what the development team needed in the tool. You see, I was out to sell the tool to the rest of the company and in doing so I was focusing on their needs instead of the development team's. This meant that instead of learning how the development team were going to contribute and communicate better, they were thinking "how can I use this new tool", which invariably led to "I need this tool to do X, and I know one that does X much better than this". Of course, that meant that they were thinking of their own needs and Jira just didn't cut it for them. Dang, I missed the plot there.

I got suggestions to consider like Phpaga (http://www.phpaga.net). This is an excellent tool for contacts, invoices, financials, and billing tasks, but it has little to nothing to do with the development process. After discussing this and writing up a small review for the management team I suggested that a CRM package was more in line. They have one so will look again at using it effectively.

Almost all applications I looked at served a single particular purpose, and added some other purposes somewhere along the scale of between "ok" and "that really sucks".

So lesson learned. What I should have done is sold it as a development tool. I could have shown the management team how much better the development team was going to be, how much more accountable, how they will now get both statistics and answers from this bunch of strange speaking weirdos that did things magical and totally unknown things that produced the final products. Now they might understand what's going on there and the tool will be well supported.

As I stated in my last post, I have a number of tools that I will touch on briefly over the next few posts, but as you might have guessed, we have finally decided on Jira. Surprisingly that decision was not made by me. The development team were also asked to review a few of the more successful tools and give their feedback but it was their own decision that we should go with Jira. While Jira did not have the nicest interface or the best reports, and it was only an issues register, it was voted in as the tool to use.

Friday 12 October 2007

Tools for the Development Team

We placed Jira onto our live server and began using it for real about 2 weeks ago now. So how's it going? Rather well as it turns out.

(Jira is from Atlassian (http://www.atlassian.com/software/jira/) and is an issue tracker tool, see earlier posts for discussions.)

I did a demo for the Development Team and they took to it like a duck to water, now that they understood what it's used for and how. I did the same for the rest of the Management Team and they took to it as well, but in a different way.

Initially it was not used as well as I'd have liked so I put a little pressure on the team by being a bit of a pain in the neck for a couple of days..

Me: "What are you working on?"

Developer: "I'm updating xyz here that needs a new abc because Freda from WxyCorp has found a glitch with it"

Me: "I don't see this in Jira, have you added it?"

Developer: "Um... (ticidatacida) .. I have now (grin)"

Me: "Oh, OK then. You know that if you are working on anything, no matter what it is, it .."

Developer: ".. yea yea, I know, - ..'it needs to be in Jira' (sigh)"

The developers are good guys and they really understood the reasons for putting everything in Jira but being developers, they wanted to just develop. Now I note that no-one is working on anything without a Jira issue and they themselves police that nicely by suggesting that others create a Jira issue before they can start on any work. Nice.

This means of course, that all work is logged and reporting and reviewing can be carried out. Issues are assigned to others with questions when more information is needed, and they get an email to tell them when the issue is reassigned back to them with the answer. Once the have completed the work, they "Resolve" the issue and assign it to the QA Manager. Only the QA Manager and myself as Administrator, can "Close" an issue.

The Management Team has been impressed with Jira but they all have their own ideas of other tools that would work better for what they want to do. Some other tools that I have looked at are...

Other areas I have also look at with a mind to solving some other issues here, they include...

and I'll hope to have others added to that list soon.

I'll talk on each of those and our eventual decisions as I go along in other posts. In the meantime, I'd like to hear what tools you are using with your team or even on your own to help with development and running a software team.

Friday 28 September 2007

Installing Jira - The Development Support Tool

We are a relatively small (less than 20 employees) software development house with about half a dozen mainline products as well as any number of consulting projects going on. We have some very big name clients in several countries and therefore need to ensure work is completed on time and efficiently.

While there are normal enhancement projects and major installation projects going on, there are also a number of items that come up for customers wanting changes or where something doesn't go quite as planned. These can disrupt the programmers day and disturb the development of future enhancements or otherwise frustrate the project timelines.

We have a web based system for clients to record requests and log bugs. This is monitored by our support staff and an email is fired off to developers where necessary.

All this means that as more emails come in, the developer has to judge the urgency of the email and either drop everything to perform that task, or note that this is another item that he has to do in the future. While they are very good at what they do, I was concerned for several reasons.

  1. I don't have a view on what a developer is doing at any time, or what he has done
  2. I don't have any way to tell what the workload of a particular developer is. This means I don't know to spread the load between developers when one is overloaded and another is just doing some normal housework
  3. Programmers can easily forget tasks
  4. It doesn't assist in any process to development
  5. I can't plan to ensure enough resources are available.
  6. I could go on, but you get the idea...

Generally though, it makes the whole development area invisible to me as the manager.

I knew that I needed to install a development process, but I knew immediately, I needed a tool to give me the visibility on what's being worked on work to correctly design a development process. I had used Jira before so the decision was an easy one for me. Setting it up was easy and done in minutes, but I wanted a number of changes to the permission and notification schemes, then it was a matter if creating some projects. Done.

It worked well and I installed a testing project so that people can try out the system without disturbing real work.

Its been installed now for about a week but the uptake has not been immediate. I'll have to do a demo on Monday to show everyone how it works, assure them that its not going to take over their day with mundane admin tasks, and finally show them a few neat wow bits. After that I'll be enforcing the use of it for the next month.

If, after that trial, no-one is convinced, then I'll take it away, but I'll need to know what to replace it with if that is the case. There seems to be a few main problems with installing this work request system that I'll have to overcome. These include:

  • It seems like more work for no reason. While it may take a minute or less to create a new Jira issue, there are huge benefits for the user, the company, and the client - get over it.
  • Its a way for the boss to monitor my work so he can breathe down my neck. In fact, its a way to STOP the boss breathing down your neck as he can see what's going on instead of bugging you every half hour. As for monitoring, your not that interesting that I want to spend all my time "monitoring" what you are doing "right now", I'm a lot busier than that but a quick glance to review workloads will tell me if you have too much work and need help.
  • I get lots of little emails from lots of people wanting me to do things that only take a minute or two, why should I have to put all these into Jira?. By putting these into Jira, these little jobs will not only show you how much time they take up, but you will never, ever forget to do them. I have set up an email for each project in Jira, simply forward to the email to that address and it'll create your Jira issue instantly.
  • I'm a senior developer, surely "I" don't have to do this. Yes, sadly I'm afraid that you do.

I'm sure that once everyone is using it and the advantages are being seen, then it'll resolve any arguments. To be fair, no argument have been forthcoming, but I can sense what's not being said at times. They are a great bunch or people that I enjoy working with, I just think I can make life easier for them with this tool. I'll let you know how it goes.

Friday 21 September 2007

Back again

Thanks for your patience, I have finally shifted house and started my new role as Software Development Manager for a software house in Hamilton.

The shift included a few days in Wellington visiting friends and generally spending time away. Holidays have been a very rare occurrence for me, having at one time, spent more than 12 years without a holiday, I find that taking a break occasionally is a requirement.

The new role is interesting. Based on Oracle technologies the product is a very stable and sizeable system. The team contains some excellent technical expertise and I will be working hard to install some processes and a decent methodology to ensure that they are supported as much as possible.

That's all for now as I'm still finding my feet here.

Monday 10 September 2007

Returning the outer directory

Returning the directory before the current one should be a simple one-line call but I have so often seen a number of very highly convoluted ways of finding the last slash (\) in the directory string and using copy() and other ways that I have decided to relate a simple and direct way to obtain any or all directories prior to, and including the current one.

The problem with finding the last slash is that you have to watch out for cases where the current directory is a root directory. The good news is that you can ignore all of that.

You should know that finding the currently running exe program name including directories is Application.ExeName found in SysUtils. This should return something like..

C:\Program Files\CodeGear\Delphi\5.0\bin\MyProgram.exe

Of course the directory structure may be different as I'm sure you're not running your programs in the Delphi \bin directory :o)

But to get the directory that MyProgram.exe is running in, the call is ExtractFileDir(Application.ExeName). This returns..

C:\Program Files\CodeGear\Delphi\5.0\bin

ExtractFilePath() dies something similar. It simply returns the string up to the last delimiter (the slash). In other words, anything past the last delimiter is ignored as it is assumed to be the program name. Excellent, this means that we can use that fact on the above directory structure to return the previous directory.

PreviousDir := ExtractFilePath(ExtractFileDir(Application.ExeName));

This should return a directory structure of
C:\Program Files\CodeGear\Delphi\5.0\

We can continue to use ExtractFileDir() to return each directory in the tree until we reach the root directory (perhaps checking if PreviousDir = LastDir?). For example:

Dir := ExtractFilePath(ExtractFileDir(ExtractFileDir(Application.ExeName)));

Should return one directory up from the previous dir, or in this case:
C:\Program Files\CodeGear\Delphi\

So there you have it. A single call that returns the previous directory without having to bother with searching for the slash or worrying about the root directory issue.

Thursday 6 September 2007

Accessing Interbase/Firebird Metadata in Delphi

WARNING: Interbase and Firebird system tables are not for the faint hearted. It is not recommended that you alter anything in these tables if you ever want to use them again - and keep your job. Major stuff-ups can occur.

I was looking at some old Delphi 7 code of mine when I was attempting to get in behind the scenes of Interbase and Firebird tables and fields to learn a little more about them. I came across these little pieces of information I'd like to share.

The following will list all the tables in one grid and, for each table, list all the fields in another grid. Drop on your TIBDatabase and direct it at your Interbase or Firebird server.

Now drop a TIBTransaction, TIBQuery, TDataSource, and a TDBGrid and connect them all.

Enter the following query into the IBQuery1...

  SELECT DISTINCT RDB$RELATION_NAME as MyTable
FROM RDB$RELATION_FIELDS
WHERE RDB$SYSTEM_FLAG=0
AND RDB$VIEW_CONTEXT IS NULL
ORDER BY RDB$RELATION_NAME


and make IBQuery1 live. Now drop on another TDBGrid, TIBQuery and TDataSource and connect them. This time, select IBQuery2 and add TDataSource1 in the "DataSource" Property. This will ensure that the second query will look to the first query to fill in the parameters. That parameter will be the field MyTable.

Enter the following query string into the SQL property of IBQuery2...

  SELECT RDB$FIELD_NAME AS FIELDS
FROM RDB$RELATION_FIELDS
WHERE RDB$RELATION_NAME = :MyTable
ORDER BY RDB$FIELD_POSITION


and make that query live. When you run that program, selecting tables in the first grid will show all the field information for that table in the second grid.

So far I have the following types in the field RDB$FIELD_TYPE. These can translate to...


  • 8 = Integer
  • 10 = Float
  • 12 = Date
  • 13 = Time
  • 14 = Char
  • 35 = TimeStamp
  • 37 = VarChar
  • 261 = Blob


There'll be a definitive list somewhere but that's all I have needed so far. I got that list from the internet somewhere but it was so long ago that I have forgotten where (thanks to whoever that was).

That will get you started, but here are some other queries that will return more metadata information...

Return indexes for a table

  SELECT RDB$INDEX_NAME 
FROM RDB$INDICES
WHERE RDB$RELATION_NAME = :MyTable
AND RDB$UNIQUE_FLAG IS NULL
AND RDB$FOREIGN_KEY IS NULL


Return all Generators

  SELECT RDB$GENERATOR_NAME 
FROM RDB$GENERATORS
WHERE RDB$SYSTEM_FLAG IS NULL


Return all Triggers

  SELECT * FROM 
RDB$TRIGGERS
WHERE RDB$SYSTEM_FLAG IS NULL


The shifters are coming in the morning so I'll post this now, please excuse any typing errors. Enjoy your day.

Remember: A positive attitude may not solve all your problems, but it will annoy enough people to make it worth the effort. Herm Albright (1876 - 1944)

Wednesday 5 September 2007

Scrolling to the right place in TMemo

After programmatically inserting a number of lines I find that the TMemo will show at the last line as it normally should. However, sometimes what I really want is for TMemo to move to the first line, or perhaps even move to the top of the last lot of inserted lines.

Here are a few little gems I have picked up on my travels that work a treat.

Move to a particular line in a Memo...

function MemoMoveToLine(LineNo: integer);
begin
with Memo1 do
begin
SelStart := Perform(EM_LINEINDEX, LineNo, 0);
Perform(EM_SCROLLCARET, LinePos, 0);
end;
end;


What this is doing is calling TControl.Perform. This function actually uses a Windows message to perform some task, in this case placing the cursor at a line number, and then scrolling to that line. Let's take a look at using that function...

var
CurrentLine: integer;
begin
// Get the current position
with Memo1 do
CurrentLine := Perform(EM_LINEFROMCHAR, SelStart, 0);
AddLotsOfLines;
// Move to the line we started at
MemoMoveToLine(CurrentLine);
// Now Move to the end
MemoMoveToLine(Memo1.Lines.Count);
// Now Move to the start
MemoMoveToLine(0);
end;


And there you have it.

Ramblings


I'm well into packing in the evenings now and surrounded by boxes. The truck comes in 2 days to take my furniture to Hamilton where I will start work as Software Development Manager about mid month. I look forward to the challange of a new team and new company.

Although it will be hard to leave the Mount and this wonderful job and environment, my family is still at the Mount so I'll be back often. Its only a short drive to get here to see them and I'll probably end up with a caravan here for easy weekend stays.

I'll still be codeing in Delphi for my own programs (and sanity), always have, always will. This will ensure that the blog stays relative to Delphi, although I might introduce a few new subjects as well.

Friday 31 August 2007

What's this Soundex in Delphi?

Delphi has a Soundex routine found in StrUtils, so why am I giving you another? This blog is about how Soundex works. understanding Soundex will help you decide how and when to use it to enhance your program. I am not suggesting for a moment that you dump the already written Soundex routine within Delphi, unless you really want to, but rather I am suggesting that this may be a way to understand how a Soundex code is put together.

In the early 1980's I wrote a Soundex routine in Turbo Pascal and released it to the world through the bulletin boards that were available back then (before the Internet became readily available to the masses). By releasing it to the public domain, I was more interested in sharing than in copyrighting everything.

Over the following years, to my great amusement, I found my routine copied several times, sometimes word for word, line for line including comments, with my name replaced with someone else claiming to be the author (I would not be so amused these days). As far as I could tell with the limited searching available at the time, I was the first person to write a Soundex routine entirely in Turbo Pascal, but it is a simple routine so I could easily be wrong on that count.

The original Turbo Pascal source has been lost to .. well .. somewhere in that box of 5.5" floppies over in the corner there that I've been taking to the dump any day now for about the last 15 years.

Soundex converts a name to four characters (one letter and three numbers). The conversion will result in the same numbers for like-sounding names. For example all the following names will all result in a Soundex code of "S530" - smith, Smith, smythe, smitt, shmidt, shmidt, snith, snyth, snythe, smmith, etc. This means that if a user enters "Smith", and I search the database for the Soundex code rather than the name "Smith", I will be presented with all those and other similar sounding names.

Soundex was originally invented by Robert C. Russel and Margaret K. Odell around 1918-1922 and initially used for immigration and census information. Being before computers, the Soundex code is naturally simple so that people can calculate Soundex manually. It is used most in computer applications now. Searching your own database for names that sound like 'Smith', for spell checking and other areas.

While Soundex is clever, it is not infallible so common sense must prevail. 'Clark', and 'Klark' will come out with a different Soundex code. other examples are Bate (B300), will not be the same as Bates (B320), or Gate (G300).

In other instances names that really don't sound the same can sometimes, by sheer coincidence, have the same soundex code. Have you ever wondered why sometimes your spell checker wants to replace a mistyped word with something completely different and totally out of context? You are left wondering how on earth the spell checker came up with that word. Well, now you know. Its Soundex. However, it is still clever enough for most uses.

So what's the process? Well, it was simple for a reason (above) and still is.

  • Take the first letter
  • Replace the letters BFPV with '1'
  • Replace the letters CGJKQSXZ with '2'
  • Replace the letters DT with '3'
  • Replace the letter L with '4'
  • Replace the letters MN with '5'
  • Replace the letter R with '6'
  • Ignore all other letters and ignore double letters (e.g. the 2 T's in "Letterman").
  • If the result is less than 4 characters, then pad out with zeros.
  • If result is greater than 4 characters then use only the first 4

And there you have your Soundex code.

There are many ways to implement Soundex and I have chosen one that I hope will allow you to follow what is happening. Take a look at the Soundex routine in StrUtils for another way.

function MySoundex(sName: string): string;
var
Ch, LastCh: Char;
i : integer;
sx : string;
begin
sName := UpperCase(trim(sName));
if length(sName) < 1 then
sx := '' // got nothing, send nothing back
else
begin
LastCh := #0;
for i := 1 to length(sName) do
begin // step through each character in the name
if i = 1 then
sx := sName[1] // store the first character
else
begin
ch := #0;
if sName[i] <> LastCh then
begin
case sName[i] of
'B', 'F', 'P', 'V': ch := '1';
'C', 'G', 'J', 'K',
'Q', 'S', 'X', 'Z': ch := '2';
'D', 'T': ch := '3';
'L': ch := '4';
'M', 'N': ch := '5';
'R': ch := '6';
// Note no ELSE - ignore all other letters
end;
if ch <> #0 then
sx := sx + ch;
if length(sx) > 3 then
break; // we got all we need
end;
end;
LastCh := sname[i];
end;
while length(sx) < 4 do
sx := sx + '0'; // pad out remaining with zero
end;
result := sx;
end;


There are lots of variations and improvements on that depending on what you are doing.

So where would we use Soundex? One obvious way would be to give your users the ability to search for a name by the way it sounds.

Consider a database full of hundreds of thousands of customers. Simply add a field to the Customer table called .. oh I don't know, how about "Soundex". Then add the soundex code for the customer surname on each customer.

Because of the way Soundex works, you may find that finding all customers that sound like "Smith" may be a lot faster than finding all customers with the exact name of "Smith". How can this be? Well, since there will be less unique Soundex codes than unique surnames, the index will have less problems finding a Soundex code. Lets say that you are searching for a name in Auckland (approx 1.3 mill), an index on unique surnames will possibly total around 800,000 (rough guess). However an index on unique Soundex codes for surnames may only total about 100,000 (another wild guess with no substance at all, but you get the idea).

Ramblings


Its beautiful outside again today and although yesterday the wind was getting up a little, I can start to feel the rumblings of spring around here. Are we at last getting out of the deep dark winter that held our grip for the past few months and kept us close to our box of tissues, warm jerseys, and heaters? I hope so. It made me think of where I would be if I had accepted the offer to spend the next few years in Mongolia. I would have loved the experience of learning a new culture and language, but "The Mount" has a way of making me smile.

I climbed to the top of the mount again last weekend where the view is spectacular.
Mount Maunganui from the top of the Mount. It was sad to think I'll soon be moving inland to Hamilton, an hour and a half away.

Have a wonderful day wherever you are and God's blessings.

Thursday 23 August 2007

Have an iif() function in Delphi

There are many advantages to learning other programming languages, but there are also some problems as well. The "Immediate If" (iif) function is one of those. In most, if not all, the other languages there is an immediate if function that makes programming just a little easier and allows you to say things in much less lines. Consider the following...

    if Str = 'Top' then
s := 'Woohoo!'
else
s := 'Climb some more';


While in Delphi we can effectively write this on one line as...

    if Str = 'Top' then s := 'Woohoo!' else s := 'Climb some more';


This looks untidy. If we were using another language with immediate if, that would look like this...

    s := iif(Str='Top', 'Woohoo!', 'Climb some more');


Now I realise to some Delphi programmers that are not multiple programming language experienced, all that seems a little esoteric but it is standard use in even Excel. Here's how it works...

iif(condition, TrueResult, FalseResult) : String;


The iif() function first looks at the boolean condition. If that condition is true, then it return the TrueResult, and if it is false, then it returns the FalseResult. Here's the declaration

function iif(Test: boolean; TrueR, FalseR: string): string;


The 1st parameter is the Test condition, this can be anything (e.g.: x > y).
The 2nd parameter is the string to return if the test condition is True.
The 3rd parameter is the string to return if the test condition is False.

Here's the full function...

function iif(Test: boolean; TrueR, FalseR: string): string;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;


but in other languages, the 'type' we return can vary. For example we may want to return 99 if true or 0 of false. How do we do that in Delphi? By using the 'overload' directive.

function iif(Test: boolean; TrueR, FalseR: string): string; overload;
function iif(Test: boolean; TrueR, FalseR: integer): integer; overload;
function iif(Test: boolean; TrueR, FalseR: extended): extended; overload;

Implementation

function iif(Test: boolean; TrueR, FalseR: string): string;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;

function iif(Test: boolean; TrueR, FalseR: integer): integer;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;

function iif(Test: boolean; TrueR, FalseR: extended): extended;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;


We can even extend these to bytes or even TObject if we want to. The only thing we can't do is have the same parameter types and return a different type, e.g...

function iif(Test: boolean; TrueR, FalseR: string): string; overload;
function iif(Test: boolean; TrueR, FalseR: string): integer; overload;


This is a no-no. Why? Delphi will decide which function to run based on the parameter types. If all the parameter types are the same, Delphi will not compile as it will see this as an error.

As already stated, Delphi decides which function it will used based on the types used in the parameters. It will always use the least type for the job. For example, if you have iif()'s set up for both integer and byte, and then use iif(x=y,5,10), then Delphi will use the byte function and return a byte. In most cases this will still work ok even if you are expecting an integer returned.

Thursday 16 August 2007

Practical Development Methodologies

Over the years I have designed and installed a number of development methodologies into various organisations (I'll not list them here). What I'm interested in is a description of the development methodology that is adopted by your company, why, and how well its working.

I'd also like to know your description of the development method, not just the company's blurb sheet. For example, I was once working for a company who was the major client of a software development company. I was asked to report on the development methodology that the software company had adopted and how well it was working in favour of the client.

The company, like a great many others I know, proudly advertised their methodology as "Agile". Agile is not itself a methodology but rather a concept. There are a number of methodologies which come within the concept of "Agile". Upon investigation, the company was operating under "Iterative and Incremental Development" (IID). After getting to know both the development company, and the client, I was able to report that the IID methodology worked exceedingly well for both the development company, and their major client in this case.

In most of the methodologies I have installed, I have yet to install a stock-standard, out of the book methodology. I much prefer to look at several areas within the company I am contract to, to ensure a good fit. These areas include:


  • How the programming team currently works, what works well and what needs improvement.
  • The major applications that the team completes, e.g. large developments that take longer than 6 months to deliver, or daily support and enhancements to legacy applications.
  • The major clients of the team. This may be serving in-house applications (in which case the customer may be another business area) or small, large, or corporate external customers. each has its own unique needs.
  • The slant that management wants to portray to the outside world.
  • Both the history and the perceived future of the development team.
  • The history and quality of the delivered products.
  • The location and proximity of the team. For example, some companies I have worked with perform all their development in India with local staff consisting of Business Analysis and Technical Architects. Others have split their development team between two cities.
  • The makeup of the team. Some teams may have 'prima donna' programmers that simply will not conform to changes unless they are subtle or suit their own needs.

All these will have an effect on the eventual development methodology that will be designed and installed.

One example of a change to the standard methodology was where I designed and installed a standard Waterfall methodology to cope with external programmers, but changed the process to be used for Functions, instead of whole applications. This reduced the time it took for the external programmers to return their first cut that could be tested and, where necessary, returned for minor alterations. This meant that for any application there were several waterfalls operating at any one time. A sort of RAD-ified, or even XP'd Waterfall.

So what's your advertised methodology? What's your actual methodology? How well does it work for the team? And how well does it work for the client/customer?

You don't need to tell me the company you work for, and in most cases it may not be prudent.

Wednesday 15 August 2007

The Shell Sort

Yes, I know Delphi comes with a number of sorting routines, but I thought I'd describe a shell sort as it is fascinating little sort routine that is easily understood and a very fast routine.

[Steve: Although the "Shellsort" algorythm was first published by Donald Shell (as pointed out by Anthony Mills, thanks Anthony), this 'Shell Sort' uses a different approach that would be closer to a "Bubble Sort" with some changes. I therefore take a little 'artistic licence' in describing it in ways that people can visually understand]

The name "Shell Sort" comes from those games that are played by the magician, conman, or simply the swift of hand when they try to get money from the average passerby. Having usually 3 shells, a small coin or marble is placed under one of the shells. The shells are then swapped around at speed and the passerby is asked to bet on which shell contains the marble.



The shell sort routine does that same swapping around, but only to place all the shells in order. Consider that each shell is numbered, in this case, 1 to 5, but we'll first get our 'swift of hand' guy to jumble them up a little.



For the purposes of this blog, I'll limit the sort to only 5 integer numbers (or shells) although these could be words or extended reals or whatever. So long as you can compare them to see which is the greatest, this routine is the same or similar.

What this routine does is traverse the 5 shells many times. Each time, it will check each shell and see if it is greater than the next shell, if it is, it will swap the two shells. This will have the effect of moving the largest numbers to the right and the smaller numbers to the left - in other words, sorting them.



Consider the first time through, looking at each shell in turn, and swapping them with the next if it is larger, would, by default, move the largest number to the end.

Lets take a look at what will happen on the first run through.

35241 (look at 1st number, swap if 3 > 5)
35241 (look at 2nd number, swap if 5 > 2)
32541 (look at 3rd number, swap if 5 > 4)
32451 (look at 4th number, swap if 5 > 1)
32415

This moved the largest number (5) to the end. This means that the next time through, we would only need to look at the first 4 numbers, and so on until there is only one number left. That will tell us that the shells are sorted. BTW: Did you notice that we only stepped throuigh the first 4 numbers? That's because the last number is, well, it's the last number and doesn't have a next number to compare to.

Another way to know if they are all sorted (after all, we don't yet know if they are in order before we start) is if we traverse the shells and none needed to be swapped. If we didn't swap any then its in order and we can stop.

Let's start. Create a new project in Delphi with a form. Drop a TMemo and a TButton on the form. We'll put all the code on the OnClick event of the button, so we can follow it easier. First declare the counters; the shells themselves; and a true/false variable to tell us that no swaps were made during the last traversal.


var
i, LastShell: integer;
Shell : Array[1..5] of integer;
NoSwap: boolean;



Then we'll place a procedure inside the OnClick event (something that I would not normally do, but we're keeping it all tight for you). This procedure will simply display the current state and order of the shells so we can see what's happening.


procedure ShowShells;
var
i: integer;
s: string;
begin
// display the shell order in the TMemo
s := '';
for i := 1 to 5 do
s := s + IntToStr(Shell[i]);
Memo1.lines.Add(s);
end;


Go on, say ShowShells shix times quickly. Then we'll add another procedure. This one will swap a shell with the next and tell us that the swap occured.


procedure Swap(var First, Second: integer);
var temp: integer;
begin
Temp := First;
First := Second;
Second := Temp;
NoSwap := false;
end;


Note that I declared variable parameters, but I also could have swapped the array directly. Either way would have been acceptable in this situation.

Now we can start writing code in the OnClick event itself. First we'll fill the array with the current order of the shells.


begin
Shell[1] := 3;
Shell[2] := 5;
Shell[3] := 2;
Shell[4] := 4;
Shell[5] := 1;
memo1.Clear;
ShowShells;


Now we can do the actual sorting, using the counters. Remember, we are going down in the number of shells we need to sort each time (the largest will move to the end each time).


memo1.lines.add('now sorting...');
for LastShell := 5 downto 1 do
begin
NoSwap := true; // No swaps this time through yet
// traverse the unsorted shells
for i := 1 to LastShell-1 do
begin
if Shell[i] > Shell[i+1] then
// its greater than next, swap them
Swap(Shell[i], Shell[i+1]);
ShowShells;
end;
if NoSwap then
// we traversed the shells and didn't need to swap
// any therefore its sorted and we can stop.
break;
end;
memo1.lines.add('Done');
end;


Note that we only traversed the shells to LastShell-1? that's because we can't look to swap the lastshell with the next one as its the last shell :-)

Here's the full code...


procedure TForm1.Button1Click(Sender: TObject);
var
i, LastShell: integer;
Shell : Array[1..5] of integer;
NoSwap: boolean;

procedure ShowShells;
var
i: integer;
s: string;
begin
s := '';
for i := 1 to 5 do
s := s + IntToStr(Shell[i]);
Memo1.lines.Add(s);
end;

procedure Swap(var First, Second: integer);
var temp: integer;
begin
Temp := First;
First := Second;
Second := Temp;
NoSwap := false;
end;

begin
// assign numbers to the array
Shell[1] := 3;
Shell[2] := 5;
Shell[3] := 2;
Shell[4] := 4;
Shell[5] := 1;
memo1.Clear;
ShowShells;
memo1.lines.add('now sorting...');
for LastShell := 5 downto 1 do
begin
NoSwap := true; // No swaps this time through yet
// traverse the unsorted shells
for i := 1 to LastShell-1 do
begin
if Shell[i] > Shell[i+1] then
// its greater than next, swap them
Swap(Shell[i], Shell[i+1]);
ShowShells;
end;
if NoSwap then
// we traversed the shells and didn't need to swap
// any therefore its sorted and we can stop.
break;
end;
memo1.lines.add('Done');
end;

Tuesday 14 August 2007

The Delphi/Pascal Sentence

...or "When/Where to use semicolons".

Apologies to all you long time Pascal gurus, but in the last few days I was pondering on the advice I gave a few students learning Pascal and felt that Today's post should be directed at those learning the language.

Many new-comers find it difficult to grasp when they should be using a semicolon. The rules seem complex and inconsistant to them, when in fact they are reasonably consistant. Consider this...

"Pascal is a sentence"

Writing in Delphi Pascal is writing sentances, its that simple. Consider the following simple piece of code...

if x > 0 then
ShowMessage('x is worth something')
else
ShowMessage('x is worthless');



That equates to a sentence of "If x is greater than zero then x is worth something otherwise x is worthless.". Some people call that a Pascal statement, but I like to think of it as a sentence. Sentences are normally ended in a full stop, but in Pascal, sentances are ended in a semicolon and only the program is ended in a full stop. in the above Pascal sentence, the semicolon comes at the end as it should, and not half way through. You wouldn't say "If x is greater than zero. Then x is worth something. Otherwise. X is worthless." would you? That's 4 parts of a sentence that are seperated into 4 sentences that are meaningless.

So why can't we just place a semicolon before the else? Well think of it. If someone is talking to you in fluent english and finishes the sentence, then a little later (when you are thinking of something else), states "otherwise...", you would say "Huh? what do you mean 'Otherwise'". The Delphi Pascal compiler would state the equivelent of "Huh?" at that point and totally fail to understand you.

"OK-ish", I hear you saying "but what about all that begin/end stuff, nobody says that in real life", and normally you'd be right. Consider the following...


if x > 0 then
begin
y := x;
ShowMessage('x and y are the same');
y := y * 10;
ShowMessage('and now they are not')
end;



How does that equate to a sentence? Well, there is an overriding sentence there that says something like "If x is greater than zero then do some things...". (There's more to that sentence that I'll add soon).

You can think of begin/end as brackets if you like, that's what C and some other languages do, but Pascal is ever so slightly different. When speaking fluent Pascal (Pascal is a language after all), and you want to say some other sentences in the middle of your main sentence, you will need to let the listener know that by saying 'begin' and 'end'.

Each of the lines ending in a semicolon are their own sentences: "make y equal to x"; "say 'x and y are the same'"; "make y equal to y times 10". Being sentences in their own right, they can even have their own begin/ends for their sentences if they need them.

What about that last ShowMessage() statement? Well, here's where the Pascal sentence comes in. The overriding sentence actually says "If x is greater than zero then do some things and say 'and now they are not'.". More correctly, in fluent Pascal the true sentence is "If x is greater than zero then begin do some things and say 'and now they are not' end."

When speaking Pascal you do have to say the 'begin' and 'end' words in a sentence.

Now I can also hear you saying "but I have seen a semicolon before the 'end' many times". Yes, and pascal is smart enough to understand that you are saying nothing. Placing a semicolon before the 'end' is the equivilent of stating an empty sentence before finishing the outer sentence. Sweet nothings.

So what about Procedures and Functions? Procedures and functions are just teaching the Pascal language new words to use. For example...


Procedure Swap(var x, y: integer);

...says "I'm going to teach you the word 'Swap' and it will involve 2 whole numbers". Here's the full procedure which takes 2 numbers and swaps their values so that x equals what y did and y equals what x did (normally used in something like a simple shell sort) ...


Procedure Swap(var x, y: integer);
var i : integer;
begin
i := x;
x := y;
y := i
end;



In this case, the sentence doesn't flow unless you expand it a little.

"Here's a new Pascal Word 'Swap' that takes 'x' and 'y' as variable parameters.
'i' is a new variable just for the word 'Swap'.
begin do some things and then make y equal i end.

The 'do some things' were some other sentences as you can see.

Are you now more confused than ever? Don't worry, that's normal, you are becoming a programmer after all :-)

Friday 10 August 2007

Use of Try/Except/Finally

[Steve: I have altered the example to a more suitable one - that should teach me to quickly post then leave for the weekend without reading it].

I have seen a number of discussions recently showing that some developers can be confused over try/except and try/finally blocks. I can give an example in the kind of work that I was doing this morning.

I often read or write to an external device (let's just call it "the device" as it could be any external device). That device can sometimes send back confusing errors that I try to trap. However because those errors are confusing, I must let the user know all the various issues and ways around them.

Here's a simplified version of my code (the procedures can raise their own exception with the error message returned from the device).

var
curSave : TCursor;
begin
try
curSave := Screen.Cursor;
Screen.Cursor := crHourGlass; // Show hourglass cursor
try
OpenDeviceConnection; // each will raise an exception if error
DoStuffWithDevice;
ObtainReadings;
CalculateNewFigures;
UpdateDevice;
If not CloseConnection then // raise my own error
raise exception.create('Cant Close Connection');
finally
// return the cursor to what it was
Screen.Cursor := curSave;
end;
except
// the original exception is now being handled
// we can re-raise it, but in this case we'll
// raise a new exception with additions
on e:exception do
raise exception.create(e.message + #13+#10
+ 'Please check that the device is switched on' +#13+#10
+ 'and that the cables are properly connected.');
end;
end;



Exceptions created by any of the procedure calls will now result in an error message something like the following...



Note that I have also placed a try/finally block inside the try/except. This is to ensure that we get the cursor back.

I can raise an exception myself with "raise exception.create", the process then heads immediately to the except block, but before doing that, it must complete the finally block.

The order that finally and except will be executed depends on the order that you give it. In the above sequence, finally will be completed before the except. Usually you will see them the other way around where the exception is processed before the finally block.

Thursday 2 August 2007

More on Delphi's early days






My last post generated a bit of discussion, so I was trawling through the websites looking at the early Turbo Pascal information and I came across some interesting tidbits.

As has already been pointed out by one of the readers, there was life even before turbo Pascal. The Pascal Wiki states:
"In the 1980s Anders Hejlsberg wrote the Blue Label Pascal compiler for the Nascom-2. A reimplementation of this compiler for the IBM PC was marketed under the names Compas Pascal and PolyPascal before it was acquired by Borland ... renamed to Turbo Pascal."
Here are a few pages that I found that are worthy of looking into:

By the later 1980's I dropped the Turbo Pascal IDE in favour of MultiEdit. Its still around, and I still use it on occasion, but the Delphi IDE no longer needs it.

A reader in my last post pricked a memory or two over the character "Frank Borland". On searching, I came across this CodeGear piece about Frank. It is definately worth a read.

Ok, now playtime is over and I must get back to work.

Tuesday 31 July 2007

Delphi/Pascal Through the Ages

(I apologise that this is a long post. if you are in a hurry, then please at least read the last 3 paragraphs)

Delphi started out life as Turbo Pascal on CP/M. For those of you old enough to remember, CP/M was prior to MS-DOS, which in turn was prior to Windows. While getting to grips with Pascal MT+, I saw an add for a new compiler called Turbo Pascal in the very early 1980's and I bought myself a copy.

From this simple, small and very fast compiler I wrote a small business accounting program that I sold on the Amstrad Computer. I used some code I found in another Borland addition called 'Database Toolbox' to add file and indexing routines.

It was about mid 1980's that my accounting program The Trader Series had a serious following in New Zealand amongst Amstrad computer users and I decided to devote all my time porting it to MS-DOS, I gave up my job as Marketing Manager for Panasonic Computers in New Zealand to become a programmer. Coding in Turbo Pascal was fun. There were several enhancements since that first version 1 that I purchased, these included such powerful things as Overlays. We were restricted to 640 Kilobytes of memory so using overlays I could now write much larger programs.

There were also a lot of third party programs and additions around, including a simple debugger which helped me a great deal. "The Trader Series", the accounting program I wrote, expanded to five separate modules and over half a million lines of code. Having much greater access to memory, I totally rewrote the screen handling so that all screens were built in memory and "shifted" to the screen, making it seem lightening fast for the user. I also abandoned Turbo Toolbox and rewrote the indexing system with a doubly linked, self-balancing B++ tree. These enhancements, along with using linked lists for transactions made it the fastest accounting program on the market at the time.

I remember running a demo for the company I selected to distribute the program (I decided that I can't be both a programmer and salesman/marketing person). Their high end product took as much as 30 to 40 hours to run an end of month process. I had built up a database of 30,000 debtors, each with about a dozen transactions. After the demo they asked me to perform an end of month and turning to leave, asked when I would be back in to see the result. I told them it had been done. I spent all afternoon proving that it could run an end of month process in less than a minute. That program won the New Zealand Computer Software Awards in 1987 and became New Zealand's largest selling small business accounting program for the next 10 years.

Turbo Pascal was a great language. Object Pascal raised its head in version 4.0 and I stayed with Turbo Pascal until I sold The Trader Series in 1990.

By now Windows was getting a grip and I needed to move into this arena. Borland's Pascal for Windows just didn't do it for me. I went with a number of other languages including FoxPro, C++, Visual Basic, and I even tried a new language called Java (it'll never amount to anything but hype, or so I thought - Sigh, I have been proven wrong before).

Borland then introduced Delphi and I managed to get myself a pre-release copy. Finally a great environment. Something that equaled the environment of Turbo Pascal when it was first introduced. Delphi however had a much larger price tag, but I purchased anyway and was thrilled with the power that even this first release had. Delphi was written in Object Pascal and the source code was included. You could learn lot with that source code and programmers were able to write their own components. The Component market was created with many outstanding components pretty soon there were thousands to choose from.

Delphi, the new Turbo Pascal, was alive and kicking again. I was a contractor through the 1990's and contracted to many different companies and corporations and was able to persuade more than a few to look at Delphi for their future needs (where appropriate).

While Delphi was indeed an excellent development environment, I was more and more disappointed in its growing price tag. By 2003, Borland had priced itself completely out of the market. The price for Delphi was now about the same price as a good second hand car in New Zealand and more than almost all other development environments.

I could no longer afford my favourite development language and companies everywhere in New Zealand were coming to the same conclusion. Sadly, I was shifting to Microsoft and C#. I still had, and still use, Delphi 7, but the call to C# was strong as the companies left Borland, and as Borland lost interest in Delphi. I could not find work as a Delphi contractor any more.

Earlier this year, I was invited along to the Delphi 2007 pre-release roadshow. There I learned that Borland had shifted Delphi into a separate company and concentrated on its team tools. CodeGear had been created to take over Delphi as its flagship product. Personally I thought it should have been the other way around, but that's the way it is. The price of Delphi was dropped by several thousand from what it was in 2003, and it had been given several enhancements since I last saw it in Delphi 7.

I saw this as a resurrection of Delphi and a possible push to get Delphi back to its space as one of the top programming environments. They have a long way to go to get back the loyalty that they once had. Companies had dropped them and now few companies will look at Delphi again.
However, in my intervening years with other development environments I have learned a few things: A team of 3 dedicated Delphi developers can totally outperform a team of 40 developers in Java and other Microsoft languages for a similarly large corporate application - by a factor of several months. I have also learned that, although Delphi can do .NET, Win32 can often deliver to the customer a far superior product, much more aligned with the customer's needs, in a much reduced timeframe/cost. And I learned that even a single developer armed with some excellent Delphi tools, can take on the giants.

CodeGear, your work now is not with enhancing Delphi (other than in the helpscreens), its marketing that will win the lost corporates and large companies back. You won't win them all, the "We're a Microsoft Shop" syndrome is too well entrenched for that. But you can win over a lot and make your headway in the world again, and give back the power to the average programmer.

I must admit, it's nice to be able to get back to programming without fighting the tool every single step of the way.

Wednesday 25 July 2007

Getting the Time to Program

How many others have the same problem?

I have a number of ideas that I would love to program in my spare time. I have produced many retail programs previously with success so know the pitfalls. I have worked out most of the kinks in the logic of how to implement many of the features. I just need to program them.

The problem is that I spend a great many hours in the day programming at work. New Zealand is known for being a wonderful place to live, but its also getting known as the hardest working. Us poor Kiwis (the kiwi is our native bird and also the name we call ourselves), have the second highest rate of average hours worked per year compared to all other OECD countries. This means that when I get home, I am in serious need of relaxation and the things I want to program get put off for another night.

I have some wonderful ideas that I know will be good sellers, I just need the time to program them. Often the time I do spend on these ideas at home, ends up as code that is used in my work programs. This keeps the boss happy (I hope), but doesn't get my programs finished.

Mind you, with spending every weekend with my Fiance and her daughter, and spending about an hour on the phone to her every night (we live in different towns) does not help the situation so I suppose I'm my own worst enemy.

How do you make sure that you put aside the time and energy to program in the evenings?

Thursday 19 July 2007

Different Programming Styles

I was reading Serge’s Blog on Programmers and Temperaments this morning and it reminded me of a project I did for a company in Wellington once. It was a large international company (no, I'm not going to tell you which) and they had a very large project that they needed to produce. The project was technically difficult requiring a lot of thought each step of the way.

After a lot of design by their chief software architect, they called for programmers. About twelve of us were hired for this one project and it was really fun to get to work with a number of others on one project like that. We were given regular daily meetings to try to bring us all up to speed on the technical requirements, and then shown to a large, open plan room with a computer on each desk to choose our own work area.

What was most interesting though was a discussion that I was having with the project manager about a week later. He commented on the work styles of each of the programmers from the perspective of an observer. This wasn't a moan or gossip session, but a discussion on a real observation.


After all the meetings and the whiteboard sessions were over, we all drifted into the desks and work practices we wanted. The project manager observed the following different styles...

I found my niche in a corner desk that was on its own, then got out my pad and spent the next 2 days planning what and how I was to approach my part of the project, before I even touched the keyboard. I wanted to completely understand the requirements (which were mostly in the architect's head, given out on the whiteboard sessions). Once I understood and had a clear plan, then it was a matter of programming.

Two others immediately grabbed two desks that were joined to each other so they were sitting side by side, then simply pulled one chair over to the other desk to together at the same desk, one computer between them, and designed and programmed together. They needed to talk through what they were doing and help each other plan and program. Occasionally the second programmer would move to the computer on the other desk to program a sample procedure to help explain to the other his thoughts on a subject.

Another programmer sat down at the first available desk and started pounding out code. He needed to try out various prototypes and work from there, building up his code as he went.

Various others worked somewhere along the spectrum of the above three different styles. Some couldn't handle it and after a few days left for greener and much easier pastures.

What was interesting over the following several months was the general effectiveness of the different styles. I (admittedly somewhat surprisingly as there were some seriously experienced programmers that I enjoyed working with) ended up producing the end result much faster, followed by the pair/buddy approach. The person who immediately started coding, had to change and restart so many times that he turned out to be the least effective.

I'd love to hear your comments on what you have found in your experiences.

Tuesday 10 July 2007

My Delphi 2007 program isn't debugging!

I think I've just found a little "think first" when using Delphi 2007.

Trying to step through the code with the debugger to find out how and why something isn't happening as it should, I placed a break point on the first line of my FormCreate method. When I ran the application in debug mode, my breakpoint changed to a green cross and the program didn't stop.

Aha! said I to myself thinking myself clever (always a mistake), It's a project option. I must have turned off the debugger. Nope - that wasn't it.

Aha then! (yes, clever again) I must have changed some other compiler directive somewhere. Nope - stumped.

Realisation is now dawning on me that I wasn't half as clever as I thought I was and I eventualy even reverted to the old "Microsoft's fault" fix-all - Exit Delphi and all other programs, close down and start the whole computer up again. Load up Delphi, place the breakpoint, run and ... Nope - that wasn't it either.

Several minutes of frustration followed in which it would not be prudent to relate all the details (I am Christian after all).

Then it dawned on me. A small 'thank-you' to Him and off I go to Project Options, Build Events. Yes, I had a Post-Build event.

My normal process is to build the application, then move the application to the ..\bin directory where it needs to be in order to run correctly. Being exceptionally clever (or so I thought at the time), I placed those steps into the Post Build event. That was several weeks ago and I had forgotten.

In order to correctly debug the program today, I changed the project's output Directory to the ..\bin directory where it needed to be to run.

What all that meant was that when I ran build (F9), I expected the program to run to the breakpoint and then stop. However, Delphi knew that I wanted some things to happen in the Post build routine and copied over the just-built exe with an older version sitting in the ..\source directory. Now there is an exe running that does not have the breakpoint and does not relate to the code I am expecting to run.

A interesting morning's lesson on trying to be too clever for my own good.

Have a great day.

Steve

Thursday 5 July 2007

Where'd the CopyFileTo command go?

What a busy month for me. It feels like I've barely got back from the wedding in Brisbane and that was a couple of weeks ago.

I was working with an older Delphi program today that I hadn't loaded in Delphi 2007 as yet (possibly not since Delphi 6 or 7). When I tried to compile, it failed - it couldn't find CopyFileTo().

CopyFileTo() is an Indy function that used to reside in idGlobals. It now resides in idglobalProtocols. I simply include ", idglobalProtocols" in the 'uses' statement and it compiled.

Strangely a search of the internet did not come up with the answer - other than to point me in the direction of Indy. A search of all *.pas files under "..\CodeGear\Delphi\5.0\source\Indy\Indy10" showed me the correct file I should "Use".

Wednesday 13 June 2007

In Brisbane next week

My son is getting married so its off to Brisbane to attend the wedding.

This means that the past week has been turned upside down and I haven't done much on the blogsite because of this and other commitments.

Brisbane is a wonderful place and I wish I could have more time to see the sights. It'll be a lot warmer there than it is here at the moment so my Fiance is dragging out her summer clothes and has started packing for the trip. Being a guy, I only need a few minutes to throw some stuff into a bag before I leave, go pick her up and head to the airport.

I have just a little bit of apprehension on the length of time it will take to pick up Carol though. Based on her descriptions of decisions on things to pack, I'd best arrive at her house with plenty of time to spare.

I wouldn't miss this for the world. My son and his Fiance are the most wonderful couple and their getting married has been high on my wish list for years.

Wednesday 6 June 2007

Storing database fields to a TComboBox

While there is a TDBLookupComboBox that allows the developer to attach the TDBLookupComboBox to a table directly, it does have some issues in its use. I prefer to use a normal TComboBox and feed it from a Query. It is then a standalone ComboBox that can be used for more than just updating a field in one table with a field in another.

But how do I fill the ComboBox? And once filled, how do I know which record the user has selected?

First the Query. I am assuming that, like me, you create a field in every table that is filled with a unique ID. I can use this ID in other tables to create a join. For example, I may have a Customer table ...

CustomerID: Integer
Name: String (e.g. VarChar)

Lets say I want a ComboBox of all my customers for the user to select. The query would look something like this ...

SELECT CustomerID, Name
FROM Customer
ORDER BY Name

I am always sure to ask for the ID first and then the text that the user will want to see. Now lets fill the ComboBox with that detail. Because I may wish to do this with many comboboxes in my application and with many different queries on other tables, I have built a procedure to fill the ComboBox using AddObject.


Procedure FillCombo(cb: TComboBox; Q: TQuery);
begin
cb.items.clear;
Q.First;
while not Q.Eof do
begin
cb.Items.AddObject(Q.Fields[1].AsString, TObject(Q.Fields[0].AsInteger));
Q.Next;
end;
end;



Now I want to use that procedure to fill my ComboBox.

FillCombo(cbCustomer, MyQry);

So how then can we tell which record the user has selected? Simple, I have created a function that will return the ID of the selected record, in this case the selected Customer. If none has been selected, it will return -1.


function GetSelected(cb : TComboBox) : integer;
begin
if cb.ItemIndex = -1 then
result := -1
else
result := Integer(cb.Items.Objects[cb.ItemIndex]);
end;




So all we need to do is call it.

CustomerID := GetSelected(cbCustomer);
if CustomerID = -1 then
// none selected

Tuesday 5 June 2007

Elevate Software's new ElevateDB

I have just obtained ElevateDB from Elevate Software.

You will notice a link off this site for a Free SQL tool tool called Twenty Queries that I wrote some time ago when I wanted to find out the inner workings of SQL. That tool was for a product called DBISAM, a very good SQL database written entirely in Delphi. It compiled completely into your Delphi generated application with no BDE or other engined to install. Using my tool you could create a totally SQL call to recreate your entire database structure from the exe and therefore didn't even need to distribute the database tables with your application either.

ElevateDB is the big brother to DBISAM and I am very much looking forward to getting into developing with it. First on my list will be updating "Twenty Queries" for ElevateDB.

I'll tell you more about my experiences with it once I have had a play.

Wednesday 23 May 2007

Delphi 2007 is in the House

This week I installed delphi 2007 on my work computer. Some initial comments:

Man, the difference in loading is light speed compared to 2006.

Hey ... The help actually works!!

DelForExp, the "in IDE" source code formatter that I use, doesn't work in 2007 yet, despite the statement that it does on the website. It jams up everything with non-ending errors. Bummer, I'll have to wait for that to be fixed. GExperts seems to work fine though.

Why oh why has CodeGear gone away from offering example code in its help screens? This applies to Delphi 2006 as well, but as you will know, its help, well.. didn't.

Overall impression: Its fast, it works, its a must have update. Sad to say, Delphi 2005 and 2006 did not qualify for the "must have update" statement. So if you are still working away with the tried and true, trusty old Delphi 7 - now's the time to upgrade and boost your productivity folks.

Back to work Steve.

Roadmaps to start with Delphi in June

Jim Douglas, CEO of CodeGear, has set up his own Blog site. Today he tells us that Roadmaps of the major product lines will be published starting in June. Delphi gets the main attention as the first Roadmap to be published.

This will make interesting reading.

Tuesday 22 May 2007

Global Variable Rant

Taking over someone else's programs is never an easy task. I'm not about to judge the programmer as I was not there for the decisions made, or the time pressures and stress that was going on at the time.

However, I will ask every fledgling and seasoned programmer out there to spare a thought for best practice techniques. Trying to read code where extensive use of global variables are used is well nigh an impossible task.

Instead, create objects and define limits within those objects using such things as Sets (e.g. TMyStatus = (stActive, stInactive, stPaused);) and/or comments which allow another person to know what is happening. Please use variable names that can give some indication as to what its used for. Variable names like glbBoolsghSet02 (I made that one up) tells another programmer nothing.

Good commenting IS needed even in good Delphi code. I'll leave explaining what I mean by good commenting in another blog a little later.

Enjoy life, there's reason in that madness - its just buried a little deeper in some objects.

Too many tabs? Too much code?

I've been working on a project that has a number of tabs on the form. With two or three tabs, the full code for these can be included in the forms unit without a problem. But when you are adding more and more tabs, either you are going to have to rethink your user interface or have one big mother of a unit with a gazillion lines of code.

There is another way that I learned a number of years ago - place each tab on a different form. "What? But that's nonsense Steve, you've been smoking something!", I hear you say. Not so. Follow along for a tricky bit of coding.

Create your form with all your tabs - just don’t put anything in them for the moment. Now for each tab, create a totally separate form, place a TPanel on the form and align it to Client. Give that Panel the same name for each form so you'll remember it, like MainPanel. Then create all your components onto the MainPanel and add your logic code to that form.

Now, going back to the main form with all the tabs, in the OnChange event of the Tabs, place something like the following code...


procedure TfrmMain.pcMainTabChange(Sender: TObject);
begin
case pcMainTab.TabIndex of
1: FrmOne.MainPanel.Parent := tsOne;
2: FrmTwo.MainPanel.Parent := tsTwo;
3: FrmThree.MainPanel.Parent := tsThree;
end;
end;



And there you have it. Simple isn't it?

the tsOne, tsTwo etc are the names you can give to the individual TTabSheet you want the form's components to appear on.

What is happening here is that you are changing the parent of the MainPanel to the TTabSheet of the tab you want it to appear in. By changing the parent, you are actually telling it that it now lives on that new tabsheet instead of the form. All the code will still apply.

All the logic code for each tab is in a separate form, kept tidy and easier to follow.

You can add code to create the form at runtime if you wish, but remember to free it again in the OnChanging event.

God bless and have a wonderful day.

Monday 21 May 2007

Accessing multiple edit Components

I was asked by a friend how he could access all his components without a long and hard to maintain list in the Source Code. He had a number of Edits on his form and he wanted to add all the edits up, knowing the were all set to receive integer values.

Here's how he can do it...


function GetEdits : Integer;
var
i : integer;
comp : TComponent;
begin
result := '';
try
// fMain is the main form.
for i := 0 to fMain.ComponentCount - 1 do
begin
Comp := fMain.Components[i];
if (Comp is TEdit) then // its an edit component
if (Comp as TEdit).Text <> '' then
Result := result + StrToInt((Comp as TEdit).Text);
end;
except
result := -1; // Oops, that edit was not a number
end;
end;


Monday 14 May 2007

But I don't wanna Project Group!

I've been head down on an important change to the system so have reluctantly put aside the blog. Have no fear though as I am back onto it again.

While I was working away I came across a Delphi frustration that caused some concern. I've only recently taken over a very large amount of code from a previous programmer who used the "New Directory" theory of version control. This creating a new directory for each release.

His directory structure looks something like this...

\ProjectName\01a\
\ProjectName\01b\
\ProjectName\01c\
\ProjectName\01d\

When version 01d is released, the entire directory would by copied into a new ..\01e\ directory and work would continue there.

This works fine until I set up proper version control here. However Delphi starts to insist on changing code in the previous directory even when you are working on the new one. It also starts to insist - yes absolutely INSIST, that it must create a Project Group that includes the project in the old directory.

To get around this, I deleted the *.dsk file. These are text files that hold specific directory information that Delphi uses. Delphi will recreate this file when it next loads.

Some have suggested that I also delete *.cfg and *.dof files as well, but so far these files have been innocent.

Tuesday 3 April 2007

Development Tools

Having looked more into these development tools and this is my recommendations for going forward.

Issue Tracking.
Although Jira is a great tool and I highly recommend it, the expense is perhaps too great for somthing that has not been used in an organisation before, and therefore the benefits may not be known.

I will be looking closely at Gemini (http://www.countersoft.com/Default.aspx?PageID=10). It has a free 10 user configuration and seems, at least on the surface, to be a very effective and comparible tool. I'll let you know how it goes.

Version Control
After reading up about Microsoft Visual SourceSafe, I have decided not to go with this product either. There are more than several reports on its viability on large databases of code.

I will also be looking at Subversion. I hear its not as simple to use as SourceSafe but is free and has some very strong support amongst others I know. One quote I had was "I even resorted to reading the [words deleted here for family reading] manual, but now I understand it, its really good and simple to use".

Again, I'll keep you posted on how things pan out. If you have any comments or suggestions I'd love to hear from you.

Thursday 29 March 2007

On the move

This will be interesting. I'm on the move to the Bay of Plenty and a new job there programming in Borland's Delphi. Monday, Tuesday and Wednesday this week I was there to spend time with the outgoing programmer.

This seems like a dream job. I live at one of the best beaches in New Zealand, my house is less than 5 minutes to work, and I have a really nice office overlooking a park (all to myself with a couch and an air conditioner and opening windows).

The code looks interesting. The previous programmer has spent a lot of time on that code and I can see how it has grown from simple quick-fixes to full fledged programs being used in the marketplace. A very difficult, but perhaps good time for a new programmer to take over.

The applications are written in Delphi 7 and Delphi 2006. The outgoing programmer has spoken to them about Delphi 2007 so I hope to get that when it becomes available here.

The very first thing that I will be doing on Monday is to get my favorite source code formatter installed "DelForExp" (http://www.dow.wau.nl/aew/DelForExp.html). This allows me to format the code in a way that I can see what goes on.

While I'm certainly not criticising the previous programmer's layout, its MUCH better when I can readily see the code. To do this, I'll use DelForExp and format it to have 4 character indents instead of 2 - at least until I am familiar with the code.

I'll also be installing GExperts (http://www.gexperts.org/). GExperts is not a necessity (not like DelForExp), but it is a good tool that can certainly come in handy.

I'll also argue for a version control system. Here I'll go against the grain for some folks and recommend Microsoft's own Visual SourceSafe. I know, I know, no real branching etc. but it does what is required with the minimum of fuss (I have no link for this, you'll have to scan the Microsoft site to find it).

As the programmer was continually being interrupted with requests for changes and bug fixes and future ideas, I have already set one or two people there to have a look at JIRA (http://www.atlassian.com/software/jira/). I have used Jira in a large multi-programmer environment for the past year and I am very impressed. This issue tracking system will be able to take the pain out of remembering and processing bugs, new features, releases, discussions, process help desk issues, prioritise work, and process all the work flows that go behind all that. Try it, I found it one of the best.

Well that's about all for now. The truck comes to take my stuff out of my Auckland apartment in the morning and delivers it the next day to my new home in Mount Maunganui so I have lots of other things to do.