- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에서 말하고자 하는 바와 다를 수 있습니다.
- 모든 이미지의 출처는 클린 코드(로버트 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 |
댓글