새 예외를 넣을 자리가 한 곳뿐이었다
세션 작업으로 예외가 늘면서 전역 예외 처리 클래스가 227줄이 됐다. 인증 실패와 파일 업로드 실패가 한 파일에 나란히 있던 것을 갈래별로 가른 과정을 적었다.
세션을 다시 세는 작업을 하면서 예외를 여러 개 만들었다. 최대 세션 초과, 세션 과다, 토큰 형식 오류, 토큰 생성 실패 같은 것들이다. 만들 때마다 응답을 어떻게 내려줄지 정해야 했고, 그 자리는 한 곳뿐이었다.
전역 예외 처리 클래스 하나가 227줄이었다. 예외를 하나 넣을 때마다 이미 있는 것과 겹치지 않는지 보려고 그 파일을 위에서 아래로 훑었다. 몇 번 훑고 나서 파일이 문제라는 걸 인정했다.
한 파일에 층이 다 섞여 있었다
무엇이 들어 있는지 목록으로 뽑아 봤다.
회원 조회 실패 인증 안 됨 가입 중복
정산 수단 없음 한도 유형 없음 계정 잠김
파일 업로드 실패 요청 형식 오류 비밀번호 불일치
목록에서 규칙이 보이지 않았다. 어떤 것은 요청이 잘못된 것이고, 어떤 것은 인증을 통과하지 못한 것이고, 어떤 것은 업무 규칙에 걸린 것이다. 성격이 다른 셋이 같은 파일에 나란히 있으니 읽는 입장에서는 같은 무게로 보였다.
실무에서 걸리는 지점은 따로 있었다. 장애 문의가 오면 응답 코드부터 보는데, 그 코드가 어느 층에서 나온 것인지 파일 하나를 열어 찾아야 했다. 인증에서 막힌 것과 한도에 걸린 것을 가려내는 데 매번 같은 수고가 들었다.
줄 수보다 넣을 자리가 문제였다
227줄 자체는 견딜 만하다. 진짜 비용은 새 예외를 만들 때 나왔다. 이 예외가 기존 어느 것과 같은 부류인지, 응답 형태를 무엇에 맞춰야 하는지 판단하려면 전체를 봐야 했다.
세션 작업에서 예외를 넷 만들었으니 그 판단을 네 번 했다. 매번 같은 파일을 처음부터 읽었고, 매번 같은 결론에 도달하는 데 시간이 걸렸다. 반복되는 판단이 곧 나눌 기준이 있다는 신호였다.
무엇을 기준으로 가를지 정했다
처음 떠오른 기준은 응답 코드였다. 400으로 나가는 것과 401로 나가는 것과 409로 나가는 것을 묶는 방식이다. 이건 버렸다. 코드가 같아도 원인이 전혀 다른 경우가 많았고, 무엇보다 예외를 새로 만들 때 코드를 먼저 정하게 되는 순서가 이상했다.
고른 기준은 어느 갈래의 일이냐였다. 인증과 세션에서 나오는 것, 접근 권한에서 나오는 것, 결제와 한도에서 나오는 것, 그 밖의 업무 규칙에서 나오는 것으로 갈랐다. 새 예외를 만들 때 던지는 쪽 코드가 어디 있는지만 보면 자리가 정해진다.
flowchart TD
A["예외 발생"] --> B{"어느 갈래에서 나왔나"}
B -->|인증·세션| C["인증 핸들러"]
B -->|접근 권한| D["보안 핸들러"]
B -->|결제·한도| E["결제 핸들러"]
B -->|그 밖의 업무 규칙| F["업무 핸들러"]
B -->|갈래 없음| G["공통 핸들러"]
G --> H["아무 데도 안 걸리면 최후 처리"]
네 갈래로 나눴다
갈래마다 클래스를 하나씩 뒀다. 나누고 세어 보니 분포가 고르지 않았다.
| 갈래 | 맡은 예외 | 대표적인 것 |
|---|---|---|
| 인증 | 10 | 인증 안 됨, 세션 초과, 토큰 오류 |
| 업무 규칙 | 5 | 가입 중복, 이름 중복 |
| 결제 | 4 | 정산 수단 없음, 한도 유형 없음 |
| 보안 | 3 | 접근 거부, 계정 잠김 |
인증 쪽이 열 개로 가장 많았다. 그달 작업이 전부 그쪽이었으니 당연한 결과지만, 한 파일에 있을 때는 이 쏠림이 보이지 않았다. 나누고 나서야 어느 갈래가 커지고 있는지가 숫자로 드러났다.
나누고 보니 아무도 안 잡던 예외가 있었다
갈래별로 옮기다 보면 그 갈래에 있어야 하는데 없는 것이 눈에 띈다. 인증 쪽으로 옮기면서 권한을 찾지 못했을 때와 세션이 너무 많을 때가 목록에 없다는 걸 알았다. 던지는 코드는 있는데 받는 자리가 없었다.
그동안 어디로 갔는지는 분명했다. 최후 처리다. 갈래별 핸들러가 없으니 예상하지 못한 오류와 같은 취급을 받았고, 쓰는 쪽에는 정해진 형태의 오류 하나로만 나갔다. 권한이 없어서 막힌 것인지 서버가 잘못된 것인지 응답으로는 구분되지 않았다.
세어 보니 분리 전 열넷이던 핸들러가 분리 후 스물일곱이 됐다. 늘어난 열셋은 새로 만든 예외가 아니라 이미 던져지고 있었는데 받는 자리가 없던 것들이다. 한 파일에 다 있을 때는 무엇이 빠졌는지 볼 방법이 없었다.
공통에 남길 것을 골랐다
전부 갈래로 보낼 수는 없었다. 어느 갈래에도 속하지 않는 것들이 있다.
요청 형식 검증 실패 어느 API에서도 난다
값 검증 실패 어느 API에서도 난다
인코딩 오류 갈래가 아니라 표현의 문제
파일 업로드 실패 여러 갈래가 함께 쓴다
이것들은 공통 클래스에 남겼다. 기준은 단순했다. 던지는 쪽이 특정 갈래에 있지 않으면 공통이다. 결과적으로 공통 클래스는 227줄에서 109줄로 줄었고, 남은 것은 전부 갈래가 없는 것들이었다.
아무 데도 안 걸리는 예외를 어디로 보낼지 정했다
갈래를 나누면 빈틈이 생긴다. 어느 핸들러에도 잡히지 않는 예외가 그대로 밖으로 나가면 응답 형태가 달라진다.
공통 클래스에 최후 처리를 하나 뒀다. 어떤 예외든 여기까지 오면 정해진 형태로 감싸서 내보낸다. 이게 있으면 응답 형태는 언제나 하나로 유지되지만, 대신 예상하지 못한 오류도 조용히 감싸여 나간다. 이 성질이 나중에 문제가 됐는데 그건 다른 이야기다.
나누고 나서 달라진 것
가장 크게 달라진 것은 새 예외를 만들 때다. 던지는 코드가 인증 쪽에 있으면 인증 핸들러를 열면 된다. 전체를 훑는 단계가 사라졌다.
읽는 쪽도 나아졌다. 인증에서 나가는 응답이 무엇무엇인지 보려면 파일 하나만 열면 된다. 이전에는 227줄에서 인증 관련만 눈으로 골라내야 했다.
다만 파일 수가 넷 늘었다. 예외 하나를 찾을 때 어느 파일에 있는지 먼저 짐작해야 하는 비용이 새로 생겼다. 갈래가 분명한 예외는 짐작이 쉽지만, 애매한 것은 두 파일을 열게 된다.
파일을 나눈 것이 설계를 나눈 것은 아니다
이번에 한 일은 응답을 만드는 자리를 갈래별로 옮긴 것이다. 예외를 던지는 쪽 코드는 그대로 뒀다. 갈래가 애매한 예외는 여전히 애매하고, 어디에 둘지는 그때그때 판단했다.
기준을 응답 코드가 아니라 갈래로 잡은 것은 지금도 맞다고 본다. 다만 갈래를 넷으로 정한 근거는 그달에 만진 코드가 그 넷이었다는 것뿐이다. 다른 영역을 만졌으면 다른 개수가 나왔을 것이고, 그러면 이 나눔도 다시 봐야 했다.
파일이 커졌을 때 줄 수를 세는 것은 늦은 신호다. 같은 판단을 세 번 반복하고 있다는 것이 먼저 온다. 그때 나눌 기준은 대개 이미 손에 있다.