jtwjs Dev Wiki
  • DEV_ROAD
    • 💪🏻 생존하기
    • Week 1
      • 개발 환경 세팅
      • 타입스크립트
      • 리엑트
      • Testing Library
      • Parcel & ESLint
    • Week 2
      • JSX
      • Virtual DOM
    • Week 3
      • React Component
      • React State
    • Week 4
      • Express
      • Fetch API & CORS
      • React Hook
      • useRef & Custom Hook
    • Week 5
      • TDD
      • React Testing Library
      • MSW
      • Playwrite
      • Snapshot
    • Week 6
      • Separtion of Concerns
      • Principle
      • DI, (Dependency Injection)
      • Reflect-metadata
      • TSyringe
      • External Store
      • Follow Redux
      • usestore-ts
      • useSyncExternalStore
    • Week 7
      • Routing
      • Routes
      • Router
      • Navigation
    • Week 8
      • Design System
      • Style Basics
      • CSS-in-JS
      • Styled-Components
      • Global Style & Theme
    • Week 9
      • 개발하기 전 준비
      • 상품 목록 페이지
      • 상품 상세 페이지
      • 장바구니 페이지
    • Week 10
      • 로그인
      • 로그아웃
      • 회원가입
      • 주문 목록 & 주문 상세
    • Week 11
      • 배송 정보 입력
      • 포트원 결제 요청
      • 배송 및 결제 정보 전달
    • Week 12
      • 관리자 웹사이트개발시작
  • DEV_NOTE
    • TypeScript
      • 기본적 문법
        • Enum
        • 다형성
          • Untitled
        • 구조적 타이핑
        • 제너릭 타입
        • 컨디셔널 타입
        • 함수 메서드 타이핑
        • infer로 타입스크립트의 추론 직접 활용
        • 재귀 타입
        • 템플릿 리터럴 타입
        • 추가적인 타입 검사 satisfies 연산자
        • 타입스크립트 건망증
        • 원시 자료형에도 브랜딩 기법 사용 가능
        • 타입 좁히기
        • 유용한 타입 만들기
        • 데코레이터 함수
        • 앰비언트 선언도 선언 병합이 된다.
        • 앰비언트 선언도 선언병합이 된다.
    • Testing
      • Unit Testing
      • 단위 테스트의 두 분파
      • 좋은 단위 테스트를 구성하는 4대 요소
      • 테스트 대역과 식별할 수 있는 동작
      • 단위 테스트 스타일
      • 가치 있는 단위 테스트를 위한 리팩토링
      • 통합 테스트
      • Cross Browsing Testing
      • 기능 테스트 종류
      • React Testing Pattern
      • 프론트엔드 테스트 입문
        • 테스트 범위
        • 단위 테스트 검증
        • Mock
        • UI 컴포넌트 테스트
        • 테스트 커버리지
        • 웹 통합 테스트
        • MSW
        • 스토리북
        • 시각적 회귀 테스트
        • E2E 테스트
        • Github Actions 설정
        • 깃허브 액션에서 E2E
      • 시프트 레프트
        • 테스트 기본중의 기본
        • 단위 테스트
        • 코드 복잡도
        • 리팩터링
        • 코드 리뷰
        • 통합 테스트 패턴
        • 시스템 테스트의 자동화
        • 탐색적 테스트
      • Test Tip
      • vitest
      • playwright
      • Test Data Generator
      • MSW
    • Algorithm
      • coding test
      • Data Structure
    • Next.js
      • Data Fetching
      • Hydration
      • Next 13
      • Optimization
      • Next 15
    • Tailwind
      • Tailwind CSS
      • Theme
      • Directives
      • Tool
      • Design System
    • Storybook
      • Storybook
      • CSF3
      • CDD
      • Headless Component
    • Funtional Programming
      • 함수형 프로그래밍
      • 참조 투명성
      • 부수효과
      • 함수 합성
      • 제너릭 타입 활용하기
      • 암묵적 입출력
      • 액션과 계산, 데이터
      • 계층형 설계
      • 호출 그래프
      • 함수형 설계
      • 불변성
      • 일급 함수
      • 함수형 도구
    • Git
      • Github Actions
      • Conflict
      • Branch 전략
    • Contents Format
      • Audio
    • 3D Graphic
      • 3D keyword
      • Three.js
      • Geometry
      • Material
      • Light
      • Camera
      • Decal
      • Rotation
      • Text
      • Shadow
      • Fog
      • Post Processing
      • Animation
      • Math
        • Vector Space
        • 벡터의 연산
        • 회전 계산
      • 3D 컨텐츠가 만들어지는 과정
      • R3F
      • Env
      • Scene
      • Transform
      • R3F
      • Interaction & Raycast
      • Rendering Algorithnm
      • Blender
      • Blender
    • Accessibility
      • 접근성이란
    • Interactive Web
      • Parallax
      • Canvas
      • requestAnimationFrame
      • Effect
      • HSL
      • React.js + Canvas
      • Matter.js
    • AWS
      • DevOps
      • Amplify
      • S3
      • 클라우드 컴퓨팅
        • 온프레미스와 클라우드
        • 클라우드 도입효과
        • 클라우드 컴퓨팅의 범위
        • 컴퓨팅 옵션
          • EC2 - Virtual Machin
          • ECS, EKS - Container
          • Lambda - Serverless
        • 네트워크 가상화
        • 스토리지
        • 데이터베이스
        • 데이터 수집
        • 머신 러닝 영역
        • IoT 영역
        • 블록체인 영역
      • 클라우드 아키텍처 설계
    • Network
      • Web Server & WAS
    • System Design
      • System Design
      • Component
      • 의존성을 배제한 개발
      • Error Handling
      • Architecture
        • 모노로틱 아키텍처
        • Clean Architecture
        • Layered Architecture
        • 이벤트 기반 아키텍처
      • 상황을 파악하는 메타인지
      • 중복 문제 해결하기
      • Monorepo Arhitecture
        • 모노레포 운영과 트러블슈팅
        • Module Federation
      • 코드 병목지점
      • API 대응
      • 공통 코드
      • Infra 구축
      • 모듈 기반의 개발 방식
      • Design System
        • 최소 수준의 아키텍처 설정
        • 더 효율적인 디자인시스템 만들기
        • 디자인 시스템과 UI 라이브러리 목적
        • 디자인 토큰
      • 효율적인 업무
        • 업무 프로세스 병목 파악
      • Clean Code
      • Design Pattern
        • CQRS Pattern
        • Strangler Fig Pattern
        • 데코레이터 패턴
        • 커맨드 패턴
        • 전략 패턴
        • 옵저버 패턴
      • A/B 테스팅
      • 대규모 리엑트 웹앱 개발
        • 복잡성 관리
        • 모듈성
        • 성능
        • 디자인 시스템
        • 데이터 패칭
        • 상태 관리
        • 국제화
        • 코드 조직화하기
        • 개인화 A/B 테스팅
        • 확장 가능한 웹 아키텍처
        • 테스팅
        • 툴링
        • 기술적 마이그레이션
        • 타입스크립트
        • 라우팅
        • 사용자 중심 API 디자인
        • 리액트 미래
    • Performance
      • React DevTools
      • Component 최적화
      • Page Load
      • API
    • MFA
      • MSA
      • MFA 도입하기
      • Monorepo
        • Monorepo Tool
        • Yarn Berry Workspace
        • Turborepo
      • MFA Composition
      • SPA 통합
      • Design System
      • Package Manager
        • Yarn
        • pnpm
      • Transpiler & Bundler
        • Babel
        • Rollup
        • esbuild
        • swc
        • Webpack
        • Vite
      • 분해와 통합을 위한 여러 기술 비교
    • State Management
      • Zustand
    • React v18
      • Automatic batching
      • Suspense
      • Transition
    • SEO
      • Search Engine Optimization
      • Open Graph Element
      • Metadata
    • FE Develop
      • Scrubbing
      • Clipboard
    • Refactoring
      • 리팩토링 깊게 들여다보기
      • 긴 코드 조각내기
      • 타입 코드 처리하기
      • 유사한 코드 융합하기
      • 데이터 보호
      • 코드 추가 및 제거
    • OAuth 2.0
    • Analytics
      • Mixpanel
    • ETC
      • VSCode
    • React Hook In Action
      • useContext & Provider
      • 커스텀 훅
      • 코드 분할하기 with Suspense, lazy
      • Suspense와 이미지 적재하기
      • useTransition, uesDeferredValue
      • SuspenseList
Powered by GitBook
On this page
  • 요약
  • 간단한 if 문 리팩토링
  • #1. if 문에서 else를 사용하지 말 것
  • 스멜
  • 의도
  • 리팩터링 패턴: 클래스로 타입 코드 대체
  • 타입 코드
  • 절차
  • 클래스로 코드 이관하기
  • 클래스로의 코드 이관
  • 리팩터링 패턴: 메서드의 인라인화
  • 긴 if 문의 리팩토링
  • 메서드 전문화
  • Switch가 허용되는 유일한 경우
  • Switch를 사용하지 말 것
  • 스멜
  • 인터페이스 대신 추상 클래스를 사용할 수는 없나?
  1. DEV_NOTE
  2. Refactoring

타입 코드 처리하기

  • if 문에서 else를 사용하지 말것 과 switch를 사용하지 말것으로 이른 바인딩 제거하기

  • 클래스로 타입 코드 대체와 클래스로의 코드 이관으로 if문 제거하기

  • 함수 전문화로 문제가 있는 일반성 제거하기

  • 인터페이스에서만 상속받을 것으로 코드 간 커플링(결합) 방지하기

  • 함수 인라인화 및 삭제 후 컴파일하기를 통한 불필요한 함수 제거

요약

  • else와 switch는 프로그래밍 가장자리에만 있어야 한다.

  • else와 switch는 모두 낮은 수준의 제어 흐름 연산자이다.

  • 애플리케이션 핵심에서는 클래스로 타입 코드 대체 및 클래스로의 코드 이관 리팩터링 패턴을 사용하여 switch와 연속된 else if 구문을 높은 수준의 클래스와 메서드로 대체해야 한다.

  • 지나치게 일반화된 메서드는 리팩터링을 방해할 수 있다. 이런 경우 불필요한 일반성을 제거하기 위해 메서드 전문화 리팩터링을 사용

  • 추상 클래스와 클래스 상속은 불필요하게 긴밀한 커플링을 발생시킨다.

  • 리팩터링 후 정리하기 위한 두가지 리팩터링 패턴 1. 메서드 인라인화, 2. 삭제 후 컴파일하기로 더이상 가독성에 도움이 되지 않는 메서드 제거

간단한 if 문 리팩토링

#1. if 문에서 else를 사용하지 말 것

프로그램에서 이해하지 못하는 타입(형)인지 검사하지 않는한 if 문에서 else를 사용하지 않기

독립된 if 문은 check(검사), if-else 문은 의사결정(decision)으로 간주한다.

Decision

window.addEventListener("keydown", e => {
  if (e.key === LEFT_KEY || e.key === "a") inputs.push(Input.LEFT);
  else if (e.key === UP_KEY ||e.key === "w") inputs.push(Input.UP);
  else if (e.key === RIGHT_KEY ||e.key === "d") inputs.push(Input.RIGHT);
  else if (e.key === DOWN_KEY ||e.key === "s") inputs.push(Input.DOWN);
})

사용자의 입력을 받거나 데이터베이스에서 값을 가져오는 등 앱 외부에서 입력을 받는 프로그램의 경계에서 발생하는 경우 문제가 되지 않는다.

  • 외부의 데이터 타입을 내부에서 제어 가능한 데이터 타입으로 매핑하는 것이 중요

  • 조건절에서 두 가지 데이터 타입(KetboardEvent, string) 중 어느것도 우리가 결정할 수 없음

  • 이러한 else-if 체인 구문은 데이터의 입출력에 직접 연결되어야하고, 앱의 나머지 부분과는 분리되야함

Check

사용자가 이해할 수 있는 오류를 발생시킨다.

// Before
function average(ar: number[]) {
  if (size(ar) === 0)
    throw "Empty array not allowed";
  else
    return sum(ar) / size(ar);
}

// After
function assertNotEmpty(ar: number[]) {
  if (size(ar) === 0)
    throw "Empty array not allowed";
}

function average(ar: number[]) {
  assertNotEmpty(ar);
  return sum(ar) / size(ar);
}

스멜

  • 이 규칙은 이른 바인딩(early binding)과 관련 있음

    • 프로그램을 컴파일할 때 if-else 같은 의사결정 동작은 컴파일 시 처리되어 앱에 고정며 수정 불가

    • 반대로 늦은 바인딩(late binding)은 코드가 실행되는 순간에 동작이 결정됨

  • 이른 바인딩은 if 문을 수정해야 변경할 수 있기 때문에 추가에 의한 변경을 방해한다.

    • 역으로 늦은 바인딩은 추가를 통한 변경이 가능하게 한다.

늦은 바인딩은 클래스 타입 코드 대체, 전략 패턴의 도입이라는 리팩터링 패턴을 다룰때 확인

의도

  • if는 조건 연산자로 흐름을 제어한다.

    • 다음에 실행할 코드를 결정

  • 객체지향 프로그래밍에는 객체라는 훨씬 더 강력한 제어 흐름 연산자가 존재

  • 인터페이스를 사용한 두 가지 다른 구현이 있는 경우 인스턴스화하는 클래스에 따라 실행할 코드를 결정할 수 있음

  • 이 규칙은 더 강력하고 더 유연한 도구인 객체를 사용하는 방안을 찾게한다.

리팩터링 패턴: 클래스로 타입 코드 대체

열거형(switch, esle if)을 인터페이스로 변환하고 열거형의 값들을 클래스가 된다.

  • 각 값에 속성을 추가하고 해당 특정 값과 관련된 기능을 특성에 맞게 만들 수 있다.

  • 열거형의 값을 클래스로 변환할 때는 다른 열거 값을 고려하지 않고 해당 값과 관련된 기능을 그룹화 가능

    • 기능을 해당 값의 특징에 맞게 만들기 가능

  • 열거형에 새 값을 추가하는 것은 수많은 파일에 걸쳐 해당 열거형과 연결된 로직들을 확인해야하는 반면

  • 인터페이스를 구현한 새로운 클래스를 추가하는 것은 해당 클래스에 메서드의 구현이 필요할 뿐, 다른 코드를 수정하지 않아도 됨

클래스로 코드 이관과 함께 사용해서 추가를 통한 변경으로 이어진다.

타입 코드

정수 타입 또는 일치 비교 연산자 === 를 지원하는 모든 타입은 타입 코드로 볼수 있다.

  • 가장 일반적으로 enum을 사용

  • 타입 코드 또한 열거형이 아닌 형태로 변환 가능

const SMALL = 33;
const MEDIUM = 37;
const LARGE = 42;

// After
enum TShirtSizes = {
  SMALL = 33;
  MEDIUM = 37;
  LARGE = 42;
}

절차

  1. 임시 이름을 가진 새로운 인터페이스를 도입

    1. 인터페이스에는 열거형(enum)의 각 값에 대한 메서드가 있어야 한다.

  2. 열거형의 각 값에 해당하는 클래스를 만듦

    1. 클래스에 해당하는 메서드를 제외한 인터페이스의 모든 메서드는 false를 반환해야 함

  3. 열거형의 이름을 다른 이름으로 변경

    1. 컴파일러가 열거형을 사용하는 모든 코드에 오류를 발생시키기 위함

  4. 타입을 이전 이름에서 임시 이름으로 변경하고 일치성 검사를 새로운 메서드로 대체

  5. 남아있는 열거형 값에 대한 참조 대신 새로운 클래스를 인스턴스화하여 교체

  6. 오류가 더이상 없다면 인터페이스의 이름을 모든 위치에서 영구적인 것으로 바꿈

// Before
enum TrafficLight {
  RED, YELLOW, GREEN
}
const CYCLE = [TrafficLight.RED, TrafficLight.Yellow, TrafficLight.GREEN]
function updateCarForLight(current: TrafficLight) {
  if (current === TrafficLight.RED)
    car.stop()
  else
    car.drive()
}

// After 
//#1.새로운 인터페이스
interface TrafficLight2 {
  isRed(): boolean
  isYellow(): boolean
  isGreen(): boolean
}
//#2.새로운 클래스들
class Red implements TrafficLight2 {
  isRed() {return true}
  isYellow(): {return false}
  isGreen(): {return false}
}
class Yellow implements TrafficLight2 {
  isRed() {return false}
  isYellow(): {return true}
  isGreen(): {return false}
}
class Green implements TrafficLight2 {
  isRed() {return false}
  isYellow(): {return false}
  isGreen(): {return true}
}
//#3.기존 열거형 이름 바꾼후 에러 발생시키기
enum RawTrafficLight {
  RED, YELLOW, GREEN
}

//#4.임시 이름으로 변경 일치 여부 검사
function updateCarForLight(current: TrafficLight2) {
  if (current.isRed())
    car.stop();
  else
    car.drive();
}

//#5 열거형 값에 대한 나머지 참조를 새 클래스 인스턴스로 교체
const CYCLE =[new Red(), new Yellow(), new Green()];
//#6 더이상 오류가 없다면 인터페이스 이름을 모든 위치에서 영구적인 이름으로 변경
interface TrafficLight {...}

이 패턴은 자체적으로 많은 가치를 가지진 않지만 나중에 환상적인 개선을 가능하게 한다.

모든 값에 대한 메서드를 갖는 것도 스멜이지만, 이것은 하나의 스멜을 다른 스멜로 대체한 것

is로 시작하는 메서드들은 일시적인 것이고 오래 사용할 것은 아니라는 점에 유의

클래스로 코드 이관하기

  • handleInput 의 모든 조건은 매개변수 input과 관련있고, 이는 해당 클래스에 있어야함을 의미

class Right implements Input {
  //...
  handleInput() {
    if (this.isLeft())
      moveHorizontal(-1);
    else if (this.isRight())
      moveHorizontal(1);
    else if (this.isUp())
      moveHorizontal(-1);  
    else if (this.isDown())
      moveHorizontal(1);  
  }
}

//# 새로운 인터페이스
interface Input {
  //...
  handle(): void
}

//# 네가지 클래스 모두에서 handleInput 메서드 변경  
class Right implements Input {
  //...
  handle() { moveHorizontal(1); }
}

function handleInput(input: Input) {
  input.handle()
}

Result

//Before
function handleInput(input: Input) {
  if (input.isLeft())
    moveHorizontal(-1);
  else if (input.isRight())
    moveHorizontal(1);
  else if (input.isUp())
    moveHorizontal(-1);  
  else if (input.isDown())
    moveHorizontal(1);  
  }
}

//After
function handleInput(input: Input) {
  input.handle();
}

interface Input {
  // ...
  handle(): void
}

class Left implements Input {
  //...
  handle() { moveHorizontal(-1); }
}

클래스로의 코드 이관

이 패턴은 기능을 클래스로 옮기기 때문에 클래스로 타입 코드 대체 패턴의 자연스러운 연장선

  • 결과적으로 if 구문이 제거되고 기능이 데이터에 더 가까이 이동한다.

  • 특정 값과 연결된 기능이 값에 해당하는 클래스로 이동하기 때문에 이는 불변속성을 지역화하는데 도움된다.

  • 가장 단순한 형태로, 항상 메서드 전체를 클래스로 옮긴다고 가정

리팩터링 패턴: 메서드의 인라인화

  • 프로그램에서 더 이상 가독성에 도움이 되지 않는 메서드를 제거

  • 메서드에서 이를 호출하는 모든 곳으로 코드 옮기기 -> 안전한 방법

  • 메서드의 인라인화를 수행할 때는 모든 호출 측을 수정하여 원래의 메서드를 제거한다.

인라인화해서는 안되는 메서드

const NUMBER_BITS = 32;
function absolute(x: number) {
  return (x ^ x >> NUMBER_BITS-1) - (x >> NUMBER_BITS-1);
}
  • 목적을 위해 낮은 수준의 연산에 의존하며 메서드로 존재하는 것이 가독성에 도움이 되기에 인라인해서는 안됨

    • 이 경우 인라인하면 '작업은 동일한 추상화 수준에 있어야한다'에 반하는 스멜이 생김

$ 기호는 _처럼 다른 문자들과 동일하게 취급된다. 따라서 변수명에 사용될 수 있고 특별한 의미가 없다

긴 if 문의 리팩토링

메서드 전문화

  1. 전문화하려는 메서드 복제

  2. 메서드 중 하나의 이름을 새로 사용할 메서드의 이름으로 변경하고 전문화하려는 매개변수를 제거(또는 교체)

  3. 매개변수 제거에 따라 메서드를 수정해서 오류 제거

  4. 이전의 호출을 새로운 것을 사용하도록 변경

//Before
function canMove(start: Tile, end: Tile, dx: number, dy: number) {
  return dx * abs(start.x - end.x) === dy * abs(start.y, end.y)
      || dy * abs(start.x - end.x) === dx * abs(start.y - end.y);
}
//...
if (canMove(start, end, 1, 0))//룩
//...
if (canMove(start, end, 1, 1))//비숍
//...
if (canMove(start, end, 1, 2))//나이트
//...

//After
//#1. 전문화하려는 메서드 복제
//#1-1. 메서드 중 하나를 새로운 이름으로 바꾸고 전문화할 매개변수를 제거(또는 교체)
function canMove(start: Tile, end: Tile, dx: number, dy: number) {
  return dx * abs(start.x - end.x) === dy * abs(start.y, end.y)
      || dy * abs(start.x - end.x) === dx * abs(start.y - end.y);
}

function rookMove(start: Tile, end: Tile) {
  return 1 * abs(start.x - end.x) === 0 * abs(start.y, end.y)
      || 0 * abs(start.x - end.x) === 1 * abs(start.y - end.y);
}
//#2. 이전의 호출을 새로운 것으로 변경
if (rookCanMove(start, end))
  • 메서드를 전문화함으로써 일반화를 제거하고 훨씬 더 이해하기 쉬워짐

Switch가 허용되는 유일한 경우

let rawMap: RawTile[][] = [[...], [...], ...];
let map: Tile2[][];

function assertExhausted(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function transformTile(tile: RawTile) {
  switch (tile) {
    case RawTile.AIR: return new Air();
    //...
    default: assertExhausted(tile);
  }
}
// map 전체를 매핑하는 새로운 메서드
function transformMap() {
  map = new Array(rawMap.length);
  for (let y=0; y < rawMap.length; y++) {
    map[y] = new Array(rawMap[y].length);
    for(let x=0; x < rawMap[y].length; x++) {
      map[y][x] = transformTile(rawMap[y][x]);
    }
  }
}

window.onload = () => {
  transformMap();
  gameLoop();
}

따라서 숫자와 열거형 간의 변환이 필요하지 않으며 이전 코드와 같이 열거형 인덱스를 사용하면 된다.

transformTile은 다섯 줄 제한을 위반하낟. 또한 Switch를 사용하지 말것이라는 또다른 규칙은 타입스크립트의 트릭을 사용해 예외 케이스로 겨우 피해간다.

Switch를 사용하지 말 것

default 케이스가 없고 모든 case에 반환 값이 있는 경우가 아니라면 switch를 사용하지 말자.

  • switch는 각각 버그로 이어지는 두 가지 '편의성'을 허용하기 때문에 문제가 발생한다.

    • 1. switch로 case를 분석할 때 모든 값에 대한 처리를 실행할 필요가 없다.

      • 이를 위해 switch는 default 키워드를 지원 (default로 여러 값을 중복없이 지정 가능)

    • 2. switch를 사용하는 경우 무엇을 처리할지와 무엇을 처리하지 않을지는 이제 불변속성

      • 그러나 기본값이 지정된 다른 경우와 마찬 가지로 새로운 값을 추가할 때 이러한 불변속성이 여전히 유효한지 컴파일러를 통해 판단할 수 없게 된다.

      • 컴파일러 입장에선 우리가 새로 추가한 값의 처리를 잊은건지, default에 지정하고자 한 것인지를 구분할 방법이 없다.

    • 3. break 키워드를 만나기 전까지 케이스를 연속해서 실행하는 풀스루(fall-through) 로직이다.

      • break 키워드를 누락한것을 알아채지 못하기 쉽다.

  • 일반적으로 switch는 멀리하는것이 좋다.

  • 기능을 default에 두지 않는 것

    • 사용하는 언어가 default의 생략을 허용하지 않으면 switch를 사용하지 말아야 한다.

  • 모든 케이스에 return을 지정해서 풀스루 문제를 해결

타입스크립트에서는 컴파일러가 switch 문에서 모든 열거 값을 매핑했는지 확인할 수 있기 때문에 switch 문이 특별히 유용하다.

// 컴파일러가 모든 값을 매핑했는지 확인할 수 있게 할 경우
// 다섯 줄 제한에 맞게 변환할수 없는 몇 안되는 함수 중 하나
function assertExhausted(x: never): never {
  throw new Error("Unexpected object: " + x);
}
//...
switch (t) {
  case ...: return ...;
  //...
  default: assertExhausted(t)
}

스멜

  • switch는 컨텍스트, 즉 값 X를 처리하는 방법에 초점을 맞춘다.

  • 반대로 클래스에 기능을 밀어 넣을 때는 데이터, 즉 이 값(객체)이 상황 X를 처리하는 방법에 초점을 맞춘다.

  • 컨텍스트에 초점을 맞춘다는 것은 데이터에서 불변속성을 더 멀리 위치시켜 불변속성을 전역화하는 것을 의미

인터페이스 대신 추상 클래스를 사용할 수는 없나?

추상 클래스를 사용하여 코드의 중복을 피할 수 있지만 단점이 존재한다.

인터페이스를 사용하면 이를 통해 도입한 각 새로운 클래스에 대해 개발자는 능동적으로 무엇인가를 해야함

잘못해서 속성을 잊어버리거나, 해서는 안되는 오버라이드를 방지 할 수 있게된다.

즉, 시간이 지나서 내용을 잊어버린 채 작업할 ㄹ때 새로운 클래스를 추가해야할 때 문제가 발생한다. 이러한 개념은 중요해서 추상 클래스를 사용하지 못하도록 인터페이스만 상속받을 것이라는 규칙을 공식화 하기도 한다

규칙: 인터페이스에서만 상속받을 것

상속은 오직 인터페이스를 통해서만 받는다.

  • 단순히 클래스, 추상 클래스가 아닌 인터페이스에서만 상속할 수 있다

추상 클래스의 장점은 메서드의 기본 구현을 제공하고 다른 메서드는 추상화여 중복을 줄이고 코드의 줄을 줄이는데 효과가 있지만 단점이 훨씬 많다.

  • 코드 공유는 결합(커플링)을 유발, 이 경우 커플링은 추상 클래스의 코드가 된다.

  • 한 하위 클래스에서 추상 클래스에서 제공하는 methodA와 methodB 중 하나만 필요하다면 빈 버전으로 재정의 해야함

  • 컴파일러를 통해 재정의가 필요한 메서드인지 잡아내기 어려움

Previous긴 코드 조각내기Next유사한 코드 융합하기

Last updated 1 year ago

열거형은 자바에서와 같이 클래스가 아닌 C#에서와 같은 숫자에 대한 명칭이다.

Previous긴 코드 조각내기