You are not thinking at a high-enough level. OK, the builder fails. The attribute remains undefined. But what do you do about the code that is calling the accessor? The class' contract indicated that calling the method would always return an IO::File. But now it's returning undef. (The contract was IO::File
, not Maybe[IO::File]
, right?)
So on the next line of code, the caller is going to die ("Can't call method 'readline' on an undefined value at the_caller.pl line 42."), because it expects your class to follow the contract that it defined. Failure was not something your class was supposed to do, but now it did. How can the caller do anything to correct this problem?
If it can handle undef
, the caller didn't actually need a filehandle to begin with... so why did it ask your object for one?
With that in mind, the only sane solution is to die. You can't meet the contract you agreed to, and die
is the only way you can get out of that situation. So just do that; death is a fact of life.
Now, if you aren't prepared to die when the builder runs, you'll need to change when the code that can fail runs. You can do it at object-construction time, either by making it non-lazy, or by explicitly vivifying the attribute in BUILD (BUILD { $self->file_name }
).
A better option is to not expose the file handle to the outside world at all, and instead do something like:
# dies when it can't write to the log file
method write_log {
use autodie ':file'; # you want "say" to die when the disk runs out of space, right?
my $fh = $self->file_handle;
say {$fh} $_ for $self->log_messages;
}
Now you know when the program is going to die; in new
, or in write_log
. You know because the docs say so.
The second way makes your code a lot cleaner; the consumer doesn't need to know about the implementation of your class, it just needs to know that it can tell it to write some log messages. Now the caller is not concerned with your implementation details; it just tells the class what it really wanted it to do.
And, dying in write_log
might even be something you can recover from (in a catch block), whereas "couldn't open this random opaque thing you shouldn't know about anyway" is much harder for the caller to recover from.
Basically, design your code sanely, and exceptions are the only answer.
(I don't get the whole "they are a kludge" anyway. They work the exact same way in C++ and very similarly in Java and Haskell and every other language. Is the word die
really that scary or something?)