본문 바로가기
Study/클린코드

[클린코드] 7장. 오류 처리

by Nahwasa 2023. 1. 2.

스터디 메인 페이지

 

- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에서 말하고자 하는 바와 다를 수 있습니다.

- 모든 이미지의 출처는 클린 코드(로버트 C. 마틴 저) 책 입니다.

 


 

7장 오류 처리

⚈ 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.

 

오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다.

 

오류 코드보다 예외를 사용하라.

 

Try-Catch-Finally 문부터 작성하라

  • try 블록은 어떤 면에서 트랜잭션과 비슷하다. try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다.
  • ☆ finally는 try 부분이 정상처리되든지 catch로 빠졌든지, 그 내부에서 return을 하던지 아무튼 실행된다.

unchecked exception을 사용하라

  • checked exception은 OCP를 위반한다. 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다.
  • ☆ unchecked exception : RuntimeException을 상속한 Exception을 얘기한다. 별도로 throws로 알려주지 않아도 되고, try - catch로 처리할 필요도 없는게 unchecked exception이다.
  • e.g. checked exception인 SQLException은 별도로 처리안하면 에러가 뜬다. unchecked exception은 안뜬다.

  • checked와 unchecked를 각각 상속해서 만든 경우에도 동일하게 작동한다. (참고로 이하의 코드에서 RuntimeException을 상속한 경우의 코드에서는 throws 부분도 뺴도 상관없다.)

 

☆ OCP

  • 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
  • 확장 = 동작의 관점, 수정 = 코드의 관점
  • 확장에 대해 열려 있다 = 어플리케이션 요구사항이 변경될 때 이 변경에 맞게 새로운 ‘동작’을 추가해서 어플리케이션의 기능을 확장할 수 있다.
  • 수정에 대해 닫혀 있다 = 기존의 코드를 수정하지 않고도 어플리케이션의 동작을 추가하거나 변경할 수 있다.
  • 아무튼 확장해도 코드는 수정해야 하잖아!
  • OCP를 수용하는 코드 = 컴파일타임 의존성(코드에서 드러나는 클래스들 사이의 관계) 수정하지 않고도 런타임 의존성(실행시에 협력에 참여하는 객체들 사이의 관계)을 쉽게 변경할 수 있는 코드.

e.g.

예를들어 아래와 같은 코드가 있다.

public class Admin {
    public void sendDocument(String type) {
        switch (type) {
            case "kakaotalk":
                KakaoTalkSender.send();
                break;
            case "sms" :
                SmsSender.send();
                break;
        }
    }
}

public class KakaoTalkSender {
    private Receiver getReceiverInfomation() {
        ...
    }

    public void send(String type, String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class SmsSender {
    private Receiver getReceiverInfomation() {
        ...
    }

    public void send(String type, String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

 

여기에 이메일도 추가하게 되면 호출하는 쪽도 바뀌어야한다.

public class Admin {
    public void sendDocument(String type) {
        switch (type) {
            case "kakaotalk":
                KakaoTalkSender.send();
                break;
            case "sms" :
                SmsSender.send();
                break;
            case "email" :
                EmailSender.send();
                break;
        }
    }
}

public class KakaoTalkSender {
    private Receiver getReceiverInfomation() {
        ...
    }

    public void send(String type, String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class SmsSender {
    private Receiver getReceiverInfomation() {
        ...
    }

    public void send(String type, String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class EmailSender {
    private Receiver getReceiverInfomation() {
        ...
    }

    public void send(String type, String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

 

하지만 이런식으로 짜면

public class Admin {
    public void sendDocument(DocumentSender documentSender) {
        documentSender.send("...");
    }
}

public abstract class DocumentSender {
    Receiver getReceiverInfomation() {
        ...
    }
    
    public abstract void send(String message);
}

public class KakaoTalkSender extends DocumentSender {
    @Override
    public void send(String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class SmsSender extends DocumentSender {
    @Override
    public void send(String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

 

기존 코드를 수정하지 않고도(documentSender.send는 그대로다.) 동작을 추가할 수 있다.

public class Admin {
    public void sendDocument(DocumentSender documentSender) {
        documentSender.send("...");
    }
}

public abstract class DocumentSender {
    Receiver getReceiverInfomation() {
        ...
    }
    
    public abstract void send(String message);
}

public class KakaoTalkSender extends DocumentSender {
    @Override
    public void send(String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class SmsSender extends DocumentSender {
    @Override
    public void send(String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

public class EmailSender extends DocumentSender {
    @Override
    public void send(String message) {
        Receiver receiver = getReceiverInfomation();
        ...
    }
}

 

외부 API를 사용할 때는 감싸기 기법이 최선이다.

  • 호출하는 라이브러리 API를 감싸면서 예외 유형 하나를 반환하는 식으로 짜는 것
  • 감싸기 기법을 사용하면 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다.

 

예외가 논리를 따라가기 어렵게 만든다.

  • 클래스를 만들거나 객체를 조작해 특수 사례를 처리하자.
  • ☆ 이하는 개인적으로 자주 쓰는 방식인데, 아래와 같은 경우처럼 별도로 예외를 처리하지 않게 할 수 있다.

list가 null일 경우를 별도 처리

@GetMapping
public ResponseEntity<?> displayList(@RequestBody Map<String, Object> dto){
    List<Map<String, Object>> requestList = request.get("list");
    if (requestList == null)
        return ...

    for (Map<String, Object> element : requestList) {
        ...
    }
}

 

대신 아래처럼 처리하면 별도 처리할 필요가 없다.

@GetMapping
public ResponseEntity<?> displayList(@RequestBody Map<String, Object> dto){
    List<Map<String, Object>> requestList = request.getOrDefault("list", new ArrayList<>());

    for (Map<String, Object> element : requestList) {
        ...
    }
}

 

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

[클린코드] 9장. 단위 테스트  (0) 2023.01.11
[클린코드] 8장. 경계  (0) 2023.01.02
[클린코드] 6장. 객체와 자료 구조  (0) 2023.01.02
[클린코드] 5장. 형식 맞추기  (0) 2023.01.02
[클린코드] 4장. 주석  (0) 2022.12.21

댓글