Parameter checking

One of the worst parts of Delphi’s RTL is the fact that parameters are not being checked for correctness in a function call. What this means is that the offending routine will not function as intended in certain circumstances. This usually leads to “side-effects”. Let’s take the Delete routine in System unit for example:

function TrimStringPart(const S: String; 
    const C: Char): String;
var
  I: Integer;
begin
  Result := S;
  I := Length(Result);

  { Find the position of character C in the string }
  while (I > 0) and (Result[I] <> C) do Dec(I);

  { And remove anything after (+ it) }
  Delete(Result, I, Length(Result) - I + 1);
end;

This function will search for a given character in the string starting from the end. The part of the string starting with that character is deleted. This function works as expected in all cases, but a non-Delphi trained developer can spot a problem immediately: What happens if the character is not found in the string? I becomes 0 (zero) and Delete is called with invalid parameters. Well, lucky that Delete is all-forgiving and will simply exit when this happens. The logically correct function would look like this:

function TrimStringPart(const S: String;
const C: Char): String;
var
I: Integer;
begin
Result := S;
I := Length(Result);

{ Find the position of character C in the string }
while (I > 0) and (Result[I] <> C) do Dec(I);

{ And remove anything after (+ it) }
if I > 0 thenΒ  // <-- CHECK IF CHAR WAS FOUND Delete(Result, I, Length(Result) - I + 1); end; [/sourcecode] This code adds a check to see if the character was found prior to trying to delete parts of the string. It may be useless since Delete already does this, but it increases the readability of the code. Sadly I too rely on side-effects of Delete sometimes. I do not bother specifying how much to cut from a string if I want everything starting from element I to be removed. I just pass Length(S).

And that makes me wonder: how many applications would break if Delphi would throw exceptions when invalid parameters are passed to a RTL function?

7 Comments

  1. I rely a lot on the “safeness” of passing “too much” information to string functions, such as COPY, DELETE etc.

    For example:

    P:=POS(‘ ‘,STR);
    IF P>0 THEN StrippedFirstWord:=COPY(STR,P+1,$7FFFFFFF) ELSE StrippedFirstWord:=”;

    I don’t care how many characters are after the space – I just want them all. That’s one of the things that annoys the h*ll out of me when I (at work – not at home! :-)) have to program in C#. It’s a real PITA to be forced to do these kinds of checks – so much so, that I have made my own COPY functions that I use in these cases…

    Likewise, your DELETE in the *first* example, I would simply code

    DELETE(Result,I,$7FFFFFFF)

    or

    Result:=COPY(Result,I+1,$7FFFFFFF)

    No need to spend code bytes and CPU cycles to calculate the number of characters to delete at EACH AND EVERY CALL, when this just as easily could be done within the routine itself.

  2. Why not define a overloaded version of Delete and Copy then? Those would not have the last parameter (the count of elements). That would be more correct and safe IMO.

    And yes, I would have passed Length(Result) there (to avoid “magic” numerical constants), but for the sake of correctness I had to type in the proper calculation of the length.

  3. I see this as exactly the opposite of what you describe. The Delete routing checks the result and does nothing, so it does check. If you ask to delete 10 characters from point Y and only 8 exist, how is that really an error? The end result is exactly the same as if 10 characters did exist after all.

    I know that dotNet will through exceptions in cases like this, but I do not see this as a saner thing to do – rather yet another way to raise very expensive exceptions (dotNet makes them VERY expensive to raise unlike the VCL)

  4. Hi Alex,

    I understand your problem with the “invalid” parameters, and the problems they can cause. However I think that the RTL should be implemented with the minimum of checks to ensure as fast execution as possible. One of the really great things about Delphi is that the compiler produces very fast, and highly optimized code. This is essential to some of the programs I write, and there really is no need to check your parameters, if you know that they can only be valid.

    If I need to write some number crunching code that must be as effective as possible, then I would like as few param-checks as possible to speed up execution. If all RTL routines checks their parameters by default, then it would slow the resulting code so much that I would have to find another language to write my number crunchers, and I would rather not πŸ™‚

    If- or when – I need to check my parameters, I must do is my self.

  5. The parameter values you describe are not ‘invalid’. The description of the Delphi procedure ‘delete’ is as follows (check the documentation)
    —-
    In Delphi code, Delete removes a substring of Count characters from string S starting with S[Index]. S is a string-type variable. Index and Count are integer-type expressions.
    If index is larger than the length of the string or less than 1, no characters are deleted.
    If count specifies more characters than remain starting at the index, Delete removes the rest of the string. If count is less than or equal to 0, no characters are deleted.
    —-
    You are not, therefore, relying on ‘side-effects’ at all.

  6. @Thomas Mueller – Yes, I know there is an overload for copy, but thanks anyway πŸ˜‰

    @Xepol – Exceptions should NOT be used to control code behavior. That being said, exceptions would not be thrown if you do everything correctly from the start. If, you make a mistake and pass 0 it would notify you. I see this as the best tool to avoid more complex mistakes. Sometimes you may pass 0 even if you did not indent to which would result in a “silent failure”.

    @Frank – We’re not talking about Delphi pro’s here πŸ™‚ Think about many new developers which come from “strict” environments. That can be really hard for them. Knowing each side-effect is a daunting task.

    @Ralph – I did not say it’s a side-effect per-se. It can lead to side-effects if you’re new to Delphi and simply do not read the documentation (which happens a lot since the name of the function + parameters say a lot themselves).

    I believe there are 2 schools of thought here, first – The library assumes you know what you are doing and if not, it’s your fault. And the second – the library tends to always assume the worst about programmers and tends to make as many checks as possible to avoid side-effects.

    While I love freedom, sometimes having something/someone watch over your back is also a good thing.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.