2010-01-29 41 views
10

Jaki jest najlepszy sposób radzenia sobie z niepowodzeniem w metodzie budowniczej?Co powinienem zrobić, jeśli metoda budowniczego łosia nie powiedzie się?

Na przykład:

package MyObj; 
use Moose; 
use IO::File; 

has => 'file_name' (is => 'ro', isa => 'Str',  required =>1 ); 
has => 'file_handle' (is => 'ro', isa => 'IO::File', lazy_build => 1); 

sub _build_file_handle { 
    my $self = shift; 
    my $fh = IO::File->new($self->file_name, '<'); 

    return $fh; 
} 

Jeśli _build_file_handle nie uzyskać uchwyt, budowniczy powróci undef, która zawiedzie typ wiązania.

Mogę użyć powiązania w ograniczeniu typu file_handle, aby zaakceptować wartość undef jako prawidłową. Ale wtedy predykat has_file_handle zwróci wartość true, nawet jeśli ma wartość undef.

Czy istnieje sposób sygnalizowania awarii budowniczego, a atrybut powinien pozostać wyczyszczony?

+4

Dlaczego nie po prostu wyrzucić wyjątek? – friedo

+0

@friedo, proszę wyjaśnij, jak poradzisz sobie z wyjątkami w tym przypadku. Obawiam się, że jeśli wyrzucę fatalny wyjątek w konstruktorze, oznacza to, że za każdym razem, gdy uzyskuję dostęp do 'file_handle', będę musiał być gotowy na złapanie wyjątku. – daotoad

+1

Jeśli nie masz wyjątku, będziesz musiał sprawdzić, co powróciłeś i zrobić coś innego, jeśli się nie uda - sprawdzenie wyjątku to prawdopodobnie mniej kodu. – Mark

Odpowiedz

6

„Najlepsza” jest subiektywna, ale musisz zdecydować, który ma większy sens w kodzie:

  1. czy można kontynuować w kodzie kiedy filehandle nie budować (czyli jest warunek do odzyskania), konstruktor powinien zwrócić undef i ustawić ograniczenie typu na 'Maybe[IO::File]'. Oznacza to, że będziesz musiał również sprawdzić, czy atrybut jest zdefiniowany za każdym razem, gdy go używasz. Możesz również sprawdzić, czy ten atrybut został poprawnie zbudowany w BUILD, i zdecydować się na podjęcie dalszych działań w tym miejscu (jak wspominał friedo w swoim komentarzu), np. wywołanie clear_file_handle, jeśli jest to undef (ponieważ budowniczy zawsze przypisuje wartość do atrybutu, zakładając, że nie zginie oczywiście).

  2. w przeciwnym wypadku, pozwól budowniczemu zawieść, albo jawnie rzucając wyjątek (który możesz wybrać, aby złapać wyższy poziom), albo po prostu zwróć undef i pozwól, aby ograniczenie typu zawiodło. W każdym razie twój kod umrze; masz po prostu wybór sposobu, w jaki umiera i jak obszerny jest ślad stosu. :)

PS. Możesz również spojrzeć na Try::Tiny, którego Moose używa wewnętrznie, i jest w zasadzie tylko opakowanie dla * idiomu do eval { blah } or die ... *.

* Ale zrobione dobrze! i w fajny sposób! (Wydaje mi się usłyszeć wiele szeptać mi do ucha z #moose ..)

+0

Spojrzałem na Try :: Tiny. Byłem niezdecydowany, aby przeskoczyć na dowolny moduł obsługi wyjątków, ponieważ zostałem spalony źle, używając poprzednich "cudownych nowych" budowniczych klas i programów obsługi wyjątków. Długo trwało dla mnie, aby dać Moose szansę. – daotoad

+2

Nie rozumiem; Wypróbuj :: Tiny jest naprawdę trywialne i nie robi prawie nic poza tym, co jest konieczne do obejścia zachowania, które jest prawie błędem w Perlu. Jestem niezdecydowany, aby zaangażować się w coś tak dalekosiężnego jak Moose, ale możesz przeczytać i zrozumieć źródło Try :: Tiny za około 5 minut. Co cię pali? – hdp

+1

Eter, dzięki. W końcu poświęciłem czas na sprawdzenie Try :: Tiny i przeszedłem dość dokładny test sniff. Kiedy kod jest właściwie zbudowany, nie jest zbyt trudno zminimalizować liczbę bloków "try {}". Do tej pory jestem całkiem zadowolony z Try :: Tiny. – daotoad

2

Czy istnieje sposób, aby zasygnalizować, że konstruktor nie powiodła się, a atrybut powinien pozostać wyczyszczone?

No. To nie miałoby sensu, budowniczy zadziała jeśli atrybut jest cleaered, jeśli to zdaje się w budowniczego to właśnie ogień podczas wprowadzania następnego połączenia do niego i pozostają w wyczyszczone stan. Marnowanie dużo pracy, tylko do zestaw-coś-jeśli-to-działa-i-jeśli-nie-nadal.

Sugestia type-union jest dobra, ale trzeba napisać kod, który może działać z dwoma radykalnie różnymi przypadkami: uchwyt pliku i nieistniejący uchwyt pliku. To wygląda na kiepski pomysł.

Jeśli uchwyt pliku nie jest niezbędny dla zadania, prawdopodobnie nie jest współdzielony z tym samym zakresem rzeczy z dostępem do obiektu. W takim przypadku obiekt może po prostu podać metodę, która generuje uchwyt pliku z obiektu.Robię to w kodzie produkcyjnym. Nie daj się nabrać na to, aby wszystko było leniwym atrybutem, niektóre rzeczy są funkcjami atrybutów i nie zawsze ma sens dołączanie ich do obiektu.

sub get_fh {                 
    my $self = shift;               

    my $abs_loc = $self->abs_loc;            

    if (!(-e $abs_loc) || -e -z $abs_loc) {         
    $self->error({ msg => "Critical doesn't exist or is totally empty" }); 
    die "Will not run this, see above error\n";        
    }                   

    my $st = File::stat::stat($abs_loc);          
    $self->report_datetime(DateTime->from_epoch(epoch => $st->mtime));  

    my $fh = IO::File->new($abs_loc, 'r')         
    || die "Can not open $abs_loc : $!\n"         
    ;                   

    $fh;                  

}                   

zupełnie innym podejściem jest podklasy IO::File z meta danych na temat pliku, który chcesz zachować. Czasami jest to skuteczne i wspaniałe rozwiązanie:

package DM::IO::File::InsideOut; 
use feature ':5.10'; 
use strict; 
use warnings; 

use base 'IO::File'; 

my %data; 

sub previouslyCreated { 
    $data{+shift}->{existed_when_opened} 
} 

sub originalLoc { 
    $data{+shift}->{original_location} 
} 

sub new { 
    my ($class, @args) = @_; 

    my $exists = -e $args[0] ? 1 : 0; 

    my $self = $class->SUPER::new(@args); 

    $data{$self} = { 
    existed_when_opened => $exists 
    , original_location => $args[0] 
    }; 

    $self; 

}; 
9

Nie myślisz o poziomie wystarczająco wysokim. OK, budowniczy nie działa. Atrybut pozostaje niezdefiniowany. Ale co zrobić z kodem wywołującym akcesor? Umowa klasy wskazywała, że ​​wywołanie metody zawsze zwróci plik IO ::. Ale teraz wraca undef. (Umowa była IO::File nie Maybe[IO::File], prawda?)

więc w następnej linii kodu, rozmówca będzie die ("Nie można nazwać metodą«readline»na nieokreślonej wartości w the_caller.pl linii 42 "), ponieważ oczekuje, że twoja klasa będzie postępować zgodnie z ustaloną przez nią umową. Niepowodzenie nie było czymś, co powinna robić twoja klasa, ale teraz tak się stało. W jaki sposób rozmówca może zrobić cokolwiek, aby rozwiązać ten problem?

Jeśli może obsłużyć numer undef, osoba dzwoniąca w rzeczywistości nie potrzebowała uchwytu pliku ... więc dlaczego poprosiła o niego obiekt?

Mając to na uwadze, jedynym rozsądnym rozwiązaniem jest śmierć. Nie możesz spełnić warunków umowy, na którą się zgodziłeś, a die to jedyny sposób, w jaki możesz wyjść z tej sytuacji. Po prostu zrób to; śmierć jest faktem.

Teraz, jeśli nie jesteś przygotowany na śmierć, gdy budowniczy działa, musisz zmienić, gdy kod, który może zawieść, działa. Możesz to zrobić w czasie budowy obiektu, albo czyniąc go leniwym, albo jawnie ożywiając atrybut w BUILD (BUILD { $self->file_name }).

Lepszym rozwiązaniem jest, aby nie narażać uchwyt pliku do świata zewnętrznego w ogóle, a zamiast zrobić coś takiego:

# 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; 
} 

Teraz wiesz, kiedy program umrze; w new lub w write_log. Wiesz, ponieważ doktorzy tak mówią.

Drugi sposób sprawia, że ​​kod jest dużo czystszy; Konsument nie musi wiedzieć o implementacji twojej klasy, po prostu musi wiedzieć, że może napisać kilka komunikatów. Teraz osoba dzwoniąca nie jest zainteresowana twoimi szczegółami implementacji; po prostu mówi klasie, czego naprawdę chce.

I śmierć w write_log może nawet być czymś, z czego można odzyskać (w bloku catch), podczas gdy "nie można otworzyć tej przypadkowej, nieprzejrzystej rzeczy, o której nie powinieneś wiedzieć", jest znacznie trudniejsze, aby osoba dzwoniąca odzyskać od.

Zasadniczo zaprojektuj swój kod w porządku, a wyjątki są jedyną odpowiedzią.

(Nie rozumiem całego "oni są kludge" tak działają w taki sam sposób w C++ i bardzo podobnie w Javie i Haskell i każdym innym języku. Czy słowo die jest naprawdę tak przerażające czy coś?)

+0

Ty i inni argumentujecie przekonywująco za wyjątki. Miałem nadzieję, że ich nie wykorzystam z powodu ** faktu, że są zepsute. Rzucanie wyjątków jest łatwe i działa przyjemnie. Łowienie ich jest okropne. Blokuj 'eval' jest zepsute. Napisałem już wystarczająco evalplateplate, by wydrukować Kaplicę Sykstyńską. – daotoad

+3

Użyj opcji Try :: Tiny, która została zasugerowana wiele razy w wielu wątkach. Perl się nie zmieni, ponieważ boisz się bibliotek w Stack Overflow. Więc albo skorzystaj z biblioteki, napisz szablon, napraw perl lub odejdź. – jrockway

+0

Po spaleniu przez wypróbowanie różnych technik wewnętrznych (pamiętaj, że chwilowa moda) i Error.pm i innych modułów try/catch, podniosłem nos i nie dałem Try :: Tiny szansy, na którą zasłużył. Twoje argumenty na temat wyjątków, jak również innych, doprowadziły mnie do wniosku, że były one właściwym projektem. Za to ci dziękuję. – daotoad

Powiązane problemy