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?
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.
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.
With reference to the quoted comment: the exception swallower is hiding more than the stated intention. Invalid characters or a value that 'doesn't fit' and cannot be stored, would also raise exceptions that are summarily swallowed. This may be acceptable, but my guess is that these possibilities were more likely overlooked. Although far less likely, even exceptions totally unrelated to the conversion itself would be swallowed: such 'out of memory' or 'stack overflow'.
If an exception is raised in StrToFloat then assignment to _value is skipped. So it retains whatever its previous value happened to be.
This is not necessarily bad if its previous value is predictable and its previous value is acceptable for purpose.
But the value might not be predictable. Especially if it's an uninitialised local variable: it will have whatever happened to be on the stack at the time of the call. But you might also find that if it's a member of an object that it has a predictable but very difficult to discern previous value - making the code hard to read.
Even when it seems predictable, that value might not be "fit for purpose". Consider a form reading edit control values into properties on a button click. On first click the value was valid an property set. But on second click, value has been changed and property is not updated - yet it holds the clearly no longer valid value from the first click.
Finally, the most important concern is that if something goes wrong, the method that swallowed the exception cannot perform its task correctly. And other code that called the method (yet presumably relies on its correct behaviour) is blissfully unaware of the problem. Usually, this simply leads to a delayed error (which is much more difficult to fix). The root problem is hidden, so something goes wrong later. E.g.
Another method called the above expecting _value would be correctly assigned for some calculations.
The root error is hidden, so _value is incorrect.
Later calculation might produce EDivByZero or simply an incorrect result.
If incorrect result is persisted, the problem could remain hidden for years.
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").
Option 1
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:
If user types "Hello" an exception will be raised, aborting the current operation. The user will see an error message stating 'Hello' is not a valid floating point value..
Similarly not entering any value will result in: '' 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;
Option 2
Since you're specifically concerned about raising an error when the TEdit empty you may be able to consider this as a special case.
It might make sense to assume that when the user doesn't provide a value, 0 is a suitable default. NB! Only do this if it really is an appropriate default. (See next point)
The value might be optional. In which case you don't want to report an error if something optional is not provided. Rather set a flag associated with the value that indicates it was not provided. (NB: Don't overload a valid value to indicate 'value not provided'; otherwise you won't be able to differentiate the two.)
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.
Option 3
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);
Related
Delphi (10.3 Rio) emits spurious H2077 warnings for code like:
x := TFoo.Create;
y := nil;
try
y := function_that_can_throw;
// use x and y
finally
x.Free;
y.Free;
end;
Note: the warning would still be unwanted even if the compiler could prove that the function cannot throw, since AFAIK there is no way to lock the function into non-throwingness by declaring it nothrow as in other languages and to assert the nothrow property at the call site. Hence the code must be written under the assumption that the function can throw.
I would like to suppress the unhelpful/erroneous hint, but apparently it is not possible to suppress hint H2077 specifically, only all hints or none. I would like to leave hints enabled if possible, so I'm wondering if there is another option for suppressing H2077 in this situation.
Also, I would like to avoid having to code a redundant second try/finally frame, since it clutters the source and creates unnecessary object code. The simplest and most obvious alternative - calling an empty dummy procedure like pretend_to_use(y) which takes a TObject parameter and does nothing with it - would create an unnecessary global dependency and most likely superfluous function calls as well. Hence I'd like your advice on a better solution...
EDIT: it turns out that Andreas has a point and the above snippet does not create the spurious warning (special coding in the compiler?). Here is an amended snippet that does cause the unwanted hint:
TIdStack.IncUsage;
y := nil;
try
y := function_that_can_throw;
// use y and the Indy stack
finally
TIdStack.DecUsage;
y.Free;
end;
The Indy stack thing is from something I'm currently working on, but entering/leaving critical sections would perhaps be a more common situation.
If you really want to suppress H2077, here's how I do it.
In my "utilities include" unit I have routines like:
procedure preventCompilerHint(I: integer); overload;
procedure preventCompilerHint(S: string); overload;
These are EMPTY routines, consisting simply of begin end; blocks.
I simply call these routines to show the compiler that I am actually "using" the variable in question.
If you're like me & like to be able to do a build and see zero hints and zero warnings... Well, this is how I handle the H2077.
Some may say this is less than elegant. At times that may be true. At other times I simply want to suppress this hint and move on.
Do with this as you will...
NOTE: I removed the sample code as (a) it wasn't related to my suggestion here; and (b) it was generating more interest than the suggestion itself.
I have the following function
function calculate(s:string):integer;
begin
try
strtoint(s);
do_something;
except
result:=-1;
end;
end;
Is there any consequence of using exceptions like this?
If the intent is to check the validity of the input string then there are better methods than an exception handler. To convert from string to integer and detect invalid input use TryStrToInt:
var
value: Integer;
...
if TryStrToInt(s, Value) then
Result := ...
else
Result := -1;
This function returns True if the string can be converted to an integer, and if so that integer is returned via the second argument, an out parameter. Otherwise the function returns False. In your code you ignore the converted integer value but it is available if you need it.
This is to be preferred over an exception handler, in my view, because it is more explicit and concise. Not to mention avoiding the performance overhead of raising and catching an exception.
Of course, your code will detect failures other than an invalid string. One potential source for errors is your do_something procedure. If you really want to swallow any exceptions raised by that procedure then an exception handler is needed. However, I rather suspect that it is more likely that your original intent was to catch invalid string input only. In which case your code was incorrect.
Another source of errors is if s happens to be an invalid string variable. But if that happens then the entire basis of your program is pulled from under you and I personally don't think you should expect to handle such scenarios gracefully.
Part of the problem with advising you is that your code is probably not truly representative. For instance it does not appear to set the return value if the input is valid. And we don't know for sure what the intent of your exception handler is. I'm guessing that you mean to trap errors in the call to StrToInt but I cannot tell that for sure.
It very much depends on the specification of the Calculate method.
If the specification stipulates that -1 is returned in the event of any and all exceptions then this is fine and (formatting issues aside) does the job as concisely as possible.
If the contract is only that the method returns -1 for invalid (non-numeric) strings then there is a potential issue that DoSomething itself might raise other exceptions (including possibly unrelated conversion errors) which will then be incorrectly handled, to yield -1 from the Calculate method (making it impossible to distinguish between invalid values of s and other errors.
In the latter case it would be more correct to handle the specific contract with respect to the string parameter in a way that avoids relying on an exception (since an error in this case is not "exceptional" but a specific input case to be handled) and allow exceptions from DoSomething to be handled by the caller, if appropriate.
To test for a valid numeric string you can use TryStrToInt and only call DoSomething if s is determined to be a valid integer. Assuming that the result of the Calculate function is -1 for invalid inputs and the result of the DoSomething operation on the numeric value of s otherwise:
function calculate(s: String):integer;
begin
if NOT TryStrToInt(s, result) then
result := -1;
else
result := DoSomething(result);
end;
The use of result in this way is a matter of personal preference. Some would argue that you should not use result for working storage like this and that you should use a separate local variable to hold the integer value of s, named accordingly:
function calculate(s: String): Integer;
var
inValue: Integer;
begin
if NOT TryStrToInt(s, inValue) then
result := -1;
else
result := DoSomething(inValue);
end;
FWIW: I personally would favour the latter. :)
Why don't you simply go for something like this:
function calculate(s:string):integer;
begin
result:=-1;
strtoint(s);
do_something;
result:=.... <--- whatever value you want to return or maybe result:=do_something as Deltics shows
end;
For some of my procedures and functions, I have implemented various checks on parameters in order to force execution to stop if parameters are out of range in one way or another.
I find it better to check for this in my own code rather than have an abnormal crash due to a perhaps bad memory-write.
Consider the simple code:
PROCEDURE Test(OneDigitNumbers:BYTE);
BEGIN
IF OneDigitNumbers>9 THEN ProduceErrorMessage;
END;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12);
end.
I have no problem in actually producing an error message, my only "problem" is that the debugger in Delphi always point to the procedure creating the exception.
Is there a method of creating this exception or error message so that the debugger point to the line where the parameter is out of range?
In my example, it should point to :
Test(12);
and maybe say something like "Parameter out of range. Valid range is 0-9. Parameter passed was: 12"
Even an answer to say that this is NOT possible will be useful (if you know for sure that this is not possible), because then I will just forget about this and make an alternative method for debugging.
To answer the question as asked, you can make the test function inline:
procedure Test(OneDigitNumbers: byte); inline;
The compiler will then write the code for Test into each calling function. Whilst you can do this, my advice is that you do not. It's just a trick but I don't think it really helps you.
If you want to raise the exception at the return address, you can do this:
raise Exception.CreateFmt(
'Exception blah blah at %p.',
[ReturnAddress]
) at ReturnAddress;
If you want to go further up the stack, then you'll have to use something like CaptureStackBackTrace. Combine the back trace with raise at and you can raise the exception at any point in the call stack, if really you think that's a good idea. I do not think it's a good idea, as I explain below.
If you use a good debugging tool, like madExcept, then the call stacks in the madExcept bug reports will tell you all you need to know when an error occurs.
With the extra clarification in the comments, it seems that what you really want to happen is for the exception to contain information from higher up the call stack. To my mind it is a violation of encapsulation to ask the callee to report information about its caller. So if you want to include information from the caller, let the caller catch the exception, add the information, and re-raise.
You're looking for a subrange type.
type
TOneDigitNumber = 0..9;
procedure Test(OneDigitNumbers: TOneDigitNumber);
begin
// Do something
end;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12); // compiler error '[DCC Error] MyStuffTest.pas(33): E1012 Constant expression violates subrange bounds
end.
A bit of an elaboration on my comment to your question
type
EMyOwnRangeError = class(ERangeError)
// You can also add your own member variables for easier inspection
public
constructor CreateFrom(const aRangeError: ERangeError);
end;
constructor EMyOwnRangeError.CreateFrom(const aRangeError: ERangeError);
begin
// Do whatever you need to inspect the call stack in aRangeError
// and modify the message and/or set any extra member variable that you
// you define on EMyOwnRangeError.
// No help from me on this, quite simply because I don't have Delphi
// installed on the machine I am currently working at.
end;
procedure MySpecialTest(const aWhatever: Byte);
begin
try
if (aWhatever < 0) or (aWhatever > SOMEUPPERRANGE) then
raise ERangeError.Create;
// Normal code for MySpecialTest
except
on E: ERangeError do raise EMyOwnRangeError.CreateFrom(E);
else
raise; // Make sure other exceptions are propagated.
end;
end;
I have now tested basically the approach in the idea I got from David Heffernan.
I simply added this simple code in one of my units of reuseables:
PROCEDURE TestError(Par:BYTE);
BEGIN
TRY
FINALLY
IF Par>9 THEN Raise Exception.CreateFmt('Error Blah blah blah at ',[Par]) AT #Par;
END;
END;
When this procedure is called with a parameter higher than 9 then it forces an exception.
Delphi ask "Break or Continue" and I click Break.
The result is almost what I would like, but it is so close that I can live with that.
The debugger pops up with a nice red line on the line AFTER the one calling the procedure.
I tried withouth the TRY-Finally-End also, and then it is plain wrong, actually showing the red line another level back from the callstack..
Anyway. I feel that this result is an awful lot better than what I had before. Now my debugging will be a joy rather than a pain.
Thank you :)
I subclassed a control in order so I can add a few fields that I need, but now when I create it at runtime I get an Access Violation. Unfortunately this Access Violation doesn't happen at the place where I'm creating the control, and even those I'm building with all debug options enabled (including "Build with debug DCU's") the stack trace doesn't help me at all!
In my attempt to reproduce the error I tried creating a console application, but apparently this error only shows up in a Forms application, and only if my control is actually shown on a form!
Here are the steps to reproduce the error. Create a new VCL Forms application, drop a single button, double-click to create the OnClick handler and write this:
type TWinControl<T,K,W> = class(TWinControl);
procedure TForm3.Button1Click(Sender: TObject);
begin
with TWinControl<TWinControl, TWinControl, TWinControl>.Create(Self) do
begin
Parent := Self;
end;
end;
This successively generates the Access Violation, every time I tried. Only tested this on Delphi 2010 as that's the only version I've got on this computer.
The questions would be:
Is this a known bug in Delphi's Generics?
Is there a workaround for this?
Edit
Here's the link to the QC report: http://qc.embarcadero.com/wc/qcmain.aspx?d=112101
First of all, this has nothing to do with generics, but is a lot more likely to manifest when generics are being used. It turns out there's a buffer overflow bug in TControl.CreateParams. If you look at the code, you'll notice it fills a TCreateParams structure, and especially important, it fills the TCreateParams.WinClassName with the name of the current class (the ClassName). Unfortunately WinClassName is a fixed length buffer of only 64 char's, but that needs to include the NULL-terminator; so effectively a 64 char long ClassName will overflow that buffer!
It can be tested with this code:
TLongWinControlClassName4567890123456789012345678901234567891234 = class(TWinControl)
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
with TLongWinControlClassName4567890123456789012345678901234567891234.Create(Self) do
begin
Parent := Self;
end;
end;
That class name is exactly 64 characters long. Make it one character shorter and the error goes away!
This is a lot more likely to happen when using generics because of the way Delphi constructs the ClassName: it includes the unit name where the parameter type is declared, plus a dot, then the name of the parameter type. For example, the TWinControl<TWinControl, TWinControl, TWinControl> class has the following ClassName:
TWinControl<Controls.TWinControl,Controls.TWinControl,Controls.TWinControl>
That's 75 characters long, over the 63 limit.
Workaround
I adopted a simple error message from the potentially-error-generating class. Something like this, from the constructor:
constructor TWinControl<T, K, W>.Create(aOwner: TComponent);
begin
{$IFOPT D+}
if Length(ClassName) > 63 then raise Exception.Create('The resulting ClassName is too long: ' + ClassName);
{$ENDIF}
inherited;
end;
At least this shows a decent error message that one can immediately act upon.
Later Edit, True Workaround
The previous solution (raising an error) works fine for a non-generic class that has a realy-realy long name; One would very likely be able to shorten it, make it 63 chars or less. That's not the case with generic types: I ran into this problem with a TWinControl descendant that took 2 type parameters, so it was of the form:
TMyControlName<Type1, Type2>
The gnerate ClassName for a concrete type based on this generic type takes the form:
TMyControlName<UnitName1.Type1,UnitName2.Type2>
so it includes 5 identifiers (2x unit identifier + 3x type identifier) + 5 symbols (<.,.>); The average length of those 5 identifiers need to be less then 12 chars each, or else the total length is over 63: 5x12+5 = 65. Using only 11-12 characters per identifier is very little and goes against best practices (ie: use long descriptive names because keystrokes are free!). Again, in my case, I simply couldn't make my identifiers that short.
Considering how shortening the ClassName is not always possible, I figured I'd attempt removing the cause of the problem (the buffer overflow). Unfortunately that's very difficult because the error originates from TWinControl.CreateParams, at the bottom of the CreateParams hierarchy. We can't NOT call inherited because CreateParams is used all along the inheritance chain to build the window creation parameters. Not calling it would require duplicating all the code in the base TWinControl.CreateParams PLUS all the code in intermediary classes; It would also not be very portable, since any of that code might change with future versions of the VCL (or future version of 3rd party controls we might be subclassing).
The following solution doesn't stop TWinControl.CreateParams from overflowing the buffer, but makes it harmless and then (when the inherited call returns) fixes the problem. I'm using a new record (so I have control over the layout) that includes the original TCreateParams but pads it with lots of space for TWinControl.CreateParams to overflow into. TWinControl.CreateParams overflows all it wants, I then read the complete text and make it so it fits the original bounds of the record also making sure the resulting shortened name is reasonably likely to be unique. I'm including the a HASH of the original ClassName in the WndName to help with the uniqueness issue:
type
TWrappedCreateParamsRecord = record
Orignial: TCreateParams;
SpaceForCreateParamsToSafelyOverflow: array[0..2047] of Char;
end;
procedure TExtraExtraLongWinControlDescendantClassName_0123456789_0123456789_0123456789_0123456789.CreateParams(var Params: TCreateParams);
var Wrapp: TWrappedCreateParamsRecord;
Hashcode: Integer;
HashStr: string;
begin
// Do I need to take special care?
if Length(ClassName) >= Length(Params.WinClassName) then
begin
// Letting the code go through will cause an Access Violation because of the
// Buffer Overflow in TWinControl.CreateParams; Yet we do need to let the
// inherited call go through, or else parent classes don't get the chance
// to manipulate the Params structure. Since we can't fix the root cause (we
// can't stop TWinControl.CreateParams from overflowing), let's make sure the
// overflow will be harmless.
ZeroMemory(#Wrapp, SizeOf(Wrapp));
Move(Params, Wrapp.Orignial, SizeOf(TCreateParams));
// Call the original CreateParams; It'll still overflow, but it'll probably be hurmless since we just
// padded the orginal data structure with a substantial ammount of space.
inherited CreateParams(Wrapp.Orignial);
// The data needs to move back into the "Params" structure, but before we can do that
// we should FIX the overflown buffer. We can't simply trunc it to 64, and we don't want
// the overhead of keeping track of all the variants of this class we might encounter.
// Note: Think of GENERIC classes, where you write this code once, but there might
// be many-many different ClassNames at runtime!
//
// My idea is to FIX this by keeping as much of the original name as possible, but
// including the HASH value of the full name into the window name; If the HASH function
// is any good then the resulting name as a very high probability of being Unique. We'll
// use the default Hash function used for Delphi's generics.
HashCode := TEqualityComparer<string>.Default.GetHashCode(PChar(#Wrapp.Orignial.WinClassName));
HashStr := IntToHex(HashCode, 8);
Move(HashStr[1], Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)-8], 8*SizeOf(Char));
Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)] := #0;
// Move the TCreateParams record back were we've got it from
Move(Wrapp.Orignial, Params, SizeOf(TCreateParams));
end
else
inherited;
end;
I'm automating Word with Delphi, but some times I got an error message:
The requested member of the collection
does not exist
It seems that the Item member of the Styles collection class does not always exist and some times causes the above mentioned error. My workaround is to catch the exception and skip it, but is there anyway to detect it instead of using the try...except block? The problem with the try...except block is that when debugging the raised exception is annoying...
My code example:
var
aWordDoc: _WordDocument
i: Integer;
ovI: OleVariant;
wordStyle: Style;
begin
for i := 1 to aWordDoc.Styles.Count do
begin
ovI := i;
try
wordStyle := aWordDoc.Styles.Item(ovI);
except
Continue;//skip if any error occurred.
end;
//do something with wordStyle
end;
end
If the compiler accepts it, but it sometimes cannot happen to exist, it is probably IDispatch based latebinding. IDispatch objects can be queried. Maybe carefully working yourself up the tree querying every object for the next would work.
You would then roughly be doing what the compiler does, except that that one throws an exception if somethines doesn't exist. (and if the exception comes in from COM, maybe a slightly different code path can test more).
Sorry to have no readily made code.
I get that message when a bookmark that I'm trying to fill from Word doesn't exist so i have a process that checks first, but I'm not sure the same method would work for you.
procedure MergeData(strBookMark, strData : string);
begin
if WinWord.ActiveDocument.Bookmarks.Exists(strBookMark) = True then
WinWord.ActiveDocument.FormFields.Item(strBookMark).Result := strData;
end;
It has nothing to do with the Item function not being there. The Item function does exists, but the index you give seems to be wrong.
See this msdn article.
An invalid index seems really weird, because you are performing a for loop from 1 to Styles.Count. So if there is no Style, you should not enter the loop.
The only plausible explanation I can think of is that while you are in your loop, the Styles.Count changes and you are getting out of bounds. Are you deleting styles in your loop perhaps? Try a loop going from Styles.Count downto 1 or try a While loop, evaluating Styles.Count at every iteration.
Other things I can think of, but are very unlikely:
While assigning I to ovI, it gets converted to an OleString, so Word searches for a style named "I", instead of a Style at I
While assigning I to ovI, something in the conversion goes wrong and it gets in the range of $FFFFFFA5 - $FFFFFFFF, which are constants for Builtin styles.
try checking if it is null or not with a IF statement