본문 바로가기

Java

[Java] 예외처리 시 주의 팁

예외 부분을 공부하다가 외국 사이트에서 예외 처리 시 실수할 수 있는 대표적인 예시들을 설명을 잘 설명되어 있어서 정리하게 되었다. 

https://stackify.com/common-mistakes-handling-java-exception/ 

 

7 Common Mistakes You Should Avoid When Handling Java Exceptions

Handling an exception in Java is one of the most common but not necessarily one of the easiest tasks. Here are several common mistakes you should avoid.

stackify.com

 

 

 

 

1. 구체적인 예외 클래스 사용하기 

java.lang.Exception, java.lang.Throwable 같은 상위 예외 클래스보다는 구체적인 예외 크래스를 사용하는 것이 좋다. 

상위 클래스의 예외를 throws 하게 되면 상위 메서드에서는 전달된 예외들을 어떤 메서드로부터 왔는지 구분하기 어렵다. 

 

doSomething 메서드에서 구체적인 예외를 넘겼지만, 

doNotSpecifyException에서 단순히 Exception를 throws 해버리면 다음 상위 메서드에서는 어떤 예외가 넘어올 수 있을지 알 수 없다. 

public void doNotSpecifyException() throws Exception {
	doSomething();
}

public void doSomething() throws NumberFormatException, IllegalArgumentException {
	// do something
}

 

그래서 doNotSpecifyException() 대신 specifySpecificExceptions()처럼 구체적으로 예외처리 핸들링해주면 좋다. 

public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException {
	doSomething();
}

 

 

2.Catch specific exceptions

1번과 마찬가지로 상위 메서드에서 catch시 모든 예외를 다 catch 할 수 있는 Exception, Throwable 대신 구체적인 exception을 catch 하는 것이 좋다. 특히 메서드가 더 깊어질수록, 라이브러리를 구현할수록 실수가 발생할 있기 때문에 주의해야 한다. 

 

장점 

1. 예외 클래스별로 로직 분기 가능

2. 예상치 못한 예외를 catch 하는 것을 방지해준다. 예를 들어, 더 상위 메서드에서 처리해야 할 예외를 하위에서 catch로 잡게 되어 의도하지 않은 예외가 발생할 수 있다. 

 

 

3. 로그 남기고 throw exception하지 말기

예외처리에서 로그 남기는 위치은 예외를 처리하는 catch에서 남기는 것이 좋다. 

 

만약 catch문에서 예외를 catch 후 로그를 남기고 다시 상위 메서드로 throw 하는 것은 좋지 않다. 

단순히 생각했을 때 예외가 발생한 곳에서 로그를 남겨야 할 것 같다고 했지만 그렇지 않다. 

 

  • 상위 메서드에서 하위 메서드에서 발생 가능한 예외까지 고려한 로직을 짰을 수 있다. 그런 경우 필요 없는 로그 처리로 상위 메서드에서 로그를 처리한 것이 맞다. 
  • 현재 catch 한 곳에서 로그 분석을 위한 충분한 정보가 없을 수 있다. ( ex 예외 throws 시 중간에 catch한 경우 )
  • call stack을 거치면서 로그를 항상 남긴다면 중복되는 로그가 생길 것이다. 

 

 

아래 코드에서 doEvenMore()에서 예외 처리할 때 로그를 남기고, 하위 메서드인 doMore(), doSomething()는 로그를 남기지 않고 그대로 throws 하는 방법이다. 

public void doEvenMore() {
	try {
		doMore();
	} catch (NumberFormatException e) {
		// handle the NumberFormatException
	} catch (IllegalArgumentException e) {
		// handle the IllegalArgumentException
	}
}

public void doMore() throws NumberFormatException, IllegalArgumentException {
	doSomething();
}

public void doSomething() throws NumberFormatException, IllegalArgumentException {
	// do something
}

 

 

4. original exception 빠뜨리지 말기 

상위 메서드에서 예외를 받아 새로운 예외로 감쌀 때 기존 예외 정보를 빠뜨리지 않도록 주의 해야 한다. 

 

예를 들어 다음 코드처럼 custom exception을 만들 때 오리지널 예외도 받도록 해야 한다.

try {
	doSomething();
} catch (NumberFormatException e) {
	throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
	throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

 

 

5. 예외 일반화하지 말기 

바로 예제를 보면 NumberFormatException, IllegalArgumentException 예외를 catch 후

throws Exception로 일반화하여 예외를 throw(예외 전환) 한 코드가 있다. 

public void doNotGeneralizeException() throws Exception {
	try {
		doSomething();
	} catch (NumberFormatException e) {
		throw new Exception(e);
	} catch (IllegalArgumentException e) {
		throw new Exception(e);
	}
}

 

주의 팁 1번과 비슷하지만 다소 다르다.

일부러 하나로 일반화하고 상위 메서드에서 예외를 다시 구분하여 처리하려는 의도를 가질 수 있지만,, 그럼 더 읽기 어렵고 복잡한 코드가 생긴다. 

try {
	doNotGeneralizeException();
} catch (Exception e) {
	if (e.getCause() instanceof NumberFormatException) {
		log.error("NumberFormatException: " + e);
	} else if (e.getCause() instanceof IllegalArgumentException) {
		log.error("IllegalArgumentException: " + e);
	} else {
		log.error("Unexpected exception: " + e);
	}
}

 

 

그래서 처음부터 일반화 없이 구체적으로 예외 throw를 했다면 깔끔하게 처리가 된다. 

try {
	doSomething();
} catch (NumberFormatException e) {
	throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
	throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

 

 결론

- 예외는 thow나 catch 시 가능하면 구체적으로 사용하는 것이 좋다. 

- 예외 로그는 예외를 처리하는 위치에서 하기

- custom exception처럼 예외 전환 시 넘어 온 예외 데이터 빼먹지 않도록 주의