[Clean Code]클래스
Clean Code
클래스
클래스 체계
- 클래스를 정의하는 표준 자바 관례에 따르면, 가장 먼저 변수 목록이 나오고 변수 목록 다음에는 공개 함수가 나온다.
- 변수: 정적 공개 상수 -> 정적 비공개 변수 -> 비공개 인스턴스 변수 -> 공개 변수
- 변수 목록 다음에는 공개 함수가 나온다. 즉, 추상화 단계가 순차적으로 내려간다.
클래스는 작아야 한다!
- 클래스를 설계할 때도, 함수와 마찬가지로 ‘작게’가 기본 규칙이다.
- 함수는 물리적인 행 수로 크기를 측정했다면, 클래스는 맡은 책임을 센다.
- 작명은 클래스 크기를 줄이는 첫 번째 관문이다. 간결한 이름이 떠오르지 않는다면 필경 클래스 크기가 너무 커서 그렇다.
단일 책임 원칙(Single Responsibility Principle)
- 클래스나 모듈을 변경할 이유가 하나뿐이어야 한다는 원칙이다.
- SRP는 객체 지향 설계에서 더욱 중요한 개념이다.
- 큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다. 작은 클래스는 각자 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다.
응집도
- 클래스는 인스턴스 변수가 작아야 하고, 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
- 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다.
- 응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이다.
- 때때로 몇몇 메서드만이 사용하는 인스턴스 변수가 생기는데, 이는 새로운 클래스로 쪼개야 한다는 신호이다.
변경하기 쉬운 클래스
- 대다수 시스템은 지속적인 변경이 가해진다. 그리고 뭔가 변경할 때마다 시스템이 의도대로 동작하지 않을 위험이 따른다. 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
- 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다. 이상적인 시스템이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지 않는다.
변경이 필요해 '손대야' 하는 클래스 public class Sql{ public Sql(String table, Column[] columns) public String create() public String insert(Object[] fields) public String selectAll() } 닫힌 클래스 집합 abstract public class Sql{ public Sql(String table, Column[] colunns) abstract public String generate(); } public class CreateSql extends Sql{ public CreateSql(String table, Column[] columns) @Override public String generate() } public class SelectSql extends Sql{ public SelectSql(String table, Column[] columns) @Override public String generate() } public class InsertSql extends Sql{ public InsertSql(String table, Column[] columns, Object[] fields) @Override public String generate() private String valuesList(Object[] fields, final Column[] columns) } 클래스가 서로 분리되었기 때문에, 함수 하나를 수정했다고 다른 함수가 망가질 위험이 사라졌다. 테스트 관점에서 모든 논리를 구석구석 증명하기도 쉬워졌다.
변경으로부터 격리
- 객체 지향 프로그래밍에는 구체적인 클래스와 추상 클래스가 있다. 구체적인 클래스는 상세한 구현(코드)을 포함하며 추상 클래스는 개념만 포함한다.
- 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위혐에 빠진다. 그래서 인터페이스와 추상 클래스를 사용해 구현에 미치는 영향을 격리한다.
public interface StockExchange{ Money currentPrice(String symbol); } public Portfolio{ private StockExchange exchange; public Portfolio(StockExchange exchange){ this.exchange = exchange; } } public class PortfolioTest{ private FixedStockExchangeStub exchange; private Portfolio portfolio; @Before protected void setUp() throws Exception{ exchange = new FixedStockExchangeStub(); exchange.fix("MSFT",100); portfolio = new Portfolio(exchange) } @Test public void GivenFiveMSFTTotalShouldBe500() throws Exception{ portfolio.add(5, "MSFT"); Assert.assertEquals(500, portfolio.value()); } }
- StockExchange Interface는 주식 기호를 받아 현재 주식 가격을 반환한다는 추상적인 개념을 포함한다. -> 이와 같은 추상화로 실제 주가를 얻어오는 출처나 얻어오는 방식 등과 같은 구체적인 사실을 모두 숨긴다.
- 위와 같은 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다. 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미다.
- 이렇게 결합도를 최소로 줄이면 자연스럽게 또 다른 클래스 설계 원칙인 DIP(Dependency Inversion Principle)를 따르는 클래스가 나온다. 본질적으로 DIP는 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.