'운영일지'라는 카테고리를 만들어보았다.
아무래도 현재 팀은 서비스 운영하는데 초점을 두고 있기 때문에 운영을 하면서 생긴 노하우, 지식이 많을 것이라고 생각했다.
다만, 팀에 들어온지 한달이 안되었기 때문에 업무자체가 단순 오류수정이 많아 성능을 개선한다거나 구조를 변경한다거나 이런건 없어서 초반에는 Clean Code에 관련한 내용이 더 많을 것 같다.
그리고 운영일지의 글 흐름은 문제 상황(운영 접수 건) > 고민사항 > 해결 방안 순으로 진행하려고 한다. 필자와 동일한 고민을 했던 사람들에게 공감을 이끌어낼 수 있는 글을 앞으로 작성하려고 한다.
(띵동) 쪽지가 도착했습니다.
쪽지 내용을 살펴보면 변경이력을 관리할 때, DB에서 사용하는 코드 값이 아닌, 사용자가 알 수 있는 실제 값으로 변경해달라는 요청이다.
즉, 현재는 아래 이미지와 같이 변경이력이 쌓이고 있었다.
이건 충분히 오해가 있으므로 코드값을 정의된 이름으로 변경해줘야할 것같다.
이름으로 변경하게 되면 아래와 같이 노출될 것이다.
고민사항
우선, 왜 이렇게 코드값을 노출하는지 파악해보았다. History Table을 확인해보니 '001', '002'와 같이 코드 값을 삽입하고 있었고, 이를 그대로 노출하는 것이였다.
그러면 정말 심플하게, 해당 history 데이터를 select할 때, enum으로 변환해주면 되지 않을까? 라고 생각했다.
private NoticeType type;
private NoticeCategory category;
즉, Mybatis의 TypeHadnler를 활용해서 enum 값을 매핑해오고 enum의 code가 아닌 value로 치환해서 응답한다면 쉽게 문제를 해결할 수 있을것이다.
그러나,
우리팀 구조를 파악해보니, enum을 사용하지 않고 code-value 값을 redis에 저장하고 있었다. 해당 code-value를 사용하기 위해서는 CommonCodeUtil을 호출하여 enum 형태의 코드 값을 받아와야 했다.
CommonCode는 이중 Map 자료구조로 설계되어 있었다. (Map<String, Map<String,String>>)
{
"noticeType": {
"001": "중요",
"002": "일반"
},
"noticeCategory": {
"001": "정보",
"002": "경제",
"003": "쇼핑"
}
}
이를 활용한 기존의 서비스로직은 아래와 같이 구성되어 있었다.
Map<String, Map<String, String>> commonCodeMap = CommonCodeUtil.generate();
for (History history : histories) {
if ("noticeType".equals(history.getColumnName())) {
history.updateOriginalColumn(commonCodeMap.get("noticeType").get(history.getOriginalColumn()));
history.updateChangeColumn(commonCodeMap.get("noticeType").get(history.getChangeColumn()));
} else if ("noticeCategory".equals(history.getColumnName())) {
history.updateOriginalColumn(commonCodeMap.get("noticeCategory").get(history.getOriginalColumn()));
history.updateChangeColumn(commonCodeMap.get("noticeCategory").get(history.getChangeColumn()));
} else if ("noticeLevel".equals(history.getColumnName())) {
history.updateOriginalColumn(commonCodeMap.get("noticeLevel").get(history.getOriginalColumn()));
history.updateChangeColumn(commonCodeMap.get("noticeLevel").get(history.getChangeColumn()));
} else if ("noticeStatus".equals(history.getColumnName())) {
history.updateOriginalColumn(commonCodeMap.get("noticeStatus").get(history.getOriginalColumn()));
history.updateChangeColumn(commonCodeMap.get("noticeStatus").get(history.getChangeColumn()));
} else if ("noticePriority".equals(history.getColumnName())) {
history.updateOriginalColumn(commonCodeMap.get("noticePriority").get(history.getOriginalColumn()));
history.updateChangeColumn(commonCodeMap.get("noticePriority").get(history.getChangeColumn()));
}
}
/* 생략 */
각각 다른 시점에 다른 개발자가 운영이슈를 처리하다 보니 저렇게 하나하나씩 if-else문이 들어가있다.
여기서 나는 선택해야 한다. IF문 똑같이 추가하고 퇴근할지..? 아니면 리펙토링하며 조금의 시간을 더 쓸지..?
해결방안
슈퍼스타라 약속이 많은편인데, 다행이 오늘 약속이 없으니 후자를 선택해서 진행해보려고 한다.
사실 야근은 과장이긴 하지만, 리펙토링을 진행하면 영향범위를 파악해야하고 테스트 범위가 커진다.
우선, 위의 if 지옥은 꽤나 심플하게 수정이 가능하다. 생각해보면 "noticeType".equals(history.getColumnName()) 이 과정이 불필요한 것이다. 왜냐면 우리는 Map 자료구조를 사용하고 있기 때문이다.
Map<String, Map<String, String>> commonCodeMap = CommonCodeUtil.generate();
for (History history : histories) {
if (!commonCodeMap.containsKey(history.getColumnName())) {
continue;
}
Map<String, String> codeMap = commonCodeMap.get(history.getColumnName());
history.updateOriginalColumn(codeMap.getOrDefault(history.getOriginalColumn(), ""));
history.updateChangeColumn(codeMap.getOrDefault(history.getChangeColumn(), ""));
}
commonCodeMap에 key가 포함되어 있지 않으면 코드 값이 아니라고 판단하여 continue를 진행한다.
만약, key가 포함되어 있는 경우, sub map을 꺼내서 사용한다. 다만, codeMap.get()을 했더니 값이 없어 null이 발생할 수 있다. 예를 들어 codeMap에는 '001', '002' key값이 있는데, history.getOriginalColumn()의 결과가 '003'인 경우이다.
이런 상황은 올바르지 않은 코드 값이라고 판단하여 빈 값으로 응답하는 것으로 협의되었다. 웹 상에서는 올바르지 않은 코드의 실제 값은 빈 값으로 노출될 것이다.
결론
오늘은 정말 간단한 운영업무를 처리해보았다. 참고로 저 함수를 리펙토링하면서 commonCodeMap이 null인 경우, codeMap이 null인 경우 등의 테스트케이스를 생성형 AI를 활용해서 도출해보았다. 생성형 AI를 통해 테스트코드를 빠르게 도출할 수 있었고 생각지도 못했던 엣지케이스도 발견할 수 있었다.
그리고 운영이슈를 하다보면 생각보다 if 딜레마에 빠지는 것 같다. 각각 운영 담당자가 다르다보니 그때그때 이슈를 처리하다 if문을 추가하다보니 if지옥에 빠지는 것 같다. 앞으로 if 지옥을 만나게 된다면 싹 다 수정 해야겠다 ^_^
그럼 20000~~