Line(1)
[Line - 코드 품질 개선 기법 1편 - 한 번 엎지른 는 다시 주워담지 못한다.](https://techblog.lycorp.co.jp/ko/techniques-for-improving-code-quality-1)
한 줄 요약 : 사용자가 잘못 입력한 것은 친절하게(Null/Optional) 알려주고, 개발자가 코드를 잘못 짠 건 확실하게(Exception)으로 터뜨려라.
들어가며
개인프로젝트나 학생수준의 프로젝트에서는 사실 크게 느껴지지 않았으나, 오픈소스 기여에 참여하여 코드리뷰를 받거나 하면서 궁금했던 점이 있었다. 기능 구현 이외에도 null값을 포함한 예외처리와 관련하여 어디에 어느정도 수준으로 어디까지 해야 하는지 애매했다. 잘못된 입력값이 들어왔을 때 Exception을 Throw해야하는지, 아니면 단순히 null을 반환하게 두던지…
이번 포스팅에서 약간의 팁과 방향성을 알게 되었다.
내용
프로그래밍 언어와 처리 시스템에 따라 다르지만, 에러를 표현하는 방법이 다양하다. 그중 어떤 방법을 사용할지는 해당 에러가 어느정도로 복구할 수 있는 것인지 참고하면 좋다.
복구 가능성?
네트워크 에러, 잘못된 입력과 같이 `호출자가 해결해서 회복할 수 있는 오류`. "다시 해주세요" 수준으로 프로그램을 이어갈 수 있음.
이와 반대로 잘못 작성된 내부 로직과 같이 `호출자가 스스로 회복할 수 없는 결함` 이라면 프로그램을 이어나가선 안된다. 내부 로직 자체가 이상해서 아무리 입력을 다시 보내고 해도 잡을 수가(복구 할 수가) 없다.
대락 아래와 같이, 복구가능성의 정도에 따라 에러를 어떻게 처리할지 정할 수 있다.
복구 가능
↑
┃ 0 기본값
┃ 1 단순 도메인 에러
┃ 2 에러값을 포함한 합 타입(sum type)이나 널러블(nullable) 에러값(혹은 다중 반환값)
┃ 3 확인된 예외
┃ 4 확인되지 않은 예외
┃ 5 캐치할 수 없는 에러
↓
복구 불가능
0.기본값
주요 상황 : 에러가 났는데, 프로그램에 지장이 없어서 호출자가 에러인지 아닌지 몰라도 됨.
ex) DB조회 결과가 없으면 빈 리스트 반환
// 예를들어, 특정 목록을 조회하여 보여주는 경우, 없으면 그냥 크기가 0인 리스트 반환하면 된다.
public List<User> getUsers() {
try {
return userRepository.findAll();
} catch (Exception e) {
return Collections.emptyList(); // 기본값
}
}
1. 단순 도메인 에러
0과 비슷하다. 대신 호출자가 데이터 부재를 인지하고, 흐름을 바꿔야 하는 경우.
public User getUser(Long id) {
// 없으면 null 반환 (또는 Optional)
return userRepository.findById(id).orElse(null);
}
// [호출자 코드]
User user = getUser(999L);
// ★ 중요: 데이터가 없으면 로직을 진행할 수 없음. 반드시 확인(Check)해야 함.
if (user == null) {
log("유저가 없어요")
}
// 2. 데이터가 있을 때만 로직 진행
login(user);
2. 에러값을 포함한 합 타입
실패했고, 이유는 이거야…..
성공했을때의 값과, 실패했을때의 이유를 명확히 구분해서 줘야 하는 경우.
// 성공하면 Success(User), 실패하면 Error(Reason) 반환
public Response getUser(Long id) {
if (id < 0) return new Response.Error("Invalid ID");
User user = findUser(id);
if (user == null) return new Response.Error("User Not Found");
return new Response.Success(user);
}
3. 확인된 예외
언어차원에서 강제로 에러 처리를 요구함.
ex : java의 경우, throw가 붙은 메서드
// 호출하는 쪽에서 반드시 try-catch로 감싸야 함
public void readFile() throws IOException {
// 파일 읽기 로직
}
4. 확인되지 않은 예외
개발자가 코드를 잘못 짰거나, 시스템 문제로 복구 불가능, 호출자가 잡을 필요도 없고 잡아도 해결 못함
예시 : java의 RuntimeException, NullPointerException, IllegalArgumentException
// 입력값이 null이면 안 되는데 들어옴 -> 개발자 잘못 -> 그냥 터뜨림
public void printName(String name) {
if (name == null) throw new IllegalArgumentException("Name cannot be null");
System.out.println(name);
}
5. 캐치할 수 없는 에러
프로그램 사망과 같이 프로그램이 죽어야 하는 상황.
Ex : OOM, StackOverFlow
따라서, 공급자(비즈니스 메소드를 짜서 public으로 제공하는)는 위와 같은 에러의 특징들을 알아뒀다가 적절한 에러 처리 방법을 사용해야 한다.