출처 : 데브피아(조휘경님 글)


보통 실무에서의 프로젝트는 공부할 때와는 달리 크기가 방대하다. 따라서, 시간이 지나면 지날수록 그 복잡성은 증가할 수 밖에 없다. 이 방대함과 복잡성에서 버그를 줄이기 위해 프로그래머들이 택할 수 있는 유일한 방법은 잘 정리 정돈하는 것, 그리고 주석과 진단 매크로 등으로 도배하는 것이다. 그 중 아주 효율적인 방법중의 하나가 try catch 문을 생활화하는 것이다.



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.cpp
http://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 문을 사용해보자.



PS) 글을 쓰다보니 TRY CATCH 문이 만능인것처럼 보일수가 있겠네요.. 아래 리플 달아주신 분들 감사합니다.

몇자 변경했습니다. 역시 논란이 있는부분입니다. 예외처리의 남용은 자칫 성능의 훼손이나 혹은 판독성마저 훼손할 수 있으니까 여러분 나름대로의 판단이 요구되는 부분이겠습니다.. ^^*
Posted by 장안동베짱e :