- 발행일
handleAjaxError를 다시 고민했다 — 커밋 두 개 451줄, 들여쓰기를 빼면 120줄
handleAjaxError를 다시 고민했다
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
8월이 되면서 트리 화면이 셋이었습니다. 메타데이터 클래스, 분류체계, OID. 7월 17일에 세 파일이 metaClassTree.js·conceptClassTree.js·oidClassTree.js로 이름을 맞추고 공통 함수를 commonClassTreeUtils.js로 뽑기 시작한 상태였어요.
그 공통 파일에 handleAjaxError가 있었습니다.
function handleAjaxError(request, status, err, customMessage = "") {
const prefix = customMessage ? customMessage + "\n" : "";
alert(
prefix +
"code:" +
request.status +
"\n" +
"error:" +
err +
"\n" +
"message:" +
request.responseText
);
}
네 번째 인자로 customMessage를 받게 만들어 뒀는데, 세 트리 파일이 전부 error: handleAjaxError로 함수를 그대로 넘기고 있었습니다. 그러니 alert에는 상태 코드와 서버 응답만 뜨고, 어느 요청이 실패한 건지는 안 나왔어요. 조회가 실패한 건지 저장이 실패한 건지 사용자도 저도 모르는 alert이었습니다.
16:25 — if 한 겹을 풀었다
먼저 든 커밋(8118f846)의 제목은 "first-fail 법칙으로 유도 하기"입니다. 148줄 추가, 145줄 삭제. 그런데 공백을 무시하고 보면 5줄 추가, 2줄 삭제예요.
+ // 관리자 버튼 표시/숨김 처리
+ if (!checkAccessLevel()) {
+ $("#addClassBtn, #editClassBtn, #deleteClassBtn").hide();
+ }
+
// 관리자 페이지에서만 버튼 이벤트 추가
- if (checkAccessLevel()) {
- $("#addClassBtn").on("click", function (e) {
+ $("#addClassBtn").on("click", function (e) {
관리자 버튼 네 개의 클릭 핸들러가 if (checkAccessLevel()) { ... } 한 덩어리 안에 들어 있었습니다. 그 겹을 풀어서 권한이 없으면 버튼을 숨기고 끝내고, 핸들러는 조건 없이 붙였어요. 실패 조건을 먼저 처리하고 본문을 평평하게 두는, 프런트에서 늘 하던 정리입니다. 145줄이 움직인 건 그 안의 코드가 전부 한 단 왼쪽으로 온 것뿐이에요.
같은 시각에 분류체계 트리는 다른 방식이었습니다. 6월 25일부터 핸들러 안에서 if (!checkAccessLevel()) return;으로 빠져나가고 있었어요. 메타데이터 트리는 숨기고 무조건 바인딩, 분류체계 트리는 바인딩하고 안에서 return. 세 파일이 같은 일을 두 방식으로 하게 된 날이기도 합니다.
17:51 — alert에 문맥 한 줄을 붙였다
한 시간 반 뒤 커밋(4a7e249a)이 제목의 그 커밋입니다. 5개 파일에 451줄 추가, 384줄 삭제. 공백을 무시하면 115줄 추가, 48줄 삭제입니다. 나머지는 commonClassTreeUtils.js의 들여쓰기를 2칸에서 4칸으로 바꾸고 구획 주석을 넣은 거예요.
// =============================================================================
// HTTP 요청 관리
// =============================================================================
실제로 바뀐 건 세 가지입니다. 첫째, 세 트리 파일의 error: handleAjaxError 여섯 곳이 전부 문맥을 넘기는 꼴이 됐습니다.
- error: handleAjaxError,
+ error: function (request, status, err) {
+ handleAjaxError(request, status, err, "메타 클래스 기본 속성 조회 중 오류가 발생했습니다.");
+ },
customMessage 인자는 7월 17일부터 있었는데 아무도 안 넘기고 있던 걸 이날 채운 겁니다. "다시 고민했다"는 제목치고는 함수 본문은 그대로이고 부르는 쪽만 바꿨어요.
둘째, 메타데이터 트리의 버튼 바인딩에 .off("click")을 앞세웠습니다.
- $("#addClassBtn").on("click", function (e) {
+ $("#addNodeBtn")
+ .off("click")
+ .on("click", function (e) {
분류체계 트리는 여덟 곳, OID 트리는 여섯 곳에 이미 이렇게 돼 있었고 메타데이터 트리만 0이었습니다. 초기화가 두 번 돌면 핸들러가 두 번 붙는 종류의 문제인데, 실제로 두 번 붙는 걸 봤는지 다른 파일과 맞추느라 넣었는지는 커밋에 없습니다.
셋째, 버튼 id를 #addClassBtn에서 #addNodeBtn으로 바꿨습니다. 분류체계와 OID 트리가 이미 NodeBtn이었고 메타데이터 트리만 ClassBtn이었어요. 템플릿의 버튼 셋도 같이 바꿨습니다. 공통화하려면 id부터 같아야 하니까요.
무엇이 바뀐 것처럼 보이고, 무엇이 바뀌었나
| 겉보기 | 실제 |
|---|---|
| 두 커밋 599줄 추가, 529줄 삭제 | 공백 빼면 120줄 추가, 50줄 삭제 |
handleAjaxError를 다시 고민 | 함수 본문 무변경. 호출부 여섯 곳에 메시지 한 줄씩 |
| first-fail 법칙 | if 한 겹을 풀어 145줄이 한 단 왼쪽으로 |
| 공통 유틸 404줄 변경 | 들여쓰기 2칸에서 4칸, 구획 주석 셋. 로직 무변경 |
이 표를 그때 그렸으면 커밋을 둘로 나눴을 겁니다. 들여쓰기 커밋 하나, 로직 커밋 하나로요. 그때는 파일을 열어 보이는 대로 정리하고 한 번에 올렸습니다.
검증
- 손으로 확인했습니다. 관리자·비관리자로 들어가 버튼이 보이고 안 보이는 것까지는 봤을 텐데, 실패 alert를 무엇으로 만들어 봤는지는 기록이 없습니다.
- 공백 무시 줄 수(5/2, 115/48)와
.off("click")개수(메타데이터 0에서 6, 분류체계 8, OID 6에서 7)는 이 글을 쓰면서git show -w와 grep으로 셌습니다. 그날 센 숫자가 아닙니다. - 핸들러가 실제로 두 번 붙던 증상은 재현하지 않았습니다.
남은 것 · 한계
handleAjaxError는 여전히alert입니다. 상태 코드와responseText를 그대로 띄우는 구조라, 서버가 HTML 오류 페이지를 돌려주면 alert에 HTML이 통째로 뜹니다. 문맥 한 줄을 앞에 붙였을 뿐 그 구조는 그대로예요.- 공통 유틸 안의
defaultHandlers는 여전히error: handleAjaxError입니다. 세 트리 파일의 호출부만 고치고 공통 파일 안의 두 곳은 빈손으로 남겼습니다. - 접근 판단이 세 파일 두 방식이 됐습니다. 숨기고 무조건 바인딩하는 쪽은 비관리자 페이지에도 핸들러가 붙어 있어요. 버튼이 안 보이니 누를 수는 없지만 콘솔에서
.click()을 부르면 돕니다. 서버가StaffOnlyMixin으로 막고 있어서 화면 문제로 끝나지만 깔끔한 상태는 아니었습니다. 다음날 "코드 공통화" 커밋이 이어졌고, 옵션과 핸들러까지 한 곳으로 모은 건 2026년 5월2026-05-16분류체계와 메타클래스 트리 공통 로직을 헬퍼 4종으로 추출한 회고두 페이지가 따로 들고 있던 jstree 옵션과 이벤트 핸들러를 비교해 차이가 거의 없음을 확인하고, 공통 헬퍼 4종으로 추출한 리팩토링 회고입니다.입니다. .hide()로 숨긴 버튼은 왜 못 쓰는지 말하지 않습니다. 1년 뒤 잠긴 버튼에 사유를 붙이는 글2026-08-01disabled를 쓰면 "왜 안 되는지"를 말할 수 없다 — 잠긴 버튼에 사유를 붙인 회고잠긴 버튼에 사유를 title로 달아뒀는데 아무리 호버해도 안 떴습니다. disabled 요소는 마우스·키보드 이벤트를 통째로 죽여서 네이티브 title조차 안 뜬다는 걸 그때 알았어요. aria-disabled로 바꿔 의미만 남기고 클릭은 핸들러에서 막았습니다. 그 다음엔 말풍선이 스크롤 패널에 잘리고, 휠 관성 때문에 떴다 사라지는 문제가 이어졌습니다.에서 이 문제를 다뤘는데, 이날은 숨기면 끝이라고 봤습니다.- 들여쓰기와 로직을 한 커밋에 섞었습니다. 리뷰하는 사람은
git show -w를 알아야 이 커밋을 읽을 수 있어요.
관련 글: 분류체계와 메타클래스 트리 공통 로직을 헬퍼 4종으로 추출한 회고2026-05-16분류체계와 메타클래스 트리 공통 로직을 헬퍼 4종으로 추출한 회고두 페이지가 따로 들고 있던 jstree 옵션과 이벤트 핸들러를 비교해 차이가 거의 없음을 확인하고, 공통 헬퍼 4종으로 추출한 리팩토링 회고입니다. · disabled를 쓰면 "왜 안 되는지"를 말할 수 없다2026-08-01disabled를 쓰면 "왜 안 되는지"를 말할 수 없다 — 잠긴 버튼에 사유를 붙인 회고잠긴 버튼에 사유를 title로 달아뒀는데 아무리 호버해도 안 떴습니다. disabled 요소는 마우스·키보드 이벤트를 통째로 죽여서 네이티브 title조차 안 뜬다는 걸 그때 알았어요. aria-disabled로 바꿔 의미만 남기고 클릭은 핸들러에서 막았습니다. 그 다음엔 말풍선이 스크롤 패널에 잘리고, 휠 관성 때문에 떴다 사라지는 문제가 이어졌습니다. · jsTree 기본 사용법2025-04-23jsTree 기본 사용법 — 플러그인·API 정리와 실무에서 걸린 지점jQuery 기반 트리 플러그인 jsTree의 플러그인·API·이벤트를 표로 정리한 레퍼런스입니다. 실무 트리 화면에 붙이면서 걸린 지점들을 뒤에 덧붙였습니다 — check_callback의 진짜 의미, 루트 노드 id가 #이라는 함정, 기본 contextmenu를 버리고 모달로 간 이유까지. · 처음 만든 트리를 열흘 만에 통째로 다시 짰다2025-06-20처음 만든 트리를 열흘 만에 통째로 다시 짰다 — 템플릿 안 550줄이 어디서 무너졌나입사 두 달째에 메타데이터 클래스 트리를 처음 만들었습니다. 템플릿 하나에 스크립트 550줄을 넣었고, 공개 화면에는 같은 것을 한 벌 더 넣었어요. 아흐레 뒤 스크립트를 파일로 빼면서 노드를 고를 때마다 페이지를 통째로 이동하게 만들었다가, 다음날 그걸 걷어내고 트리를 목록 화면으로 옮겼습니다. 첫 판이 어디서 무너졌는지를 커밋 셋으로 되짚습니다. · 파일 33개를 한 커밋에 넣었다2025-07-17파일 33개를 한 커밋에 넣었다 — 제목은 한 가지 일이라고 말하고, 안에는 다섯 가지가 있었다입사 석 달째, 파일 33개에 1,228줄을 더하고 823줄을 지운 커밋을 올렸습니다. 제목은 스크립트 하나를 바꿨다는 한 줄이었어요. 지금 세어 보니 그 안에 이름 바꾸기, 새 모듈 다섯, 인라인 스크립트 분리, 설정 변경, 뷰 정리가 같이 들어 있었습니다. 그중 하나는 두 달 뒤 팀원이 지웠고, 하나는 1년 뒤 버그로 돌아왔습니다.