본문 바로가기
RECORDS/CleanCode

클린코드 #10

by _wavy 2024. 3. 16.

10장 클래스

깨끗한 클래스 작성을 소개하는 장. 클래스는 작고 변경이 쉬워야 한다. 좋은 클래스 코드 작성을 위해서는 수차례 리팩토링이 필요하며 이때는 테스트 코드 작성 후 코드 변경 시마다 테스트를 수행해 원래 프로그램과 동일하게 동작하는지 확인한다.

 

1. 클래스 체계

  • 변수 목록
    • 정적 공개 상수(static public)
    • 정적 비공개 변수(static private)
    • 비공개 인스턴스 변수(private instance)
  • 함수 목록
    • 공개 함수
    • 비공개 함수

2. 객체지향 프로그래밍의 SOLID 원칙

  • 단일 책임 원칙(SRP, Single Responsibility Principle): 하나의 클래스는 하나의 책임을 가져야 한다.
  • 개방-폐쇄 원칙(OCP, Open/Closed Principle): 클래스는 확장에 개방적이고 변경(수정)에 폐쇄적이어야 한다.
  • 리스코프 치환 원칙(LSP, Liskov Substitution Principle): 하위 클래스는 상위 클래스가 사용되는 곳에 대체 가능해야 한다.
  • 인터페이스 분리 원칙(ISP, Interface Segregation Principle): 인터페이스는 클라이언트에 필요한 기능만 제공해야 한다.
  • 의존 역전 원칙(DIP, Dependency Inversion Principle): 클래스는 구체적인 구현이 아니라 추상화에 의존해야 한다.

3. 작고 변경이 쉬운 클래스 만들기

  • 쪼개기: 만능 클래스를 여러 개의 단일 책임 클래스로 분리(SRP)
    • 클래스 명으로 Processor, Manager, Super 같은 모호한 표현 사용 지양
    • 클래스 설명이 만일(if), 그리고(and), 하며(or), 하지만(but) 사용 없이 25단어 내외로 가능하도록
// BAD
class SuperDashboard { 
  getLastFocusedComponent() {}
  setLastFocused(lastFocused) {}
  getMajorVersionNumber() {}
  getMinorVersionNumber() {}
  getBuildNumber() {}
}

// super 클래스에서 version에 대한 책임 분리
class Version {
  getMajorVersionNumber() {}
  getMinorVersionNumber() {}
  getBuildNumber() {}
}
  • 격리하기: 응집도(cohesion)를 높이고 결합도를 낮춤(OCP& DIP)
    • 응집도: 클래스(모듈) 내 요소들이 서로 의존하는 정도
    • 결합도: 클래스(모듈) 간의 서로 의존하는 정도
    • 즉, 한 클래스 내의 변수와 메서드 의존도를 높이고, 클래스 외부의 의존도는 낮추도록 하라는 것
// 예시 1
// BAD
class Sql {
  constructor(table, columns) {}

  create() {}

  insert(fields) {}

  selectAll() {}

  findByKey(keyColumn, keyValue) {}

  select(column, pattern) {}

  select(criteria) {}

  preparedInsert() {}

  columnList(columns) {}

  valuesList(fields, columns) {}

  selectWithCriteria(criteria) {}

  placeholderList(columns) {}
}

// GOOD
class Sql {
  constructor(table, columns) {}
  generate() {}
}

class CreateSql extends Sql {
  constructor(table, columns) {
    super(table, columns);
  }
  generate() {}
}

class SelectSql extends Sql {
  constructor(table, columns) {
    super(table, columns);
  }
  generate() {}
}

class InsertSql extends Sql {
  constructor(table, columns, fields) {
    super(table, columns);
  }
  generate() {}
  valuesList(fields, columns) {}
}

class SelectWithCriteriaSql extends Sql {
  constructor(table, columns, criteria) {
    super(table, columns);
  }
  generate() {}
}

class SelectWithMatchSql extends Sql {
  constructor(table, columns, column, pattern) {
    super(table, columns);
  }
  generate() {}
}

class FindByKeySql extends Sql {
  constructor(table, columns, keyColumn, keyValue) {
    super(table, columns);
  }
  generate() {}
}

class PreparedInsertSql extends Sql {
  constructor(table, columns) {
    super(table, columns);
  }
  generate() {}
  placeholderList(columns) {}
}

class Where {
  constructor(criteria) {}
  generate() {}
}

class ColumnList {
  constructor(columns) {}
  generate() {}
}
// 예시 2: 외부 API 사용에 대한 의존도 낮추기
interface StockExchange {
  currentPrice(symbol: string): Money;
}

class Portfolio {
  private exchange: StockExchange;

  constructor(exchange: StockExchange) {
    this.exchange = exchange;
  }
  // 추가 기능에 따라 메서드들을 구현
}

class TokyoStockExchange implements StockExchange {
  currentPrice(symbol: string): Money {
    // 실제 TokyoStockExchange API를 호출하여 주식의 현재 가격을 반환하는 로직을 작성
    return new Money();
  }
}

// 테스트 코드
class FixedStockExchangeStub implements StockExchange {
  currentPrice(symbol: string): Money {
    return new Money(100);
  }
}

class PortfolioTest {
  private exchange: FixedStockExchangeStub;
  private portfolio: Portfolio;

  constructor() {
    this.exchange = new FixedStockExchangeStub();
    this.exchange.fix('MSFT', 100); // 테스트용 데이터 설정
    this.portfolio = new Portfolio(this.exchange);
  }

  GivenFiveMSFTTotalShouldBe500(): void {
    this.portfolio.add(5, 'MSFT');
    // 테스트 코드 작성
  }
}

'RECORDS > CleanCode' 카테고리의 다른 글

클린코드 #9  (0) 2024.03.15
클린코드 #7  (0) 2024.03.11
클린코드 #6  (0) 2024.03.10

댓글