2013-08-22 10 views
16

Jaki jest zapach projektu, słaba praktyka rekursji? kiedy zobaczyłem Resharpera sugerującego ulepszenia, szybko rozejrzałem się po Google. Zobaczyłem wiele komentarzy dotyczących re-factoring rekurencji ogona do iteracji i odnoszących się do niego jako zapach projektu.Dlaczego zamiast rekurencji ogona należy użyć iteracji?

public static void DebugOutput2(Exception ex) { 
    if (ex == null) { 
     return; 
    } 
    Debug.WriteLine(ex.Message); 
    if (ex.InnerException != null) { 
     DebugOutput2(ex.InnerException); 
    } 
} 

// WAS REFACTORED TO 

public static void DebugOutput(Exception ex) { 
    if (ex == null) { 
     return; 
    } 
    while (true) { 
     Debug.WriteLine(ex.Message); 
     if (ex.InnerException != null) { 
      ex = ex.InnerException; 
      continue; 
     } 
     break; 
    } 
} 

EDIT: w oparciu o C# kompilator leczenia komentarza. Wygląda na to, że teraz jest rekurencyjny.
Target .net 4.5. C# 5.0

ILDASM wyjściowy dla wersji ogon rekurencji: Pokazuje rekurencyjnej wezwanie i nie iteracji

.method public hidebysig static void DebugOutput(class [mscorlib]System.Exception ex) cil managed 
{ 
    // Code size  54 (0x36) 
    .maxstack 2 
    .locals init ([0] bool CS$4$0000) 
    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldnull 
    IL_0003: ceq 
    IL_0005: ldc.i4.0 
    IL_0006: ceq 
    IL_0008: stloc.0 
    IL_0009: ldloc.0 
    IL_000a: brtrue.s IL_000e 
    IL_000c: br.s  IL_0035 
    IL_000e: ldarg.0 
    IL_000f: callvirt instance string [mscorlib]System.Exception::get_Message() 
    IL_0014: call  void [System]System.Diagnostics.Debug::WriteLine(string) 
    IL_0019: nop 
    IL_001a: ldarg.0 
    IL_001b: callvirt instance class [mscorlib]System.Exception [mscorlib]System.Exception::get_InnerException() 
    IL_0020: ldnull 
    IL_0021: ceq 
    IL_0023: stloc.0 
    IL_0024: ldloc.0 
    IL_0025: brtrue.s IL_0035 
    IL_0027: nop 
    IL_0028: ldarg.0 
    IL_0029: callvirt instance class [mscorlib]System.Exception [mscorlib]System.Exception::get_InnerException() 
    IL_002e: call  void ca1.Program::DebugOutput(class [mscorlib]System.Exception) 
    IL_0033: nop 
    IL_0034: nop 
    IL_0035: ret 

} // end of method Program::DebugOutput 
+1

Należy pamiętać, że choć CLR obsługuje wywołania ogona, [kompilator C# ich nie emituje] (http://www.thomaslevesque.com/2011/09/02/tail-recursion-in-c/) (ostatni raz sprawdziłem). W każdym razie jest mało prawdopodobne, że wysadzisz stos tą konstrukcją, a wydajność nie jest tak naprawdę problemem, ponieważ i tak już masz do czynienia z wyjątkami. –

+4

Odrzuciłbym pętlę 'while' na rzecz czegoś bliższego' while (ex! = Null) {Debug.WriteLine (ex.Message), ex = ex.InnerException} '. – Kevin

+0

Zastanów się, co się dzieje, używając rekurencji ogona z perspektywy wydajności. Musimy stworzyć ramkę stosu, skopiować pewne rzeczy i zadzwonić, a następnie wywołać funkcję. W zależności od rekurencji może to prowadzić do dmuchanego stosu (chociaż nie w tym przykładzie, prawdopodobnie) i na pewno gorszej wydajności, jaką jest nierekurencyjna iteracja. – rileyberton

Odpowiedz

16

Bo ludzie błędnie bardziej troszczą się o mikro optymalizacje niż jasny i czytelny kod.

+2

preferuję rozwiązanie rekurencyjne DRY, które jest łatwe do odczytania i utrzymywania niż oszczędność kilku nanosekund. Pamiętaj, że bardzo elegancka iteracja została wyróżniona przez Kevina. Jak zwykle możesz pisać dobre i złe rekurencje. Ale wywoływanie rekursji, nieprzyjemnego zapachu, wydaje mi się najwyższe. Zwłaszcza, gdy zastosowanie mają SOLIDNE efekty projektowe. Więc teraz +1. poczeka, aby zobaczyć, co się stanie, zanim zaakceptuje odpowiedź. Dzięki ILMTitan –

+0

Można również usunąć nadmiarowe sprawdzanie zerowe w InnerException w rekurencyjnym rozwiązaniu. – ILMTitan

8

wykonuje dodatkowe (rekursywne) wywołanie funkcji, co również oznacza przydział stosu, dla każdego poziomu (każdego obsługiwanego obiektu).

Generalnie iteracja to better, ponieważ nie powoduje dodatkowego wywołania/alokacji.

Oczywiście, różnica będzie widoczna w przypadku istnieją wiele obiekty w obsłudze, który jak sądzę nie jest to przypadek w swoim przykładzie. Dla ciebie to nie jest problem i myślę, że celem jest nauczenie cię lepszej praktyki w ogóle.

Powiązane problemy