Java - 클린코드 with Java 18기에서 배운 점 정리

클린코드 with Java 18기

Posted by Yan on March 31, 2024

클린코드

객체 간 의존관계 연결, 인자 수를 최소화하기

메소드(함수)에 인자 수를 최소화한다 - 클린코드 책 중에서

  • 메소드에서 이상적인 인자 개수는 0개이다. 다음은 1개이고, 다음은 2개이다.
  • 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 있어도 사용하면 안 된다.

인자를 줄이는 방법

  • 관련이 있는 인자를 묶어서 하나의 클래스로 만든다.
  • 얘시) List winningLotto + int bonusNumber => WinningLotto 로 묶음

클래스간의 의존간계 연결 방식

클래스를 분리할 때 고민해야할 부분은 클래스 간의 의존관계를 어떻게 연결할 것인가? 이다.

1. 상속(is-a)관계, 조합(has-a)관계

  • 일급 Collection를 구현할 때 접근 방법으로 상속과 조합 방법으로 구현할 수 있다.
  • 객체의 중복을 제거할 때 상속과 조합 방법으로 구현할 수 있다.
    • 상속: 하위 클래스가 상위 클래스의 모든 기능을 상속받아 의존 관계를 형성한다.
    • 조합: 하나의 클래스가 다른 클래스의 인스턴스를 포함하여 의존 관계를 형성한다.

상속(is-a) 관계

  • 상속은 부모 클래스의 모든 속성과 메소드를 자식 클래스가 물려받는 관계다.
  • 즉, 자식 클래스는 부모 클래스의 특징을 그대로 가지면서 추가적인 기능을 제공할 수 있습니다.
상속을 사용하는 이유:
  • 코드 재사용: 부모 클래스에 정의된 코드를 자식 클래스에서 공유하여 코드 중복을 줄일 수 있습니다.
  • 계층 구조 형성: 클래스 간의 계층 구조를 만들어 코드를 체계적으로 관리할 수 있습니다.
  • 다형성: 상속 관계를 통해 다형성을 구현하여 유연한 코드를 작성할 수 있습니다.

예시)

1
2
3
4
5
6
7
8
9
10
11
class Animal {
    public void move() {
        System.out.println("움직입니다.");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("멍멍");
    }
}

조합(has-a) 관계

  • 조합은 한 클래스가 다른 클래스의 인스턴스를 포함하는 관계다.
  • 즉, 한 객체가 다른 객체를 구성 요소로 가지고 있는 것.
조합을 사용하는 이유:
  • 유연성: 상속보다 유연하게 객체를 구성할 수 있다.
  • 재사용성: 독립적인 클래스를 조합하여 다양한 기능을 구현할 수 있다.
  • 책임 분리: 각 클래스가 명확한 책임을 가지도록 분리하여 시스템의 복잡도를 줄일 수 있다.

예시)

1
2
3
4
5
6
7
8
9
10
class Car {
    private Engine engine;

    // ...
}

class Engine {
    // ...
}
  • 일반적으로 상속보다는 조합을 사용하는 것이 더 유연하고 확장 가능한 시스템을 만들 수 있다.
  • 상속은 강한 결합을 야기할 수 있고, 상위 클래스에 변경이 발생하면 하위 클래스에도 영향을 미칠 수 있기 때문.

2. 인터페이스를 통한 의존

  • 가장 일반적이고 유연한 방법
  • 추상적인 인터페이스를 정의하고, 구체적인 클래스들이 이 인터페이스를 구현하여 의존 관계를 형성한다.
1
2
3
4
5
6
7
8
9
10
11
interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    // 파일로 로그를 기록하는 로직
}

class ConsoleLogger implements Logger {
    // 콘솔에 로그를 출력하는 로직
}

3. 생성자 주입을 통한 의존

  • 객체 생성 시 의존하는 객체를 생성자를 통해 주입한다.
  • 장점: 객체 생성 시 의존성을 명확하게 드러난다.
1
2
3
4
5
6
7
class MyClass {
    private Logger logger;

    public MyClass(Logger logger) {
        this.logger = logger;
    }
}

3. Setter 주입:

  • 객체 생성 후 setter 메소드를 통해 의존하는 객체를 주입
  • 장점: 생성자 주입보다 유연하지만, 의존성이 명확하지 않을 수 있다.
  • 사양되는 방식

4. 인터페이스 메소드 파라미터:

  • 메소드 호출 시 필요한 의존 객체를 파라미터로 전달합니다.
  • 필드 주입: 필드에 직접 의존 객체를 할당한다.
  • 단점: 결합도가 높아져 유지보수가 어려울 수 있다.

디미터 법칙

  • 객체 지향 프로그래밍에서 객체 간의 결합도를 낮추고 유지보수성을 높이기 위한 설계 원칙 중 하나다.
  • “최소 지식의 원칙(Principle of Least Knowledge)”이라고도 불리며, 간단히 말해 “한 객체는 자신과 직접적인 관계가 있는 객체에 대해서만 알아야 한다”는 것을 의미한다.

디미터 법칙 위반 예시

1
2
3
4
5
6
7
8
class Customer {
    private Order order;
    // ...

    public String getShippingAddress() {
        return order.getShippingAddress().getCity();
    }
}

왜 알아야 하는가?

  • 낮은 결합도: 객체 간의 의존성을 줄여 하나의 객체에 변경이 발생했을 때 다른 객체에 미치는 영향을 최소화한다.
  • 높은 유지보수성: 코드 변경 시 발생할 수 있는 문제를 줄이고 시스템의 안정성을 높인다.
  • 테스트 용이성: 각 객체를 독립적으로 테스트하기 쉽도록 만들어준다.

디미터 법칙을 지키려면

  • 메소드 체이닝 줄이기: . 연산자를 남발하여 여러 객체를 연속적으로 호출하는 것을 피한다.
  • 중간 객체를 통해 값 전달: 필요한 값을 직접 가져오는 대신, 해당 값을 제공하는 메소드를 호출하여 값을 전달받는다.
  • 객체의 책임 분리: 각 객체가 명확한 책임을 가지도록 설계하여 객체 간의 의존성을 줄인다.
reference