Dev/Books

[CleanCode] 10장. 클래스

kyeoneee 2020. 5. 31. 22:03
반응형

클래스 체계

클래스 정의 표준 자바 관례에 따른 클래스 구성 순서

  • public satic 상수
  • private static 변수
  • private instance 변수
  • public 함수
  • private 함수 - 자신을 호출하는 public 함수 직후에 위치

캡슐화

변수와 유틸리티 함수는 가능한 공개하지 않아야 함
테스트를 위해 protected 선언/패키지 전체 공개, but!!!! 캡슐화를 풀어주는 결정은 최후의 수단


클래스는 작아야 한다!

클래스를 만들때의 규칙은 무조건 작게!
얼마나 작아야 할까?
함수와는 다르게 맡은 책임의 수가 기준

클래스의 책임?
네이밍이 나타낸다. 구현 과정에서 간결한 이름이 떠오르지 않거나 클래스 이름이 모호하다는 것은 책임이 많다는 반증

  • 단일 책임의 원칙

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

  • Bad : 버전에 대한 책임과 Component에 대한 책임을 모두 가지고 있음*

     public class SuperDashboard extends JFrame implements MetaDateUser {
       public Component getLastFocusedComponent()
       public void setLastFocused(Component lastFocused)
       public int getMajorVersionNumber()
       public int getMinorVersionNumber()
       public int getBuildNumber()
     }

    Good : SuperDashboard 클래스는 Component에 대한 책임만을 갖고 별도의 Version에 대한 책임을 가지는 클래스를 분리

       public class Version {
         public int getMajorVersionNumber()
         public int getMinorVersionNumber()
         public int getBuildNumber()
       }
    
       public class SuperDashboard extends JFrame implements MetaDateUser {
         public Component getLastFocusedComponent()
         public void setLastFocused(Component lastFocused)
       }
  • 응집도

    클래스는 인스턴스 변수 수가 적어야 함
    메서드가 클래스 변수를 많이 사용 할수록 클래스는 응집도가 더 높아짐
    응집도가 높다? 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이기도 함
    몇몇 메서드만이 사용하는 인스턴스 변수가 많아지게 되면 클래스를 쪼개야 한다는 신호

  • 응집도를 유지하면 작은 클래스 여럿이 나온다

    큰 함수의 일부를 작은 함수로 나눌 수 있을 때 나눌 부분에서 사용되는 변수가 큰 함수 내의 다른 곳에서 사용되지 않으면 클래스로 분리가 가능하다.
    클래스로 분리하였을 때 응집도가 떨어진다면 다시 클래스를 쪼개야 한다.


변경하기 쉬운 클래스

깨끗한 시스템? 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춤
새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지 않도록 클래스를 설계하는 것이 매우 중요!!!

Bad :아래의 클래스는 새로운 SQL문을 지원할 때(책임1), 기존 SQL문을 수정할 때(책임2) 수정이 필요하므로 SRP 위반

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)
}

Good :
공개 인터페이스를 전부 SQL 클래스에서 파생하는 클래스로 분리
비공개 메서드는 해당 메서드를 사용하는 클래스로 이동
공통된 인터페이스는 따로 클래스로 분리
아래와 같이 분리 후에는 새로운 SQL문 추가 시, 기존 SQL문 수정 시에 기존의 클래스를 건드릴 이유가 없어짐

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()
}

결과적으로 OCP(Open-Closed-Priciple)도 지킬 수 있게됨
OCP란? 확장에 개방적이로 수정에 폐쇄적이어야 한다는 객체 지향 설계의 핵심 원칙 중 하나

  • 변경으로부터 격리
    인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리
    매번 달라지는 API로 테스트 코드를 짜기 힘듦 -> API를 구현하는 기능을 인터페이스로 분리하라!
    테스트 시 해당 인터페이스를 상속받아 API를 흉내내는 테스트용 클래스를 만들어 테스트가 가능해짐
    e.g.,

    public interface stockExchange {
      Money currentPrice(String symbol);  // API의 결과값을 뱉어주는 메서드
    }
    
    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());
      }
    }

    결합도가 낮아짐!
    -> 각 시스템 요소(클래스)가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미
    결합도가 낮아지면 자연스럽게 DIP(Dependency-Inversion-Priciple)을 따르는 클래스가 구현 됨
    DIP란? 클래스가 상세한 구현이 아니라 추상화에 의존해 다른 클래스의 변경에 영향받지 않도록 한다는 원칙

반응형

'Dev > Books' 카테고리의 다른 글

[DDD Start!] 1장. 도메인 모델 시작  (0) 2020.06.06
[CleanCode] 9장. 단위 테스트  (0) 2020.05.18
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15