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;
Related
I'm not sure if this is some generic problem or it's because of Spring4D implementation, but I can't use default parameter values for creating comparer.
type
TMyClass = class
class function MyComparer(AParam: Boolean = False): IComparer<TMyClass>;
end;
implementation
class function TMyClass.MyComparer(AParam: Boolean): IComparer<TMyClass>;
begin
Result := TComparer<TMyClass>.Construct(
function (const L, R: TMyClass): Integer
begin
Result := 0;
end);
end;
When I create a list without the specified parameter, I get an error message about missing parameters.
TCollections.CreateSortedObjectList<TMyClass>(TMyClass.MyComparer);
E2035 Not enough actual parameters
However without any parameters or with all parameters specified it works. Is there any reason why I can't do that?
I don't have Spring4D to hand to test, but I'm guessing that what's happening is something similar to this where Delphi's syntax rules allowing omission of parentheses when executing a method which takes no parameters introduces ambiguity. Here, where you do :
TCollections.CreateSortedObjectList<TMyClass>(TMyClass.MyComparer);
...the compiler can't be sure if you mean to pass the method MyComparer directly (to the overload of CreateSortedObjectList which takes a method pointer type TComparison<T>) or whether you mean to execute the method and pass the return value. In this case you want to do the latter, so you can be explicit for the compiler and include the parentheses
TCollections.CreateSortedObjectList<TMyClass>(TMyClass.MyComparer());
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);
what is the best way to handle invalid functions calls with not specified arguments for functions which do not have User interface access
function safeSQRT ( x : Real) : real;
begin
/// check for valid params .....
if (x<0) then
begin
exit;
/// create a exception here ??
end;
end;
With this signature
function safeSQRT(x : Real): Real;
you have but two options to signal an error:
Raise an exception.
Return a sentinel value that indicates an error.
The third option is to change the signature
function safeSQRT(x : Real; out retval: Real): Integer;
where the function return value is now an error code.
If you return an error code you force the caller to explictly check every single return value of each call. That's a heavy burden. Think of the fun involved in calling even the most mundane Win32 API to picture the burden.
If you return a sentinel value, then the same burden is placed on each and every caller.
If you raise an exception then the caller's job is easier. The caller can usually ignore exceptions and let them bubble upwards and be handled be a central exception handler.
The flip side is when the error condition occurs commonly and needs immediate handling. In such scenarios raising an exception is usually the wrong choice. If the error must always be handled at the call site then using exceptions makes it likely that the caller will forget to handle it.
I need a clarification of this case.
According my tests the Result variable is defined to:
Boolean=False, Integer=0, String='', Object=nil etc from the first line.
But I have never seen an official reference for this.
It also make sense as this gives the hint.
[DCC Warning] Unit1.pas(35): H2077 Value assigned to 'TForm1.Test' never used
function TForm1.Test: Boolean;
begin
Result := False;
// Some arbitrary code here
Result := True;
end;
But what happens if I comment out the first line and there is an exception somewhere before last line? Is Result = False ?
If Result is undefined this means that I always have to start every function by defining Result in case of exception later. And this make no sense for me.
As stated by the official Delphi documentation, the result is either:
CPU register(s) (AL / AX / EAX / RAX / EAX:EDX) for ordinal values and elements contained in a register;
FPU register (st(0) / XMM1);
An additional variable passed as a latest parameter.
The general rule is that no result value is defined by default. You'll have to set it. The compiler will warn you about any missing result set.
For a string, dynamic array, method pointer, or variant result, the
effects are the same as if the function result were declared as an
additional var parameter following the declared parameters. In other
words, the caller passes an additional 32-bit pointer that points to a
variable in which to return the function result.
To be accurate, the var parameter is not only for managed types, but only for record or object results, which are allocated on the stack before calling, so are subject to the same behavior.
That is, for instance, if your result is a string, it will passed as an additional var parameter. So it will contain by default the value before the call. It will be '' at first, then if you call the function several times, it will contain the previous value.
function GetString: string;
// is compiled as procedure GetString(var result: string);
begin
if result='' then
result := 'test' else
writeln('result=',result);
end;
function GetRaise: string;
// is compiled as procedure GetRaise(var result: string);
begin
result := 'toto';
raise Exception.Create('Problem');
end;
var s: string;
begin
// here s=''
s := GetString; // called as GetString(s);
// here s='test'
s := GetString; // called as GetString(s);
// will write 'result=test' on the console
try
s := GetRaise; // called as GetRaise(s);
finally
// here s='toto'
end;
end;
So my advices are:
Fix all compiler warning about unset result;
Do not assume that a result string is initialized to '' (it may be at first, but not at 2nd call) - this is passed as a var parameter, not as a out parameter;
Any exception will be processed as usual, that is, the running flow will jump to the next finally or except block - but if you have a result transmitted as a var parameter, and something has been already assigned to result, the value will be set;
It is not because in most cases, an unset result ordinal value (e.g. a boolean) is 0 (because EAX=0 in asm code just before the return), that it will be next time (I've seen random issues on customer side because of such unset result variables: it works most time, then sometimes code fails...);
You can use the exit() syntax to return a value, on newer versions of Delphi.
You state:
If Result is undefined this means that I always have to start every function by defining Result in case of exception later.
You are concerned that the return value of a function is undefined if the function raises an exception. But that should not matter. Consider the following code:
x := fn();
If the body of the function fn raises an exception then, back at the call site, x should not be assigned to. Logically the one-liner above can be thought of as a two-liner:
call fn()
assign return value to x
If an exception is raised in line 1 then line 2 never happens and x should never be assigned to.
So, if an exception is raised before you have assigned to Result then that is simply not a problem because a function's return value should never be used if the function raises an exception.
What you should in fact be concerned about is a related issue. What if you assign to Result and then an exception is raised? Is it possible for the value you assigned to Result to propagate outside of the function? Sadly the answer is yes.
For many result types (e.g. Integer, Boolean etc.) the value you assign to Result does not propagate outside the function if that function raises an exception. So far, so good.
But for some result types (strings, dynamic arrays, interface references, variants etc.) there is an implementation detail that complicates matters. The return value is passed to the function as a var parameter. And it turns out that you can initialise the return value from outside the function. Like this:
s := 'my string';
s := fn();
When the body of fn begins execution, Result has the value 'my string'. It is as if fn is declared like this:
procedure fn(var Result: string);
And this means that you can assign to the Result variable and see modifications at the call site, even if your function subsequently raises an exception. There is no clean way to work around it. The best you can do is to assign to a local variable in the function and only assign Result as the final act of the function.
function fn: string;
var
s: string;
begin
s := ...
... blah blah, maybe raise exception
Result := s;
end;
The lack of a C style return statement is felt strongly here.
It is surprising hard to state accurately which type of result variables will be susceptible to the problem described above. Initially I thought that the problem just affected managed types. But Arnaud states in a comment that records and objects are affected too. Well, that is true if the record or object is stack allocated. If it is a global variable, or heap allocated (e.g. member of a class) then the compiler treats it differently. For heap allocated records, an implicit stack allocated variable is used to return the function result. Only when the function returns is this copied to the heap allocated variable. So the value to which you assign the function result variable at the call site affects the semantics of the function itself!
In my opinion this is all a very clear illustration of why it was a dreadful mistake, in the language design, for function return values to have var semantics as opposed to having out semantics.
No, Result has no (guaranteed) default value. It is undefined unless you give it a value. This is implied by the documentation, which states
If the function exits without assigning a value to Result or the
function name, then the function's return value is undefined.
I just tried
function test: integer;
begin
ShowMessage(IntToStr(result));
end;
and got a message with the text 35531136.
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