이펙티브 자바 item 내 코드에 적용해보기

dojinyou·2023년 3월 23일
1
post-thumbnail

저는 LIVID에서 Effective Java 책을 이용해 스터디하고 있습니다. 스터디는 매주 자신이 맡은 Item에 대해서 정리하고 발표를 하는 방식으로 진행됩니다. 그 중 제가 Item 69: 예외는 진짜 예외 상황에서만 사용하라 를 맡게 되었습니다. 해당 아이템을 정리하며 이전에 제가 잘못 작성한 코드가 생각나 해당 아이템의 내용을 적용해보았습니다.

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을 적용해본 것 같습니다. 책에서 배운 것을 내 코드에 녹여내어 코드가 더 개선되는 즐거움을 제대로 느낄 수 있었던 것 같습니다. 앞으로도 계속 공부를 하면서 공부한 내용을 잘 적용하기 위해 고민해보아야 할 것 같습니다.

profile
더 좋은 세상을 만드는 데 기술로 기여하고 싶습니다

1개의 댓글

comment-user-thumbnail
2023년 3월 27일

잘 먹고 갑니다~

답글 달기