SW/Java

Java : Lambdas를 활용하여 Cleaner 코드 작성

얇은생각 2023. 9. 10. 07:30
반응형

더 깨끗한 코드와 더 나은 문제 분리를 달성하기 위한 Java 리팩토링의 실제 예를 보여드리겠습니다. 더 깨끗한 코드와 더 나은 관심사 분리를 달성하는 것을 목표로 하는 자바 리팩토링의 실제 예를 보여줍니다. 그 아이디어는 전문적인 환경에서 코딩에 대한 내 경험에서 비롯되었습니다.

 

 

Java : Lambdas를 활용하여 Cleaner 코드 작성

 

 

프로덕션 코드의 원스 어폰 어 타임

일부 도메인 데이터를 유지하는 코드 작업을 할 때 결국 다음과 같은 결과가 나왔습니다:

public void processMessage(InsuranceProduct product) throws Exception {
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            upsert(product);
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

private void upsert(InsuranceProduct product) throws SQLException {
    //content not relevant
}

 

 

processMessage는 프레임워크 계약의 일부이며 처리된 모든 메시지를 유지하기 위해 호출됩니다. 코드는 idemporent 데이터베이스 업셋을 수행하고 오류 발생 시 재시도 로직을 처리합니다. 내가 우려한 주요 오류는 재설정해야 하는 시간 초과 JDBC 연결이었습니다.

깨끗한 코드 관점에서 processMessage의 초기 버전에 만족하지 않았습니다. 코드에 뛰어들 필요 없이 즉시 의도를 드러내는 것을 기대했습니다. 이 방법은 무엇을 하는지 알기 위해 이해해야 하는 낮은 수준의 세부 사항으로 가득 차 있습니다. 또한 재사용이 쉽게 되도록 재시도되는 데이터베이스 작업과 재시도 로직을 분리하고 싶었습니다.

 

 

덜 절차적, 더 선언적

첫 번째 단계는 updateDatabase() 호출을 람다 구동 변수로 이동하는 것입니다. IDE Inplo functional variable refactoring을 사용하여 이 문제를 해결하도록 합니다. 유감스럽게도 다음과 같은 오류 메시지가 나타납니다:

해당 기능 인터페이스를 찾을 수 없습니다

이러한 이유는 upsert 메서드와 호환되는 SAM 인터페이스를 제공하는 기능적 인터페이스가 없기 때문입니다. 이 문제를 해결하기 위해 매개 변수를 허용하지 않고 반환하지 않으며 SQLException을 던지는 단일 추상 메서드를 선언하는 사용자 지정 기능적 인터페이스를 정의해야 합니다. 다음은 제공해야 하는 인터페이스입니다:

@FunctionalInterface
interface SqlRunnable {
    void run() throws SQLException;
}

 

 

사용자 지정 기능 인터페이스가 있는 상태에서 리팩토링을 반복해 보겠습니다. 이번에는 성공합니다. 또한 변수 할당을 for 루프 앞으로 이동합니다:

public void processMessage(InsuranceProduct product) throws Exception {
    final SqlRunnable handle = () -> upsert(product);
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            handle.run();
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

 

 

추출 메소드 리팩토링을 사용하여 for 루프와 해당 내용을 retryOnSqlException이라는 새 메소드로 이동합니다:

public void processMessage(InsuranceProduct product) throws Exception {
    final SqlRunnable handle = () -> upsert(product);
    retryOnSqlException(handle);
}

private void retryOnSqlException(SqlRunnable handle) throws SQLException {
    //skipped for clarity
}

 

 

마지막 단계는 인라인 변수 리팩토링을 사용하여 핸들 변수를 인라인화하는 것입니다.

최종 결과는 아래와 같습니다.

public void processMessage(InsuranceProduct product) throws Exception {
    retryOnSqlException(() -> upsert(product));
}

 

 

이제 프레임워크 입력 방법은 무엇을 하고 있는지 명확하게 설명합니다. 한 줄의 길이에 불과하기 때문에 인지 부하가 없습니다.

지원 코드에는 의무를 수행하고 재사용 가능성을 활성화하는 방법에 대한 세부 정보가 포함되어 있습니다:

private void retryOnSqlException(SqlRunnable handle) throws SQLException {
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            handle.run();
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

@FunctionalInterface
interface SqlRunnable {
    void run() throws SQLException;
}

 

 

결론

processMessage 메서드는 이제 상위 수준의 코드를 가진 선언적 접근법을 사용함으로써 그 의도를 명확하게 표현합니다. 재시도 로직은 데이터베이스 작업으로부터 분리되어 그 자체의 방법에 배치되는데, 이것은 좋은 명명 덕분에 그 의도를 정확하게 드러냅니다. 또한 Lambda 구문은 재시도 기능을 다른 데이터베이스 작업과 쉽게 재사용할 수 있게 합니다.

반응형