티스토리 뷰
| 10. 클래스
-
|| 클래스 체계
- 클래스를 정의하는 표준 자바 관례 (순차적인 추상화 단계)
1. 변수 목록
- static public
- static private
- 비공개 인스턴스 변수
2. 공개 함수
3. 비공개 함수
||| 캡슐화
- 때로 변수나 유틸리티 함수를 protected로 선언하여 테스트 코드에 접근을 허용하는 방법도 있다.
- 하지만, 그 전에 비공개 상태를 유지할 온갖 방법을 강구하고 캡슐화를 풀어주는 결정은 언제나 최후의 수단으로!
|| 클래스는 작아야 한다!
- 클래스를 만들 때 첫 번째 규칙은 크기, 클래스는 작아야 한다. (두 세 번째 규칙도 크기다...)
- 함수와 마찬가지로 '작게'가 기본 규칙
- 함수는 물리적인 행 수로 크기를 측정했다면,
클래스는 맡은 책임을 센다.
* 클래스 이름은 해당 클래스 책임을 기술해야 한다.
||| 단일 책임 원칙 (Single Responsibility Principle, SRP)
- 클래스나 모듈을 변경할 이유가 단 하나뿐이어야 한다.
> Before
1 2 3 4 5 6 7 | public class SuperDashboard extends JFrame implements MetaDataUser { public Component getLastFocusedComponent() public void setLastFocused(Component lastFocused) public int getMajorVersionNumber() public int getMinorVersionNumber() public int getBuildNumber() } | cs |
- SuperDashboard는 소프트웨어 버전 정보를 추적 (버전 정보는 달라진다)
- SuperDashboard는 Swing 컴포넌트를 관리 (버전 정보는 달라진다)
- Version을 관리하는 독자적인 클래스를 만들 필요가 있다.
> After
1 2 3 4 5 | public class Version { public int getMajorVersionNumber() public int getMinorVersionNumber() public int getBuildNumber() } | cs |
* 규모가 있는 수준의 시스템이 가지는 복잡성을 다루려면 체계적인 정리가 필수
큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다 !
||| 응집도 (Cohesion)
- 클래스는 인스턴스 변수가 작아야 한다.
- 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
(모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높다)
> 응집도가 높은 Stack.java Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); elements.remove(topOfStack); return element; } } | cs |
||| 응집도를 유지하면 작은 클래스 여럿이 나온다
- 큰 함수를 작은 함수 여럿으로 나누기만 해도 클래스 수가 많아진다.
- 큰 함수 일부를 작은 함수로 빼내면서
-> 큰 함수에 정의된 변수를 사용? -> 인수로 넘긴다? -> 인스턴스 변수로 승격시켜주자.
-> 클래스가 응집력을 잃는다? -> 독자적인 클래스로 분리하자.
|| 변경하기 쉬운 클래스
-
- 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
(어떤 변경이든 클래스에 손대면 다른 코드를 망가뜨릴 잠정적인 위험이 존재)
> Before
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) } | cs |
> After
- 비공개 메서드는 해당하는 파생 클래스로
- 모든 파생 클래스가 공통으로 사용하는 비공개 메서드는 유틸리티 클래스에
..
- update 문을 추가한다 하더라도 기존 클래스를 변경할 필요가 전혀 없다.
- SRP와 OCP(Open-Closed-Principle)를 지원
- 새 기능에 개방적인 동시에 다른 클래스를 닫아놓는 방식으로 수정에 폐쇄적
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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() } | cs |
* 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직!
|| 변경으로부터 격리
- 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다.
- 결합도가 낮다는 뜻은 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미.
- 결합도를 최소로 줄이면 DIP(Dependency Inversion Principle)를 따르게 된다.
(클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙)
> Example
- TokyoStockExchange라는 상세한 구현 클래스가 아닌 StockExchange Interface에 의존
- StockExchange Interface는 주식 기호를 받아 현재 주식 가격을 반환한다는 추상적인 개념을 포함
- 이와 같은 추상화로 실제 주가를 얻어오는 출처나 얻어오는 방식 등과 같은 구체적인 사실을 모두 숨긴다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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()); } } | cs |
출처 : 클린 코드 (Robert C. Martin)
'Books' 카테고리의 다른 글
[클린 코드: Clean Code] 12. 창발성(emergent creativity) (0) | 2021.01.21 |
---|---|
[클린 코드: Clean Code] 11. 시스템(System) (0) | 2021.01.20 |
[클린 코드: Clean Code] 9. 단위 테스트 (0) | 2021.01.16 |
[클린 코드: Clean Code] 8. 경계(외부 API) (0) | 2021.01.16 |
[클린 코드: Clean Code] 7. 오류 처리 (0) | 2021.01.13 |