Tue, 31 Oct 2006

Delphi Naming Non-Conventions

It often happens in programming that you need to name two things that are similar but different. This can be a difficult task, but one would expect that if you were designing a language or its standard library you'd put extra effort into your naming. Sadly, it sometimes seems that the people designing Delphi didn't put in that effort. For a simple example, consider division by zero. If you try to divide by zero, Delphi generates an exception at runtime, but if you were doing an integer divide the exception is EDivByZero, whereas floating-point division generates EZeroDivide.

For a more extreme example, let's look at String comparisons. Delphi has functions for native Delphi strings and C-style null-terminated strings, functions for case-sensitive and case-insensitive comparison, functions that understand multibyte character coding systems and use Windows locale info and functions that don't, and some null-terminated functions that also take a character count (analogous to strncmp).

Function String Type Multibyte-aware Case-Sensitive Specify Length
= String No Yes No
CompareText String No No No
CompareStr String No Yes No
AnsiCompareText String Yes No No
AnsiCompareStr String Yes Yes No
StrIComp PChar No No No
StrLIComp PChar No No Yes
StrComp PChar No Yes No
StrLComp PChar No Yes Yes
AnsiStrIComp PChar Yes No No
AnsiStrLIComp PChar Yes No Yes
AnsiStrComp PChar Yes Yes No
AnsiStrLComp PChar Yes Yes Yes

Granted, the null-terminated functions are pretty consistent with their Ls and Is, but I can never remember which of CompareStr and CompareText is which (not to mention remembering which names are for null-terminated strings and which are for Delphi strings). And the rule of thumb is to always use the Ansi functions, because they honor locale orderings. Presumably the non-Ansi functions are around because they're faster (they do a straight numeric compare on the ordinal value of each character), but the naming implies that they should be the default.

Another hairy mess of naming I run into moderately often is the task of turning a TDateTime into a string. Should I use DateToStr or TimeToStr, which each take one parameter (the TDateTime variable) and use several global variables to determine their formatting? Should I use FormatDateTime, which takes two parameters: the TDateTime variable and a format string? Should I use DateTimeToStr, which works just like FormatDateTime, but takes a third parameter, a string variable passed by reference, into which the result is placed? (DateToStr, TimeToStr, and FormatDateTime are Delphi functions while DateTimeToStr is a Delphi procedure.)

I consider Format to be Delphi's analog to sprintf, and use it exclusively, but Delphi provides FmtStr, Format, and FormatBuf. Format is a function that returns a formatted Delphi string. FmtStr is a procedure that gets its result variable passed in by reference. FormatBuf is the direct analog to snprintf: you must pass in the result variable and the format string as PChars, as well as the lengths of those two strings; the result is placed into the passed variable and the function returns the length of that string.

StrUpper upcases an entire PChar. AnsiStrUpper does the same, but understands multibyte encodings and honors the Windows locale. UpperCase and AnsiUpperCase are the same, but for Delphi strings. UpCase upcases a single character.

Low gives the lowest value of a range type or the lowest index of an array. Lo gives the low-order byte of an integer. Likewise, High gives the highest value of a range type or the highest index of an array while Hi gives the high-order byte of an integer. (More properly, Hi returns the second-lowest-order byte of an integer; it assumes all integers are 16-bit.)

Trunc takes a floating point number and truncates it to an integer. Truncate deletes all data in a file past the current seek point. I use Trunc just rarely enough that I almost always use Truncate when I mean to use Trunc.

I suppose all languages have similar issues (I'm reminded of Common Lisp with aref, svref, bit, sbit, nth, elt, char, schar, and (I suppose) row-major-aref), but in most other cases, I don't often find myself trying to decide which of several similarly-named functions is the right one and which is wrong. Delphi's setup just feels more muddied to me.

Phil! Gold