더 깨끗한 코드와 더 나은 문제 분리를 달성하기 위한 Java 리팩토링의 실제 예를 보여드리겠습니다. 더 깨끗한 코드와 더 나은 관심사 분리를 달성하는 것을 목표로 하는 자바 리팩토링의 실제 예를 보여줍니다. 그 아이디어는 전문적인 환경에서 코딩에 대한 내 경험에서 비롯되었습니다.
프로덕션 코드의 원스 어폰 어 타임
일부 도메인 데이터를 유지하는 코드 작업을 할 때 결국 다음과 같은 결과가 나왔습니다:
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 구문은 재시도 기능을 다른 데이터베이스 작업과 쉽게 재사용할 수 있게 합니다.
'SW > Java' 카테고리의 다른 글
Java 및 Gradle로 AI 애플리케이션 구축 (0) | 2023.10.19 |
---|---|
Java : InputStream 문자열로 변환하는 방법, 개요, 설명 (0) | 2023.09.17 |
Java : ZIP 파일 추출 및 암호 보호 제거 방법 (0) | 2023.09.08 |
Java 프로젝트 : 리소스 파일의 인코딩 문제 해결 (0) | 2023.08.21 |
2023년 웹 개발을 위한 최고의 Java 프레임워크 5가지 (0) | 2023.08.14 |