저는 LIVID에서 Effective Java 책을 이용해 스터디하고 있습니다. 스터디는 매주 자신이 맡은 Item에 대해서 정리하고 발표를 하는 방식으로 진행됩니다. 그 중 제가 Item 69: 예외는 진짜 예외 상황에서만 사용하라
를 맡게 되었습니다. 해당 아이템을 정리하며 이전에 제가 잘못 작성한 코드가 생각나 해당 아이템의 내용을 적용해보았습니다.
해당 아이템은 예외를 예외 상황에서만 사용하는 것을 강조하며 특히 “흐름제어에 사용하지 말라”고 합니다.
try {
int i = 0;
while(true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {}
위 코드는 간단한 예시입니다. 코드에서 try-catch
문을 활용하여 range 인덱스를 벗어날 때 발생하는 ArrayIndexOutOfBoundsException
을 제어하여 반복문을 종료시키고 있습니다.
이 코드는 가독성이 좋지 못하며, try-catch
블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한이 되어 좋지 않습니다.
for(Mountain m : range)
m.climb();
예시 코드는 위 코드와 같이 개선할 수 있습니다. for-each
문을 통해 깔끔한 코드로 개선되었습니다.
제가 적용해볼 코드는 신고하기 기능의 일부 코드입니다. 신고하기는 아래와 같은 요구사항을 가지고 있습니다.
요구사항
1. 신고는 신고자와 피신고 대상으로 식별될 수 있으며, 신고자는 동일한 피신고 대상에 대해서 2번 신고할 수 없다.
2. 피신고 대상은 상품, 댓글, 사람 총 3가지이다.
3. 상품과 댓글을 신고되어지면 이것을 작성한 사람에 대해서도 신고 되어야 한다.
3번 요구사항에 의해 작성자에 대한 신고가 추가적으로 발생될 때 문제가 발생하였습니다. 이미 신고자와 작성자가 피신고자인 신고가 있을 경우 중복된 신고라는 예외가 발생하였습니다. 그 결과 상품에 대한 신고까지 전체 롤백이 되어버렸습니다.
저는 당시에 이 문제를 try-catch
문을 이용해서 흐름제어하여 해결하였습니다.
public class ReportExecuteForProduct implements ReportExecuteStrategy {
@Override
@Transactional
public void execute(User reporter, long productId, String reason) {
// product에 대한 신고 및 페널티 정책 처리
// ...
// 상품의 작성자에 대해서 추가적인 신고
try {
long writerId = product.getWriterId();
reportExecuteForUser.execute(reporter, writerId, reason);
} catch (IllegalArgumentException ignored) {
}
}
}
위 코드를 개선할 수 있는 방향으로 2가지를 생각해보았습니다.
첫번째 방법은 User에 대한 신고 처리 전 중복 체크를 통해 흐름 제어를 한다.
입니다. 신고 처리 전 repository를 통해 동일한 신고자와 피신고 대상에 대한 신고 존재 여부를 확인합니다. 이를 통해 신고 요청을 보낼 지를 결정할 수 있습니다.
public class ReportExecuteForProduct implements ReportExecuteStrategy {
@Override
@Transactional
public void execute(User reporter, long productId, String reason) {
// product에 대한 신고 및 페널티 정책 처리
// ...
// 상품의 작성자에 대해서 추가적인 신고
long writerId = product.getWriterId();
if (reportRepository.existsByReporterIdAndTypeAndTypeId(
reporter.getId(),
Report.Type.USER,
writerId
)) return;
reportExecuteForUser.execute(reporter, writerId, reason);
}
}
다른 한가지 방법은 이미 신고가 되어있다면 신고를 추가하지 않고 신고가 된 것처럼 처리한다.
입니다. 멱등성을 보장하면서 내부적으로는 중복신고가 되지 않도록 처리하는 것 입니다.
// 상품에 대한 신고 처리
public class ReportExecuteForProduct implements ReportExecuteStrategy {
@Override
@Transactional
public void execute(User reporter, long productId, String reason) {
// product에 대한 신고 및 페널티 정책 처리
// ...
// 상품의 작성자에 대해서 추가적인 신고
long writerId = product.getWriterId();
reportExecuteForUser.execute(reporter, writerId, reason);
}
}
// 유저에 대한 신고 처리
public class ReportExecuteForUser implements ReportExecuteStrategy {
@Override
@Transactional
public void execute(User reporter, long reportedUserId, String reason) {
if (reportRepository.existsByReporterIdAndTypeAndTypeId(
reporter.getId(),
Report.Type.USER,
reportedUserId
)) return;
// 신고 처리 및 신고에 따른 페널치 정책 처리
// ...
}
}
이펙티브 자바를 공부하면서 제 코드에 최대한 적용하려고 노력해왔었는 데, 정말 딱 맞는 item을 적용해본 것 같습니다. 책에서 배운 것을 내 코드에 녹여내어 코드가 더 개선되는 즐거움을 제대로 느낄 수 있었던 것 같습니다. 앞으로도 계속 공부를 하면서 공부한 내용을 잘 적용하기 위해 고민해보아야 할 것 같습니다.
잘 먹고 갑니다~