ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 12. 메서드(함수) : 좋은 클래스에는 좋은 메서드가 있다
    Study/내 코드가 그렇게 이상한가요? 2024. 2. 13. 06:23
    반응형

    메서드 설계가 좋지 않으면 클래스 설계가 나빠지고, 반대로 메서드 설계가 좋으면 클래스 설계도 좋아진다. 클래스 설계 방법을 염두에 두고, 메서드를 어떻게 설계하면 좋을지 집중적으로 알아보자.

    1. 반드시 현재 클래스의 인스턴스 변수 사용하기

    인스턴스 변수를 안전하게 조작하도록 메서드를 설계하면, 클래스 내부가 정상적인 상태인지 보장할 수 있다(3장 참고). 각 메서드는 반드시 현재 클래스의 인스턴스 변수만 사용하도록 설계하는 것을 기본 원칙으로 잡는다. 여기에 각 메서드에 인스턴스 변수를 안전하게 조작하는 로직을 추가하거나, 생성자에는 완전 생성자 패턴을 사용해서 가드를 만들어두면 인스턴스 변수를 안전하게 사용하기 위한 첫걸음을 뗀 것이다.

    다른 클래스의 인스턴스 변수를 변경하는 메서드는 응집도가 낮은 구조를 유발한다. 꼭 사용하고 싶다면, 변경된 내용을 다루는 새로운 인스턴스를 생성하고 이를 리턴하는 형태로 구현하는 것이 좋다.

    2. 불변을 활용해서 예상할 수 있는 메서드 만들기

    메서드가 의도하지 않게 다른 부분에 영향을 주지 않도록, 불변을 활용해서 예상치 못한 동작 자체를 막을 수 있게 설계하자.

    3. 묻지 말고 명령하라

    어떤 클래스가 다른 클래스의 상태를 판단하거나, 상태에 따라 값을 변경하는 등 '다른 클래스를 확인하고 조작하는 메서드 구조'는 응집도가 낮은 구조이다. 주로 사용되는 getter/setter는 '다른 클래스를 확인하고 조작하는 메서드 구조'가 되기 쉽다. '묻지 말고 명령하라'라는 접근 방법을 생각하며, 호출하는 쪽에서 복잡한 처리를 하지 않고, 호출되는 메서드 쪽에서 복잡한 처리를 하는 형태로 만드는 것이 좋다.

    4. 커맨드/쿼리 분리

    int gainAndGetPoint() {
        point += 10;
        return point;
    }

    위와 같은 상태 변경과 추출을 동시에 하는 코드는 여러 문제의 원인이 되며, 사용하기도 힘든 메서드가 되기 쉽다. 커맨드/쿼리 분리(CQS, Command-Query Separation)는 메서드는 커맨드 또는 쿼리 중에 하나만 하도록 설계해야 한다는 패턴이다.

    메서드 구분 종류 설명
    커맨드 상태를 변경하는 것
    쿼리 상태를 리턴하는 것
    모디파이어 커맨드와 쿼리를 동시에 하는 것

    gaminAndGetPoint라는 모디파이어를 커맨드와 쿼리로 분리하여 코드를 단순하게 만들어보자.

    /**
    * 포인트 증가(커맨드)
    */
    void gainPoint) {
        point += 10;
    }
    
    /**
    * 포인트를 리턴(쿼리)
    * @return 포인트
    */
    int getPoint() {
        return point;
    }

    5. 매개변수

    입력 값으로 사용되는 매개변수 설계 시 주의해야 할 사항은 아래와 같다.

    1. 불변 매개변수로 만들기
      • 매개변수를 변경하면 값의 의미가 바뀌어서, 어떤 의미를 나타내는지 유추하기 어렵고, 어디에서 변경되었는지 찾긷고 힘들다. final 수식자를 붙여서 불변으로 만들자. 매개변수를 변경하고 싶다면, 불변 지역 변수를 만들고, 여기에 변경 값을 할당하는 형태로 구현한다.
    2. 플래그 매개변수 사용하지 않기
      • 플래그 매개변수를 받는 메서드는 가독성이 낮아지게 만든다. 전략 패턴을 사용하는 등, 다른 구조로 설계를 개선하자.
    3. null 전달하지 않기
      • null을 활용하는 로직은 NullPointerException이 발생할 수 있으며, 매번 null을 확인하는 로직을 추가해야 한다. 매개변수로 null을 전달하지 않게 설계를 하자.
      • null을 전달하지 않게 설계하려면, null에 의미를 부여해서는 안 된다. 예를 들어, 장비를 찾용하지 않은 상태를 null로 나타내지 말고 Equipment.EMPTY 등으로 표현하자.
    4. 출력 매개변수 사용하지 않기
      • 출력 매개변수는 응집도가 낮은 구조를 만든다. 매개변수는 입력 값으로 사용하는 것이 기본이다.
    5. 매개변수는 최대한 적게 사용하기
      • 메서드에 매개변수가 많다는 것은 메서드가 여러 가지 기능을 처리한다는 의미이다. 메서드가 처리할 게 많아지면, 그만큼 로직이 다양해져 여러 문제를 일으킨다. 매개변수가 많아질 수밖에 없다면, 별도의 클래스로 만드는 방법을 검토해 보자.

    6. 리턴 값

    리턴 값을 설계할 때의 주의 사항도 알아보자.

    1. '자료형'을 사용해서 리턴 값의 의도 나타내기
      • 리턴 값이 특정한 의미를 가질 때, 단순히 기본 자료형을 사용하지 말고 독자적인 자료형을 사용해서 의도를 명확하게 나타내는 것이 좋다.
      • Price.add 메서드를 int 자료형을 리턴하도록 만들면, 상품 가격 합계, 할인 금액, 배송비 등 어떤 금액을 의미하는 지 알기 힘들다. 가격을 리턴한다는 의도를 명확히 하기 위해 Price 자료형을 리턴해야 한다.
    2. null 리턴하지 않기
      • 매개변수로 null을 전달하지 않는 것이 좋듯이, null을 리턴하지 않는 것이 좋다.
    3. 오류는 리턴 값으로 리턴하지 말고 예외 발생시키기
      • return new Location(-1,-1)
      • 위 코드는 위치를 이동시키는 메서드에서 이동 후의 좌표가 잘못된 경우, 오류 값 Location(-1,-1)로 이동시키는 코드이다.
      • 이러한 구현은 호출하는 쪽에서 '오류가 있을 때, 오류 값으로 Location(-1,-1)을 리턴한다'라는 사실을 알고 있어야 하며, 이를 활용한 오류 처리를 확실하게 해야 한다.
      • 이렇게 여러 의미를 나타내는 값을 중의적(double meaning)이라고 한다. Location(-1,-1)은 특정 좌표값과 오류를 다루는 두 가지 의미를 동시에 다루는 중의적 값이다.
      • 잘못된 상태에서는 어떠한 관용도 베풀지 않고 곧바로 예외를 발생시키자.
    반응형
Designed by Tistory.