아래 코드는 api를 실행(RestTemplate를 이용)하여, 요청한 달의 표준 근무 일을 구하는 로직을 나타낸 클래스이다.
@Component
public class ApiConvertor {
private final RestTemplate restTemplate;
private final ApiProperties apiProperties;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public ApiConvertor(RestTemplate restTemplate, ApiProperties apiProperties) {
this.restTemplate = restTemplate;
this.apiProperties = apiProperties;
}
public long countNumberOfStandardWorkingDays(YearMonth yearMonth) throws MalformedURLException, URISyntaxException {
List<HolidayResponse.Item> items = itemsOfResponse(yearMonth);
int lengthOfMonth = yearMonth.lengthOfMonth();
long numberOfWeekends = WeekendCalculator.countNumberOfWeekends(yearMonth);
long numberOfHolidays = items.size();
long numberOfWeekDays = lengthOfMonth - numberOfWeekends;
Set<LocalDate> holidays = convertToLocalDate(items);
numberOfHolidays = minusDuplicateHolidays(numberOfHolidays, holidays);
return numberOfWeekDays - numberOfHolidays;
}
private List<HolidayResponse.Item> itemsOfResponse(YearMonth yearMonth) throws MalformedURLException, URISyntaxException {
String solYear = String.valueOf(yearMonth.getYear());
int month = yearMonth.getMonthValue();
String solMonth = (month < 10) ? "0" + month : String.valueOf(month);
String stringURL = apiProperties.combineURL(solYear, solMonth);
URL url = new URL(stringURL);
HolidayResponse holidayResponse = restTemplate.getForObject(url.toURI(), HolidayResponse.class);
List<HolidayResponse.Item> items = holidayResponse.getBody().getItems();
return items;
}
private long minusDuplicateHolidays(long numberOfHolidays, Set<LocalDate> holidays) {
numberOfHolidays -= holidays.stream()
.filter(WeekendCalculator::isWeekend)
.count();
return numberOfHolidays;
}
private Set<LocalDate> convertToLocalDate(List<HolidayResponse.Item> items) {
return items.stream()
.map(item -> LocalDate.parse(item.getLocdate(), DATE_FORMATTER))
.collect(toSet());
}
public long calculateStandardWorkingMinutes(long numberOfStandardWorkingDays) {
return numberOfStandardWorkingDays * 8 * 60;
}
}
이 중에서 itemsOfResponse(YearMonth yearMonth) 메서드는 몇가지 문제점이 있다.


URL 은 Java20 부터 deprecated 된 걸 확인할 수 있다. 위의 코드에서 보는바와 같이 인텔리제이같은 ide가 경고 표시로 알려준다.
따라서, 기존의
URL url = new URL(stringURL);
HolidayResponse holidayResponse = restTemplate.getForObject(url.toURI(), HolidayResponse.class);
이 코드들을 리팩터링할 수 있다.
URI uri = new URI(stringURL);
HolidayResponse holidayResponse = restTemplate.getForObject(uri, HolidayResponse.class);
또, 이렇게 리팩터링함으로써 기존의 throws MalformedURLException 역시 제거할 수 있게 된다.

가장 처음 이 코드를 구현했을 때는 위와 같이 예외가 발생할만한 곳에 throws를 선언하여 대충 마무리 지을려고 했다. 그러나, 이렇게 대충대충 넘어가게 되면 문제가 발생한다.
이 itemsOfResponse(..)가 쓰이는 곳에서도 전부 throws를 선언하게 만들게 된다. 그렇게 되면 다른 사람이 코드를 볼 때, 도대체 어느 곳에서 이 예외가 발생하는 건지 헷갈리게 한다.
따라서, 이러한 문제를 해결하려면 이 예외가 발생하는 곳을 정확하게 찾아 예외처리를 해주어야 한다. URISyntaxException은 이름만 봐도 URI에 관련된 예외라고 여겨질 수 있다. 실제로 Javadoc을 확인해보면,

주어진 URI가 정확하게 파싱되지 않았을 때 일어나는 checked exception이라는걸 확인할 수 있다.

중요한건 checked exception이라는 것이다. checked exception 은 프로그램의 로직에서 벗어나는 예외이기 때문에 코드 작성자가 따로 로직을 설정할 수 없다.
따라서, 위와 같이 try-catch 블록으로 감싸주고 예외 객체를 RuntimeException으로 감싸서 예외처리를 해준다.

throws를 막연하게 계속 작성해주기 보단 예외가 발생하는 근원에서 명확하게 예외처리를 해주면 이점이 생긴다. itemsOfResponse(YearMonth yearMonth)를 쓰는 메서드도 결국 throws 선언을 없앨 수 있다. 그러면 이 countNumberOfStandardWorkingDays(YaerMonth yearMonth)를 쓰는 모든 곳(예를들어, 서비스단의 코드 혹은 테스트 코드)에서도 throws를 삭제할 수 있다.