it 책/클린코드

에러 핸들링

테오구 2022. 5. 23. 11:04
728x90

오류 코드보다는 예외 사용하기

오류 코드를 사용하게 되면 상단에 오류인지 확인하는 불필요한 로직이 들어가게 됩니다. 오류의 범주에 들어가지 않은 상태를 나타내는 것이 아니라면, 예외(Exception)로 명시적으로 에러 처리를 표현해주는게 좋습니다.

enum ErrorCodes{
	VALUE_ERROR = "VALUE_ERROR"
}

function weCanRaiseError(){
	return ErrorCodes.VALUE_ERROR
}

function useUglyFunction(){
	result = weCanRaiseError()
	if(result === ErrorCodes.VALUE_ERROR){

	}
}
function weCanRaiseError(name, message){
	if(...){
		throw new Error(`${name} 에러 발생, 메세지는 이렇습니다. ${message}`)
	}
}

function useAwesomeFunction(){
	try{
		... 실행로직
	} catch (e){
		if(e instanceof EvalError){
			weCanRaiseError(e.name, e.message)
		} else if (e instanceof RangeError) {
	    weCanRaiseError(e.name, e.message)
	  }
	}
}

예외 클래스 잘 정의하기

기본 Exception만 쓰기 보단 내장된 built in Exception을 잘 활용하면 좋습니다.

상황에 맞게 Custom Exception을 만들어 사용하는 것도 좋습니다

class WithParameterCustomException extends Error{
	private kwargs: { name: string }
	private msg: string
	constructor(msg: string, kwargs: { name: string }){
		super()
		this.msg = msg
		this.kwargs = kwargs
	}

	Exception(){
		return `message ${this.msg} with parameter ${this.kwargs}`
	}
}

throw new WithParameterCustomException("문제가 있습니다", {"name": "grab"})
class CustomError extends Error {
  constructor(foo = 'bar', ...params) {
    // Pass remaining arguments (including vendor specific ones) to parent constructor
    super(...params);

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, CustomError);
    }

    // Custom debugging information
    this.foo = foo;
    this.date = new Date();
  }
}

try {
  throw new CustomError('baz', 'bazMessage');
} catch(e){
  console.log(e.foo); //baz
  console.log(e.message); //bazMessage
  console.log(e.stack); //stacktrace
}

에러 핸들링 잘하기

에러를 포착했다면 잘 핸들링해줘야 합니다.

function weCanRaiseError(){
	throw new Exception("Error!")
}

// BAD: 에러가 났는지 확인할 수 없게 됩니다.
function useUglyFunction1(){
	try{
		weCanRaiseError()
	} catch(e){
		continue
	}
}

// BAD: 로그만 남긴다고 끝이 아닙니다.
function useUglyFunction2(){
	try{
		weCanRaiseError()
	} catch(e){
		console.log(e)
	} finally{
		
	}
}

// GOOD
function useAwesomeFunction(){
	try{
		weCanRaiseError()
	} catch(e){
		console.error(e)
		notifyError(...) // 예측 불가능한 외부 I/O(API, DB) 이슈라면 회사 내 채널에 알리기(이메일, 슬랙 etc)
		throw new OtherException(e) // 만약 이 함수(useAwesomeFunction)를 호출하는 다른 함수에서 추가로 처리해야 한다면 에러를 전파하기
} finally{
		// 에러가 발생하더라도 항상 실행되어야 하는 로직이 있다면 finally 문을 넣어주기
	}
}

에러 핸들링을 모을 수 있으면 한곳으로 모읍니다. 보통 같은 수준(같은 레이어, 같은 로직)의 로직을 처리한다면 한 곳으로 모아서 처리하는 게 더 에러를 포착하기 쉽습니다.

  • as-is
function act1(){
	try{
		weCanRaiseError1()
	} catch(e){
		...
	}
}

function act2(){
	try{
		weCanRaiseError2()
	} catch(e){
		...
	}
}

function act3(){
	try{
		weCanRaiseError3()
	} catch(e){
		...
	}
}

// 에러가 날 지점을 한눈에 확인할 수 없습니다. 
// act_1이 실패하면 act_2가 실행되면 안 된다면? 핸들링하기 어려워집니다.

function main(){
	act_1()
    act_2()
    act_3()
}
  • to-be
function userException(name, message) {
	this.name = name;
	this.message = message;
}

function dataValidationException(name, message) {
	this.name = name;
	this.message = message;
}

function exceptionHandling() {
	try {
		const a = 1;
		const b = 0;
		if( b == 0) {
			throw new userException('divideByZeroException', '0으로 나눌 수 없습니다');
		}
		if(!a || !b){
			throw new dataValidationException('dataValidationException', '데이터가 존재하지 않습니다.');
		}
	} catch(e) {
		if(e instanceof userException) {
			console.error(e.name + "::" + e.message);
		}
		if(e instanceof dataValidationException) {
			console.error(e.name + "::" + e.message);
		}
	} finally {
		console.log("finally excute");
		}
}

728x90

'it 책 > 클린코드' 카테고리의 다른 글

클린 코드 - 코드 indent 줄이기(Guard Clausing, Polymorphism)  (0) 2022.05.23
클래스  (0) 2022.05.22
함수  (0) 2022.05.22
주석, 포맷팅  (0) 2022.05.21
클린 코드(네이밍)  (0) 2022.05.20