I believe in safe programming. In my book that means that my programs should fail whenever I do something wrong. Even better - they should automatically detect my future stupidities and warn me when I make them.
For example, take this completely made-up example:
type
TEnumeration = (enOne, enTwo, enThree);
function DoSomethingWith(enum: TEnumeration): integer;
begin
case enum of
enOne: Result := 1;
enTwo: Result := 2;
enThree: Result := 3;
end;
end;
Pretty typical, huh? We'll, I won't write it like that. Never.
Let's say that in the future TEnumeration gets extended to include enFour. Now you have to hunt all places where it is used and adapt the code. Maybe you'll miss one or two, maybe not. I usually do :(
At least in fragments like the one above, a little planning can save you hours of debugging. By adding just one line, you can be automatically reminded that you failed to update the code at this very place:
type
TEnumeration = (enOne, enTwo, enThree);
function DoSomethingWith(enum: TEnumeration): integer;
begin
case enum of
enOne: Result := 1;
enTwo: Result := 2;
enThree: Result := 3;
else raise Exception.Create('DoSomethingWith: Unexpected value');
end;
end;
(For commenters: Yes, I'm aware that this is simply a precondition check.)
[
Assert would be more appropriate here.
ReplyDeleteIt would allow you to catch the error at runtime in testing AND get actual line numbers and it will allow you to ensure that code you deploy never throws these kinds of errors in deployed code.
Why don't u just use Range Chek compiler option ? :)
ReplyDeleteWhy not ?
ReplyDeletetype
TEnumeration = (enOne, enTwo, enThree);
function DoSomethingWith(enum: TEnumeration): integer;
begin
case enum of
enOne..enThree:
Result := Ord(enum)+1
else raise Exception.Create('DoSomethingWith: Unexpected value');
end;
end;
Personally i try, whenever possible and meaningful, to write code like this
ReplyDeletetype
TEnumeration = (enOne, enTwo, enThree);
function DoSomethingWith(enum: TEnumeration): integer;
const
ResValues: array[TEnumeration] of integer = (1, 2, 3);
begin
Result := ResValues[enum];
end;
Now the compiler will hit me if the TEnumeration is extended, and i know from long experience, that the compiler is a lot wiser than i.
I don't like asserts.
ReplyDeleteIf there is an error in the code, the code must die. It is of no use if the program continues to work and generates wrong result. That's why I'm coding almost all pre/postconditions as hard exceptions.
I'm using asserts only occasionaly, for catching conditions that should not happen but which don't cause the program to malfunction.
@undefined: Of what use would that be? There is no range check error here.
ReplyDelete@rodrigo: Because that was not a point of this exercise. I was talking about a general approach here. Usually such case statements appear when it is not possible to generalize the code in case statement.
ReplyDeleteContinues here
ReplyDeleteException and assertion catch the error at run time only. This kind of problem can be caught at compile time.
ReplyDeletefunction TForm1.DoSomethingWith(enum: TEnumeration): integer;
begin
{$if (((Low(TEnumeration) <> enOne) or
(High(TEnumeration) <> enThree)))}
{$Message Error 'enum: TEnumeration range uncomplete management'}
{$ifend}
case enum of
enOne: Result := 1;
enTwo: Result := 2;
enThree: Result := 3;
end;
end;
This is an excellent idea and I'll surely adopt it.
ReplyDeleteThe only problem is that it fails if TEnumeration is extended as
TEnumeration = (enOne, enTwo, enFour, enThree);
Yeah, I know, this _should_ not be done. Still ...