"리팩토링: 루비 에디션" 후기
2018년 01월 18일

서론

이 책을 접할 때 마침 개인적으로 ruby(정확히는 rails를 위한 ruby) 공부를 하고 있었고,

또한 리팩토링 책도 유명하긴 하지만 java 기반으로 알고 있었는데, 루비 기반으로 된 책이면 둘 다 접할 기회라 생각하여 읽게 되었다.

구성

크게 리팩토링이 무엇인지, 왜 쓰는지, 언제 써야 하는지 등 개요가 나오고 단위 테스트 잠깐 소개, 그 이후는 열거 방식으로 나열되어 있다.

현재 열거 부분 첫 챕터인 메서드 정리 부분까지 본 상태인데, 아직 초반이라 그런지 그 전부터 체득된 패턴들이 많이 나와서 익숙했다.

뒷 부분에는 클래스 추출 등 데이터 설계까지 고려하는 부분이 나오는 것 같은데 읽어봐야 알 것 같다.

메서드 챕터 부분은 메서드를 리팩토링하면서 접할 수 있는 모듈화, 메서드 내 파라미터 등을 어떻게 처리할지 등에 대한 사례들 있는데 사실 이런 과정들은 경험에 의해 체득됬었지, 이렇게 패턴별로 나열해서 생각해본적은 없었던 것 같다.

죽 보면서 이렇게 이 때는 이런게 필요하겠다 하는 식으로 정리해서 내 것으로 가지고 있으면 언젠간 도움이 될 거 같다.

ruby

oop, 동적 타입, 스크립트 언어 ( +함수형 프로그래밍 )

사실 ruby는 큰 기대를 안하고 rails 공부를 위해 접했는데, 신선한 면이 많은 언어였다.

특히 컬렉션을 다루는 enumerable 메서드들이 편리한게 많다.

arr = (1...4).select { |x| x % 2 == 0 } # => [2]

최근 애용하고 있는 lodash에도 이러한 컬렉션 메서드들이 꽤 있다.

그 외 매개변수가 아닌 코드 블럭 자체를 파라미터로 넘기는 등 접할 땐 당황스럽지만 편리한 기능들이 있었다.

리팩토링

Chap 7: 객체 간의 기능 이동

  • Move Method
    • 메서드가 자신이 속한 클래스보다 다른 클래스의 기능을 더 많이 이용할 경우 그 클래스로 메서드 이동
  • Move FIeld
    • 필드가 자신이 속한 클래스보다 다른 클래스에서 더 많이 사용될 경우 그 클래스로 필드 이동
  • Extract Class <-> Inline Class
    • 클래스 분리/합침
      • Hide Delegate ( 대리 객체 은폐 ) <-> Remove Middle Man ( 과잉 중개자 제거 )
        • 객체 내 메서드에서 내부 객체 메서드를 콜하는 상황일때 Forwardable 모듈을 이용 위임하라는 내용인데 루비 한정으로 보여짐
        • 굳이 다른 언어에서 구현하려면 랩핑하는 메서드를 추가하는 정도

Chap 8: 데이터 체계화

  • Self Encapsulate Field
    • indirect/direct vailable access
    • 필드를 직접 접근하지 말고 getter/setter 역할을 하는 메서드를 사용하라는 내용
    • oop에서 당연히 필요한 요소로 생각했으나 반대의 입장도 있는 듯
  • Replace Data Value with Object
    • 한 필드가 단순 데이터에서 복잡한 데이터를 표현해야 할 경우 그 필드를 객체로 전환
  • Change Value to Reference <-> Reference to Value
  • Replace Array with Object
    • row = [ Liverpool, 15 ] => row.name = "Liverpool"; row.wins = "15"
    • 이런 식으로 다른 타입의 데이터들을 나열해서 넣는 케이스가 흔하지는 않은 것 같다.
  • Replace Hash with Object
  • Change Unidirectional Association to Bidirectional <-> Bi to Uni
    • 두 클래스가 서로의 기능을 공유할때 참조/연결되게끔 메서드를 작성
    • 혹은 반대로 종속성/버그발생 증가를 막기 위해 연결을 푸는 경우도 있다.
  • Replace Magic Number with Symbolic Constant
    • 특수한 의미가 있는 상수는 상수명을 명시해준다.
  • Encapsulate Collection
    • 컬렉션은 일반 데이터와 달리 반환할 때 사본을 반환하는 것이 좋다. 데이터 조작 가능성이 있기 때문에.
    • 컬렉션을 바로 set 하는 대신 컬렉션 아이템을 add/remove하는 메서드를 작성
  • Replace Type Code with Polymorphism/Module Extension/State/Strategy
    • 타입 코드가 클래스의 기능에 영향을 미칠때 -> 메서드 내에서 조건 분기를 시키는 대신 module으로 재정의/확장해 사용
  • Replace Subclass with Fields
    • 상수 데이터만 반환하는 하위클래스들이 있을 경우 상위클래스 필드로 합침
    • Person, Male, Female
    • 이런 케이스도 설계가 매우 잘못되지 않는 이상 보기 힘든 케이스로 보임
  • Lazily Initialized Attrtibute <-> Eagerly Initalized Attribute
    • 초기화를 접근 시 해야 하는가? <-> 생성 시 해야 하는가?
    • 접근 시 하는 경우
      • 초기화해야할 필드가 많아져도 가독성 유지 가능
    • 생성 시 하는 경우
      • 초기화 로직이 생성자 안에 캡슐화됨
      • 값 질의 시 일관된 결과 / 디버깅 시 문제 가능성 없음

Chap 9: 조건문 간결화

  • Decompose Conditional
    • 읽기에 복잡한 조건문은 메서드로 따로 뺌
  • Recompose Conditional
    • 삼항 연산자 대입문을 or을 이용해 표기
parameters = params ? params : []; parameters = params || [];
  • 조건문을 명시적 return 문으로 교체
return 2 if days_rented > 2 1
  • Consolidate Conditional Expression
    • 조건문을 합침으로써 가독성 증가 및 메서드 추출의 가능성을 만들 수 있음
  • Consolidate Duplicate Conditional Fregments
    • 조건문의 모든 구간에 같은 코드가 있으면 밖으로 빼낸다. 당연한 내용.
  • Remove Control Flag
# don't do this done = false until done do if ( condition ) # do something done = true end value -= 1 end
# instead of ... until done do if ( condition ) # do something return value end value -= 1 end
  • done 같은 제어 플래그를 쓰는 대신 return 으로 빠져나가게끔 작성한다.
    • 이러한 제어 플래그는 구조적 프로그래밍에서 사용했던 문법의 잔재
  • Replace Nested Conditional with Guard Clauses
    • if-then-else 구조는 읽을 때 if 절과 else 절의 중요성이 똑같다고 판단하게끔 한다.
    • if-else 절이 복잡할때 특이한 case들은 검사( 감시절 : guard clause )하여 return 시킨다
    • “이것은 드문 경우이니 이 경우가 발생한다면 작업을 수행한 후 빠져나가라”
    • 현재 코드의 model 코드에 비슷한 코드
  • Replace Conditional with Polymorphism
    • 타입 등으로 다른 동작을 조건문을 통해 분기시킬 때 재정의(다형성)을 이용
  • Null 검사를 널 객체에 위임
    • 위와 비슷하게 null 검사도 조건문을 통해 검사하지 말고 클래스에 해당하는 널 클래스를 작성
    • 필요성?
  • Introduce Assertion
    • 현재 코드에선 단위 테스트가 이 기능을 하고 있는 것 같다.
    • 또한 단위 테스트가 실제 로직 코드 파일과 분리되어 있고 다양한 케이스를 추가할 수 있음

Chap 10: 메서드 호출 단순화

  • Rename Method
  • 네이밍. 당연해서 부가 설명 필요 없음
  • Add Parameter <-> Remove Parameter
  • 매개변수가 계속 길어지면 가독성이 안좋아지는 경우가 많다. 추가보단 정리를 권함
  • 매개변수가 많을 시 객체로 묶을 수 있는 지 체크
  • Separate Query from Modifier
  • 상태 변경 등 액션 + 리턴을 동시에 하는 메서드가 있으면 둘을 분리
  • 합쳐져 있을 경우 눈에 띄지 않는 부작용이 생길 수 있음
    • ex 여러번 값 조회 테스트 시 내부 상태 값이 예상치 못하게 변하는 케이스
  • 멀티스레딩 환경 개발 시에도 예외는 없다. 값 조회, 상태 변경 기능을 분리 작성 후, 제 3의 메서드에서 합쳐서 사용하면 문제도 안생기고, 모듈화된 두 메서드도 재사용 가능하다.
  • Replace Parameter with Explicit Methods
  • 매개변수에 따라 분기해 다른 코드를 실행할 경우엔 아예 메서드를 분리시켜 버리라는 내용
set_value("height", 10) , set_value("width", 50) -> height(10), width(50),
  • 매개변수가 가변적이면 하지 않는게 좋다.
  • Preserve Whole Object - 객체에서 가져온 여러 값을 파라미터로 전달하고 있는 경우, 아예 객체를 전달
  • Replace Parameter with Method
    • A메서드 결과를 B메서드에 파라미터로 전달한다. 근데 B 내에서도 A메서드를 호출 가능
    • -> B 내에서 A메서드를 호출하게 한다. 당연함
  • Introduce Parameter Object
    • 전달되는 파라미터가 붙어 다닐 경우 객체로 묶는 것을 고려 ( Preserve Whole Object 와 비슷 )
  • Remove Setting Method
    • 필드 값이 변경 후 수정되면 안될 경우 쓰기 메서드를 제거
  • Hide Method
    • 메서드가 다른 클래스에 사용되지 않을 경우 private으로 작성
  • Replace Constructor with Factory Method
    • 생성자 내에 생성할 객체 타입이 변하거나 로직이 복잡할 경우 팩토리 메서드로 전환
  • Replace Error Code with Exception
    • 에러 코드 대신 예외를 사용하라는 내용
    • ruby에 예외 기능이 있어 사용하라는 것 같은데 언어에 따라 다를 수도 있을 것 같다.
    • 시스템 상 예측하지 못한 에러면 예외를 발생시키는게 맞지만 단지 비즈니스 로직 상 실패를 처리할 경우를 모두 예외처리를 하는 것도 안좋은 방식이라는 글을 본적이 있다. ( Java를 비판하는 글로 기억 )
  • Introduce Gateway / Introduce Expression Builder
    • 외부 시스템이나 리소스의 복합 API를 연동해야 할 경우 공통으로 지나는 부분을 모듈화
      • builder 패턴
    • 현재 앱단 코드의 service 쪽을 생각하면 된다.

Chap 11: 일반화 처리

  • Pull Up Method <-> Push Down Method
    • 여러 하위클래스에 중복 메서드가 있으면 상위 클래스로 메서드 이동
    • 상위클래스에 있는 기능이 일부 하위클래스에만 관련돼 있을 때는 관련 하위클래스로 이동
  • Extract Module <-> Inline Module
    • 여러 클래스에 같은 동작이 있을 때는 모듈로 분리하고 각 클래스에서 include
    • 문법은 다르지만 java의 interface와 비슷한 의도로 추정
  • Extract Subclass
    • 클래스에 일부 인스턴스만 사용하는 기능이 있을 경우
    • 하위클래스 작성 후 Push Down Method
  • Introduce Inheritance
    • 여러 클래스의 기능이 비슷할 경우
    • 상위 클래스 작성 후 공통 기능을 Pull up Method
  • Collapse Hierarchy
    • 상위클래스와 하위클래스가 별로 다르지 않을때는 합침
  • Form Template Method
    • 하위클래스들의 메서드의 진행 순서는 비슷하지만 같지는 않은 경우 (a+b), (a+b)
    • 메서드 내 공통 부분을 묶어서 템플릿 메서드의 조합으로 만듬 (a와 b)
    • 그 후 Pull up Method (a+b)
  • Replace Inheritance with Delegation
    • 하위클래스가 상위클래스의 극히 일부만 사용할 경우 상속의 의미가 적어진다.
    • 필드를 추가 후 필요한 기능만 위임해서 사용
    • ex) Hash의 기능 일부를 쓴다고 Hash를 통째로 상속 받는 것보단 Hash 형태 의 필드를 추가 후 필요한 기능만 위임 처리
  • Replace Delegation with Hierarchy
    • 대리 객체의 위임을 많이 작성하게 된다면 차라리 계층 구조로 만드는 것이 나음
  • Replace Abstract Superclass with Module
    • 상속 구조이나 상위클래스를 인스턴스화 시킬 의도가 없을 때
    • Java의 Abstract가 ruby에는 없으므로 module로 구현

비판 - 마틴 파울러 - 내용이 길고 장황한 것에 비해 남는게 없다 ? - -> 그나마 호평을 받은 책은 '클린 코드' 정도.