Showing posts with label Delphi best practice. Show all posts
Showing posts with label Delphi best practice. Show all posts

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.

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.

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 :-)

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

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, 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.