배경
사실 은행의 애플리케이션 코드들은 대부분 CRUD 위주입니다. 데이터 중심적이고 거래 단위입니다. 데이터에서 한 차원 더 높은 추상화(객체 라던가)를 찾기는 힘듭니다. 그런 것 때문에 관리와 변경에 복잡성이 높아지기도 합니다. 단위 테스트의 편이성도 낮아집니다. 테이블에서 서비스(컨트롤러), 화면 까지 커플링이 상당히 높습니다. 뭔가 작은 것이라도 하나 바꾸려면 커플링된 것들을 모두 바꿔야 합니다. 실수하기 쉽죠.
TDD 계열의 책에서 봤던 것으로 기억나는데, 금융 애플리케이션을 개발하면서 어떤 ‘모형’을 만들어간다는 얘기였습니다. 충분히 추상화되고 구성 또한 잘 되어서, 유연하며 확장하기 쉽고 테스트 하기도 쉬운 애플리케이션은 개발자들에게 꿈만 같은 이야기일 것입니다. 특히나 은행 같은 기술 도입에 상대적으로 늦은 곳에서는요.
문제 정의
가끔 기회가 닿으면 일부분이라도 배운 것을 활용해 보려고 시도하곤 했습니다. 이번에도 하나의 업무 모형을 코드로 바꿔보는 일을 해봤습니다. 기술신용평가 관련된 내용이었습니다. 우선 상황은 이렇습니다.
- 코드의 기능은 여러 분류로 나뉘어진 평가항목을 각각의 가중치에 따라 계산을 하고 그룹별로 소계하여 중간 등급을 계산, 최종적으로 점수값과 그에 따른 등급을 산출하는 것입니다. 예를 들어 대표자의 관련업종 경력은 1년 마다 3점, 최대 50점이고 10점 마다 나누어 등급을 E ~ A로 산출합니다. 그냥 예시 입니다. 이런 구체적인 평가내용을 소항목 이라고 합니다.
- 소항목이 몇 개 모여 중항목 을 이룹니다. 거기도 마찬가지로 등급을 산출합니다. 평가점수는 포함되어 있는 소항목의 가중치를 합계한 것을 최대값으로 합니다.
- 중항목도 몇 개가 모여서 대항목을 이룹니다. 대항목은 최종적으로 합산하여 기술등급 으로 계산됩니다. 기술등급은 1에서 10까지의 단계로 정의됩니다.
- 이렇게 계층화된 평가를 통해 기술등급을 산출하는 것을 기술평가의 핵심 비즈니스 모델이라고 볼 수 있습니다. (물론 평가를 하는 사람들이 가장 핵심적인 자원입니다. IT시스템은 규정과 도움을 주는게 주 역할이죠.)
여기에 조금 더 해서 평가모형 이라는게 들어옵니다. 이것의 역할은 모형을 세분화하여 평가모형과 가중치를 달리하는 것입니다. 계층의 윗 단계인 기술등급/대항목/중항목 까지의 구성은 같지만 소항목은 약 30개 * 평가모형 약 10개 총 300개의 평가규칙이 존재하게 됩니다. 원래는 평가모형이 단 하나만 있었습니다. 사업이 번창(?)함에 따라 업종과 업력의 특성을 반영하기 위해 평가모형을 세분화한 것입니다. 기존에 있던 하나의 평가모형을 10가지로 확장하는 것이 이번 포스팅의 핵심입니다.
방향 설정
이럴 때 제가 보통 생각하는 방법은 본질적인 것과 비 본질적인 것 을 나누는 것입니다. 본질적인 것이란 원래의 속성으로 달리 간편한 해법을 생각할 수 없는 것들이죠. 어느 책(논문?)에서는 incident와 accident라고 불렀던 것 같네요. 패턴 상으로는 구성에 관한 것 이라고 볼 수 있을 것 같네요. 저는 단순하게 본질/비본질로 일단 나눠보았습니다. 위의 문제에서 본질적인 부분은 대략 이런 것들 같습니다.
- 평가모형에는 종류가 있음.
- 평가모형에 따라 소항목의 구성과 가중치가 서로 다름.
- 중항목은 소항목 몇 가지로 구성됨. 대항목은 중항목 몇 가지로 구성됨.
- 소/중/대항목의 평가 결과는 점수와 등급 으로 계산됨.
- 계산방법 : 등급을 점수로 환원 또는 입력값에 따라 계산.
비본질적인 부분은 어떤게 있을까요?
- 소항목의 종류 : 평가모형에 따라 소항목의 구성만 알 수 있으면 됨.
- 종류별 가중치 : 평가모형/항목에 따라 다름. 주기적으로 변경될 수 있음.
- 평가모형은 일종의 계산기 역할만 하면 됨. 상태(특정 값)을 저장할 필요는 없음. 사실상 평가모형의 종류와 평가모형(계산) 자체는 아무런 상관이 없음.
본질적인 부분은 코드로 넣고 비본질적인 부분은 설정(데이터)화 할 수 있도록 하는게 좋을 것 같습니다.
개발하기
기존에 하나의 평가모형만 다룰 때 작성한 코드는 잘 동작하고 있었습니다. 어찌보면 평가모형이 여러개가 되었다라는 것만 처리하면 됩니다. 평가모형의 종류에 대해 다루는 것은 Repository(저장소)를 떠오르게 합니다. 저장소에는 평가모형 여러개가 들어가있고 필요할 때 꺼내서 값을 전달해주면 계산해 주는 역할을 합니다. 대략 이런 코드를 구상합니다.
public class TCBModelRepository { // 모형의 종류는 enum으로 구분 public enum Models { ING_Manufacturing, // 일반기업_제조업 ... } private static Map<Models, TCBRatingBean> repository = new HashMap<Models, TCBRatingBean>(); // 평가모형 저장 private TCBModelRepository() {;} // 생성 막기 public static Models whichModel(...) { // 모형을 구분할 수 있는 속성들 // 평가모형 구분하기 return Models.valueOf(모형이름); } // 이름으로 평가모형 가져오기 public static TCBRatingBean get(Models model) { if (repository.containsKey(model)) { return repository.get(model); } return loadModel(model); } private static TCBRatingBean loadModel(Models model) { // 리포지토리에 평가모형을 불러오는 코드 } // 테스트용. static void addModel(Models model, TCBRatingBean rating) { repository.put(model, rating); } }
이걸로 평가모형을 구분하는 것과 평가모형(계산기)를 분리하였습니다. 계산은 TCBRatingBean에서 처리하도록 합니다. 부수적인 효과로는 매번 계산기를 만들지 않아도 되는 것이 있습니다. 데이터베이스에 가중치/소/중/대항목 등을 불러오는 시간을 줄일 수 있습니다.
평가모형 계산기 자체는 좀 더 일반화를 진행하였습니다. 코드에 들어 있던 평가항목 구성과 가중치에 대한 부분을 코드에서 빼내고 데이터로 만들었습니다.
단무지하게 처리하려고 했다면 음..어찌 되었을까 상상도 안가네. 10개의 비슷한 계산기 클래스를 만든다던가..if 문을 왕창 때려넣으면 되나? 아무튼 엉망진창이 되었을 것 같네요.
평가모형을 구분하는 것과 계산하는 것을 각각의 클래스로 나누었을 때에는 다른 장점도 여럿 존재합니다. 우선 테스트하기 쉬워집니다. 리포지토리는 평가모형 자체에 대해서만 다룹니다. 평가모형이 무엇을 하는지는 관심이 없습니다. 반대로 평가모형은 계산만 신경을 쓰면 됩니다. 다른 평가모형이 뭐가 있는지 등은 아무런 영향을 미치지 않습니다. 또 다른 장점은 변경이 격리된다는 것입니다. 평가모형이 10개에서 20개로 늘어나더라도 계산 방식이 바뀌지 않는다면 계산 클래스는 변경하지 않아도 됩니다. 사실 테스트가 용이해지는 것과 변경이 격리되는 것은 서로 깊은 관계가 있습니다. 흔히 디커플링(decoupling)이라고 말하는 것의 의 효과이기 때문입니다.
결론
업무모형을 구조화된 코드로 바꾸는 간단한 예제를 살펴보았습니다. if-else를 넘어 구조화된 코드를 도입하는 것은 초급 개발자에서 중급 개발자로 넘어가기 위한 중요한 체크포인트라고 생각합니다. 보통의 방법으로는 처리하기 쉽지 않은 문제들을 만나면 어떤 구조를 도입해서 쉽게 해결할 수 있을지 고민하고 시도함으로써 개발 실력을 늘릴 수 있습니다.