środa, 8 lipca 2009

Uwaga na return wewnątrz kontrukcji: try, catch, finally (czyli kiedy return nie powoduje opuszczenia funkcji)

Jedna z zasad dobrego programowania mówi: "Funkcja powinna mieć tylko jedną instrukcję return". Dzięki takiemu podejściu zawsze łatwo jest zlokalizować punkt wyjścia z funkcji, zwykle łatwiej zrozumieć napisany kod i w konsekwencji łatwiej go "utrzymywać" (i oczywiście zmieniać). Czasami jednak dużo łatwiej (lub krócej) można napisać kod funkcji, w której pojawi się wiele instrukcji return, dlatego często nie opieramy się takiej pokusie i takie funkcje tworzymy.

W tym krótkim post'cie zaprezentowany zostanie przykład, w którym analiza kodu funkcji z wieloma instrukcjami return może dla pewnych osób okazać się zaskakująca (mi zajęło pewną chwilę zanim zrozumiałem podobną do prezentowanej konstrukcję w większym oprogramowaniu).

Spójrzmy więc na poniższy kod (w komentarzu znajduje się też wynik działania):

using System;
namespace finally_and_return
{
  class Program
  {
    static void myFunction( bool throwException, bool earlyReturn )
    {
      try
      {
        if ( earlyReturn )
          return;
        if ( throwException )
          throw new Exception( "My Exception" );
      }
      catch ( Exception ex )
      {
        Console.WriteLine( "Exception: {0}",ex.Message );
      }
      finally
      {
        Console.WriteLine( "finally" );
      }
      Console.WriteLine( "end of myFunction" );
    }
    static void Main( string[] args )
    {
      Console.WriteLine( "--------------------------------------" );
      Console.WriteLine( "example 1: call myFunction(false,false )" );
      myFunction( false, false );
      Console.WriteLine( "--------------------------------------" );
      Console.WriteLine( "example 2: call myFunction(true,false )" );
      myFunction( true, false );
      Console.WriteLine( "--------------------------------------" );
      Console.WriteLine( "example 3: call myFunction(false,true )" );
      myFunction( false, true );
      Console.ReadLine();
//result:
//   
//--------------------------------------
//example 1: call myFunction(false,false )
//finally
//end of myFunction
//--------------------------------------
//example 2: call myFunction(true,false )
//Exception: My Exception
//finally
//end of myFunction
//--------------------------------------
//example 3: call myFunction(false,true )
//finally
    }
  }
}

Wynik przykładu 1 i 2 chyba dla każdego wydaje się oczywisty. W tym miejscu chciałbym zwrócić uwagę na przypadek trzeci (example 3), gdzie wewnątrz sekcji try wykonany zostanie return, ale ... nie powoduje to opuszczenia funkcji, gdyż musi zostać jeszcze wykonany obowiązkowy blok finally (i tylko on, a nie to co po nim nastąpi). Jest to oczywiście zgodne z zasadą koncepcji: try, catch, finally, w której blok finally ma zostać wykonany zawsze, nie zależnie od tego co wydarzy się w try. Przyznacie chyba jednak, że przy analizie takiego kodu można łatwo się pomylić...

1 komentarz:

  1. Temat ten był już poruszony przez Gutka w tym poście: http://zine.net.pl/blogs/gutek/archive/2009/04/08/wyra-enie-using.aspx

    Co do samego faktu, że finally wykona się nawet przy return to jak dla mnie to jest to chyba czymś naturalnym, choć rzeczywiście muszę przyznać że czasami można o tym na chwilę zapomnieć i dziwić się, że coś jednak nie działa tak jak być powinno...

    OdpowiedzUsuń

Posty powiązane / Related posts