우아한테크코스를 진행하면서
"예외사항이 발생하는 경우 예외를 던진 후 예외사항이 발생한 지점부터 다시 입력을 받아라" 라는 요구사항이 있었다.
그래서 처음 생각한 방법은 while 반복문으로 try ~ catch 문을 감싸,
예외가 발생하지 않는 경우 retrun 을 통해 반복문을 탈출하는 방법이었다.
아래의 코드는 해당 방법을 구현한 것이다.
private int getAttempts() {
outputView.askAttemptsInput();
while (true) {
try {
return inputView.insertAttempts();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
이 방법을 사용하여 예외처리가 나는 부분을 메서드로 추출하여 예외가 발생하는 부분마다 따로 적용을 해주면 요구사항을 만족할 수 있었다. 하지만 걸리는 부분이 한가지 있다면 바로 예외가 발생하는 부분마다 적용을 해줘야한다는 점이다.
예외가 발생하는 부분마다(입력받는 부분) 적용하다보니 입력 받는 모든 부분에 동일하게 반복되는
while { try ~ catch } 가 발견되었다.
입력받는 값이 한가지면 상관이 없겠지만 많으면 많아질수록 코드에 반복되는 부분이 많아지는 문제가 발생한다.
위의 사진처럼 동일한 구조가 지속적으로 반복되며 또한 indent level 을 2레벨이나 필요로 한다.
다른 사람의 코드에서 보았던 재귀함수를 통한 메서드 호출도 마찬가지이다.
재귀함수는 예외가 발생한다고 가정하면 메서드가 종료되기 전에 메서드 자기 자신을 한번 더 호출한다.
즉, call stack 이 쌓이게 된다.
메모리에는 stack 영역이 제한적이기 때문에 무한정으로 반복되는 경우 StackOverFlow 문제가 발생하게 된다.
그렇기 때문에 재귀함수가 조금 더 직관적이여 보이고, 코드가 간결해질 순 있으나 코드가 반복되는 문제를 해결할 수 없을 뿐더러 StackOverFlow 라는 부가적인 문제까지 불러일으킬 수 있다.
결국 스터디에서 다른사람의 코드를 보고 나서야 해결책을 깨달았다.
바로 예외처리가 나는 행위 자체를 Parameter 로 넘겨주는 것이다.
즉, 동작의 파라미터화가 필요하다.
1. 함수형 인터페이스 만들기
public interface Task<T> {
T run();
static <T> T reTryUntilSuccess(Task<T> task) {
while (true) {
try {
return task.run();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
}
위의 코드를 보자.
이 함수는 T run() 이라는 추상 메서드를 하나만 가지고 있는 함수형 인터페이스이다.
이 함수형 인터페이스를 구현하는 람다방식을 이용하여 동작을 파라미터화하면 while{ try~catch } 문을 반복하지 않을 수 있다.
Task.reTryUntilSuccess(() -> inputView.insertCars());
구현한 방식을 보면 ()-> inputView.insertCars()
'파라미터는 없으며, inputView 의 insertCars() 라는 메서드를 실행한다 '
라는 의미이며 이 람다프로세스는 Task<T> 를 구현한다.
여기서 <T> 는 insertCars() 가 List<String> 을 반환하기 때문에 List<String> 타입이다.
이를 메서드 참조로 바꾸면
Task.reTryUntilSuccess(inputView::insertCars);
이렇게 간단하게 표현할 수 있다.
람다 혹은 메서드 참조를 이용하여 구현한 Task<T> 를 파라미터로
reTryUntilSuccess(Task<T> task) 메서드를 실행할 수 있는 것이다.
이를 통해 반복되는 구조의 반복을 없앨 수도 있으며 코드도 꽤나 직관적으로 변경할 수 있다.
2. 기존의 함수형 인터페이스 사용하기
사실 주로 사용하는 함수형 인터페이스들은 이미 정의가 되어있다. 그래서 사용자가 직접 함수형 인터페이스를 만드는 경우는 거의 없다고 한다.
이 외에도 다양한 함수형 인터페이스가 정의되어 있다.
이중에 나는 생성된 List<String> 을 반환받아야 하므로 Supplier<T> 의 T.get() 메서드를 사용할 것이다.
먼저 Supplier<T> supplier 를 정의하고 그 supplier 를 파라미터로 넘겨주는 방식이다.
private List<String> getCarNames() {
outputView.askCarNamesInput();
Supplier<List<String>> supplier = inputView::insertCars;
return retryUntilSuccess(supplier);
}
static <T> T retryUntilSuccess(Supplier<T> supplier) {
while(true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
이를 좀 더 간결하게 만들면
private List<String> getCarNames() {
outputView.askCarNamesInput();
return retryUntilSuccess(inputView::insertCars);
}
static <T> T retryUntilSuccess(Supplier<T> supplier) {
while(true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
의 방식으로 구현할 수 있다.
코드도 간단해졌으며 의미또한 동작 자체가 구현체가 되어 파라미터로 넘겨졌기에 getCarNames() 의 메서드가 어떤 동작을 하는지 굉장히 직관적으로 파악할 수 있는 장점이 있다.
'모던자바' 카테고리의 다른 글
모던 자바 인 액션 - 5장 마무리 (0) | 2022.12.07 |
---|---|
스트림의 활용 및 생성 - 모던 자바 인 액션 (0) | 2022.12.01 |
댓글