tags:

views:

419

answers:

8

Please, may somebody explain me, what can raise an exception in this code?

function CreateBibleNames: TStrings;
begin
  Result := TStringList.Create;
  try
    Result.Add('Adam');
    Result.Add('Eva');
    Result.Add('Kain');
    Result.Add('Abel');
  except
    Result.Free;
    raise;
  end;      
end;

Since I use delphi I have used exception handling perhaps once. I consider the code above to be written by a skillfull programmer and I do not think the exceptions are redundant. But still, using exception handling in this concept remains a mystery for me. It seems to be a safe code (without try except end). I have seen many times similar code snippets like this, that's why there is probably a good reason to write it this way in spite of my experience, that did not prove it's necessity.

Moreover when something fails, I get exception description....

Thanx

+2  A: 

The most obvious exception would be whatever Delphi uses for an OutOfMemoryException. If there isn't enough memory to add a couple strings to a list, free the whole list and re-throw the exception.

That seems like overly defensive programming for making a list of predetermined names -- generally, if you get one exception due to being out of memory, your whole application is hosed.

Mark Rushakoff
of course, this is very primitive example, but it reveals the advanced thinking... should I do the same? I see only one disadvantage: the code is less readable...
lyborko
Lyborko, the less primitive the code becomes, the more important it is to use the exception-handling idiom demonstrated in your question.
Rob Kennedy
+4  A: 

The only potential reason for an exception I can see is an OutOfMemory exception and if this is the case - all bets are off anyway. In my opinion this is an example of abusive use of a valid concept.

Excessive and unjustified use of try except clutters the code and makes it more difficult to read

mfeingold
It's only more difficult to read until you become familiar with the try-except idiom for factory functions like the one shown. Then, you barely even notice it. If there's a constructor that *isn't* immediately followed by a "try" statement, the code looks weird.
Rob Kennedy
A: 

As an interesting side note, some of the authors of Java now consider the decision to require the catching of exceptions in virtually all cases to be a mistake. That's because most exceptions really result in the program being killed, so you might as well let the runtime system kill the program and print a stack trace, rather than forcing the programmer to catch the exception and ... well, just print something and kill the program.

vy32
It is not required to catch all exceptions in the Java language. Checked exceptions can be handled, or declared in the method declaration. If a method does not handle a checked exception, the method must declare it using the throws keyword. Unchecked exceptions do not have to be handled at all.
mjustin
I don't want you working on any sort of software that deals with ensuring someone lives! Exiting the program via a crash in the case of an error should never be an option, even for Hello, World!
TofuBeer
Delphi User Code exception very rarely result in the entire application being killed.
Toby Allen
This is not an example of an exception being 'caught'... it is immediately re-raised. The code is merely performing necessary cleanup (to prevent a memory leak) should an exception occur. try..except is an exception **handling** construct, not an exception _catching_ construct. It is far too often abused as an exception catching construct to the extent that I would prefer default behaviour of re-raising the exception unless it is explicitly 'caught' in the except block (Of course payment of a pint of blood should also be required every time CatchException is used ^^).
Craig Young
Tofubeer, *handling* an exception means *fixing* whatever situation it was that caused the error. Once an exception is handled, **there should be no problem anymore**. If you encounter an exception that you weren't expecting (and thus haven't written code to fix), how can you possibly handle it? You don't even know what happened. It's not a good idea for the program to keep running, *especially* when people's lives are at stake, because the program will be running in an inconsistent state, possibly putting the people in *more* danger.
Rob Kennedy
+2  A: 

Without the source for TStringList, or authoritative documentation, you can't really know. Just for illustrative purposes, let's suppose that TStringList is written such that when memory gets tight, it starts swapping portions of the list to and from disk, allowing you to manage a list as large as your disk. Now the caller is also susceptible to the gamut of I/O errors: out of disk space, bad block encountered, network timeout, etc.

Is handling such exceptions overkill? Judgement call based upon scenario. We could ask NASA's programmers what they think.

Dewayne Christensen
+7  A: 

It's a good habit to be in when returning newly constructed objects from functions. In a more complex scenario than a string list, some invariant might be broken or other error occur, and hence an exception, and in that case you want the return value freed. Rather than have to examine every possible situation when this may occur, it's generally a good practice to have a single pattern for returning newly constructed objects and follow it everywhere.

That way, when the implementation of the objects you're constructing changes in the future, you're still safe if they happen to throw an exception.

Barry Kelly
But I would still call FreeAndNil(Result). It is true that the exception is passed on, but it is saffer for the function to return nil in this case. You never know what happens next and nil signals you that the result is not valid.
Runner
@Runner: The fact that there is an exception means the Result is **NOT** returned. And there is no reason to set a value that will never be seen to anything (nil included).
Craig Young
Hm I just checked and you are correct. Interesting :) I never noticed that, but that is probably because I never write the code this way.
Runner
+11  A: 

Okay, that code is strange, I agree, and I totally understand why it got written that way. But it was written that way because the premise underlying the code is wrong. The fact that the construct seems strange should be a "code smell", and should tell you that something might not be getting done the best way possible.

First, here's why the unusual construct in the try...except block. The function creates a TStringList, but if something goes wrong in the course of filling it, then the TStringList that was created will be "lost" out on the heap and will be a memory leak. So the original programmer was defensive and made sure that if an exception occurred, the TStringList would be freed and then the exception would get raised again.

Now here is the "bad premise" part. The function is returning an instance of TStrings. This isn't always the best way to go about this. Returning an instance of an object like that begs the question "Who is going to dispose of this thing I've created?". It creates a situation where it might be easy -- on the calling side -- to forget that a TStrings instance has been allocated.

A "Better Practice" here is to have the function take a TStrings as a parameter, and then fill in the existing instance. This way, there is no doubt about who owns the instance (the caller) and thus who should manage its lifetime.

So, the function becomes a procedure and might look like this:

procedure CreateBibleNames(aStrings: TStrings);
begin
  if aStrings <> nil then
  begin
    aStrings .Add('Adam');
    aStrings .Add('Eva');
    aStrings .Add('Kain');
    aStrings .Add('Abel');
  end;      
end;

Now this is not to say that returning an object instance is a bad thing every time -- that is the sole function of the very useful Factory pattern, for example. But for more "general" functions like this, the above is a "better" way of doing things.

Nick Hodges
Whenever someone creates an object instance they call a function to return the new instance. This function (usually called Create or CreateXXX) is a special kind of function (a constructor); but a function nonetheless. So whenever someone calls such a Create function, it is their responsibility to manage the resource (I.e. destroy the object when necessary). TIP: Do not call your procedure ` procedure **Create**BibleNames(aStrings: TStrings); ` ... because that is **NOT** what it's doing.
Craig Young
+4  A: 

I would write the code that way, for several reasons:

1) By standardizing how memory allocations look, it is easy to inspect the source code to see if something is missing, without having to read all lines. Here, you just see the .Create, and notice that exception handling is good, so you don't have to read the part between try...except.

2) By standardizing how you code memory allocations, you never forget to do it, even when necessary.

3) If you would later change the source code, insert some code, change how .Add works, replace TStringList with something else, etc., then it would not break this code.

4) It means that you don't have to think about whether TStringList.Add() will throw exceptions, or not, relieving your brain for other work that provides more value.

Lars D
+2  A: 

That kind of code is a classic "factory" pattern. That example is trivial, and may not require exception handling because it could raise exceptions in extreme corner cases. But if the factory code is much more complex, it is correct to free the instance created before re-raising any encountered exception because the caller can't know if the instance was created or not before the exception was raised. It is far better to assume the instance was not created or freed if any exception is returned. Factories are useful when the returned instance may not be always the same (i.e. any class in a given hierarchy), and when parameters to create the instance are "unknonwn" to the caller but not the factory. In such cases the caller can't pass an already created instance just to be "filled" with the proper parameters, but has just to require an instance - of course it has to be clear the caller becomes the "owner" of the created instance.

ldsandon