Preventative Programming - Code Smarter, Debug Less
Preface
This article is very old, dating back to at least 1998 and based on Delphi 4. I have left it on line as it is very popular and I receive a lot of requests for it. I have not had a chance to update it, although I plan to some day. I hope you find it useful and can apply it based on its context.
Introduction
In this article I will cover various techniques for writing Delphi programs that by design catch and highlight their own bugs and other potential problems. I will not focus on code add-ins, or tools, but strategies for effective coding, and early prevention of bugs through coding techniques.
Setting up your Environment
The Delphi IDE is the best IDE I have ever worked with. There are areas where improvements should be made, but taken as a whole it is the best to date. I will show you how to exploit it to its fullest extent. The IDE is your friend. Get to know it. There are many options that need to be explored and modified from their default settings.
Environment Options
Environment Options are settings that relate to the development environment itself, and are not project specific. These options can be access from the Environment Options item on the the Tool menu.
Color Settings
Why they are important
Everyone knows that Delphi has syntax highlighting. However most people do not know that by default most syntax highlighting is not used. Delphis default setting is set to word processor like color scheme. The default color scheme however has many different language parts set to the same colors, essentially hiding much of the syntax highlighting, and diminishing its intended effect.
The default color settings for Delphi look like this:
Unless you think about it, the default color scheme seems fine. However, were you sharp enough to notice the error in the above code? If not, take a look again. Did you see it yet? Lets turn on the message view and show you the compile error:
Okay, now that Ive shown you the error, can you pick it out exactly? Ill bet it took you at least a second to find it, most likely much more. There should be three single quotes instead of two after the "is". If we change our color scheme to fully take advantage or syntax highlighting however, see how easy it is to pick out:
Compared to:
Once you have set up this color scheme you would notice the incorrect example that the function IntToStr was not properly colored. If you want to try this out for your self, this project is available as Project1. The color scheme I have used here is a slightly modified setting from the Delphi Twilight speed setting. To change your color setting, select Tools | Environment Options, then select the color tab. I have included the registry file for this color setting (See Disclaimer below) as ContrastScheme.reg if you would like to use this color scheme. Just double click on the file, and it will be imported into your registry. Then start Delphi.
DISCLAIMER
This color scheme is very hard to read for many people. I however spend 16+ hours a day on the computer and have done extensive testing. For my eyes, this scheme does not cause eyestrain and nearly everything else does with my prolonged hours. If you suffer from eyestrain, you should try this color scheme, even though it may take some getting used to. But then again, my brain is wired quite different than most, so I have come to believe so are my eyes. Im even using the "White on blue" option in Word to write this.
Establishing a color scheme for you
Since my color scheme is not suitable for most, I will cover what is important in establishing a useful color scheme. We will start from Delphis default color scheme, however this method will work with any of Delphis color speed settings. In general, the default scheme only highlights differences between three types of syntax: comments, reserved words, and everything else. Lets take a quick look again at the default color scheme:
However , In practice we should differentiate between comments, reserved words, identifiers, symbols, literal strings and literal numbers. (Assembly also if you use it). To do this, lets modify the default color scheme. First, select Tools | Environment Options then select the Color tab. Finally, select the defaults speed setting. The dialog should now look like this:
You can change individual syntax colors by selecting the element in the list, or you can click on actual parts in the syntax example, and Delphi will select the proper element for you. Then click on the color to change the foreground for that element, or right click to select the background color. I would recommend keeping the background color the same for all the elements I am discussing.
Each of the following items to need be uniquely identified and highlighted:
- Comments Comments are already uniquely identified, but italic is hard to read. To make them easier to read, turn off italicization. You may also want to make your comments stand out. I do not comment as much as I should, so I like my comments to really stand out. Try the dark purple (Second Column, Second Row) and select bold.
- Reserved words Dark blue (First Column, Second Row)
- Identifiers Leave identifiers as they are.
- Symbols Blue (First Column, Fourth Row)
- Strings Dark red (Second Column, First Row)
- Numbers Dark green (Third Column, Second Row)
Now the settings should look like this:
You will probably need to play with them to suite your liking, but as you can see this one now fits our criteria. If you choose a dark background like I did in my color scheme, you will have a lot more foreground colors to choose from.
Preferences
Show component Captions
Delphis help merely says "Select this option to display component captions". Gee, Thanks.
This is a little known, but very useful option. I do not understand why this is not on as the default. This option will put the names of components below them at design time so that you can differentiate them from each other. Mind you that I said Components. This includes Tables, Queries, File Dialogs, etc&. Data modules ignore this setting and always display captions. Consider the following form as an example.
Before
After
Integrated Debugging
Under normal circumstances this should always be enabled. This allows you to use watches, breakpoints, and other features of Delphis IDE.
Break on Exception
Under normal circumstances this should always be enabled. When your program encounters an exception, the editor will stop and bring the editor to the line of source code on which the exception happened.
Editor
Block Indent
This setting controls how far blocks are moved when you use the indent/unindent keystrokes (Ctrl-Shift-I and Ctrl-Shift-U in the default key mapping). The de facto standard seems to be 2. This setting should be set to match the tab stops setting.
Tab Stops
Tab Stops control where the tab key will stop at in the editor. You only need to specify one set, as the editor will repeat them automatically. That is, if you set Tab Stops to "3 5", it will be interpreted as "1 3 5 7&.". The setting "3 5" is the proper one to match a Block Indent setting of 2.
Project Options (Compiler)
These are options which affect how the compiler compiles your program into machine code. They can also however affect the debugging process. Any time you change any of these options, you should to a Project | Build All to ensure that the changes are applied to all units in your project.
Optimization
The optimization tells the compiler to look for areas in your code that it can compile in alternate ways to gain speed or save memory. The Delphi compiler has many optimizations that it can perform. Most of them are not documented, but they are all passive, meaning that they will not alter how your program functions in any way.
Some of the documented code optimizations that the compiler performs are:
- Placing appropriate variables in CPU registers instead of memory
- Eliminating common sub expressions
- Generating induction variables
The Delphi documentation mentions "Other than for certain debugging situations, you should never have a need to turn optimizations off ". However this is not true. Turning on optimizations will severely effect your debugging process. Several optimizations cause the debugger not to function. One example of this are variables that are put into a register. You will not be able to set watches for these variables. Therefore, during debugging I recommend that you leave optimization turned off, however when building release versions of your program you should turn this option on.
Stack Frames
Stack frames controls how a procedure or function stores and uses its variables. The use of stack frames is standard in most compilers, however many procedures can be set up to function without the need for stack frames. Turning this option off will allow the compiler to use stack frames at its discretion.
The elimination of a stack frame, especially for a frequently called routine can significantly increase the performance of your application. However, the elimination of stack frames makes it difficult for debuggers to trace the execution of code (including Delphis). If you turn this option off, you may find that the debugger will stop on the wrong line of code during an exception. It usually stops higher up the call stack, and you have to guess where in the subroutine(s) that it stopped on that the actual exception happened. Therefore, I recommend this option be set to on for debugging, and off for production.
Runtime Errors
Range Checking and Overflow Checking should be on for debugging. Without these options, any overflows or range exceptions will not be caught, and will manifest themselves as Access Violations, unexpected program behavior, or other undefined behavior. Turning these off for production compilations will increase performance, but in my opinion it is not worth it in most applications. I would much rather receive a tech support detailing a "Integer Overflow" than one detailing "Access Violation". For debugging, these options should always be set.
Debugging = on
There are four options under debugging: Debug Information, Local Symbols, Symbol Information and Assertions. The control debugging information that is embedded into the compiled output which debuggers use. If you want to use the features in Delphis debugger, you should always leave Debug Information and Local Symbols on. Turn Symbol Information on if you wish to use the Object Browser. If you do no need the Object Browser though, you may wish to turn off Symbol Information as it can significantly increase the compile and link time of your program. These should all be off for a production compile.
Typed Pointers
By default Delphi compiles with untyped pointers. This means that any pointer is compatible with any other pointer, even if one pointer points to a string, and another points to an integer. By using typed pointers, the compiler will not let you assign pointers that are not compatible with each other without typecasting them. This causes pointers to work like normal Delphi variables.
Show Hints
Hints should always be turned on. Hints are messages that are generated by the compiler that pinpoint possible inefficiencies in your code.
Show Warnings
Warnings should always be turned on. Warnings are messages that are generated by the compiler that pinpoint possible problems in your code.
Adaptation of standard coding practices to Delphi
Most Delphi programmers do not have an academic programming background, and therefore usually are not familiar with many established coding practices. Even for many of those who are acquainted with standard practices, they may not realize how to apply them properly and effectively in Delphi. This section includes proper usage of asserts, freeing of objects, and more.
Asserts
In short, assert tests whether a boolean expression is successful. However assert is a special procedure which can be conditionally compiled in and out of the program. During debugging and testing asserts should be compiled in (See Project Options, Compiler tab). Assertions are off by default and you will need to turn them on.
Asserts should be used to verify conditions throughout your program and detect invalid conditions. Use it to test for conditions that should never exist, because some day they will and the assert statement will save you a lot of debugging. Also, since the assert statement is not compiled into the production code, you should never test for "valid" errors or conditions that cause the program to change its overall functionality. Use standard exceptions for those cases.
Example:
Procedure TmyComponent.ProcessStrings(slst: TstringList); Begin Assert(slst <>
nil, ProcessStrings received an uninitialized string list.); ... ... ... End; |
The preceding is a condition that should never happen. However with the fluidity of code during the development process, anything is possible.
Use the assert statement liberally. The more asserts you have, the less debugging you will need to do as bugs will be caught much sooner. Optimally, every few lines of code should contain an assert.
Note to those writing console applications: When an assert fails, it will raise a EAssertionFailed exception. Normally the component library catches all exceptions. However if an EAssertionFailed exception occurs in a console application and you do not trap it a runtime error 227 will occur and halt the program.
Coding Style code formatting etc&
Some order must be established as with out it you will have chaos. However too much order stifles creativity and personality and is counter productive with no tangible benefits. Most places that I have seen are on one side of the spectrum or another. Almost no structure, or structure so rigid no one can accomplish anything easily.
I recommend creating a few hard fast rules, and beyond that create guidelines.
Indentation
Establish an indentation style, and stick to it. It does not have to be the same one as your neighbor uses, and you can change it over time if you need to. But find one and use it. I have listed a few common styles below. There is no need to look into the function itself, only its layout.
Compact Compact is the style that I use. It is common among programmers who regularly work on laptops, small screens, long time programmers, and old GFA programmers.
Hanging This is probably the most common style. It is much wordier than the compact, and vertically takes up more space.
Also be sure to pick a style that is readable by others on your team. There are a few styles that are completely incompatible with others.
Alphabetize USES
Alphabetize your uses clauses, with one line for each unique starting letter. If you change a units name, or units are missing at compile time, it will be much easier to find the source of your problems. Example:
Uses PBE ,StdCtrls, SysUtils ,Windows; |
One statement per line
Do not put more than one statement per line, even if they are short.
if bReset then begin j := 0; k := 0; l := 0; end; |
if bReset then begin j := 0; k := 0; l := 0; end; |
The first block if left alone also poses another threat of being "optimized" by someone accidentally into:
if bReset then j := 0; k := 0; l := 0; |
In which the K and L assignments would be executed regardless of the if test. This kind of bug is hard to spot. Tracing over this statement can also be misleading.
Another common one liner is the if statement with short branches:
if iCondition = 5 then sOutput := 'Too high' else sOutput := Ok; |
if iCondition = 5 then sOutput := 'Too high' else sOutput := Ok; |
Avoid pointers
You should avoid using pointers when ever possible. Delphi has built in object dereferencing, and many garbage collection mechanisms. You should use these features instead. There are situations however when you must use pointers, such as when dealing with an external API, or memory buffers. So you should learn to use them properly, but reserve their use for only when necessary or prudent.
Eliminate all hints and warnings
Delphis hint and warning messages are very accurate. Because Delphi is strongly typed, Delp'is hints and warnings are usually relevant and therefore should be dealt with. Languages such as C and C++ are very loosely typed and therefore issue hundreds and often thousands of irrelevant warnings. Finding the few relevant ones is therefore impractical, and most C programmers just turn them off or merely ignore them. Most of them bring this habit with them to Delphi. I call this noise level. Delphi's noise level is very low, while C++ has a high noise level, and C has a mosntrous noise level. Therefore, you should turn on hints and warnings.
You should heed any hint or warning that the compiler displays to you. Furthermore, so that you will notice new hints and warnings, you should eliminate all hints and warnings, even if they are not relevant (which is rare).
Wrap External API Calls
External API calls often rely on return values to signal errors, and often require marshalling of parameters or pre conditions. It is likely that you will call certain API calls more than once and you should not duplicate the set up, post check or cleanup code every time you call these types of functions. In addition, such practice will create extra local variables in the function which the API function call is made.
Such API calls should be given their own wrapper functions which encapsulate the overhead and isolate the caller.
Example:
Function CurrentUser; var i: Win32Result; begin SetLength(Result, 255); i := Length(Result);
if not GetUserName(PChar(Result), i) then RaiseLastWin32Err('GetCurrentUser'); SetLength(Result,
i - 1); // Chop of #0 end; |
Comments
Commenting is like eating healthy food. It is something we all know we should do more of, but usually do not. If you are not the commenting type, please at least consider these guidelines as minimum for the rest of us who may some day inherit your code. Consider it that occasional salad you eat to make yourself feel like you are trying.
- Comment any pieces of code that had to be coded out of the ordinary. If you look at a piece of code a few days later, say "Why did I do THAT?!", and there is a valid reason for it. COMMENT IT.
- Bug workarounds. If you have to code around bugs in the operating system, third party components, or other pieces of code outside of your control, document the bug, and the workaround.
Use Constants
Do not assign meaning to numbers. If a number has derivable meaning, create a constant for it and use the constant. Not only will your code be more readable, but if the value should change in the future, you can change it in a single place.
The Alpha and the Omega (Begin and End)
Always use begin end with the exception of a single line block. However, do not ever next the single line block.
Example:
For i := 0 to Pred(slst.count) do slst[i] := slst[i] + Parsed; |
This is a valid use of a single line block. However do not nest them as follows
if slst.count > 5 then For i := 0 to Pred(slst.count) do slst[i] := slst[i] +
Parsed; |
This will compile and function. However the for is a block for the if, and the for already has a single line block. Nesting of single line blocks can be hard to read when mixed with other code. In addition, when you change code in or surrounding it is more prone to errors. The above should be rewritten as:
if slst.count > 5 then begin For i := 0 to Pred(slst.count) do slst[i] := slst[i]
+ Parsed; end; |
It is now readily apparent where the if statement ends, as before it was not.
If statements often have more than one block. Only use single line blocks if every branch of the if also has a single line block. If anyone of them requires a compound block (begin..end), use a compound block on every branch.
Mindset
Always keep a open mind when programming. Closing your mind to new ideas is the worst thing you can do to your skill set.
Kill your ego. Egos are the most evil of things to senior developers. Egos are self destructive and quickly work against teams. No matter what level you are at, face up to the fact that there are at least some other developers as good as you, if not better. They will likely however have their own specialties and will be good compliments to your skill set. If you keep your ego, it will undermine the rest of the team (especially if you are the team leader).
A team leader with an ego is often not willing to educate or help team members properly, but merely sees them as stupid grunts. Any intelligent members are seen as competition, and kept at further length. A certain degree of this occurs on almost all teams. The realization of this characteristic is the first step in creating a better team.
If you have not already, establish these goals:
- Better my team as a whole. After all, if my team does well, I do well.
- Let developers compete strictly on merits, not on seniority.
Peer review
Regularly perform peer reviews. This not only includes others review of your code, but your review of others.
By reviewing others code, you will often see better ways of doing things. Even if you are in a senior position, reviewing others code is useful as you will often see alternative ways to do things.
Having others review your code is also essential. Often a second set of eyes can spot something you forgot, suggest alternative methods, and often spot mistakes. Sometimes you can get so set down a specific track, that alternatives are often overlooked. Review by someone who is abstracted from the process can often simplify the problem and see a better faster method.
Senior programmers need to keep in mind that review of their code, even by junior programmers is essential for two reasons:
- Review by someone abstracted from the development
- Education of the reviewers
After all, the reviewers are likely team members. If you can further educate your team members you can be free to further yourself and do senior level work.
There is always something to learn
Even the most senior of developers can always learn something. Committing yourself to the fact that you know everything or that you are better than every other developer is the worst thing you can do for your career and personal development.
Delphi specific techniques
In this section I will cover techniques that specifically take advantage of features only found in Delphi. These include robust exception handling, type safety, ... It will also include coding styles and techniques that lend themselves to highlight potential code problems to the programmer before they become bugs. These techniques are also useful for helping to pinpoint and solve problems that still make it to the end user. Each technique will be backed up with real world examples and demonstrations because I know that many programmers are like myself, and may not use techniques until they have been proven of value.
TypeCasting
Delphi is a type safe language. This means that you can only assign variables to compatible types. There are situations however when the type is not known to the compiler, or you need to override this functionality. This can be done via typecasting. Typecasting is essentially coercing or forcing one type to be another.
(Just curious, is the following a sex change? Sorry. Its late here. <G>)
Var male: TMale; female: TFemale begin male := TMale.Create // Do I need multiple inheritance
for this? <G> female := TFemale(male); ... ... ... end; |
There are two ways to typecast, and most developers are not familiar with the differences. The differences are more than syntactical.
Typecasting C Style
You can typecast a statement in the following form:
Type(value)
Example:
Procedure Tform1.Button1Click(Sender: TComponent); Begin TButton(Sender).Caption :=
Click!; End; |
In this case, sender is in fact a button, but has been asigned to an ancestor type. The compiler therefore cannot access the TButtons caption property as it "sees" it as a TComponent. Typecasting forces the compiler to see it as a TButton and allows use access to the caption property.
However we really do not know that Sender is in fact a button. C-Style typecasting forces the type to change even if it is not valid. It will in fact put square pegs in round holes. If Sender is something other than a TButton, the above statement will likely cause an error at run time (Usually an access violation).
We could change it to:
Procedure Tform1.Button1Click(Sender: TComponent); Begin If Sender is TButton then TButton(Sender).Caption
:= Click!; End; |
The As Operator
With the C-Style typecast we saw that Delphi will force it to be of whatever type we cast the value to, regardless of whether or not it is valid. The as operator works on classes (The C-Style can also work on simple types) as a "safe" type cast.
Example
Procedure Tform1.Button1Click(Sender: TComponent); Begin (Sender as TButton).Caption
:= Click!; End; |
The as operator will generate an exception if this is not a valid typecast. The as operator verifies that the typecast is valid . If at run time, Sender is not of type TButton (or a descendant) then the above will generate an exception.
Try..Finally and Try..Except
Use and understand Delphis exception handlers. Use them a lot.
It is not within the scope of this article to cover these. If you would like a comprehensive reference on exception handlers I recommend the exception handling chapter in "Developing Custom Delphi 3 Components" by Ray Konopka. Information on his book can be found at Raize Software.
Wrap all local objects creations within a try finally
Since Delphi does not have the concept of procedure owned objects, any objects that you create within a procedure should be wrapped with a try finally.
Example:
Procedure ProcessStuff; Var slstItems: TstringList; Begin slstItems := TStringList.Create;
try ... ... ... Finally slstItems.Free; end; End; |
The try should come after the create. A proper constructor will clean itself up and not return a reference if there is an error. I often see the following instead:
Procedure ProcessStuff; Var slstItems: TstringList; Begin Try slstItems := TStringList.Create;
... ... ... Finally slstItems.Free; end; End; |
This is incorrect. If the creation of the string list fails, slstItems may still be undefined and freeing it will cause an error.
Anti-Statements
The Try..Finally statement should be wrapped around any anti-Statements. Anti-Statements are two statements that counteract each other. Some statements that qualify are Create..Free, Open..Close, Hourglass statements, etc&
The try should always come after the statement, and the finally should always contain the anti-statement. Statements that fail, should clean themselves up and exit with an exception as if they never had been called, and not leave the clean up to calling code. (Thus the try follows the statement instead of preceding it)
Never hard code editable date formats
Consider the following example (Included as DateFormat.dpr) in which a text box is initialized to todays date, and which the user can change the date. The user then can press a button which will show them the date they typed in long format.
procedure TformMain.butnTestClick(Sender: TObject); var dateTemp: TDateTime; begin try
dateTemp := StrToDate(editEffectiveDate.Text); except editEffectiveDate.SetFocus; raise; end; lablLongDate.Caption
:= 'You entered ' + FormatDateTime('dddddd', dateTemp); end; procedure TformMain.FormCreate(Sender: TObject);
begin editEffectiveDate.Text := FormatDateTime('MM/DD/YYYY', Date); end; |
Compile and run this example. Most likely it works, especially if you are in North America. However, if you have an alternate date format set for your machine, the example will generate an exception when you press test. Even if a user has an alternate date format set for their machine, they are likely to enter a date in the same format in which it is initially displayed, if they change it at all. If this example works for you, open control panel, select regional settings, then the Date tab. Now change date separator to - , and short date style to yyyy-MM-dd (both without the quotes). Now that youve changed it, try to run the program again.
This may seem to be a strange format to you, however this is common in many countries, and this particular one is the ISO standard. Other date formats besides this one will cause the same problem if you hard code date formats. When entering date and/or time formats for editable TEdits, TDBEdits, Field format masks and other date entry mechanisms, never use hard coded date format strings.
There is an easy solution to this. Change the line in the FormCreate to read:
EditEffectiveDate.Text := FormatDateTime(ddddd, Date); Or EditEffectiveDate.Text := DateToStr(Date); |
Now run the program. Both of these functions cause the date to be displayed in the format specified in the regional settings. The StrToDate function will now function without an exception in the button click, because it also uses the settings in regional settings to convert the dates back into TDateTime fields.
You should use DateToStr in situations like this, however ddddd (5 ds denotes to use the Short Date format defined by the system, see the help topic FormatDateTime for more details) is useful for formatting that is under program control or is controlled via input from the end user.
One last example why you should not hard code times or dates:
S := FormatDateTime( '"The meeting is on" dddd, mmmm d, yyyy, "at" hh:mm AM/PM' , StrToDateTime('2/15/95
10:30am')); |
The above example is incorrect, and will fail on the system which it is executed on if an alternate date time format has been set that greatly differs from the common North American standard.
This one should be rewritten as:
S := FormatDateTime( '"The meeting is on" dddd, mmmm d, yyyy, " at" hh:mm AM/PM' , EncodeDate(1995,
2, 15) + EncodeTime(10, 30, 0, 0)); |
The worst part of this example is that it is in the Delphi help as an example. Look up the FormatDateTime function, this example is listed at the bottom.
Finally, NEVER use two digit years. I should not have to stress this point, but it is a hard habit to break. Even in 1998. The Delphi help file examples all seem to have two digit years. So I guess we could say, Delphi is Y2K compliant, but the help file is not. Even in the example for EncodeDate, Borland used a two digit year:
procedure Tform1.Button1Click(Sender: TObject); var MyDate: TdateTime; Begin MyDate
:= EncodeDate(83, 12, 31); Label1.Caption := DateToStr(MyDate); end; |
Optimizations
A whole article could be written alone on optimizations, however I will briefly cover a few common ones. If anyone would be interested in such a beast, please feel free to Email me and I will consider it for future publication.
Use proper uses clause
Most people do not understand the difference between the two uses clauses in a unit, and when they need to reference a unit, they always add it to the one in the top of the unit (The Interface Uses). There are two uses clauses: The Interface Uses and the Implementation Uses.
Placing units in the interface uses that do not belong there increase the work that the compiler/linker must do and increase dependencies. Furthermore, circular references will be more likely to occur if you blindly use the interface clauses.
A unit should be added to the interface uses clause only if something in the interface of that unit references something in the unit. An example of this is a variable which is of a type defined in another unit.
Otherwise the unit should be added to the implementation uses clause. This is the most common case. One example of this is function calls to other units.
Avoid RecordCount
If you are using a Client / Server database, avoid using the recordcount property. When you open a query, only the first few rows are processed and retrieved. Accessing recordcount causes the server to fully process to the end of the query prematurely. Databases like Paradox and DBase do not work on set theory, and can easily return recordcounts.
Avoid FieldByName
When possible you should create your TFields at design time so that you will have early binding to them and can reference the fields as members of the form. If you use FieldByName, you will be doing string lookups in a collection of fields. These lookups are relatively slow, and when done for each field of a dataset, and/or in loops can significantly impact performance. Use of FieldByName is proper, however do not use it unnecessarily. If you cannot create the fields at run time, call FieldByName once and store it in a variable of type TField, and then use that variable.
Avoid Variants
Variants are inherently evil. They are slow, consume memory and are the opposite of type safe. They are however necessary (and even appropriate in some situations) for OLE and so Delphi supports them. Variants also have strange rules of concatenation that can lead to unexpected and varying results.
Never use variants unless necessary. If you do use them, it is best to copy them in and out of properly typed variables if you need to perform any processing on them. One example where variants do make sense is with DCom. Variant arrays can be used to pass large amounts of data easily. And keeping with my statement of "shuttling" (Even though VarLock does not quite "shuttle") Delphi offers the VarLock function to access the variant array as a type safe Delphi type.
One area that variants are often used that people do not realize is database access. You can access datasets in the following manner:
qeryTrades[Trade_ID]
The statement can appear on either side of an assignment. The above syntax however sets or returns via variants and should be avoided. Furthermore, it must do a string search for the field name.
You should always use TFields to access field values.
Conclusion
Please feel free to contact me with any comments, suggestions or questions relating to this article. Im hoping to expand it further in the future.

