본문 바로가기
DogFoot/Delphi

[Delphi] 중첩 예외처리(try...except)

by 크림슨킹 2021. 4. 23.

다중 혹은 중첩으로 예외를 처리하는 경우 결과는 어떻게될까?

결론은 예외처리 블럭 내에서 어떻게 처리하느냐에 따라 다르다.

 

단일 로직 내에서 중첩으로 예외를 처리하거나

예외처리가 되어 있는 로직 내에서 예외처리가 있는 function/procedure를 호출하는 경우가 이에 해당한다.

 

다음의 예를 보자.

 

< 단일 로직 내의 중첩 예외 처리 >

procedure TForm1.Button1Click(Sender: TObject);
var
  V: Integer;
  S: string;
begin
  V := 0;
  S := '';
  try
    try
      try
        V := StrToInt('AAA');
      except
        on E: Exception do raise Exception.Create('Error 1');	// -- 1
      end;
    except
      on E: Exception do ShowMessage('Error 2');                // -- 2
    end;
  except
    on E: Exception do Memo1.Lines.Add('Error 3');              // -- 3
  end;
end;

최초 string을 integer로 변환할 때 오류가 발생해서 1번 예외처리 블럭으로 이동된다.

1번 블럭에서는 raise문으로 예외 내용을 발생시켰다.

 

1번에서 raise에 의한 예외가 발생되었으므로 다시 2번 예외처리 블럭으로 이동한다.

2번 블럭에서는 showmessage(dialogbox/Application.MessageBox도 동일)를 이용하여 오류 내용을 표시하였다.

 

그럼 이후에는 처리가 어떻게 될까? 3번 예외 블럭으로 이동할까?

정답은 '이동하지 않고 로직을 탈출한다'이다.

1번과 2번 모두 각 예외처리 블럭에서 예외를 처리하였지만 처리 방식이 다르다.

 

raise 혹은 내부적으로 raise를 이용하는 예외처리 함수들은

오류발생 시 오류에 대한 처리를 외부 블럭 넘기는 역할을 한다.

이 때 전달받는 쪽에 예외처리가 있다면 해당 예외처리 블럭이 실행되며,

별도의 예외처리가 없다면 Application에서 오류를 받아서 사용자에게 표시하고 로직을 끝낸다.

 

이와 반대로 exception 블럭 내에서 별도의 raise 호출 없다면, 

이는 발생한 오류는 내가 알아서 끝낼테니 신경쓰기 말라는 얘기가 된다.

즉 exception블럭에서 오류를 처리했으므로 외부에서는 이를 인지하지 못하고 이후 로직은 정상 실행된다.

 

< 함수에 의한 중첩 예외처리 >

function MyFunc(Value: string): Integer
begin
  Result := 0;
  try
     Result := StrToInt(Value);
  except
     //case1: do nothing(오류 무시)
     //case2: on E: Exception do raise Exception.Create(E.Message);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  V: Integer;
  S: string;
begin
  V := 0;
  S := '';
  try
  	V := MyFunc('AAA');
  except
  	on E: Exception do ShowMessage('Error 2');
  end;
end;

 

Button1Click에는 예외처리가 되어있고 호출되는 MyFunc도 예외처리가 되어있어 중첩되어 있다.

MyFunc 내 예외블럭에서는 예외를 무시하도록 되어있다(case1)

이 경우 MyFunc에서 오류가 발생되더라도 Button1Click으로 넘겨주지 않으므로

Button1Click의 exception은 수행되지 않는다.

 

MyFunc exception에서 case2 주석을 해제, 오류시 raise를 호출하면

해당 오류에 대한 처리는 Button1Click으로 넘겨지게 된다.

따라서 Button1Click의 except블럭으로 진입하여 오류내용이 messagebox를 통해 사용자에게 표시되게 된다.

 

그렇다면 내부블럭의 예외처리는 어떻게 하는게 맞는걸까?

 

일반적으로는 내부(특히 함수나 클래스의 메소드들)에서 발생된 오류는

이를 사용하는 외부에서 전달받아 처리할 수 있도록 raise로 처리하는 것이 맞다.

외부에서 이를 messagebox를 이용해 사용자에게 표시하거나,

무정지 프로그램이라면 별도의 알림없이 log에 기록하는 것이 일반적이다.

 

배치 또는 루프작업인 경우 오류가 발생된 지점만 스킵하고 이후를 처리해야 하는 경우라면

이를 적절히 혼용하는 것이 좋겠다.

// case 1
try
  try
     A := StrToInt('aaa');
  except
     //do nothing
  end;
finally
  ShowMessaage('End');
end;
   
// case2
try
  A := StrToInt('aaa');
finally
  ShowMessaage('End');
end;

 

try...finally가 예외 외부블럭을 만든 경우는 어떻게될까?

finally 블럭은 어떠한 경우라도 반드시 실행된다. ㅎ

 

따라서 일반적으로 finally를 가장 바깥 블럭에 두고 초기화, 해제를 구성한다.

 

끝!

댓글