티스토리 뷰
코드의 구린내란?
리팩토링이 필요하다고 판단되는 "의심나는 상황"을 빗대어 "구린내" 라는 표현으로 나타낸 것
중복 코드
중복 코드는 똑같은 코드 구조가 두 군데 이상 있을 때 발생하게 됩니다. 이러한 문제는 동일한 부분을 하나로 통일하면 문제가 해결됩니다.
💡 한 클래스의 두 메서드 안에 같은 코드가 있는 경우
- 이 경우 메서드 추출 기법을 사용하여 중복되는 코드를 빼내어 별도의 메서드를 만든 후 해당 메서드를 두 곳에서 호출하는 방법입니다.
public void methodA() {
methodC();
}
public void methodB() {
methodC();
}
private void methodC() {
// 중복 로직을 담고 있는 코드를 추출
}
💡 두 하위 클래스에 같은 코드가 있는 경우
- 이 경우 메서드 추출기법을 사용하여 중복되느 코드를 빼내고 메서드 상향 기법을 사용해서 상위 클래스로 올립니다.
public class DiscountPolicy {
protected void discountPolicy() {
// 하위 클래스에서 발생하는 중복 코드를 메서드 추출을 한 뒤 메서드 상향 기법 적용
}
}
public class PercentDiscountPolicy extends DiscountPolicy { }
public class PeriodDiscountPolicy extends DiscountPolicy { }
💡 서로 상관없는 두 클래스안에 중복 코드가 있는 경우
- 모듈 추출기법을 사용하여 제 3의 클래스나 모듈로 때어낸 후 그것을 다른 클래스에서 호출하는 방법입니다.
장황한 메서드
메서드명은 메서드가 하는 일을 명확히 드러내야 합니다. 그래야 코드를 분석하는 시간을 줄일 수 있습니다. 메서드명은 되도록 짧아야하며 이를 위해서는 메서드를 과감히 쪼개야합니다. 또한 메서드명은 기능 수행 방식이 아닌 목적(기능 자체)을 나타내는 이름으로 정해야합니다.
💡 어떻게 해야할까?
- 메서드를 줄이기 위해 메서드 추출 기법을 사용해야합니다. 이때 메서드에 매개변수 및 임시변수가 많다면 메서드 추출 기법을 적용하기 어려워집니다. 그렇기 때문에 임시변수를 메서드 호출로 전환하고, 매개변수는 객체로 전환하여 전달하는 기법을 사용해야 합니다.
방대한 클래스
기능이 지나치게 많은 클래스에는 보통 엄청 많은 인스턴스 변수들이 존재하고 있을 가능성이 있습니다. 인스턴스 변수가 많아질수록 중복 코드가 발생할 확률이 높아지게 됩니다.
💡 어떻게 해야할까?
- 클래스 추출 기법을 사용하여 서로 연관된 변수들끼리 묶어 클래스로 빼내어 해결할 수 있습니다. 하위 클래스로 묶을
과다한 매개변수
메서드의 매개변수에 모든 필요한 데이터를 나열하는 것보다 모든 데이터를 가져올 수 있는 객체를 전달하는게 효율적입니다. 또한 연관된 매개변수들끼리 묶어서 하나의 클래스로 빼내어 객체를 만들 수도 있습니다.
수정의 산발
우리는 기능을 수정할 때 바로 수정이 가능하도록 코드를 작성해야합니다. 만약 바로 수정이 불가능하다면 문제가 있음을 의심해 볼 수 있습니다. 예를들어 기존 할인정책에 새로운 할인정책 추가 시 3개 또는 4개의 메서드를 수정해야 한다면 하나의 클래스를 각 할인정책에 맞게 여러 개의 변형 객체로 분리하는 것이 좋습니다.
💡 어떻게 해야할까?
- 각 책임에 맞게 클래스를 추출한뒤 공통 기능을 하는 부분은 메서드 상향 기법을 적용할 수 있습니다. 이때 단일 책임 원칙과 OCP 원칙을 고려하면 좋을거 같습니다.
기능의 산재
기능의 산재는 수정의 산발과 정 반대입니다. 수정할 때마다 여러 클래스에서 수 많은 자잘한 부분을 고쳐야한다면 이 문제를 의심해볼 수 있습니다. 수정할 부분이 여기저기 있다면 찾기 힘들고 하나씩 빼먹을 수 있습니다.
또한, 수정의 산발은 한 클래스에서 여러 수정이 발생하는 문제이고, 기능의 산재는 하나의 수정으로 인해 여러 클래스가 바뀌는 문제입니다.
💡 어떻게 해야할까?
- 이럴 때는 메서드 이동과 필드 이동을 적용해 수정할 부분들을 전부 하나의 클래스 안에 넣어야 합니다.
잘못된 소속
어떤 메서드가 자신이 속하지 않은 클래스에 더 많이 접근한다면 잘못된 소속의 구린내라 의심할 수 있습니다. 잘못 소속된 메서드가 제일 흔히 접근하는 대상은 데이터입니다.
💡 어떻게 해야할까?
- 이럴 때는 소속이 잘못된 메서드는 더 많이 접근하는 클래스에 들어가는 것이 마땅하니 메서드 이동 기법을 적용하여 더 많이 접근하는 클래스로 옮겨야 합니다. 간혹 메서드의 일부분만 잘못된 경우라면 해당 부분을 메서드 추출 기법을 적용한 후 메서드 이동을 적용해 적절한 클래스로 옮기면 됩니다.
데이터 뭉치
동일한 여러 개의 데이터 항목들이 여러 클래스의 매개변수에 자리잡고 있을 수 있습니다.
💡 어떻게 해야할까?
- 이럴 때는 동일한 여러 개의 데이터 항목들을 클래스 추출 기법을 적용할 수 있습니다. 그러고 나서 매개변수에는 만들어진 객체를 전달하는 것입니다. 이렇게 하면 매개변수의 양도 줄어들고 코드가 간결해집니다. 또한 이러한 과정에서 잘못된 소속을 파악할 수 있으며 이를 개선할 수 있습니다.
강박적 기본 타입 사용
데이터를 사용할 때 기본 타입 데이터뿐 아니라 객체도 사용해야 합니다. 자바에서는 기본 데이터 타입뿐만 아니알 문자열과 날짜 등을 나타내는 클래스가 존재합니다.
💡 어떻게 해야할까?
- 데이터 값을 값 객체를 사용할 때 다양한 이점을 누릴 수 있습니다. 여러 스레드에서 동시에 접근 시 안전하며 불변성을 가지기 때문에 더욱 좋습니다.
public class Movie {
private int price;
private Money money;
}
public class Money {
private final int value;
public Money(int value) {
this.value = value;
}
public Money add(int value) {
return new Money(this.value + value);
}
}
switch 문
switch 문을 사용하게 되면 코드의 중복을 야기시킬 수 있습니다. 우리는 객체지향의 개념 중 하나인 다형성을 사용해 이러한 문제점을 해결 수 있습니다.
💡 어떻게 해야할까?
- 우리는 다형성이라는 개념을 통해 문맥에 따라 책임을 다르게 수행할 수 있도록 구성할 수 있습니다.
// 문제가 발생하는 코드
enum DiscountPolicyType {
PERCENT, PERIOD
}
public class Movie {
private DiscountPolicyType discountPolicyType;
public void calculate() {
switch (discountPolicyType) {
case PERIOD:
// 로직
break;
case PERCENT:
// 로직
break;
default:
throw new IllegalStateException();
}
}
}
// 다형성을 통해 문제를 해결한 코드
public class Movie {
private DiscountPolicy discountPolicy;
public void calculate() {
discountPolicy.calculate();
}
}
public interface DiscountPolicy {
int calculate();
}
public class PercentDiscountPolicy implements DiscountPolicy {
@Override
public int calculate() {
return 100;
}
}
public class PeriodDiscountPolicy implements DiscountPolicy {
@Override
public int calculate() {
return 50;
}
}
평행 상속 계층
평행 상속 계층은 기능의 산재의 특수한 상황이라 합니다. 이 문제점이 있으면 한 클래스의 하위 클래스를 만들 때 매번 다른 클래스의 하위 클래스도 함께 만들어줘야 합니다. 메서드 이동과 필드 이동을 적용하면 해결할 수 있다고 하는데 뭔지 몰라서 패스합니다.!
직무유기 클래스
하나의 클래스를 생성할 때마다 우리는 해당 클래스를 유지보수 해야하며 이해하기 위한 비용이 발생합니다. 따라서 비용만큼의 기능을 수행하지 못하는 비효율적인 클래스는 없애야 합니다. 기존에는 비용 대비 효율성이 좋았으나 리팩토링을 실시한 후 기능이 축소된 클래스 또는 수정할 계획으로 작성했으나 수정을 실시하지 않아 쓸모가 없어진 클래스가 바로 이런 직무유기 클래스에 속합니다.
💡 어떻게 해야할까?
- 쓸모없어진 클래스는 상속 관계에 있을 시 계층 병합을 또는 클래스에 직접 삽입이나 모듈에 직접 삽입을 하면 됩니다.
막연한 범용 코드
미래를 너무 예측하여 이러한 코드가 필요할 수 있겠지? 라는 생각으로 작성한 코드는 추후 우리가 수정할 때 더 많은 생각을 들게 합니다. 필요한 코드는 그때가서 만들면 됩니다.
임시 필드
객체안에 인스턴스 변수가 특정 상황에서만 할당되는 경우가 있습니다. 개발자는 이러한 코드를 파악하기 힘듭니다. 또한 사용될거 같은데 사용을 하지 않고 있으면 왜지..? 라는 생각을 들게 합니다.
💡 어떻게 해야할까?
- 클래스 추출 기법을 사용해 해당 클래스에 그 변수들과 관련된 코드를 집어넣어야 합니다.
메시지 체인
메시지 체인은 클라이언트가 한 객체에 제 2의 객체를 요청하면, 제 2의 객체는 제 3의 객체에 요청하고 이렇게 연쇄적 요청이 발생하는 문제점을 의미합니다.
💡 어떻게 해야할까?
- 이런 경우 대리 객체 은폐를 적용해야 한다고 합니다. 필자의 생각으로는 디미터 법칙 같은 것을 적용하면 되지 않을까? 생각이 듭니다.
과잉 중개 메서드
객체의 주요 특징 중 한가지는 바로 캡슐화입니다. 캡슐화란 내부의 세부적인 처리를 외부에서 알 수 없도록 하는 은폐 작업입니다. 메시지 체인이 발생하면 과잉 중개 메서드도 발생할 수 있지 않을까? 생각이 듭니다.
💡 어떻게 해야할까?
- 이런 경우 과잉 중개 메서드 제거 기법을 통해 원리가 구현된 객체에 직접 접근을 해야합니다.
지나친 관여
간혹 클래스들끼리 관계가 지나치게 의존하고 있는 경우가 있습니다. 이러한 경우 엄격하게 갈라놔야 합니다.
💡 어떻게 해야할까?
- 이런 경우 메서드 이동과 필드 이동을 실시해 각 클래스를 분리해서 지나친 관여를 줄일 수 있습니다.
- 클래스의 양방향 연결을 단방향으로 전환 기법을 적용할 수 있는지 판단해서 끊을 수 있다면 끊고, 공통적으로 사용하는 가능이 있다면 모듈 추출 기법을 적용할 수 있습니다.
데이터 클래스
데이터 클래스는 필드와 읽기/쓰기 메서드만 존재하는 클래스입니다. 이런 클래스는 오직 데이터 보관만 담당하며, 거의 대부분의 구체적 데이터 조작은 다른 클래스가 수행합니다. 데이터 클래스의 모든 필드는 캡슐화를 해야하며 쓰기 메서드 제거를 적용해야 합니다.(setter 제거)
방치된 상속물
하위 클래스는 상위 클래스의 데이터와 메서드를 상속받습니다. 상속 받은 메서드나 데이터가 있다면 하위 클래스는 그것을 방치해버리는 문제를 가지게 됩니다. 이는 잘못된 상속 계층 구조 때문에 발생하는 문제입니다.
💡 어떻게 해야할까?
- 이런 경우 잘못된 상속 계층 구조로 인해 발생하는 문제입니다. 새 대등 클래스를 작성하고 메서드 하향과 필드 하향을 통해 사용되지 않는 모든 메서드나 데이터를 그 형제 클래스에 몰아 넣어야 합니다. 이렇게 하면 상위 클래스에는 모든 공통 코드만 존재하게 됩니다.
불필요한 주석
주석을 작성하는 습관은 중요합니다. 다만 주석이 구린내를 감처주는 탈취제 용도로 쓰이는 경우가 많기 때문입니다. 특정 메서드에서 어떤 코드의 기능을 설명하는 주석이 있을 때는 메서드 추출을 사용해야 합니다. 메서드 추출을 사용한 뒤 기능을 설명할 주석이 필요하다면 메서드명을 수정해야 합니다.
주석을 넣으면 돌아가는 원리를 적어둘 수도 있고 확실치 않은 부분을 표시할 수도 있습니다. 또한 어떤 코드를 작성한 이유에 대해 주석을 넣는 것은 적절합니다.
- Total
- Today
- Yesterday
- spring boot redisson destributed lock
- @ControllerAdvice
- spring boot 엑셀 다운로드
- spring boot excel download paging
- 자바 백엔드 개발자 추천 도서
- service based architecture
- 레이어드 아키텍처란
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- 트랜잭셔널 아웃박스 패턴 스프링부트
- transactional outbox pattern
- 서비스 기반 아키텍처
- transactional outbox pattern spring boot
- microkernel architecture
- pipeline architecture
- 공간 기반 아키텍처
- spring boot redisson 분산락 구현
- JDK Dynamic Proxy와 CGLIB의 차이
- space based architecture
- polling publisher spring boot
- spring boot excel download oom
- pipe and filter architecture
- 람다 표현식
- java ThreadLocal
- redis sorted set으로 대기열 구현
- redis sorted set
- spring boot poi excel download
- java userThread와 DaemonThread
- spring boot redis 대기열 구현
- spring boot redisson sorted set
- redis 대기열 구현
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |