디자인패턴으로 레거시 코드 개선하기
유지보수하고있는 레거시 프로젝트 내에는 사용자 알림을 위해 SweetAlert 라이브러리가 널리 사용되고 있었습니다. 하지만 swal 함수를 직접 호출하는 방식이 남발되어 유지보수에 어려움이 있었습니다.
이를 개선하기 위해 Builder 패턴을 적용하여 코드를 정리하고, 나아가 Factory Method 패턴과 JSDoc 타입 힌팅으로 발전시킨 리팩토링 과정을 공유합니다.
Step 1. 문제의 발단: 파편화된 라이브러리 호출 (AS-IS)
비즈니스 로직 곳곳에서 라이브러리 함수를 직접 호출하고 있어, 스타일이 통일되지 않고 중복 코드가 발생하고 있었습니다.
// 단순 경고창 하나 띄우는데도 인자가 많음
swal(aptxlang["fail"], result.message, "warning");
// 복잡한 컨펌창: 옵션 객체가 비즈니스 로직을 가림
swal(
{
title: "삭제하시겠습니까?",
text: "삭제된 데이터는 복구할 수 없습니다.",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "삭제",
closeOnConfirm: false,
},
function () {
// 콜백 지옥 시작...
},
);
문제점
- 중복 코드:
title,text등 동일한 키(Key) 반복. - 가독성 저하: 옵션 객체가 길어져 코드 흐름 방해.
- 오타 위험:
confirmBtnText등으로 오타 발생 시 디버깅 어려움.
Step 2. 1차 리팩토링: Builder 패턴 도입
이 문제를 해결하기 위해 swal.js에 Swal 클래스를 구현했습니다. 메서드 체이닝을 지원하는 Builder 패턴을 적용하여 가독성을 크게 개선했습니다.
class Swal {
#option = {};
constructor() {}
// 상태 초기화
init() {
this.#option = {};
return this;
}
// 메서드 체이닝
setTitle(str) {
this.#option["title"] = str;
return this;
}
setType(str) {
this.#option["type"] = str;
return this;
}
// 실행
run() {
return swal(this.#option);
}
}
사용 예시
const customSwal = new Swal(); // 전역 인스턴스
// 훨씬 명확해진 코드
customSwal.init().setTitle("데이터 저장").setDescription("성공적으로 저장되었습니다.").setType("success").run();
라이브러리 의존성을 Swal 클래스 안으로 격리하고, 호출부의 가독성을 높인 훌륭한 개선입니다.
Step 3. 2차 리팩토링: 안전성과 DX 더하기
1차 리팩토링 코드도 훌륭하지만, 여기에 JSDoc을 활용한 타입 힌팅과 Factory Method를 추가하여 개발 편의성을 극대화해 보았습니다.
개선 포인트
- JSDoc 타입 힌팅: VSCode 등 에디터에서 메서드 사용 시 파라미터 타입과 설명을 미리 보여주어 오타 방지.
- 불변성 확보:
init()대신 매번new로 인스턴스를 생성하여 상태 오염 원천 차단. - Factory Method: 자주 쓰는 알림(성공, 에러)을 미리 정의.
AlertBuilder (최종 제안)
/**
* SweetAlert를 Builder 패턴으로 생성하는 클래스
*/
export class AlertBuilder {
#options = {};
constructor() {
this.#options = { allowOutsideClick: false };
}
/**
* 알림창의 제목을 설정합니다.
* @param {string} title - 제목 텍스트
*/
setTitle(title) {
this.#options.title = title;
return this;
}
/**
* 알림창의 본문 내용을 설정합니다.
* @param {string} text - 본문 텍스트
*/
setText(text) {
this.#options.text = text;
return this;
}
/**
* 알림 아이콘 타입을 설정합니다.
* @param {'success' | 'error' | 'warning' | 'info' | 'question'} type
*/
setType(type) {
this.#options.type = type;
return this;
}
/**
* 취소 버튼 표시 여부를 설정합니다.
* @param {boolean} show
*/
setShowCancelButton(show) {
this.#options.showCancelButton = show;
return this;
}
/**
* 설정된 옵션으로 swal을 실행합니다.
* @returns {Promise<any>}
*/
async fire() {
return swal(this.#options);
}
// [New] 자주 쓰는 패턴 프리셋
/**
* 성공 알림을 띄웁니다.
* @param {string} title
* @param {string} [text]
*/
static success(title, text = "") {
return new AlertBuilder().setType("success").setTitle(title).setText(text).fire();
}
}
효과: VSCode 인텔리센스 (IntelliSense)
이렇게 JSDoc을 작성해두면, setType 메서드를 사용할 때 에디터가 자동으로 가능한 값('success' | 'error' ...)을 추천해줍니다.
코드를 작성할 때 문서를 찾아볼 필요 없이, 에디터의 안내만 따라가면 되므로 개발 속도가 빨라지고 실수가 줄어듭니다.
마치며
이번 리팩토링은 "날것의 코드" 에서 "구조화된 코드(Builder)" 로, 그리고 "친절한 코드(JSDoc + Factory)" 로 점진적으로 발전하는 과정을 의도했는데 잘 보여졌을 지 모르겠습니다.
- Step 1: 무분별한 직접 호출 제거.
- Step 2: Builder 패턴으로 인터페이스 통일 (User's Work).
- Step 3: JSDoc과 Factory Method로 생산성 및 안전성 강화.
단순히 "돌아가는 코드"를 넘어 "누군가 쓰기 편한 코드"를 만드는 것을 핵심으로 리팩토링을 진행했습니다