기존의 산출 구조 (~2022.08.29)

기존 SanchulDao 의 구조는 아래와 같습니다.

스크린샷 2022-08-29 오후 3.58.53.png

상세한 구조는 아래에서 CommonSanchulSoonengDao 클래스의 코드 일부를 살펴보며 설명 드리겠습니다.

CommonSanchulSoonengDao

public interface CommonSanchulSoonengDao {

    SanChulVo selectSanchul(SanChulDto params);

}

CommonSanchulSoonengDaoImpl.java

@Slf4j
@Repository("commonSanchulSoonengDao")
public class CommonSanchulSoonengDaoImpl implements CommonSanchulSoonengDao {

		@Autowired
    public ApplicationContext context;

		String[] defaultMethods = { 
						"selectPyojunHightLowWon" // 원점기준 만점 원점
            , "selectPyojunHightLowAdd" // 원점기준 가산점
            , "selectPyojunHightLowManjum" // 원점기준 만점을 구한다
            , "selectSoonengAdd" // 가산점
            , "selectSoonengNotChoice" // 선택 전 득점
            , "selectSoonengTamguChoice" //  탐구의 선택
            , "selectMySoonengScore" // 최종 득점
    };

		public void init() {
        // 서울대(SEOUL) : 11102
        String[] SEOUL = { "selectMySoonengScoreSeoul" };
        proc.put("11102", SEOUL);
        // 서울시립대(SEOUL_CITY) : 11103
        String[] SEOUL_CITY = { "selectPyojunHightLowWonSeoulCity" };
        proc.put("11103", SEOUL_CITY);
        // 건국대(KUNKUK) : 11204
        String[] KUNKUK = { "selectPyojunHightLowWonKunkuk" };
        proc.put("11204", KUNKUK);
				
				// 중략... 위 학교들 포함 55개의 학교들 별로 문자열 배열이 정의됨.

		}

		@Override
    public SanChulVo selectSanchul(SanChulDto params) {
        init();
        SanChulVo result = new SanChulVo();
        result = procSoonengResult(params);
        return result;
    }

    /**
     * 개요 : 수능 성적 산출의 모든 단계(메서드)를 실행시킨다.
     * 1. 모집요강의 대학 코드를 조회하고, 해당 대학의 별도 산출 메서드들을 조회한다.
     * @see #init()
     * 2. 공통산출 메서드들을 단계에 맞게 순차적으로 실행시키다가, 별도 산출 메서드가 특정 단계에 해당한다면
     *      별도 산출 메서드로 대체해서 실행시킨다.
     *      - ex) 별도 산출 메서드 중 "setTamguScrEtoosEdu()" 가 있다면,
     *            "setTamguScr()" 실행 단계에서  "setTamguScrEtoosEdu()" 로 대체하여 실행시킨다.
     * 3. 산출된 수능 점수가 담긴 객체를 반환한다.
     *
     * - 주석 작성자 : 송강찬 <[email protected]/>
     * @param params
     * @return 수능 환산 점수가 담긴 객체
     */
    public SanChulVo procSoonengResult(SanChulDto params) {
        SanChulVo result = new SanChulVo();
        SanChulInfoVo info = new SanChulInfoVo();
        try {
            info = params.getInfo();
            Object target = context.getBean("commonSanchulSoonengDao");
            Class<?> c = target.getClass();
            String[] sanchulMethods = (String[]) proc.get(info.getUnivCd());

            for (String defaultMethod : defaultMethods) {
                if (sanchulMethods != null && sanchulMethods.length > 0) {
                    for (String sanchulMethod : sanchulMethods) {
                        if (sanchulMethod.contains(defaultMethod)) {
                            defaultMethod = sanchulMethod;
                            break;
                        }
                    }
                }
                Method method = c.getDeclaredMethod(defaultMethod, new Class[] { SanChulDto.class });
                method.invoke(target, params);
            }
            result.setSoonengScore(params.getSoonengScore());
            result.setSoonengScoreList(params.getSoonengScoreList());
        } catch (Exception e) {
            log.debug("{}", info);
            log.error("{}", e);
            //            throw new CommonException(e.getMessage());
        }
        return result;
    }

		// 중략...

		public void selectPyojunHightLowWon(SanChulDto params) {
			// 공통 selectPyojunHightLowWon 로직 
		}

		public void selectPyojunHightLowWonSeoulCity(SanChulDto params) {
			// 서울시립대 selectPyojunHightLowWon 로직
		}

		/* 
				이하 아래 메서드 종류별로 산출 로직들이 정의되어 있음
				selectPyojunHightLowWon
				selectPyojunHightLowAdd
				selectPyojunHightLowManjum
				selectSoonengAdd
				selectSoonengNotChoice
				selectSoonengTamguChoice
				selectMySoonengScore
				...
		/*

}

위 코드는 수능 성적 산출을 담당하는 CommonSanchulSoonengDaoImpl 클래스의 코드를 일부 발췌해온 것입니다. 자바 리플렉션 API를 통해 메서드명으로 산출 로직이 정의된 메서드들을 호출(invoke)하여 사용하고 있는 구조입니다.

기본적으로 수능 성적 산출을 위한 메서드 7개를 제외하고도 55개의 대학별 따로 정의해줘야하는 메서드들이 해당 클래스에 전부 정의되어 관리가 되어지고 있습니다. 하여 CommonSanchulSoonengDaoImpl 클래스는 총 10,488 라인의 코드로 이루어진 거대한 코드 덩어리가 되었습니다. 🥲

이 거대한 덩어리의 클래스에 존재하는 수많은 수능 성적 산출 메서드들은 런타임시 메서드명을 통해 자바 리플렉션 API로 호출되어 사용되고 있는데요! 이로 인해 메서드 호출을 위한 문자열 배열이 하드코딩 되어 있어 프로그램 작성 시 컴파일상에서 에러를 감지할 수도 없고 IntelliJ와 같은 IDE가 제공하는 강력한 생산성 도구들을 사용하여 코드를 관리하는 데에도 제약을 받는 상태가 되어있습니다.

또한 런타임 환경에서 리플렉션 API가 반복적으로 호출되는 만큼 성능도 상대적으로 떨어져 있었습니다.

		// 중략 ...

		String[] defaultMethods = { 
						"selectPyojunHightLowWon" // 원점기준 만점 원점
            , "selectPyojunHightLowAdd" // 원점기준 가산점
            , "selectPyojunHightLowManjum" // 원점기준 만점을 구한다
            , "selectSoonengAdd" // 가산점
            , "selectSoonengNotChoice" // 선택 전 득점
            , "selectSoonengTamguChoice" //  탐구의 선택
            , "selectMySoonengScore" // 최종 득점
    };

		// 중략 ...

    public SanChulVo procSoonengResult(SanChulDto params) {
				// ...
        try {
            Object target = context.getBean("commonSanchulSoonengDao");
            Class<?> c = target.getClass();
						String[] sanchulMethods = (String[]) proc.get(params.getInfo().getUnivCd());
            // ...
            for (String defaultMethod : defaultMethods) {
                if (sanchulMethods != null && sanchulMethods.length > 0) {
                    for (String sanchulMethod : sanchulMethods) {
												// 해당 대학에 따로 정의된 산출 메서드가 있다면 그 메서드명을 저장
                        if (sanchulMethod.contains(defaultMethod)) {
                            defaultMethod = sanchulMethod;
                            break;
                        }
                    }
                }
								// 메서드명으로 메서드를 리플렉션하여 호출(invoke)
                Method method = c.getDeclaredMethod(defaultMethod, new Class[] { SanChulDto.class });
                method.invoke(target, params);
            }
						// ...
        } catch (Exception e) {
						// ...
        }
        return result;
    }

		// 중략 ...