Reputation: 491
Browsing some legacy code, I encounter some "empty" except blocks. They all were implemented for a similar reason, that is to handle a conversion from a text in TEdit to a numeric value. As the TEdit might be empty, there should be no error message in such a situation:
procedure TmyForm.EditExit(Sender: TObject);
begin
...
try
_value := StrToFloat(Edit.Text);
except
end;
...
end;
That works well but not really a good practice I guess. Is there a better way to get the same behavior?
Upvotes: 2
Views: 772
Reputation: 14832
TLDR Don't seek a 'one size fits all' blanket replacement option for the exception swallowers. Rather identify the specific problems you're trying to address in each case and ensure your code explicitly conveys its intent.
You may already know this, but I want to highlight some problems in the code presented. Understanding these problems yields distinct possibilities in what could go wrong. It helps to think about what behaviour you want in each case, and you should ensure your code explicitly reflects your decisions making it easier to read and maintain.
begin
...
try
_value := StrToFloat(Edit.Text);
except
end;
...
end;
The reason for the exception swallowing is explained as:
As the TEdit might be empty, there should be no error message in such a situation.
StrToFloat
then assignment to _value
is skipped. So it retains whatever its previous value happened to be.
_value
would be correctly assigned for some calculations._value
is incorrect.EDivByZero
or simply an incorrect result.As mentioned before, how you fix the exception swallowers depends on your intent in each case (yes it's more work this way, but if you're fixing something it helps to fix it "properly").
Do you really need to hide the fact that the "user made a mistake"?
Assuming there are no other error handling mistakes in the code:
'Hello' is not a valid floating point value.
.'' is not a valid floating point value.
.So certainly an option worth serious consideration is to: Simply delete the swallower.
begin
...
_value := StrToFloat(Edit.Text);
...
end;
Since you're specifically concerned about raising an error when the TEdit
empty you may be able to consider this as a special case.
0
is a suitable default. NB! Only do this if it really is an appropriate default. (See next point)In either case it's worthwhile handling the special case explicitly and allowing default error reporting to kick in otherwise.
begin
...
if (Trim(Edit.Text) = '') then
_value := 0
//or _valueIsNull := True;
else
_value := StrToFloat(Edit.Text);
...
end;
NOTE: You could of course also set a default of 0
in the control value in advance of the user updating controls. This makes the default clear to the user. And if the user chooses to delete the default, then reverting to option 1 informs the user that this is not allowed.
The default error handling can be made more user-friendly. E.g. You might want a more informative message, ensure focus is set to the 'error control', or set a hint message.
In this case you'd want to use TryStrToFloat()
as per David's answer. As far as I can tell this was introduced in Delphi 7. (In older versions you may be able to use TextToFloat()
. Examine the implementation of StrToFloat()
for an example.)
The following is but one possible example to demonstrate how you could write some simple utility functions to encapsulate your specific needs and make your special handling of certain cases explicit.
type
TDefaultMode = (dmNone, dmDefaultEmptyValue, dmDefaultInvalidValue);
function ReadFloatFromEditControl(AEdit: TCustomEdit; ADefaultMode: TDefaultMode = dmNone; ADefaultValue: Double = 0.0): Double;
begin
if not TryStrToFloat(AEdit.Text, Result) then
begin
if (ADefaultMode = dmDefaultEmptyValue) and (Trim(AEdit.Text) = '') then
Result := ADefaultValue
else if (ADefaultMode = dmDefaultInvalidValue) then
Result := ADefaultValue
else
begin
AEdit.SetFocus;
raise EConvertError.Create('['+AEdit.Text+'] is an invalid floating point value.');
end;
{If default is applied, replace value in edit control}
AEdit.Text := FloatToStr(Result);
end;
end;
{Use as follows:}
v1 := ReadFloatFromEditControl(Edit1);
v2 := ReadFloatFromEditControl(Edit2, dmDefaultInvalidValue);
v3 := ReadFloatFromEditControl(Edit3, dmDefaultEmptyValue, 1.0);
Upvotes: 4
Reputation: 612794
You should use TryStrToFloat
:
if TryStrToFloat(Edit1.Text, _value) then
// do something with _value
This is a function that returns a boolean indicating success of the conversion. The converted value, on success, is returned in an out parameter.
Upvotes: 5