C 에 입문하는 모든 사람에게 GO TO 문은 사용하지 말아야 하는 것으로 거의 불문율처럼 여겨진다. 이는 GOTO 문의 남발이 코드의 복잡성을 증가시키기 때문이다. 그러나, 참 아이러니하게도 GOTO 문은 코드를 대폭 간결하게 처리할 수 있는 방법이기도 하다. 특히 에러처리에 있어서는 GOTO 문은 그 진가를 발휘한다. 그래서 그 대안으로 try catch 문을 제공하는 것이다.
다음 문장을 살펴보자.
A 수행;
If (A 수행이 성공하면)
{
B 수행;
If (B 수행이 성공하면)
{
C 수행;
If (C 수행이 성공하면)
{
OK;
}
else
{
에러처리;
}
}
else
{
에러처리;
}
}
else
{
에러처리;
}
위 문장을 try catch 문으로 다시 정리해 보면 아래와 같다.
try
{
A 수행;
If (A 수행 실패)
ThrowException ();
B 수행;
If (B 수행 실패)
ThrowException ();
C 수행;
If (C 수행 실패)
ThrowException ();
D 수행;
}
catch
{
에러 처리;
}
코드의 양도 양이지만, A->B->C->D 로 작업이 진행된다는 코드의 판독성이 상당히 증가하는 걸 알 수 있다.
일단 예외처리에 관한 기본적 지식은 관련 서적이 많으니 참고하길 바라고, 여기에서는 MFC에서 제공하지만 쓰기에는 약간 불편한 CException 클래스를 확장하여 좀더 편리하게 사용할 수 있는 방법에 대해 알아보기로 한다.
보통 try catch 문을 사용하면 아래와 같다.
try
{
// Do something to throw an exception.
throw "Alarm Error !!";
}
catch (char* szExcepy)
{
::AfxMessageBox (szExcepy);
}
그런데, 이를 MFC적 사고에서 MFC가 제공하는 CException 클래스를 사용하면,
TRY
{
// Do something to throw an exception.
CException* const e = new CException (TRUE);
THROW (e);
}
CATCH_ALL (e)
{
e->ReportError ();
}
END_CATCH_ALL
오류 메시지는 “사용가능한 오류메세지가 없습니다.” 이다. 오히려, 사용하기 더 어렵게 되었다는 것을 느낄 것이다. 그러나 CException 는 기초클래스일 뿐이고 ReportError () 함수 또한 가상함수로 만들어져 있어 사용자가 이를 확장하여 쉽게 처리할 수 있음을 암시하고 있다.
자 이제 우리는 CException 클래스에서 상속받아 확장된 CExceptionEx 를 만들것이다.
목적은 코드를 좀더 간결하게 처리하고, 원하는 에러 메시지를 효과적으로 발생시킬 수 있도록 하는것이다.
간략히 처리하기 위해서는 CExceptionEx* const e = new CExceptionEx (TRUE, “에러메세지”); 로 처리할 수 있도록 클래스를 정의해야 한다.
먼저 에러 메시지를 담을 CString 멤버 변수를 하나 갖고, 생성자를 추가한다.
다음 가상함수 ReportError () 에서 추가한 CString 변수를 출력하도록 처리한다.
우선 CExceptionEx 의 클래스 정의를 살펴보면 다음과 같다.
class CExceptionEx : public CException
{
protected:
DECLARE_DYNAMIC(CExceptionEx)
private:
CString m_strMessage;
public:
CExceptionEx ();
CExceptionEx (const BOOL bAutoDelete, LPCTSTR lpszMessage);
public:
virtual ~CExceptionEx ();
virtual int ReportError (UINT nType = MB_OK, UINT nError = 0 );
};
다음으로 ::AtxThrowXXXException () 처럼 전역함수를 만들어 사용해야 한다.
이는 다음과 같이 정의하였다.
void TfcThrowException (const UINT nFormatID, ...);
void TfcThrowException (const LPCTSTR lpszFormat, ...);
void TfcThrowNotSupportedException (void);
이는 리소스의 스트링 테이블의 문자열도 이용할 수 있고, 인자를 갖는 문자열을 사용할 수 있도록 처리하였다.
에러 메시지 박스 출력에 관련된 함수도 자신만의 예쁜 메시지 박스나, 스트링 테이블을 이용할 수 있고, 인자를 갖는 문자열을 이용할 수 있도록 전역적으로 정의하자.
이와 관련된 함수 정의는 아래와 같다.
void TfcMessageBox (const UINT nFormatID, ...);
void TfcMessageBox (const LPCTSTR lpszFormat, ...);
int TfcMessageBox (LPCTSTR lpszMsg, UINT nType, LPCTSTR lpszTitle = NULL);
int TfcMessageBox (const UINT nFormatID, UINT nType, LPCTSTR lpszTitle = NULL);
CString GetStringFromID (const UINT uFormatID, ...);
구현부는 별로 어렵지 않게 만들어져 있으므로 아래에서 다운 받아 확인해 보면 된다.
http://myhome.konetic.or.kr/UserUploadData/gobuksun/ExceptionEx.cpphttp://myhome.konetic.or.kr/UserUploadData/gobuksun/ExceptionEx.h
자, 그럼 어떻게 TRY CATCH 문을 사용하게 되었는지 예로 살펴보자.
만일 리소스 스트링 테이블에
IDS_USERID_NOT_EXIST 는 “아이디 %s는 존재하지 않습니다.”
IDS_INVALID_PASSWORD 는 “%s님의 비밀번호가 다릅니다.”라고 정의되어 있다고 가정한다.
TRY
{
CString strUserID = _T(“거북선”);
BOOL bSuccessLogin = FALSE;
// strUserID 아이디 검색
if (FALSE == bSuccessLogin)
::TfcThrowException (IDS_USERID_NOT_EXIST, strUserID);
// 패스워드 체크
if (FALSE == bSuccessLogin)
::TfcThrowException (IDS_INVALID_PASSWORD, strUserID);
}
CATCH_ALL (e) // 로그인 실패
{
// e->ReportError (REPORT_AS_TRACE); 이면 메시지 박스로 출력하지
// 않고 TRACE 처럼 결과창에만 보여준다.
e->ReportError ();
}
END_CATCH_ALL
이와 같이 사용 할 수 있다.
자신이 맡은 프로젝트의 성격에 따라, 에러 메시지 뿐만 아니라 에러 번호 같은 것을 멤버변수로 두어 각기 다르게도 처리할 수 있을 것이다.
실무에서 수많은 버그를 방지하는 방법은 빠져 나갈 수 있는 구멍을 철저히 막아버리는 것이다. 약간 귀찮아 보이지만 버그와 에러를 방지하는 방법으로써 Stdafx.h 파일에 #include "ExceptionEx.h"를 추가하고 TRY CATCH 문을 생활화하자.