[Clean Code]클래스

2 분 소요

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는 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.

태그: ,

카테고리:

업데이트: