728x90
로버트 C.마틴의 클린 코드를 읽고 정리한 내용입니다.
클래스 체계
- 가장 먼저 변수 목록이 나온다.
- public static 변수가 가장 먼저 나온다.
- 다음으로 private static 변수가 나온다.
- 이어서 private 변수가 나온다. 공개 변수가 필요한 경우는 거의 없다.
- public method가 나온다.
- private method는 자신을 호출하는 public method 직후에 넣는다.
즉, 추상화 단계가 순차적으로 내려간다. 그래서 프로그램은 신문 기사처럼 읽혀야 한다.
캡슐화
- 변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 한다는 규칙도 없다.
- 때로는 변수나 유틸리티 함수를 protected로 선언해 테스트 코드에 접근을 허용하도록 한다.
- 같은 패키지 안에서 테스트 코드가 함수를 호출하거나 변수를 사용해야 한다면 그 함수나 변수르르 protected로 선언하거나 패키지 전체로 공개한다.
- 하지만 그 전에 private 상태를 유지할 방법을 고민해야 한다. 캡슐화를 풀어주는 결정은 가장 최후의 수단이 되어야 한다.
클래스는 작아야 한다.
- 클래스는 작아야 한다. 그렇다면 얼마나 작아야 할까? 어떤 기준으로 클래스의 크기를 가늠할까?
- 클래스의 책임이 얼마나 많냐에 따라서 클래스의 크기를 가늠한다.
- 클래스 이름은 클래스 책임을 기술해야 한다. 클래스 이름이 쉽게 떠오르지 않는다면 클래스의 책임이 너무 많기 때문이다.
- 예를 들어, MemberService에 대한 설명이 “인증 관련 로직을 처리하며, 회원 관련 로직도 처리한다.”라면 “~하며,”가 클래스에 책임이 너무 많다는 증거가 된다.
단일 책임 원칙(SRP, Single Responsibility Principle)
- 단일 책임 원칙은 클래스나 모듈을 변경할 이유가 단 하나뿐이라는 원칙이다.
- 클래스는 변경할 이유가 하나여야 한다는 의미이다.
- 아까 말한 MemberService는 인증 로직과 회원 로직을 모두 관리한다. 클래스를 변경할 이유가 두 가지가 된다. AuthService를 만들어 인증 로직을 관리하게 한다.
- 큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다. 작은 클래스는 각자 맡은 책임이 하나이며, 변경할 이유가 하나여야 한다. 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행해야 한다.
응집도
- 클래스는 인스턴스 변수의 수가 적어야 한다.
- 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
- 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스의 응집도가 높다.
- 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 높다.
- 응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이다.
- 다음은 Stack을 구현한 코드이다. Stack 클래스는 응집도가 아주 높다. size()를 제외한 다른 두 메서드가 변수를 모두 사용한다.
public class Stack {
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
public int size() {
return topOfStack;
}
public void push(int element) {
topOfStack++;
elements.add(element);
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0)
throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack);
element.remove(topOfStack);
return element;
}
}
- ‘함수를 작게, 매개변수 목록은 짧게’라는 전략을 따르다 보면 때때로 몇몇 메서드만이 사용하는 인스턴스 변수가 많아진다. 이것은 새로운 클래스로 쪼개야 한다는 신호이다. 응집도가 높아지도록 변수와 메서드를 적절히 분리해 새로운 클래스 두세 개로 쪼개야 한다.
변경으로부터 격리
- 객체지향 프로그래밍에는 구체적인 클래스와 추상 클래스가 있다.
- 구체적인 클래스는 상세한 구현(코드)를 포함하며 추상 클래스는 개념만 포함한다.
- 상세한 클래스에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다. 그래서 인터페이스와 추상 클래스를 사용해 구현에 미치는 영향을 격리한다.
- 상세한 구현에 의존하는 클래스는 테스트하기도 어렵다.
- Portfolio 클래스를 만든다고 가정할 때 외부 TokyoStockExchange API를 사용해서 포트폴리오 값을 계산한다고 한다. 이러면 테스트 코드는 시세 변화에 영향을 받는다. 테스트 코드를 짜기가 쉽지 않아진다.
- Portfolio 클래스에서 TokyoStockExchange API를 직접 호출하는 대신 StockExchange라는 인터페이스를 생성한 후 메서드 하나를 선언한다.
- 다음으로 StockExchange 인터페이스를 구현하는 TokyoStockExchange 클래스를 구현한다. Portfolio 생성자를 수정해 StockExchange 참조자를 인수로 받는다.
- 이제 TokyStockExchange 클래스를 흉내내는 테스트용 클래스를 만들 수 있다.
- 테스트용 클래스는 StockExchange 인터페이스를 구현하며 고정된 주가를 반환한다.
- 위와 같이 결합도를 낮추면 유연성과 재사용성이 높아진다. 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소, 변경으로부터 잘 격리되어 있다는 의미이다. 시스템 요소가 잘 격리되어 있으면 각 요소를 이해하기 더 쉬워진다.
- 결합도를 낮추면 자연스럽게 또 다른 클래스 설계 원칙인 DIP(Dependency Inversion Principle)을 따르는 클래스가 나온다. DIP는 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.
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);
porfolio = new Portfolio(exchange);
}
@Test
public void GivenFiveMSFTTotalShouldBe500() throws Exception {
portfolio.add(5, "MSFT");
Assert.assertEquals(500, portfolio.value());
}
}
728x90
'책' 카테고리의 다른 글
면접을 위한 CS 전공지식 노트 - 시간 복잡도 (0) | 2023.04.18 |
---|---|
Clean Code - 의미있는 이름 (0) | 2023.04.17 |
Clean Code - 객체와 자료구조 (1) | 2023.04.12 |
Clean Code - 단위 테스트(FIRST) (0) | 2023.04.11 |
면접을 위한 CS 전공지식 노트 - 조인 (JOIN) (0) | 2023.04.11 |