Don't give up!

[클린코드] 10장 클래스 본문

개발서적/클린 코드

[클린코드] 10장 클래스

Heang Lee 2021. 7. 29. 23:51
이 글은 로버트 마틴의 '클린 코드'를 읽고 TIL(Today I Learned)로써 정리한 내용을 기록하는 글입니다.
자세한 내용은 책을 통해 확인하실 수 있습니다.

알라딘: 클린 코드 Clean Code (aladin.co.kr)

 

클린 코드 Clean Code

로버트 마틴은 이 책에서 혁명적인 패러다임을 제시한다. 그는 오브젝트 멘토(Object Mentor)의 동료들과 힘을 모아 ‘개발하며’ 클린 코드를 만드는 최상의 애자일 기법을 정제해 책 한 권에 담았

www.aladin.co.kr

클래스

클래스 체계

  • 표준 자바 관례
    • static public 상수
    • static private 변수
    • private 인스턴스 변수
    • public 함수
    • private 함수

캡슐

  • 변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 한다는 법칙도 없다.
  • 같은 패키지 안에서 테스트 코드가 함수를 호출하거나 변수를 사용해야 한다면 그 함수나 변수를 protected로 선언하거나 패키지 전체로 공개할 수 있다. (캡슐화를 풀어주는 결정은 언제나 최후의 수단이다.)

클래스 크기

  • 함수와 마찬가지로 클래스는 작아야 한다.
  • 클래스는 클래스가 맡은 책임으로 크기를 측정한다.
  • 클래스에 대해 간결한 이름이 떠오르지 않는다면 클래스가 맡은 책임이 많다는 뜻. (클래스 크기가 크다.)
  • 클래스 설명은 if, and, but을 사용하지 않고서 25단어 내외로 가능해야 한다.

단일 책임 원칙

  • 클래스나 모듈을 변경할 이유(책임)는 단 하나뿐이어야 한다.
  • 변경할 이유(책임)를 파악하려 애쓰다 보면 코드를 추상화하기도 쉬워진다.
  • 작은 클래스가 많은 시스템이든 큰 클래스가 몇 개뿐인 시스템이든 돌아가는 부품은 그 수가 비슷하다.
  • 개발자가 무엇이 어디에 있는지 쉽게 찾기 위해서는 체계적인 정리가 필수다.
  • 큼직한 다목적 클래스 몇 개로 이루어진 시스템은 변경을 가할 때 당장 알 필요가 없는 사실까지 들이밀어 독자를 방해한다.
//나쁜 코드 - Super와 같이 모호한 단어가 담긴 클래스명(함수가 갖는 책임이 많음.)
public class SuperDashboard extends JFrame implements MetaDataUser {
  public Component getLastFocusedComponent() { ... }
  public void setLastFocused(Component lastFocused) { ... }
  public int getMajorVersionNumber() { ... }
  public int getMinorVersionNumber() { ... }
  public int getBuildNumber() { ... }
}

//개선 - 추상화된 클래스로 책임을 분리
public class Version {
  public int getMajorVersionNumber() { ... }
  public int getMinorVersionNumber() { ... }
  public int getBuildNumber() { ... }
}

public class SuperDashboard extends JFrame implements MetaDataUser {
  public Component getLastFocusedComponent() { ... }
  public void setLastFocused(Component lastFocused) { ... }
}

응집도

  • 클래스는 인스턴스 변수 수가 작아야 한다.
  • 일반적으로 메서드가 변수를 더 많이 사용할 수록 메서드와 클래스는 응집도가 더 높다.
  • 응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미.
  • 큰 함수를 작은 함수 여럿으로 쪼개다 보면 작은 클래스 여럿으로 쪼갤 기회가 생기고 구조가 투명해진다.

변경하기 쉬운 클래스

  • 대다수 시스템은 지속적인 변경이 가해진다.
  • 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
//나쁜 예시 - 단일 책임 원칙을 위반하는 Sql 클래스
public class Sql {
  public Sql(String table, Column[] columns) { ... }
  public String create() { ... }
  public String insert(Object[] fields) { ... }
  public String selectAll() { ... }
  public String findByKey(String keyColumn, String keyValue) { ... }
  public String select(Column column, String pattern) { ... }
  public String select(Criteria criteria) { ... }
  public String preparedInsert() { ... }
  private String columnList(Column[] columns) { ... }
  private String valuesList(Object[] fields, final Column[] columns) { ... }
  private String selectWithCriteria(String criteria) { ... }
  private String placeholderList(Column[] columns) { ... }
}

//개선 - 추상 클래스, 유틸리티 클래스 선언으로 단일 책임 원칙, 열림 닫힘 원칙을 준수
abstract public class Sql {
  public Sql(String table, Column[] columns) { ... }
  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 class SelectWithCriteriaSql extends Sql { 
  public SelectWithCriteriaSql(String table, Column[] columns, Criteria criteria) { ... }
  @Override public String generate() { ... }
}
    
public class SelectWithMatchSql extends Sql { 
  public SelectWithMatchSql(String table, Column[] columns, Column column, String pattern) { ... }
  @Override public String generate() { ... }
}
    
public class FindByKeySql extends Sql
  public FindByKeySql(String table, Column[] columns, String keyColumn, String keyValue) { ... }
  @Override public String generate() { ... }
}
    
public class PreparedInsertSql extends Sql {
  public PreparedInsertSql(String table, Column[] columns) { ... }
  @Override public String generate() { ... }
  private String placeholderList(Column[] columns) { ... }
}
    
public class Where {
  public Where(String criteria) { ... }
  public String generate() { ... }
}
    
public class ColumnList {
  public ColumnList(Column[] columns) { ... }
  public String generate() { ... }
}

변경으로부터 격리

  • 요구사항은 변하기 마련이다. 따라서 코드도 변하기 마련이다.
  • 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다.
  • 상세한 구현에 의존하는 코드는 테스트가 어렵다.
  • 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리할 수 있다.
  • 결합도가 낮다는 것은 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미.
  • 시스템 요소가 서로 잘 격리되어 있으면 각 요소를 이해하기도 쉬워진다.
//좋은 예시 - 추상화를 사용하여 구체적인 구현을 모두 숨기고 인터페이스에 의존.
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());
  }
}

'개발서적 > 클린 코드' 카테고리의 다른 글

[클린코드] 11장 시스템  (0) 2021.07.31
[클린코드] 9장 단위테스트  (0) 2021.07.27
[클린코드] 8장 경계  (0) 2021.07.27