No está pensando en un nivel lo suficientemente alto. OK, el constructor falla. El atributo permanece indefinido. Pero, ¿qué haces con el código que llama al usuario? El contrato de la clase indicaba que llamar al método siempre devolvería un IO :: File. Pero ahora está volviendo undef. (El contrato fue IO::File
, no Maybe[IO::File]
, ¿verdad?)
Así que en la siguiente línea de código, la persona que llama se va a morir ("No se puede llamar al método 'readline' en un valor indefinido en the_caller.pl línea 42 . "), porque espera que su clase siga el contrato que definió. El fracaso no era algo que tu clase debía hacer, pero ahora lo hizo. ¿Cómo puede la persona que llama hacer algo para corregir este problema?
Si puede manejar undef
, la persona que llama no necesita un identificador de archivo para empezar ... entonces, ¿por qué le pidió uno a su objeto?
Con esto en mente, la única solución sensata es morir. No puede cumplir el contrato que acordó, y die
es la única forma en que puede salir de esa situación. Así que haz eso; la muerte es un hecho de la vida.
Ahora, si no está preparado para morir cuando se ejecuta el constructor, deberá cambiar cuando se ejecute el código que puede fallar. Puede hacerlo en tiempo de construcción del objeto, ya sea haciéndolo no perezoso, o reviviendo explícitamente el atributo en BUILD (BUILD { $self->file_name }
).
Una mejor opción es no exponer el identificador de archivo con el mundo exterior en absoluto, y en lugar de hacer algo como:
# 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;
}
Ahora se sabe cuando el programa se va a morir; en new
, o en write_log
. Ya sabes, porque los documentos lo dicen.
La segunda forma hace que su código sea mucho más limpio; el consumidor no necesita saber sobre la implementación de su clase, solo necesita saber que puede indicarle que escriba algunos mensajes de registro. Ahora la persona que llama no está preocupada con los detalles de su implementación; simplemente le dice a la clase lo que realmente quería que hiciera.
Y, morir en write_log
incluso puede ser algo de lo que pueda recuperarse (en un bloque catch), mientras que "no pudo abrir esta opacidad aleatoria que de todos modos no debería saber" es mucho más difícil recuperar de.
Básicamente, diseñe su código de manera sensata, y las excepciones son la única respuesta.
(No entiendo el todo "son un poco pesado" de todos modos. Funcionan exactamente de la misma manera en C++ y de manera similar en Java y Haskell y en todos los demás idiomas. ¿Es la palabra die
realmente tan espeluznante o algo así?)
¿Por qué no lanzar una excepción? – friedo
@friedo, explique cómo manejaría las excepciones en este caso. Mi preocupación es que si lanzo una excepción fatal en el constructor, eso significa que cada vez que accedo 'file_handle', tendría que estar listo para atrapar una excepción. – daotoad
Si no tiene una excepción, tendrá que verificar lo que devuelve y hacer algo diferente si falla; es probable que la comprobación de una excepción sea un código menor. – Mark