24. 죽은 프로그램은 거짓말을 하지 않는다.
방어적 코딩 + 에러 핸들링에 관한 이야기
설마가 사람 잡음
그런 일은 절대 일어날 리 없어 ~ 라는 사고에 빠지기 쉽다
있을 수 없는 일이 발생한 경우 우리는 그 사실을 알아야 한다.
코드를 짤 때 무의식적으로 해피 패스만 가정한다.
사용자가 여기에 음수를 넣겠어?DB 연결이 설마 끊어지겠어?
MQ 서버가 실패하는 경우는 적어 그냥 실패하면 어쩔수 없는거지..
그곳이 버그의 온상
논리적으로 도달할 수 없는 코드라도, 하드웨어 오류 + 누군가의 실수로 데이터가 오염되면 실행될 가능성이 있음.
예외 처리는 로그 남기기 용 이 아니다.
잡은 후 그냥 놓아주는 것은 물고기 뿐....// ❌ 비효율적인 방식 (Catch and Release) public void processScore(int score) throws Exception { try { addScoreToBoard(score); } catch (InvalidScoreException e) { log.error("점수 오류"); // 로그 찍고 throw e; // 다시 던짐 (해결한 게 아님) } catch (ServerDownException e) { log.error("서버 다운"); // 로그 찍고 throw e; // 다시 던짐 } catch (TransactionException e) { log.error("트랜잭션 오류"); // 로그 찍고 throw e; // 다시 던짐 } }중요한 로직은
addScoreToBoard인데에러처리가 뭔 10줄이 넘는다.
결합도의 문제도 있음
새로운 예외를 던지도록
addScoreToBoard의 설계가 수정된 경우, 이 코드를 또 수정해야한다.
하위 모듈의 변화가 상위 모듈에 불필요한 수정을 낳게되는 것이다.
무의미한 예외 처리는 의미 없다.
만약 예외를 진짜 남기고 싶다면..?
스프링의 최상단에서 에러를 잡을 수 있다.
GlobalHandler 에서 잡을 수 있기에, 굳이 비즈니스 로직의 흐름이 희석되는 일이 발생하는 것 보다는 상단에서 잡아야함.
진짜 남겨야하면..
의미있는 에러를 던지자.
단순히 log.error(”에러남 등의 대충대충 X”)
BusinessException ... 유저 ID 몇번에서 무슨 오류 발생
모든 에러는 정보다.
모든 오류는 정보를 준다.. 그놈의 오류 메시지 좀 읽어라
오류가 났다고 무작성 AI 한테 쳐집어놓고 그러지말고, 에러 메시지와 스택 트레이스를 통해 문제의 원인, 상태를 인지하는 연습을 해라.
오류는
방해꾼이 아니라, 프로그램이 나에게 보내는 구조 신호 이다.Fail Fast + Read Correctly
좀비보다는 시체가 낫다
일반적으로 죽은 프로그램이 끼치는 피해는 이상한 상태의 프로그램이 끼치는 피해보다 훨씬 적은 법이다.
좀비 프로그램
에러가 났는데 억지로 try-catch 로 넘겨서 계속 돌아가는 것?
.. DB 정합성이나 로직의 정합성이 더 중요하다
왜 에러를 감싸버리는건지 비난
하위 모듈은 마음껏 죽을 수 있게 두고
TM, 이나 GE 같은 상위 모듈에서 처리하게 두라
28. 결합도 줄이기
소프트웨어는 다리가 아니라
다리나 탑같이 무언가를 단단하게 설계해야 할 때, 여러분은 아마 부품들을 서로 결합할 것이다… 하지만 소프트웨어를 설계하는 경우 언젠가 형태를 바꾸려고 할 것이다. 바라는 것이 정확히 반대다.
물리적 구조물 vs 소프트웨어
다리는 모양이 변경되지 않아야 하지만, 소프트웨어는
변화가 숙명
유연성
소프트웨어가 유연하려면 모듈간의 연결이 느슨해야함.
너무 단단히 엮여 있으면, 하나를 고치려다 전체를 들어내야 하는 상황이 발생함.
열차 사고
여러 객체를 점으로 줄줄이 소세지
초반엔 정말 편한데, 규모가 커질수록 유지보수의 지옥이 된다.
customer.getOrders().find(orderId).getTotals().apply(...)같은 케이스로 …
묻지말고 답하기
외부 객체의 내부 상태를
물어보지마라너가 알아서 해, 난 내일만 할거야..
외부 동작은 외부 동작이고, 난 내 동작만 하게 만들어야함.
내 동작 행위 일부를 외부에 주입하고 싶다면, Supplier 나 Function 으로 행위를 주입하는 것도 방법일 듯?
public void processPayment(PaymentRequest request) {
// ❌ 묻지 않고 (Don't Ask): if (request.type().equals("CARD") && request.amount() > 100000)
// ✅ 명령합니다 (Tell): 주입된 검증 행위에게 '검증하라(test)'고 명령!
if (!paymentValidator.test(request)) {
throw new IllegalArgumentException("결제 요청이 유효하지 않습니다: " + request.type());
}
데메테르 법칙
1980년대 말 이안 홀랜드 라는 개발자가 데메테르 라는 프로젝트를 수행하는 도중, 개발자들에게 깨끗하고 결합도가 낮은 함수를 작성하는 방법을 알려주기 위해 일련의 지침으로 등장함.
점 하나만 쓰셈
객체 간의 체인을 줄이라는 말임.
체인을 안쓴다면, 어떻게 해야하나?
각각의 객체에게 위임라는 것임.
// ❌ LoD 위반: 열차 사고 (Train Wreck) public void applyDiscount(Customer customer, ...) { customer.orders.find(orderId).getTotals().grandTotal = ...; }Customer, Order 등의 객체가 존재한다면..
// ✅ LoD 준수 (최종 형태) public void applyDiscount(Customer customer, int orderId, int discount) { // 1. 딱 하나의 점(.)만 사용합니다. // 2. Customer 객체에게 "이 주문에 할인을 적용해줘"라고 명령(Tell)합니다. customer.applyDiscountToOrder(orderId, discount); }// Customer.java 내부 public void applyDiscountToOrder(int orderId, int discount) { Order order = this.orders.find(orderId); // 첫 번째 점(.) // 찾아낸 Order 객체에게 할인을 적용하라고 명령합니다. (두 번째 점은 Order 내부에서 처리됨) order.applyDiscount(discount); }어차피 DB를 타야한다면.. 레포지터리에게 위임될 것 같은 로직이다.
뭐 암튼 하고자하는 말은 각자의 엔티티나 도메인 객체의 역할이 있을텐데
각자 그 도메인 객체의 역할 안에서 본인의 일을 해야 한다는 의미같다.
전역 데이터의 해악
“어디서나 접근할 수 있는 데이터는 교묘하게 애플리케이션 컴포넌트 간의 결합을 만들어 낸다.”
전역 데이터 피해라
전역적이여야 할 만큼 중요하면 API 로 감싸라..
API 로 감싸라는 게 무슨말이냐?
래퍼 클래스를 하나 만들어서 거기서 관리하라는 말임
DB config RedisManger .. 등의 클래스를 만들고 거기서 값을 관리할 수 있는데
그런 식으로 만들어서 관리하셈
yaml 같은 것도 그대로 가져다가 쓰지말고
특정 래퍼 클래스를 만들고 거기서 호출하도록 하라는 말임.
32. 설정
외부 설정으로 어플리케이션 조정하기
나중에 바뀌리라 알고 있는 것, 소스코드 본체 바깥에 표현할 수 있는 것을 찾기.
설정 더미에 던져 넣기
DRY(Don`t Repeat Yourself) 의 확장판
코드에 환경정보를 중복하지 말것.
현대의 클라우드 기반 환경에는 환경마다 DB 주소 포트 인증 키가 모두 다르기에
소스 코드에 박지말고
환경을 바꿀 때마다 코드를 다시 빌드해야하는 문제 발생..
근데 이건 뭐 요즘의 프로그램의 경우 대부분 환경별로 세팅을 다르게 할 수 있도록 다 장치가 있음
yml 이나 properties 가 그럼.
정적 설정 대신 API 뒤로 숨기기
설정값을 그대로 @Value 같은 값으로 직접 불러오지말고
별도의 래퍼 config 만들기
서비스형 설정
설정 정보를 애플리케이션 외부에서 관리하는 것은 동일하지만, 일반 파일이나 데이터베이스가 아니라 서비스 API 뒤에서 관리하는 것을 선호한다.
설정값 하나를 바꾸기 위해 전체 애플리케이션을 멈추고 다시 시작해야 하는 상황은 현대인의 삶에 어울리지 않는다.
Spring Cloud Config 등으로 secret manager 를 통해 외부에서 엔드포인트나 포트 비밀번호 등을 불러와서 코드 수정이 필요없게 만들라는 의미다.
가용성이 높아지고, 무중단 배포가 가능해짐.
도도 코드를 작성하지 말 것
환경에 적응하지 못하는 생물은 멸종한다... 여러분의 프로젝트가, 여러분의 경력이 도도의 전철을 밟지 않도록 하라.
코드가 실행되는 환경에 적응할 수 있는 유연성을 제공해야
살아남을 수 있다.
코드가 변경될 때마다 빌드해야 하는 정적인 코드는 변화가 잦은 현대 IT 생태계에서 살아남기가 힘들다.