클래스

여태까지는 코드 행과 블록을 올바로 작성하는 방법에 초점을 맞췄다.
하지만 코드의 표현력과 함수에 아무리 신경을 쓰더라도 더 높은 단계(클래스)까지 신경쓰지 않으면 깨끗한 코드를 얻기는 어렵다.

클래스 체계

캡슐화

변수와 유틸리티 함수는 가능한 공개하지 않는 편이 맞지만, 반드시 숨길 필요는 없다.
때로는 protected로 선언해 테스트 코드에서 사용을 허용하기도 한다.

클래스는 작아야 한다.

클래스를 만들 때 첫 번째 규칙은 크기이다.
그렇다면 대체 얼마나 작아야 하는가???
함수 : 물리적인 행 수
클래스 : 맡은 책임

우선 클래스 이름은 책임을 기술해야 한다.
Processor, Manager, Super 등과 같이 모호한 단어가 있다면 해당 클래스는 여러 책임을 갖고 있는 것이다.

또한 클래스의 설명은 if, and, or, but 을 사용하지 않고 25자 내로 설명가능해야 한다.
ex) "SuperDashboard는 마지막으로 포커스를 얻었던 컴포넌트에 접근하는 방법을 제공하며, 버전과 빌드 번호를 추적하는 메커니즘을 제공한다."
-> ~하며(and) 가 틀렸다? (만약 그렇다면 클래스가 너무 많아지는 것 아닌가?)

단일 책임 원칙(SRP)

클래스나 모듈을 변경할 이유가 단 하나 뿐이어야 한다.

SRP는 가장 이해시키기 쉬운 개념 중 하나다.
하지만 설계자가 가장 무시하는 규칙 중 하나다.
수많은 책임을 떠안은 클래스를 접하기가 정말 쉽다.
Why? SW를 동작시키는 것과 SW를 깨끗하게 만드는 것은 별개이다.
-> 대부분의 개발자들은 "깨끗한 SW" 보다 "돌아가는 SW"에 초점을 둔다. (사실인 것 같음... ㅂㅂㅂㄱ)
또한, 단일 책임 클래스(SRP를 만족하는 클래스)가 많아지면 큰 그림을 이해하기가 어렵다고 생각한다.(이것도 맞는 것 같은데...)

차이점 :

  1. 작은 서랍을 많이 두고 기능과 이름이 명확한 컴포넌트를 다룰 것인가?
  2. 큰 서랍을 몇 개 두고 서랍안에 많은 컴포넌트를 넣어 다룰 것인가?

응집도

클래스는 인스턴스 변수 수가 적어야 한다.
메소드 내에서 클래스 인스턴스를 많이 사용할 수록 응집도가 높다.

응집도를 유지하면 작은 클래스 여러개가 나온다.

큰 함수를 작은 함수 여럿으로 나누기만 해도 여러개의 클래스가 나온다.

if)

  1. 큰 함수 내의 어떤 로직을 작은 함수로 빼내고 싶다.
  2. 그 로직 내에서 큰 함수에 있는 로컬 변수 4개를 사용한다.
  3. 작은 함수의 매개변수로 4개를 모두 넘겨야 하나?
    -> No, 클래스 프로퍼티로 승격시키면 매개변수는 필요없어진다.
  4. 그렇다면 해당 프로퍼티는 작은 함수에서만 사용하는 것이 아닌가? 즉, 응집력이 낮아지는 것 아닌가?
    -> 해당 프로퍼티와 함수를 새로운 클래스로 쪼개라

변경하기 쉬운 클래스

SQL(select, selectAll, insert, findByKey ...)을 수행하는 클래스를 기능 별로 각각의 클래스로 나눈다.
(-> 하지만, 클래스가 너무 많아지는데..? 뭐 나눔으로써 SRP, OCP를 지원한다고 해도, 각각을 확인하기 또한 불편한 것 아닌가?)

변경으로부터 격리

요구사항을 변하기 마련이다. 따라서 코드 또한 변할 것이다.
OOP에서는 concrete Class 와 abstract Class가 존재한다.

ex) 주식 Portfolio 클래스를 만든다고 가정하자
Portfolio 클래스는 외부 TokyoStockExchange API를 사용해 값을 계산한다.
따라서 테스트 코드를 작성해도 시시 각각으로 변하는 시세로 인해 검증이 어려워진다.

-> Portfolio 클래스에서 TokyoStockExchange API를 직접 호출하는 대신 StockExchange라는 인터페이스를 생성한 후 메서드를 선언한다.

//abstract
public interface StockExchange{
    Money currentPrice(String symbol);
}

-> 다음으로 StockExchange 인터페이스를 구현하는 TokyoStockExchange Class를 구현한다.
또한, Portfolio 생성자를 통해 StockExchange를 주입한다.

public Portfolio{
    private StockExchange exchange;
    public Portfolio(StockExchange exchange){
        this.exchange = exchange;
    }
    //......
}

이제 TokyoStockExchange 를 흉내내는 테스트용 클래스를 만들 수 있다.
테스트 용 클래스는 StockExchange 인터페이스를 구현하며 고정된 주가를 반환한다.
또한 항상 일정한 주가를 반환한다.

따라서 이제는 시시각각 주가가 변동해도 테스트코드를 작성할 수 있다.

public class PortfolioTest{
    private FixedStockExchangeStub exchange;
    private Portfolio portfolio;

    @Before
    protected void setUp() throws Exception{
        exchange = new FixedStockExchangeStub();
        exchange.fix("BCT", 100);
        portfolio = new Portfolio(exchange);
    }

    @Test
    public void GivenFiveBCTTotalShouldBe500() throws Exception{
        portfolio.add(5, "BCT");
        Assert.assertEquals(500, portfolio.value());
    }
}

위와 같이 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성이 높아진다.
결합도가 낮다 : 각 시스템 요소가 다른 요소로부터, 변경으로부터 잘 격리되어 있다.
결합도를 낮춤으로써 DIP를 따르는 클래스가 나온다.

DIP : concrete Class 가 아닌 abstract Class에 의존해야 한다.

우리가 만든 Portfolio Class는 Tokyo...라는 concrete Class가 아닌 StockExchange 인터페이스에 의존한다.
따라서 Tokyo... 가 아닌 NY...., Korea... 를 주입해서 사용할 수도 있다.

+ Recent posts