rkaehdaos / learning-martinfowler_refactoring_2nd

learning-martinfowler_refactoring_2nd
0 stars 0 forks source link

9. 데이터 조직화 #9

Closed rkaehdaos closed 2 years ago

rkaehdaos commented 2 years ago

데이터 구조 → 프로그램에서 중요한 역할 수행 → 데이터 구조에 집중한 리팩터링의 묶음

차례

rkaehdaos commented 2 years ago

9.1 변수 쪼개기 - Split Variable

image

왼쪽에서 존재하던 가변 데이터가 사라짐

배경

변수의 용도 : 다양

변수 추출하기[6-3]와 비교 : → 목적이 다르다

  • 유사점 : 중간 단계에 사용되는 변수를 추가
  • 차이점:
    • 추출하기 : 표현식을 변수로 추출
    • 쪼개기 : 여러번 사용되는 한 변수를 여러 변수로 쪼갠 것 , 변수 하다당 역할 하나를 갖도록 강제화

재성님이 하셨던 문장슬라이드 8-6

const pricingPlan = retrievePricingPlan();
const order = retrieveOrder();
const baseCharge = pricingPlan.base;
let charge;
const chargePerUnit = pricingPlan.unit;
const units = order.units;
let discount;
charge = baseCharge + units * chargePerUnit; // charge 첫 대입
let discountTableUnits = Math.max(units - pricingPlan.discountThreshold, 0);
discount = discountTableUnits * pricingPlan.discountFactor;
if (order.isRepeat) discount += 20;
charge = charge - discount;  // charge 두 번째 대입 , 다른 의미, discountCharge등의 다른 변수로 쪼개기
chargeOrder(charge);

절차

  1. 변수를 선언한 곳과 값을 처음 대입하는 곳에서 변수 이름을 바꾼다
    • 이후의 대입이 항상 i=i+<무언가>형태라면? → 수집변수 → 쪼개면 안된다
    • 수집 변수 : 총합 계산, 문자열 연결, 스트림에 쓰기, 컬렉션에 추가하기등의 용도로 흔히 쓰인다
  2. 가능하면 불변으로 선언한다
  3. 이 변수에 2번째로 값을 대입하는 모든 참조를 새로운 변수명으로 바꾼다
  4. 2번째 대입시 변수를 원래 이름으로 다시 선언
  5. 테스트
  6. 반복

악취

rkaehdaos commented 2 years ago

예시

포인트

acc 변수에 값이 2번 대입된다

  1. 첫번째 힘이 유발한 초기 가속도
  2. 2번째 힘까지 반영된 후의 가속도를 저장하는 함수
  3. 쪼개야 한다

함수나 파일에서 특정 심벌이 쓰인 위치를 시각적으로 강조해주는 코드 편집기를 사용하면 도움이 된다

rkaehdaos commented 2 years ago

변수쪼개기 예시2

function discount(inputValue, quantity) {
    if (inputValue > 50) inputValue = inputValue - 2;
    if (quantity > 100) inputValue = inputValue - 1;
    return inputValue;
}
rkaehdaos commented 2 years ago

9-2 필드 이름 바꾸기 - Rename Field

image

6-7 변수 이름 바꾸기와 유사하지만 클래스의 필드 변수에 한한 케이스라는 것이 차이점

악취

배경

이름은 중요하다, 레코드 구조체의 필드 이름은 특히 더 중요하다

개발을 진행하면서 깊어진 데이터 이해도를 프로그램에 반드시 반영해야 한다

절차

  1. 레코드 유효범위 제한적?→ 필드 접근하는 모든 접근하는 모든 코드 수정 후 테스트 (종료)
  2. 레코드 캡슐화가 되어있는지 확인 →되어있지 않다면 [레코드 캡슐화7.1]
  3. 캡슐화된 객체 안의 private 필드명 변경, 그에 맞게 내부 메서드들 수정
  4. 테스트
  5. 생성자의 매개변수 중 필드와 이름이 겹치는 게 있다면? [함수 선언 바꾸기 6.5]로 변경
  6. 접근자들 이름 바꿔준다[6.5]

예시

rkaehdaos commented 2 years ago

예시에서

rkaehdaos commented 2 years ago

레코드 캡슐하니 ??

이름 변경할 곳이 4곳

입력데이터 구조를 내부 데이터 구조로 복제했으므로 둘을 구분해야 독립적으로 작업할 수 있다 → 별도 필드를 정의하고 생성자/접근자에서 둘을 구분해서 사용하도록 해야 한다

rkaehdaos commented 2 years ago

정리

데이터 구조를 불변으로 만들 수 있는 프로그래밍 언어 사용시

rkaehdaos commented 2 years ago

9.3 파생 변수를 질의 함수로 바꾸기 - Replace Derived Variable with Query

image

7-4 임시 변수를 질의 함수로 바꾸기와 유사(과정이), 목적은 다름

파생 변수

  • 2 개(또는 그 이상)의 기본 변수에서 파생된 변수
  • 백분율, 비율, 지수, 비율
  • 기본 변수에 의존하므로 기본 변수중 하나 이상의 변경으로 파생 변수의 변경 발생 가능하지만 갱신이 안됨
  • 문제를 일으킬 수 있음
  • 질의함수 재정의 필요 : (값이 필요할 떄 다른 변수 값으로부터 계산하는)사용.

만약 discount setter가 없었으면 바꿀 필요가 없다

또다른 차이점 : 목적

  • 임시변수 기법 : 스코프 확대 의도(함수 스코프에서 클래스 스코프로)
  • 파생변수는 이미 클래스 스코프에 존재

배경

가변 데이터의 문제

절차

  1. 변수 값이 갱신 되는 지점을 모두 찾는다 → 필요 시 [변수 쪼개기 9.1]을 활용해서 각 갱신 지점에서 변수를 모두 분리
  2. 해당 변수의 값을 계산해주는 함수를 만든다
  3. 해당 변수가 사용되는 모든 곳에 어셔션 추가하여 함수 계산 결과가 변수의 값과 같은지 확인 → 필요하면 변수 캡슐화하기를 적용해서 어셔션이 들어갈 장소를 마련한다
  4. 테스트
  5. 변수를 읽는 코드를 모두 함수 호출로 대체한다
  6. 테스트
  7. 변수를 선언하고 갱신하는 코드를 [죽은 코드 제거하기 8.9]로 없앤다

악취

예시

작지만 확실하고 보기 흉한 예

rkaehdaos commented 2 years ago

중복?

class ProductionPlan {
    constructor() {
        this._adjustments = [];
        this._production = 0;
    }

    get production() {return this._production;}

    applyAdjustment(anAdjustment) {
        this._adjustments.push(anAdjustment)
        this._production += anAdjustment.amount
    }
}

일반적인 코드 중복이 아닌 데이터 중복

rkaehdaos commented 2 years ago

신중하게 어셔션을 추가해서 검증해보자

rkaehdaos commented 2 years ago

!!!!!!!!!!!!!! 처음부터

rkaehdaos commented 2 years ago

앞과 똑같은 어서션을 작성하면 초기값0이아니면 실패한다.

image

이 파생 데이터 대체할 방법 ? 간단

rkaehdaos commented 2 years ago

책은 여기가 끝이지만 더 진행해보자

rkaehdaos commented 2 years ago

다만 이번에는 인라인하지 않고 속성으로 남겨두는게 좋다

rkaehdaos commented 2 years ago

9-4 참조를 값으로 바꾸기 - Change Reference to Value

image

배경

VO

절차

  1. 후보 클래스가 불변인지, 혹은 불변이 될 수 있는지 확인
  2. 각각의 setter 하나씩 제거[11.7]
  3. 이 값 객체 필드들을 사용하는 동치성 비교 메서드를 만든다
    • 대부분 언어는 이런 상황에 사용할 수 있도록 오버라이딩 가능한 동치성 비교 메서드 제공
    • 동치성 비교 메서드 오버라이드시 해시코드 생성 메서드도 함께 오버라이드 필요

      제 기억으로는 이펙티브 자바에도 아마 비슷한 내용이 있었던 것으로 기억

예시

악취

rkaehdaos commented 2 years ago

대부분의 OOP언어는 값 기반 동치성 비교를 위해 오버라이드 가능한 동치성 검사 수단 기본 제공

  • Ruby : ==연산자 오버라이드
  • Java : Object.equals() 오버라이드

동치성 검사 메서드(equals)를 오버라이드 할 때는 해시코드 생성 메서드도 함께 오버라이드

  • Java : `Object.hashCode() 메서드

    jdk의 List 해시 코드 - 이펙티브 자바 : 매직넘버 31 HashMap : CLRS 나눗셈 방법 롬복 : 예전 277 사용, 현재 59 사용

rkaehdaos commented 2 years ago

9.5 값을 참조로 바꾸기 - Change Value to Reference

앞의 reverse, 앞의 것의 배경과 같이 연관해서 생각해보자

배경

값을 참조로 바꾸면 엔티티 하나당 객체도 단 하나만 존재

가변 데이터 악취

  • 참조에는 반드시 가변 데이터 악취의 문제가 발생
  • 특히 한 객체가 여러 장소에서 사용될 때
  • 예) id 777인 Customer의 객체가 여러 곳에서 사용되기
  • Value 객체인 경우엔 애초에 각자가 별개이므로 상관없음
  • 그러면 이런 객체들을 한대 모아놓고 클라이언트들의 접근 관리해주는 일종의 저장소가 필요하다
  • 저장소로 객체들을 캡슐화
  • 저장소로만 객체들 접근

최근에 말하는 엔티티 : 도메인 모델의 엔티티 : 따라서 도메인 모델의 Entity VS Value객체로 구분해서 생각 엔티티 : 실체, 객체라는 의미, 식별자를 가진다 DB의 엔티티 : 실제로 SQL, DB에 존재하는 것이 아닌 일종의 개념 DB에서 엔티티는 논리 모델에서 사용, 테이블은 물리 모델에서 사용, 테이블은 DB, SQL에 실제로 존재, 물리적 구조

DTO VS VO

  • VO는 위에서 봤듯이 불변, read-only 특징, 값 자체를 의미→따라서 같은 객체 보장을 위해 equals, hashcode를 재정의해줘야 한다
  • DTO : 레이어간 데이터 전소응ㄹ 위해 정의된 객체 : 주로 json 직렬화등 직렬화에 사용되는 객체
  • DTO는 원래 DAO 패턴에서 유래 : DAO에서 DB 처리 로직을 숨기고 DTO값을 내보내는 용도로 활용
  • 현재는 거의 DTO는 엔티티를 프레젠테이션 단까지 내보내지 않기 위해 쓰이는 , 다 내보낼 필요도 없고
  • DTO : getter/setter만을 가지고 비지니스 포함X
  • 둘의 차이 개념상의 차이 : 값 자체에 의미가 있는 VO, 전달된 데이터보존의DTO
  • VO는 setter가 없음 : 불변, readonly가 보장되어야 존재 자체의 신뢰성 확보
rkaehdaos commented 2 years ago

절차

  1. 같은 부류에 속하는 객체들을 보관할 저장소를 만든다 (이미 있다면 생략)
  2. 생성자에서 이 부류의 객체들 중 특정 객체를 정확히 찾아내는 방법이 있는지 확인
  3. 호스트 객체의 생성자들 수정해서 필요한 객체를 이 저장소에서 찾도록 한다.
rkaehdaos commented 2 years ago

예시

주문 클래스

rkaehdaos commented 2 years ago

현재 고객이 값 객체

엔티티 표현 객체가 여러개 만들면 혼란

rkaehdaos commented 2 years ago

step 1 - 저장소 객체

리포지토리 패턴

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. - Edward Hieatt and Rob Mee 도메인 객체에 접근하는 컬렉션과 비슷한 인터페이스를 사용해 도메인과 데이터 매핑 계층 사이를 중재한다 - 에드워드 하얏트, 롭 미 리포지토리에 의해 캡슐화되는 매핑 코드가 적절한 작업을 내부적으로 처리 데이터 저장소에 저장된 객체의 집합과 이를 대상으로 수행하는 작업을 개념상으로 캡슐화해 지속적 계층에 대한 좀 더 객체 지향적 관점을 제공한다

  • 데이터 출처에 상관없이 돌일 인터페이스로 데이터에 엑세스 가능 - DataSource캡슐화
  • 책에서는 이 앞에서 데이터 매퍼 패턴(도메인 객체를 DB 접근 상세로부터 격리하기 위해)을 소개 했는데 복잡한 도메인 모델의 경우 쿼리 구성 코드가 집중되는 매핑 계층을 기반으로 다른 추상 계층을 만듬 - 레포지토리 계층
  • 도메인은 하위에 상관없이 레포지토리만 바라봄
  • 레포지토리 교체로 단위테스트를 통한 검증 가능
rkaehdaos commented 2 years ago

step2 : 주문의 생성자에서 올바른 고객 객체를 얻어오는 방법 : 여기서는 고객ID가 입력 데이터 스트림으로 전달 되서 해결

rkaehdaos commented 2 years ago

이제 특정 주문과 관련된 고객 정보를 갱신하면 같은 고객을 공유하는 주문 모두에서 갱신된 데이터 사용

예시 문제점

rkaehdaos commented 2 years ago

9.6 매직 리터럴 바꾸기 - Replace Magic Literal

배경

매직넘버, 매직리터럴

매직 리터럴은 대체로 숫자가 많지만 다른 타입의 리터럴도 특별한 의미를 지닐 수 있다

솔루션 : 해당 값이 쓰이는 모든 곳을 적절한 이름의 상수로 바꿔주는 방법이 가장 좋다

상수를 과용하는 것을 자제하자

방법

  1. 상수를 선언하고 매직 리터럴을 대입한다
  2. 해당 리터럴이 사용되는 모든 곳을 모두 찾는다
  3. 찾는 곳 각각에서 리터럴이 새 상수와 같은 의미로 쓰였는지 확인하여, 같은 의미라면 상수로 대체 후 테스트한다.