Czy warto… złapać pętlę w środku lub na zewnątrz?

185

Mam pętlę, która wygląda mniej więcej tak:

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

Jest to główna treść metody, której jedynym celem jest zwrócenie tablicy liczb zmiennoprzecinkowych. Chcę zwrócić tę metodę, nulljeśli wystąpi błąd, więc umieszczam pętlę w try...catchbloku, tak jak to:

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

Ale potem pomyślałem również o umieszczeniu try...catchbloku w pętli, w ten sposób:

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

Czy jest jakiś powód, wydajność lub w inny sposób, aby preferować jedno od drugiego?


Edycja: Wydaje się, że konsensus polega na tym, że łatwiej jest umieścić pętlę wewnątrz try / catch, prawdopodobnie w ramach własnej metody. Nadal jednak trwa debata, która jest szybsza. Czy ktoś może to przetestować i wrócić z jednolitą odpowiedzią?

Michael Myers
źródło
2
Nie wiem o wydajności, ale kod wygląda na bardziej przejrzysty, gdy try catch znajduje się poza pętlą for.
Jack B Nimble,

Odpowiedzi:

132

WYSTĘP:

Nie ma absolutnie żadnej różnicy wydajności w tym, gdzie umieszczone są struktury try / catch. Wewnętrznie są one implementowane jako tabela zakresu kodu w strukturze, która jest tworzona podczas wywoływania metody. Podczas wykonywania metody struktury try / catch są całkowicie poza obrazem, chyba że nastąpi rzut, a następnie lokalizacja błędu jest porównywana z tabelą.

Oto odniesienie: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Tabela jest opisana mniej więcej w połowie.

Jeffrey L. Whitledge
źródło
Okej, więc mówisz, że nie ma różnicy oprócz estetyki. Czy możesz połączyć się ze źródłem?
Michael Myers
2
Dzięki. Przypuszczam, że wszystko mogło się zmienić od 1997 roku, ale raczej nie zmniejszyłyby wydajności.
Michael Myers
1
Absolutnie to trudne słowo. W niektórych przypadkach może to hamować optymalizacje kompilatora. Nie jest to koszt bezpośredni, ale może to być pośrednia kara za wyniki.
Tom Hawtin - tackline
2
To prawda, że ​​powinienem był trochę zapanować z entuzjazmem, ale dezinformacja działała mi na nerwy! W przypadku optymalizacji, ponieważ nie wiemy, w jaki sposób wpłynie to na wydajność, myślę, że wróciłem do wypróbowania i przetestowania (jak zawsze).
Jeffrey L Whitledge,
1
nie ma różnicy w wydajności, tylko jeśli kod działa bez wyjątków.
Onur Bıyık
72

Wydajność : jak powiedział Jeffrey w odpowiedzi, w Javie nie ma to większego znaczenia.

Zasadniczo , dla czytelności kodu, wybór miejsca, w którym można przechwycić wyjątek, zależy od tego, czy pętla ma być przetwarzana, czy nie.

W twoim przykładzie wróciłeś po złapaniu wyjątku. W takim przypadku umieściłem try / catch wokół pętli. Jeśli chcesz po prostu złapać złą wartość, ale kontynuować przetwarzanie, włóż ją do środka.

Trzeci sposób : zawsze możesz napisać własną statyczną metodę ParseFloat i obsługiwać wyjątki w tej metodzie zamiast w pętli. Uczynienie obsługi wyjątków izolowanym dla samej pętli!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}
Ray Hayes
źródło
1
Patrz bliżej Wychodzi z pierwszego wyjątku w obu przypadkach. Na początku też myślałem o tym samym.
kervin
2
Byłem świadomy, dlatego powiedziałem w jego przypadku, ponieważ wraca, gdy napotyka problem, wtedy skorzystam z zewnętrznego wychwytu. Dla jasności stosuję tę zasadę ...
Ray Hayes,
Niezły kod, ale niestety Java nie ma luksusu z naszymi parametrami.
Michael Myers
ByRef? Minęło tak dużo czasu, odkąd dotknąłem Javy!
Ray Hayes,
2
Ktoś inny zasugerował TryParse, który w C # /. Net pozwala na bezpieczne parsowanie bez zajmowania się wyjątkami, który jest teraz replikowany, a metodę można ponownie wykorzystać. Jeśli chodzi o pierwotne pytanie, wolałbym pętlę wewnątrz haczyka, po prostu wygląda czystiej, nawet jeśli nie ma problemu z wydajnością ..
Ray Hayes
46

W porządku, po tym jak Jeffrey L. Whitledge powiedział, że nie ma różnicy w wydajności (od 1997), poszedłem i przetestowałem to. Przeprowadziłem ten mały test porównawczy:

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

Sprawdziłem wynikowy kod bajtowy za pomocą javap, aby upewnić się, że nic nie zostało wstawione.

Wyniki pokazały, że przy niewielkich optymalizacjach JIT Jeffrey ma rację ; absolutnie nie ma różnicy w wydajności na Javie 6, VM klienta Sun (nie miałem dostępu do innych wersji). Łączna różnica czasu jest rzędu kilku milisekund w całym teście.

Dlatego jedynym czynnikiem, który wygląda najczystiej. Uważam, że drugi sposób jest brzydki, więc trzymam się albo pierwszego, albo Raya Hayesa .

Michael Myers
źródło
Jeśli moduł obsługi wyjątków pobiera przepływ poza pętlę, to wydaje mi się, że najczystsze jest umieszczenie go poza pętlą - zamiast umieszczania klauzuli catch najwyraźniej w pętli, która jednak zwraca. Używam linii białych znaków wokół „złapanego” ciała takich metod „catch-all”, aby jaśniej oddzielić ciało od otaczającego bloku try .
Thomas W
16

Chociaż wydajność może być taka sama, a to, co „wygląda” lepiej, jest bardzo subiektywne, nadal istnieje dość duża różnica w funkcjonalności. Weź następujący przykład:

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

Pętla while znajduje się w bloku try catch, zmienna „j” jest zwiększana, aż osiągnie 40, drukowana, gdy j mod 4 wynosi zero, a wyjątek jest zgłaszany, gdy j osiągnie 20.

Przed szczegółami oto inny przykład:

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

Ta sama logika jak powyżej, z tą różnicą, że blok try / catch znajduje się teraz w pętli while.

Oto wyjście (podczas try / catch):

4
8
12 
16
in catch block

I inne wyjście (spróbuj / catch in while):

4
8
12
16
in catch block
24
28
32
36
40

Tutaj masz dość znaczącą różnicę:

podczas try / catch wypada z pętli

spróbuj / złap w czasie, gdy pętla będzie aktywna

seBaka28
źródło
Więc owiń try{} catch() {}pętlę, jeśli pętla jest traktowana jako pojedyncza „jednostka”, a znalezienie problemu w tej jednostce powoduje, że cała „jednostka” (lub pętla w tym przypadku) idzie na południe. Umieść try{} catch() {}wewnątrz pętli, jeśli traktujesz pojedynczą iterację pętli lub pojedynczy element w tablicy jako całą „jednostkę”. Jeśli wystąpi wyjątek w tej „jednostce”, pomijamy ten element, a następnie przechodzimy do następnej iteracji pętli.
Galaxy
14

Zgadzam się ze wszystkimi postami dotyczącymi wydajności i czytelności. Są jednak przypadki, w których to naprawdę ma znaczenie. Kilka innych osób wspomniało o tym, ale może to być łatwiejsze z przykładami.

Rozważ ten nieco zmodyfikowany przykład:

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

Jeśli chcesz, aby metoda parseAll () zwróciła null, jeśli wystąpią jakiekolwiek błędy (jak w oryginalnym przykładzie), umieść try / catch na zewnątrz w następujący sposób:

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

W rzeczywistości powinieneś prawdopodobnie zwrócić tutaj błąd zamiast wartości zerowej i ogólnie nie lubię mieć wielu zwrotów, ale masz pomysł.

Z drugiej strony, jeśli chcesz po prostu zignorować problemy i przeanalizować dowolne ciągi znaków, umieść try / catch wewnątrz pętli w następujący sposób:

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}
Matt N.
źródło
2
Dlatego powiedziałem: „Jeśli chcesz po prostu złapać złą wartość, ale kontynuować przetwarzanie, włóż ją do środka”.
Ray Hayes
5

Jak już wspomniano, wydajność jest taka sama. Jednak wrażenia użytkownika niekoniecznie są identyczne. W pierwszym przypadku szybko zawiedziesz (tj. Po pierwszym błędzie), jednak jeśli umieścisz blok try / catch w pętli, możesz przechwycić wszystkie błędy, które powstałyby dla danego wywołania metody Podczas analizowania tablicy wartości z ciągów, w których spodziewane są pewne błędy formatowania, zdecydowanie są przypadki, w których chciałbyś być w stanie przedstawić wszystkie błędy użytkownikowi, aby nie musiał próbować ich naprawiać jeden po drugim. .

użytkownik19810
źródło
To nie jest prawda w tym konkretnym przypadku (wracam w bloku catch), ale ogólnie warto o tym pamiętać.
Michael Myers
4

Jeśli nie powiedzie się wszystko, to pierwszy format ma sens. Jeśli chcesz móc przetwarzać / zwracać wszystkie elementy, które nie zawiodły, musisz użyć drugiego formularza. To byłyby moje podstawowe kryteria wyboru między metodami. Osobiście, jeśli to wszystko albo nic, nie użyłbym drugiej formy.

Joe Skora
źródło
4

Dopóki masz świadomość tego, co musisz osiągnąć w pętli, możesz umieścić try catch poza pętlą. Ale ważne jest, aby zrozumieć, że pętla zakończy się natychmiast po wystąpieniu wyjątku i nie zawsze może być to, czego chcesz. Jest to w rzeczywistości bardzo częsty błąd w oprogramowaniu Java. Ludzie muszą przetwarzać wiele elementów, takich jak opróżnianie kolejki i fałszywie polegać na zewnętrznej instrukcji try / catch obsługującej wszystkie możliwe wyjątki. Mogą również obsługiwać tylko określony wyjątek w pętli i nie oczekiwać żadnego innego wyjątku. Następnie, jeśli wystąpi wyjątek, który nie jest obsługiwany wewnątrz pętli, wówczas pętla zostanie „wstępnie zaprogramowana”, kończy się prawdopodobnie przedwcześnie, a zewnętrzna instrukcja catch obsługuje wyjątek.

Gdyby pętla miała za zadanie opróżnić kolejkę, wówczas najprawdopodobniej pętla mogłaby się zakończyć, zanim ta kolejka zostanie naprawdę opróżniona. Bardzo częsta wina.

Gunnar Forsgren - Mobimacja
źródło
2

W twoich przykładach nie ma funkcjonalnej różnicy. Uważam, że twój pierwszy przykład jest bardziej czytelny.

Jamie
źródło
2

Powinieneś preferować wersję zewnętrzną niż wewnętrzną. To tylko konkretna wersja reguły, przenieś wszystko poza pętlę, którą możesz przenieść poza pętlę. W zależności od kompilatora IL i kompilatora JIT dwie wersje mogą, ale nie muszą, mieć różne parametry wydajności.

Z drugiej strony powinieneś prawdopodobnie spojrzeć na float.TryParse lub Convert.ToFloat.

Orion Adrian
źródło
2

Jeśli umieścisz try / catch w pętli, będziesz zapętlać po wyjątku. Jeśli umieścisz go poza pętlą, zatrzymasz się, gdy tylko zostanie zgłoszony wyjątek.

Joel Coehoorn
źródło
Zwracam wartość NULL w wyjątku, więc w moim przypadku nie byłoby to kontynuowane.
Michael Myers
2

Moim zdaniem, bloki try / catch są niezbędne, aby zapewnić odpowiednią obsługę wyjątków, ale tworzenie takich bloków ma wpływ na wydajność. Ponieważ pętle zawierają intensywne powtarzalne obliczenia, nie zaleca się umieszczania bloków try / catch w pętlach. Ponadto wydaje się, że tam, gdzie występuje ten warunek, często jest wychwytywany „Wyjątek” lub „RuntimeException”. Należy unikać przechwycenia wyjątku RuntimeException w kodzie. Ponownie, jeśli pracujesz w dużej firmie, konieczne jest prawidłowe zarejestrowanie tego wyjątku lub zatrzymanie wyjątku środowiska wykonawczego. Cały punkt tego opisu jestPLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

surendrapanday
źródło
1

ustawienie specjalnej ramki stosu dla try / catch powoduje dodatkowe obciążenie, ale JVM może być w stanie wykryć fakt powrotu i zoptymalizować to.

w zależności od liczby iteracji różnica w wydajności będzie prawdopodobnie nieistotna.

Jednak zgadzam się z innymi, że umieszczenie go poza pętlą sprawia, że ​​korpus pętli wygląda na czystszy.

Jeśli istnieje szansa, że ​​kiedykolwiek będziesz chciał kontynuować przetwarzanie zamiast wyjść, jeśli jest niepoprawny numer, wtedy kod powinien znajdować się w pętli.

Matt
źródło
1

Jeśli jest w środku, zyskasz narzut struktury struktury try / catch N razy, a nie tylko raz na zewnątrz.


Za każdym razem, gdy wywoływana jest struktura Try / Catch, nakłada się to na wykonanie metody. Wystarczy odrobina pamięci i tików procesora potrzebnych do poradzenia sobie ze strukturą. Jeśli uruchamiasz pętlę 100 razy, i dla hipotetycznej przyczyny, powiedzmy, że koszt to 1 tyknięcie za próbę / złapanie połączenia, wtedy posiadanie Try / Catch w pętli kosztuje 100 tyknięć, w przeciwieństwie do tylko 1 tyknięcia, jeśli jest to poza pętlą.

Stephen Wrighton
źródło
Czy potrafisz trochę rozwinąć ogólne koszty?
Michael Myers
1
Okej, ale Jeffrey L. Whitledge mówi, że nie ma dodatkowych kosztów ogólnych. Czy możesz podać źródło, które mówi, że istnieje?
Michael Myers
Koszt jest niewielki, prawie nieistotny, ale tam jest - wszystko ma swój koszt. Oto artykuł na blogu z Nieba programisty ( tiny.cc/ZBX1b ) dotyczący platformy .NET, a także post grupy dyskusyjnej Reshat Sabiq dotyczący JAVA ( tiny.cc/BV2hS )
Stephen Wrighton
Rozumiem ... Więc teraz pytanie brzmi, czy koszt poniesiony przy wejściu do try / catch (w którym to przypadku powinien być poza pętlą), czy też jest stały przez czas trwania try / catch (w którym to przypadku powinien być w pętli)? Mówisz, że to nr 1. I wciąż nie jestem do końca przekonany, że jest jakiś koszt.
Michael Myers
1
Według tej książki Google Book ( tinyurl.com/3pp7qj ) „najnowsze maszyny wirtualne nie wykazują kary”.
Michael Myers
1

Chodzi przede wszystkim o to, by zachęcić do pierwszego stylu: umożliwienia skonsolidowania obsługi błędów i obsługi tylko raz, a nie natychmiast w każdym możliwym miejscu błędu.

wnoise
źródło
Źle! Wyjątkiem jest określenie, jakie wyjątkowe zachowanie należy zastosować, gdy wystąpi „błąd”. Zatem wybór wewnętrznego lub zewnętrznego bloku try / catch naprawdę zależy od tego, jak chcesz obsłużyć leczenie pętli.
gizmo
Można to zrobić równie dobrze za pomocą błędów poprzedzających wyjątek, takich jak przekazywanie flag „wystąpił błąd”.
wnoise
1

włóż to do środka. Możesz kontynuować przetwarzanie (jeśli chcesz) lub możesz rzucić pomocny wyjątek, który informuje klienta o wartości myString i indeksie tablicy zawierającej złą wartość. Myślę, że NumberFormatException powie ci już złą wartość, ale zasadą jest umieszczenie wszystkich pomocnych danych w zgłaszanych wyjątkach. Pomyśl o tym, co byłoby dla ciebie interesujące w debuggerze w tym momencie programu.

Rozważać:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

W razie potrzeby naprawdę docenisz taki wyjątek z jak największą ilością informacji.

Kyle Dyer
źródło
Tak, jest to również ważny punkt. W moim przypadku czytam z pliku, więc wszystko, czego naprawdę potrzebuję, to ciąg, którego nie udało się przeanalizować. Więc zrobiłem System.out.println (ex) zamiast zgłosić inny wyjątek (chcę przeanalizować tyle plików, ile jest legalne).
Michael Myers
1

Chciałbym dodać własne 0.02co dwóch konkurencyjnych zagadnieniach, gdy patrzę na ogólny problem miejsca, w którym należy ustawić obsługę wyjątków:

  1. „Szersza” odpowiedzialność try-catchbloku (tj. Poza twoją pętlą w twoim przypadku) oznacza, że ​​zmieniając kod w pewnym późniejszym momencie, możesz przez pomyłkę dodać linię obsługiwaną przez istniejący catchblok; być może nieumyślnie. W twoim przypadku jest to mniej prawdopodobne, ponieważ wyraźnie łapieszNumberFormatException

  2. Im „węższa” odpowiedzialność try-catchbloku, tym trudniejsze staje się refaktoryzowanie. Szczególnie, gdy (jak w twoim przypadku) wykonujesz instrukcję „nielokalną” z poziomu catchbloku ( return nullinstrukcji).

oxbow_lakes
źródło
1

To zależy od obsługi awarii. Jeśli chcesz tylko pominąć elementy błędu, spróbuj w środku:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

W każdym innym przypadku wolałbym spróbować na zewnątrz. Kod jest bardziej czytelny, bardziej przejrzysty. Może lepiej byłoby zgłosić wyjątek IllegalArgumentException w przypadku błędu, jeśli zwraca wartość null.

Arne Burmeister
źródło
1

Wrzucę moje 0,02 $. Czasami kończy się konieczność dodania „w końcu” później w kodzie (ponieważ kto kiedykolwiek pisze swój kod doskonale za pierwszym razem?). W takich przypadkach nagle sensowniej jest spróbować try / catch poza pętlą. Na przykład:

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

Ponieważ jeśli pojawi się błąd, czy nie, chcesz tylko raz zwolnić połączenie z bazą danych (lub wybrać ulubiony typ innego zasobu ...).

Ogre Psalm 33
źródło
1

Innym aspektem nie wymienionym powyżej jest fakt, że każdy try-catch ma trochę wpływ na stos, co może mieć wpływ na metody rekurencyjne.

Jeśli metoda „outer ()” wywołuje metodę „inner ()” (która może wywoływać się rekurencyjnie), spróbuj zlokalizować try-catch w metodzie „outer ()”, jeśli to możliwe. Prosty przykład „awarii stosu”, którego używamy w klasie wydajności, kończy się niepowodzeniem przy około 6400 klatkach, gdy try-catch jest w metodzie wewnętrznej, i przy około 11,600, gdy jest w metodzie zewnętrznej.

W prawdziwym świecie może to stanowić problem, jeśli używasz wzoru złożonego i masz duże, złożone struktury zagnieżdżone.

Jeff Hill
źródło
0

Jeśli chcesz uchwycić wyjątek dla każdej iteracji lub sprawdzić, co to jest wyjątek generowany i złapać wszystkie wyjątki w itertaion, umieść try ... catch wewnątrz pętli. Nie spowoduje to przerwania pętli, jeśli wystąpi wyjątek i można przechwycić każdy wyjątek w każdej iteracji w całej pętli.

Jeśli chcesz przerwać pętlę i zbadać wyjątek za każdym razem, gdy zostanie zgłoszony, użyj try ... catch out of the loop. Spowoduje to przerwanie pętli i wykonanie instrukcji po złapaniu (jeśli występuje).

Wszystko zależy od twoich potrzeb. Wolę używać try ... catch wewnątrz pętli podczas wdrażania, ponieważ jeśli wystąpi wyjątek, wyniki nie są niejednoznaczne, a pętla nie ulegnie uszkodzeniu i nie wykona się całkowicie.

Juned Khan Momin
źródło