주제
개념, 원리
방법
정리
1.
초난감 DAO 자체의 리소스 반환문제
2.
메서드 추출을 해봄(중첩은 사라지는게 아님 늘 메서드에 정적으로 남아있음)
3.
템플릿 메서드 패턴의 적용 (적용하는 것까진 좋은데 클래스가 많아짐)
4.
전략 패턴의 설정 (전략을 설정하는 건 좋은데 너무 정적으로 클래스를 선택하는 부분이 내부에 남아 있음)
5.
로컬 클래스를 활용하거나, 익명클래스를 활용 (좋긴한데 코드읽기가 복잡)
6.
전략 패턴을 Spring DI로 해결해보면 어떨까 하는 관점 (DI로 하는건 좋은데 연쇄적으로 DI되어 있어서 테스트하기 빡세다)
7.
Jdbc Context가 의존관계로 드러나는 문제점 존재 그래서 수동 DI로 변경 ( 어느정도 완성도 있게 만들수 있음, 스프링의 자동 DI를 포기해도 DI의 본질적 활용이 가능해짐)
8.
그럼 Jdbc Context를 템플릿 콜백의 형태로 만들어보면 어떨까? (너저분하게 너무 UserDao에 몰려 있어서 콜백의 형태로 만들어 봄
9.
JdbcTemplate를 사용해보자 (이 과정으로 모두 축약한 Jdbc 템플릿이라는게 존재함)
10.
jdbcTemplate를 최적화 (메서드를 이용하거나 내부오브젝트를 하나만 사용하는 형태로 관리는 방법을 설명함)
3장 템플릿?
확장에는 자유롭고, 변경에는 닫혀 있다는 ocp원리를 다시 생각.
어떤 부분은 변경을 통하여 기능이 다양해지고 확장하려는 성질이 있고, 어떤 부분은 고정되어 있음
초난감 DAO?
예외 반환이 없어서 try/catch/finally로 만들어줌
왜냐하면 리소스가 터져버릴 수 있음
변하는 것과 변하지 않는 것
try/catch/finally의 문제점은 2중 중첩이고 모든 메서드가 반복된다. 이중 하나만 빼먹어도 조진다.
분리와 재사용을 위한 디자인 패턴 적용
메서드 추출
따로 예외처리를 관리
메서드 추출 관리
템플릿 메서드 패턴의 적용
전략 패턴의 적용
등의 적용이 있다. 이런 맥락에서 나도 한번 만들어 볼려고 한다.
내가 자주 사용했던 POI 라이브러리가 있다. excel파일을 핸들링하려고 쓰는데 이게 대충 엄청 불편하게 되어 있다. 일단 바꿔 볼껀 엑셀 셀타입별 리턴과, 엑셀파일의 버전별 읽기 기능을 조금 더 쉽게 만들어 볼까한다.
엑셀 파일 읽기
엑셀 셀 타입별 값 가져오기
제일 쓰면서 불편했던건 타입별 반환 값이 다르다는 거다. 엑셀의 인식문제 같다. 어떤 사람은 숫자로 넣었는데 텍스트로 인식될때가 있고 어떤 사람은 숫자로 인식되고 이런 문제가 많이 있었다. 이걸 토비의 스프링에서 본 내용처럼 좀 더 고쳐보자
일단은 엑셀 파일 읽기
엑셀 셀 타입별 값 가져오기
Spring 프로젝트로 변경해보기
만들면서 느낀 몇몇의 문제점
일단 간과하고 만든 점이 있다. 다른 Thread에서 만약 동일한 파일 path에 접근하여 동일한 파일을 읽어야 한다면 무한한 기다림이 시작된다. 그래서 synchronized 를 이용해야할 필요가 있다. 그리고 빈으로 등록된 ExcelService내부에 index 즉, 어느위치의 시트에, 몇번째 row에, 몇번째 col을 불러오는지에 대한 정보가 있다. 이것도 다른 Thread가 접근하거나 건드리면 데이터가 박살이 나버린다. 그래서 조심해야한다.
이번 템플릿장에서 느낀점
결국 확장성과 변경, 수정에 강한 코드를 작성하는 방법을 가르쳐주었다. 내 예제에서는 그러한 점을 좀 찾기가 힘들었다. 처음 생각했던 예제 코드는 엑셀의 라이브러리를 어디에든 쓸 수 있도록 확장하고 변경에 강하게 만드는 것이었다. 하지만 이러한 점이 맥락에서 안나타났다. 다른 예제도 조금 생각해봐야할 것 같다.
재미있는 사실 처음에 Converter를 어떻게 등록할까 하다가 빈으로 넣으면 어떨까? 하는 생각으로 만들어봣는데 된다.
// configuration
@Bean
public List<CellConverter> cellConverters(){
List<CellConverter> cellConverters = new ArrayList<>();
cellConverters.add(cell -> cell.getStringCellValue());
cellConverters.add(cell -> cell.getCellFormula());
cellConverters.add(cell -> cell.getNumericCellValue());
return cellConverters;
}
...
//ExcelService
public class ExcelService {
@Getter
@Autowired
private WorkBookMaker maker;
@Autowired
private List<CellConverter> cellConverters; // 이 부분
...
Java
복사
다시만들어 보는 excel 라이브러리
목표
•
템플릿을 만들어보자
진행 시나리오
1.
엑셀 파일을 읽는다.
2.
엑셀 파일의 시트를 찾는다.
3.
엑셀의 row, col을 찾는다.
4.
자료의 형태 맞게 원하는 값을 추출한다.
a.
텍스트형 자료
b.
숫자형 자료
c.
함수형 자료
4번의 부분을 변하는 부분과 변하지 않는 부분으로 만들어보자
엑셀 파일 읽기
파일을 읽는 부분은 이부분만 사용할 것임
엑셀 파일의 sheet, row, col를 찾기
String path = System.getProperty("user.dir") + File.separator + "testFile" + File.separator;
String xls = "xls버전.xls"; // 버전이 좀 많다.
// 엑셀 파일 읽기
FileInputStream inputStream = new FileInputStream(path + xls);
Workbook workbook = WorkbookFactory.create(inputStream);
// sheet, row, col 찾기
Cell cell = workbook.getSheetAt(0).getRow(0).getCell(0);
// 자료 형에 따른 값 가져오기
CellType cellType = cell.getCellType();
switch (cellType){
case NUMERIC:
System.out.println(cell.getNumericCellValue());
break;
case FORMULA:
System.out.println(cell.getCellFormula());
break;
case STRING:
System.out.println(cell.getStringCellValue());
break;
default:
throw new RuntimeException("없는 타입");
}
Java
복사
기존의 엑셀 자료형을 읽는 부분
이런 상황에서는 자료형이 추가 되거나 부가 작업을 해야한다면 내 프로덕션 코드에 수정을 많이 해야한다.
자료의 형태에 맞게 원하는 값 추출
이를 통하여 템플릿이 뭔지 대충 감을 잡을 수 있었다. 템플릿을 통하여 변화에 강하고 확장에 용이한 코드가 어떤 느낌인지 알 수 있었다. 또한 테스트 코드를 통하여 추가나 변경 사항을 바로바로 테스트 할 수 있기 때문에 더 확장할때 염려할 것이 없다.
미흡한 점
1.
CellValueTemplate을 테스트 할때 Mock작업이 상당히 많이 존재한다. “이걸 어떻게 해결해야할까?” 또는 “이렇게 Mock을 만들어 테스트 하는 것이 맞는가?”, “Mock을 만들어 테스트 할 수 있는 상황을 만든게 잘한 것인가?”에 대한 의문이 든다. 하지만 알지 못했다.
2.
CellValueTemplate라고 만들었지만 솔직히 이름이 잘못된 것 같다.
3.
CellRegister을 만들때 addReader()을 사용한다. 그리고 이를 통하여 구현체를 직접 빈으로 등록해준다. 전에 스프링을 쓸때 이러한 패턴으로 등록하는 Formatter를 보아서 따라 해봤다. 근데 맞는지 의문이 든다.
4.
클라이언트 측에서 ExcelFactory와 CellValueTemplate만 받아서 사용하기만 하면 된다. 여기서 의문인점은 이 둘이 개념상으로 연관 관계를 가진다. 그렇지만 내가 만든 코드에서는 연관 관계가 나타나지 않는다. 이것이 맞는지 의문이다(약간 DB 관점으로 본 것이다. Excel파일이 존재하지 않는데 Cell이 어떻게 존재하는가? 그렇다면 합쳐야하는 것이 아닌가? 그럼 엄청 규모가 커질텐데? ).
5.
테스트 코드 또는 프로덕션 코드에 주석을 달아 표시하는 방법을 잘모르겠다. 그래서 주저리주저리 적었는데 다음에는 조금 더 알아보고 사용해야 할 것 같다.
6.
테스트 코드를 작성할때 @ExtendWith()을 통하여 원하는 클래스만 등록하여 사용하려고 했었다. 하지만 에러가 많이 발생했다. 이유를 찾아보니 Poi 라이브러리를 사용하고 있어서 거기에 등록되어 있는 Cell이라는 녀석을 못가져와서 발생한 문제였다. 그렇다면 지금 내 코드가 단위 테스트로의 적합성에 대한 의문이 든다(물론 단위도 정하기 나름이고 내가 만든 application자체가 Poi에 의존적이긴 하지만 그래도 의문이 생긴다.).
7.
CellValueTemplate의 getValu(Cell cell) 메서드의 구현을 보면 이상한 부분(.findCellReader(cell.getCellType().toString()))이 있다. 솔직히 어떻게 고쳐야 할지 막막하다(Cell타입으로 받아서 String타입으로 변경해줘야 할지, 아니면 지금처럼 둬도 되는지, 이렇게 바꾼 이유는 6번의 맥락에서 오류가 자꾸 나서 고친 거다. Cell에 의존적인데 Mock으로 만들어야할지 아니면 String으로 변환해서 사용해야할지 고민하다가 이렇게 바꾸어 주었다).
8.
application.properties에 test.file.path.xls를 만들어 사용했다. 솔직히 아닌 것 같다. 통합 테스트, WorkBookFactory테스트 말고는 쓰임이 없는데 저렇게 만들어도 될지 모르겠다.
9.
CellRegister의 boolean addRegistry(String cellType, CellReader cellReader) 메서드에 파라미터 타입이 잘못되었다. 처음에는 단순히 “key로 찾으면 되겠지?” 하면서 cellType을 넣었는데 만들고 바꿀려고 보니 List를 사용하면 저게 필요가 없다. 오히려 index가 필요하다. 그래서 좀 말이 안되는 것 같다.
좋았던 점 (물론 남이 보면 아닐 수 있다)
1.
추가, 변경에 내 코드가 변하는 일이 없었다(그렇게 할려고 했으니깐).
2.
진짜 모듈을 만드는 느낌이라 기분 좋았다(이렇게 만들어 본 건 처음이다.).
3.
맞는지 아닌지 모르지만 개인적으로CellRegister 의 addRegistry() 메서드는 좋았다(내가 원하는 걸로 만들어서 기분이 너무 좋았다).
4.
추가 및 변경 검증을 시도했을 때 정말 변경되는 사항이 없어서 좋았다.
5.
10트 만에 만들어서 다행이다.
6.
고민을 많이 할 수 있어서 좋았다. “단순한 문제라서 대충하지 뭐”이렇게 생각하지 않아서 다행이다. 이런 고민이 상당히 재미있었다.
7.
테스트 자체가 내가 만든 코드를 대변해주는 걸 알아서 좋았다. “잘 만든 코드는 테스트 하기 쉽다” 를 알았다.