리팩토링2 스터디 - 002

14 분 소요

2장 리팩터링 원칙

리팩터링 정의

  • 사전적 의미
    • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
  • 리팩토링의 특별한 원칙
    • 코드가 항상 정상작동해야 한다.
    • 수 많은 단계로 나누어 진행
    • 겉보기 동작이 동일해야 한다(기능은 똑같지만 성능은 다를 수 있음)
    • 리팩토링 과정에서 발생한 버그는 리팩토링 후에도 남있어야 한다
    • 리팩토링의 목적은 코드를 이해하고 수정하기 쉽게 만드는 것이다

두 개의 모자

  • “기능 추가” 와 “리팩터링”
    • 기능 추가
      • 기존 코드는 절대 건디리지 않고 새 기능을 추가하기만 한다
      • 진척도는 테스트를 통과하는지 확인하는 방식으로 측정한다
    • 리팩터링
      • 오로지 코드 재구성에만 전념한다
      • 테스트도 새로 만들지 않는다(부득이 인터페이스를 변경해야 할 때만 기존 테스트를 수정한다)
  • 기능 추가와 리팩터링 모자를 바꿔 가면서 한 번에 한 가지 일만 수행해야 한다

리팩터링하는 이유

소프트웨어 설계가 좋아진다

  • 아키텍처를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 기반 구조가 무너지기 쉽다
  • 이러한 소프트웨어는 코드만 봐서는 설계를 파악하기 어려워진다
  • 설계의 파악이 어려워 질 수록 설계의 유지가 어려워지고 부패되는 속도가 빨라진다
  • 규칙적인 리팩토링은 코드의 구조를 지탱해준다
  • 중복 코드 제거는 설계 개선 작업의 중요한 축을 차지한다
  • 시스템이 빨라지지는 않지만 코드가 갈수록 실수 없이 수정하기 쉬워진다
  • 모든 코드가 언제나 고유한 일을 수행함을 보장할 수 있다

소프트웨어를 이해하기 쉬워진다

  • 코드는 컴퓨터만 사용하는 것이 아니다
  • 사실 프로그래밍에서 가장 중요한 것은 사람이다
  • 코드를 이해하기 쉽게 만드려면 리듬에 변화를 주어야 한다
  • 리팩토링은 코드의 목적을 더 잘 드러나게, 의도를 더 명확하게 전달하도록 개선한다
  • 다른 사람은 나 자신일 수도 있다(자신이 다시 그 코드를 보게될 가능성)

버그를 쉽게 찾을 수 있다

  • 코드의 의도가 명확해질 수록 버그를 찾기 쉬워진다
  • 리팩토링은 견고한 코드를 작성하는데 무척 효과적이다

프로그래밍 속도를 높일 수 있다

  • 한 시스템을 오래 개발하다보면 처음에는 진척이 빠르다가 나중에는 새 기능 하나 추가하는 데 훨씬 오래 걸린다
  • 새로운 기능을 추가하는 데 기존 코드베이스에 잘 녹여낼 방법을 찾는 데 드는 시간이 늘어난다는 것이다
  • 기능을 추가하면 버그가 발생하는 일이 잦고, 이를 해결하는데는 더 많은 시간이 걸린다
  • 소프트웨어 품질이 높으면 새 기능을 더 빨리 추가할 수 있다
  • 모듈화가 잘 되어 있으면 전체 코드베이스 중에 작은 일부만 이해하면 된다
  • 지구력 가설(Design Stamina Hypothesis)
    • 내부 설계에 심혈을 기울이면 소프트웨어의 지구력이 높아져서 빠르게 개발할 수 있는 상태를 더 오래 지속할 수 있다
  • 20년 전에는 설계를 잘하려면 코딩을 시작하기전 설계부터 완벽해야 한다는 것이 정설이었다
  • 리팩토링을 하면 기존 코드의 설계를 개선할 수 있고, 요구사항이 바뀌더라도 설계를 지속해서 개선할 수 있다
  • 결국 빠른 개발이라는 숭고한 목표를 달성하려면 리팩터링이 필요하다

언제 리팩터링해야 할까

  • 3의 법칙
    • 처음에는 그냥한다
    • 비슷한 일을 두 번째로 하게 되면, 일단 계속 진행한다
    • 비슷한 일을 세 번째 하게 되면 리팩터링한다

준비를 위한 리팩터링: 기능을 쉽게 추가하게 만들기

  • 리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전이다
  • 직진을 하기 보다는 잠깐, 지도를 보고 가장 빠른 경로를 찾아가는 것이 리팩토링이다
  • 버그을 잡을 때도 오류를 일으키는 코드가 세 곳에 복제되어 있다면, 우선 하나로 합치는 작업을 진행한다
  • 질의 코드에 섞여 있는 갱신 로직을 분리하면 두 작업이 꼬여서 생기는 오류를 크게 줄일 수 있다
  • 준비를 위한 리팩터링(Preparatory Refactoring)으로 상황을 개선해 놓으면 버그가 수정된 상태가 오래 지속될 가능성을 높이는 동시에, 다른 곳의 버그 발생 가능성도 줄일 수 있다

이해를 위한 리팩터링: 코드를 이해하기 쉽게 만들기

  • 코드를 수정하려면 우선 그 코드가 하는 일을 파악해야 한다
  • 코드가 리팩토링할 여지가 없는지 찾아본다
  • 로직 구조, 함수 이름 등을 분석한다
    • 잘못된 로직 구조
    • 잘못 지어진 함수 이름
  • 리팩터링을 하면 머리로 이해한 내용을 코드에 옮겨 담을 수 있다
  • 이해를 위한 리팩터링(Comprehension Refactoring)
    • 역할을 이해한 변수는 적절한 이름으로 바꿔줌
    • 긴 함수는 잘게 나눈기
    • 이러한 행위로 설계를 파악할 수 있음
    • 밖을 잘 보기 위한 창문닦기
  • 코드를 분석할 때 리팩터링을 하면 더 깊은 수준까지 이해하게 된다

쓰레기 줍기 리팩터링

  • 비효율적 코드를 발견할 때가 있다
    • 로직이 쓸데없이 복잡
    • 매개변수화한 함수 하나면 될 일은 거의 똑같은 함수 여러 개로 작성
  • 원래 하던 일을 하던 중 관련없는 일에 너무 많은 시간을 뺏앗길 수 있다
  • 쓰레기 줍기 리팩터링(Litter-Pickup Refactoring)
    • 수정할 수 있는 것은 즉시 수정
    • 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음, 하던 일 이후 처리
  • 조금이나마 개선을 하고 코드를 흝어볼 때마다 조금씩 개선하면 문제는 해결된다
  • 리팩토링의 멋진 점은 각각의 작은 단계가 코드를 깨뜨리지 않는다는 것이다

계획된 리팩터링과 수시로 하는 리팩터링

  • 기회가 될때 진행
    • 준비를 위한 리팩터링
    • 이해를 위한 리팩터링
    • 쓰레기 줍기 리팩터링
  • 개발에 들어가기 전 리팩터링 일정을 따로 잡아두지 않고, 기능을 추가하거나 버그를 잡는 동안 리팩터링을 함께 진행
  • 프로그래밍 과정에 리팩터링을 녹이는 방식
  • 보기 싫은 코드를 발견하면 리팩터링하자, 잘 작성된 코드 역시 수많은 리팩터링을 거쳐야 한다
  • 무언가 수정하려 할 때는 먼저 수정하기 쉽게 정돈하고 그런 다음 수정하자
  • 새 가능을 추가하기 쉽게 코드를 수정하는 것이 가장 빠르게 추가하는 길이다
  • 리팩토링 작업 대부분은 드러나지 않게, 기회가 될 때마다 해야 한다
  • 버전 관리 시스템에서 리팩터링 커밋과 기능 추가 커밋을 구분하지 말라

오래 걸리는 리팩터링

  • 팀 전체가 오랜 시간을 들여 리팩터링에 매달리기 보다는 주어진 문제를 해결하는데 몇 주에 걸쳐 조금씩 해결해가는 편이 효과적일 때가 많다
  • 누구든지 원하는 방향으로 조금씩 개선하는 식이다
  • 추상화로 갈아타기(Branch By Abstraction)
    • 라리브러리를 교체할 때는 기존 것과 새 것 모두를 포용하는 추상 인터페이스부터 마련하라
    • 기존 코드가 추상 인터페이스로 호출되도록 변경하라

코드 리뷰에 리팩터링 활용하기

  • 코드 리뷰는 지식을 전파하는 데, 경험이 많은 개발자의 노하우를 전수하는데, 대규모 소프트웨어를 이해하는데 도움이 된다
  • 깔끔한 코드를 작성하는데도 도움이 된다
  • 리팩터링은 다른 이의 코드를 리뷰하는 데도 도움이 된다
  • 개선안들을 제시하는데 그치지 않고, 그중 상당수를 즉시 구현해 볼수 있어 코드 리뷰결과를 도출하는 데에도 도움이 된다
  • 풀요청 모델(Pull Request Model)
    • 코드 작성자 없이 검토하는 방식
  • 풀요청 모델에서는 리팩토링은 효과적이지 않다
  • 코드를 작성한 사람과 함께 리팩토링 하는것이 가장 효과적(Pair Programming)

관리자에게는 뭐라고 말해야 할까

  • 관리자가 기술에 정통하고 설계 지구력 가설도 잘 이해하고 있다면 리팩토링의 필요성을 쉽게 설득할 수 있다
  • 기술을 잘 모르는 관리자에게는 리팩터링한다고 말하지 말라
    • 개발자는 프로다
    • 효과적인 소프트웨어를 빠르게 만드는 것이 중요
    • 가장 빠른 개발 방법은 리팩토링을 하는 것
    • 버그를 해결하기 위해서도 이해를 위한 리팩토링을 하는 것이 중요

리팩터링하지 말아야 할 때

  • 지저분한 코드가 없을 때
  • 외부 API 다루듯 호출해서 쓰는 코드라면 지저분해도 그냥 둔다(큰 영향력 없음 코드)
  • 내부 동작을 이해해야할 때 리팩토링을 한다
  • 새로 작성하는 것이 더 쉬울 때

리팩터링 시 고려할 문제

새 기능 개발 속도 저하

  • 리팩터링은 ‘클린 코드’나 ‘바람직한 엔지니어링 습관’처럼 도적적인 이유로 정당화 하면 안된다
  • 리팩터링은 본질은 코드베이스를 예쁘게 꾸미는 데 있지 않다, 오로지 경제적인 이유로 하는 것이다
  • 리팩터링은 개발 기간을 단축하고자 하는 것이다

코드 소유권

  • 함수를 호출하는 코드의 소유자가 다른 팀 혹은 API로 제공된 것이라면 리팩토링에 방해가 된다
  • 기존 함수를 유지하되 함수 본문에서 새 함수를 호출하도록 수정한다
  • 기존 인터페이스를 폐기 대상으로 지정하고 시간이 흐른뒤 삭제하거나 영원히 남겨둬야 할 수 도 있다
  • 코드의 소유권을 팀에 두는 것이 바람직 하다
  • 브랜치를 따서 수정하고 커밋을 요청하는 오픈소스 개발 모델은 코드 소유권을 엄격히 제한하는 방식과 완전히 풀어서 변경을 통제하기 어려운 방식을 절충한 것으로 대규모 개발에 어울린다

브랜치

  • 코드베이스에서 브랜치를 따서 어느 정도 쌓이면 마스터에 통합해서 다른 팀원과 공유하는 방식이 있다
  • 이러한 방식은 작업이 끝나지 않은 코드가 마스터에 섞이지 않고, 기능이 추가될 때마다 버전을 명확히 나눌 수 있고, 기능에 문제가 생기면 이전 상태로 되돌리기 쉽다
  • 이러한 방식은 독립 브랜치로 작업하는 시간이 길어질 수록 마스터와 통합하기 어렵다
  • 이러한 고통을 줄이고자 각 개발자가 자신의 개인 브랜치로 리베이스, 머지를 한다
  • 하지만 여러 기능이 동시에 개발될 때에는 이런 식으로 해결할 수 없다
  • 머지와 통합을 명확히 구분해야 한다
    • 마스터를 브랜치로 머지하는 작업은 단방향이다. 브랜치만 바뀌고 마스터는 그대로다
    • 통합은 마스터를 개인 브랜치로 가져와서(pull) 작업한 결과를 다시 마스터에 올리는(push) 양방향 처리를 뜻한다. 마스터와 브랜치가 모두 변한다
  • 최신 버전 관리 시스템은 복잡한 변경 사항을 텍스트 수준에서 머지하는 데는 매우 뛰어나지만, 코드의 의미는 전혀 이해하지 못한다
  • 개발 기간이 길어질 수록 머지가 복잡해 지기 때문에 브랜치의 통합 주기를 2~3일 단위로 짧게 관리해야 한다는 사람이 많다
  • 더 짧은 통합 방법
  • 이 방식을 Continuous Integration(CI), 또는 트렁크 기반 개발(Trunk-Based Development) TBD라 한다
  • CI에 따르면 모든 팀원은 하루에 최소 한 번은 마스터와 통합한다
  • 마스터와의 차이가 줄어들어 머지의 복잡도를 상당히 낮출 수 있다
  • 이러한 CI를 위해 건강한 마스터를 유지하고, 거대한 기능을 잘게 쪼개고, 각 기능을 기능 토글(feature toggle) 기능 플래그(feature flag)을 적용하여 완료하지 않는 기능이 시스템을 망치지 않게 해야한다
  • CI는 리팩터링과 궁합이 아주 좋다
  • 리팩터링을 하다 보면 코드베이스 전반에 자잘하게 수정하는 부분이 많다
  • 기능별 브랜치에서는 머지 과정에서 심각한 문제가 발생할 수 있다
  • CI와 리팩터링을 합쳐 익스트림 프로그래밍 eXtream Prgramming(XP)라 한다
  • 기능별 브랜치를 하더라도 브랜치를 자주 통합할 수 있다면 문제 발생을 낮출 수 있다

테스팅

  • 리팩토링시 동작을 유지하고 오류를 빠르게 잡기 위해서는 코드의 다양한 측면을 검사하는 테스트 스위트(Test Suite)가 필요하다
  • 이를 빠르고 실행할 수 있어야 수시로 테스트하는 데도 부담이 없다
  • 즉 리팩토링하기 위해서는 자가테스트 코드(Self-testing code)를 마련해야 한다
  • 자가테스트 코드는 리팩토링을 지원하고, 새 기능 추가도 훨씬 안전하게 진행할 수 있도록 도와준다
  • 테스트 주기가 짧다면 버그를 휠씬 쉽게 찾을 수 있다
  • 테스팅은 리팩터링 시 버그가 생길 위험을 제거해 준다
  • 뛰어난 자동 리팩토링 기능을 제공하는 환경이라면 굳이 테스트를 하지 않아도 오류가 생기지 않는다
  • 그래서 검증된 리팩터링 방식만 조합해서 사용하자는 흐름이 등장하기도 했다
  • 자가 테스트 코드는 통합 과정에서 발생하는 충돌을 잡는 메커니즘으로 활용되어 CI와도 밀접하게 연관된다
  • CI에 통합된 테스트는 XP의 권장사항이자 지속적 배포(Continuous Delivery CD)의 핵심이다

레거시 코드

  • 레거시 코드를 리팩터링 하기 위해서는 테스트를 보강하는 것이다
  • 프로그램에서 테스트를 추가할 틈새를 찾아 시스템을 테스트 해야한다
  • 이러한 틈새를 만들 때 리팩토링이 활용된다
  • 캠핑 규칙에 따라 처음 왔을 때보다 깨끗하게 치우는 방식으로 조금씩 개선해 나간다

데이터베이스

  • 진화형 데이터베이스 설계(Evolutionary Database Design) 와 데이터베이스 리팩터링
  • 커다란 변경들을 쉽게 조합하고 다룰 수 있는 데이터 마이그레이션 스크립트를 작성하고 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 이 스크립트로 처리하게끔 통합하는데 있다
  • 데이터 구조의 원래 선언과 데이터 구조를 호출하는 코드를 모두 찾아서 한 번에 변경해야 한다
  • 데이터 베이스 리팩터링은 프로덕션 환경에 여러 단게로 나눠서 릴리스 하는 것이 대체로 좋다
  • 병렬 수정(Paraller Change or Expand Contract)
    • 필드 이름을 바꿀 때 첫 커밋에서는 새로운 데이터베이스 필드를 추가만 하고 사용하지 않는다
    • 기존 필드와 새 필드를 동시에 업데이트하도록 설정한다
    • 데이터베이스를 읽는 클라이언트들을 새 필드를 사용하는 버전으로 조금씩 교체한다
    • 이 과정에서 버그를 해결하고 필요 없어진 예전 필드를 삭제한다

리팩터링, 아키텍처, 애그니(YAGNI) → 다시 봐야함

  • 리팩터링이 아키텍처에 미치는 실질적인 효과는 요구사항 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해준다는 데 있다
  • 소프트웨어 설계전에 요구사항을 모두 확정하는 것은 불가능 하다
  • 그래서 방안으로 향후 변경에 유연하게 대처할 수 있는 유연한 메커니즘(flexibility mechanism)을 소프트웨어에 심어두어야 한다
  • 함수를 범용성으로 만들고, 다양한 예상 시나리오에 대응하기 위한 매개변수들을 추가한다(유연성 매커니즘)
  • 그러나 유연성 메커니즘을 잘못 구현할 때도 있고, 함수가 비대해 지거나 잘못 설계된 메커니즘으로 변화에 대응하는 능력을 떨어뜨릴 수 있다
  • 반면 리팩토링을 활용하면 요구사항 변화에 따라 유연하게 현재까지 파악한 요구사항만 해결하는 소프트웨어를 만들고, 아키텍처도 그에 맞게 리팩터링해서 바꾼다
  • 소프트웨어 복잡도에 지장을 주지 않는 메커니즘은 마음껏 추가하지만, 복잡도를 높일 수 있는 유연성 메커니즘은 반드시 검증을 거친 후에 추가한다
  • 리팩터링을 미루면 훨씬 힘들어진다는 확신이 들 때만 유연성 매커니즘을 미리 추가한다
  • 간결한 설계(Simple Desing), 점진적 설계(incremental Design), YAGNO(애그니 You Aren’t Going To Need It)등을 부른다
  • 진화형 아키텍처 원칙(Evolutionary Architecture)
    • 아키텍처 관련 결정을 시간을 두고 반복해 내릴 수 있다는 장점을 활용하는 패턴과 실천법을 추구한다

리팩터링과 소프트웨어 개발 프로세스

  • XP의 두드러진 특징은 지속적 통합, 자가 테스트 코드, 리팩터링 등의 개성이 강하면서 상호 의존하는 기법들을 하나로 묶은 프로세스라는 점이다
  • 자가 테스트 코드와 리팩터링을 묶어서 테스트 주도 개발(Test-Driven Development)이라 한다
  • 리팩터링의 첫 번쨰 토대는 자가 테스트 코드다
  • 오류를 확실히 걸러내는 테스트를 자동으로 수행할 수 있어야 한다
  • 각 팀원이 다른 사람의 작업을 방해하지 않으면서 언제든지 리팩토링을 할 수 있어야 한다
  • 지속적 통합을 적극 권하는 이유는 이것 때문이다
  • 추측에 근거한 수많은 유연성 메커니즘을 갖춘 시스템보다는 단순한 시스템이 변경하기가 훨씬 쉽다

리팩터링과 성능

  • ‘직관적인 설계’ VS ‘성능’
  • 리팩터링을 하면 소프트웨어가 느려질 수 있는건 사실이다 다만 동시에 성능을 튜닝하기는 더 쉬워진다
  • 하드 리얼타임(Hard Real-Time) 시스템을 제외한 소프트웨어를 빠르게 만드는 비결은, 먼저 튜닝하기 쉽게 만들고 원하는 속도가 나게끔 튜닝하는 것이다
  • 소프트웨어를 작성하는 방법
    • 시간 예산 분배(time budgeting) 방식
      • 하드 리얼타임 시스템에서 많이 사용
      • 설계를 여러 컴포넌트로 나눠서 컴포넌트마다 자원(시간과 공간) 예산을 할당
      • 컴포넌트는 할당된 자원 예산을 초과할 수 없다
      • 단, 주어진 자원을 서로 주고받는 메커니즘을 제공할 수는 있다
      • 엄격한 시간 엄수를 강조한다
    • 끊임없이 관심을 기울이기
      • 프로그램은 전체 코드 중 극히 일부에서 대부분의 시간을 소비한다
      • 특정 부분을 명확한 구조에서 찾아내어 수정
  • 리팩터링 최적화
    • 성능 튜닝에 투입할 시간을 벌 수 있다
    • 프로그램 성능을 더 면밀하게 분석할 수 있다
  • 리팩토링 단계에서 느려진 프로그램을 최적화 단계 에서 코드를 튜닝하기 쉬워 더 빠른 결과물을 만든다

리팩터링의 유래

  • 워드 커닝햄과 켄트 벡 1980년부터 스몰토크를 활용해 개발
    • 스몰토크는 기능이 풍부한 소프트웨어를 빠르게 작성할 수 있는 굉장히 역동적인 환경
    • 리팩토링을 활용해보기 좋았다
    • 컴파일-링크-실행 주기가 상당히 짧음
    • 수정도 빠르게 할 수 있음
    • 객체 지향이라 인터페이스만 잘 정의해두면 내부 수정이 외부에 미치는 영향을 최소로 줄일 수 있음
    • 그 결과로 XP가 탄생
  • 두 사람의 아이디어가 스몰토크 커뮤니티에 큰 반향을 일으키면서 리팩토링이란 개념이 스몰 토크 개발 문화에 중요한 요소로 자리 잡았음
  • 랄프 존슨
    • UIUC 대학교 교수
    • 스몰토크 커뮤니티 주도하던 인물
    • 디자인 패턴 4인의 갱 Gang of Four(GoF)
  • 랄프의 박사 과정 학생인 빌 옵다이크
    • 프레임워크에 관심이 많음
    • 리팩토링을 다른 언어들에게도 적용할수 있다고 생각
    • 리패터링을 개발 도구에서 지원하는 방안을 파악
    • C++ 프레임워크 개발에 유용한 리팩터링에 관심이 많음
    • 의미 보존(semantic-preserving) 리팩터링 기법들을 연구한 결과, 리팩터링 후에도 의미가 보존되는지를 검증하는 방법과 이를 도구로 구현하는 방법을 발표
  • 존 브랜트와 돈 로버츠
    • 빌이 제기한 리팩토링 도구 아이디어를 발전시켜 최초의 리팩터링 도구인 리팩터링 브라우저 개발

리팩터링 자동화

  • 리팩터링을 지원하는 도구의 등장
  • 인텔리제이 IDEA, 이클립스에서 메서드 이름을 바꾸는 작업을 메뉴 항목에서 클릭으로 처리
  • 이 자동 리팩터링 기능은 2000년대 초 자바 커뮤니티에 급속도로 퍼짐
  • 젯브레인 인텔리제이 출시 때 내세운 대표 기능 중 하나가 리팩터링 기능
  • 리팩터링을 자동화하는 가장 어설픈 방법은 소스 코드의 텍스트를 직접 조작하는 것
    • 찾아 바꾸기
    • 허점이 많기 때문에 테스트해보기 전에는 결과를 신뢰하면 안된다
    • Emacs에 해당 매크로를 만들어 활용하는 식으로 리팩터링 속도를 높여라
  • 자동 리팩터링을 제대로 구현하려면 텍스트 상태가 아닌, 구문 트리(syntax tree)로 해석해서 다뤄야 한다
  • 구문 트리가 코드의 원래의미를 보존하는데 유리하다
  • 정적 타입 언어라면 안전하게 구현할 수 있는 리팩터링 수가 늘어난다
  • Salesperson 클래스 Server 클래스에 동일한 이름의 addClient 함수 텍스트로 이름 변경하면 문제발생 가능성 있음
    • 정적 타입이라면 메소드가 속한 클래스를 정확히 알아낼 수 있음

더 알고 싶다면

  • 리팩터링 연습
    • 윌리엄 웨이크 “리팩터링 워크북”
    • 다양한 예제 제공
  • 디자인 패턴과 접목
    • 조슈아 케리에프스카 “패턴을 활용한 리팩토링”
  • 특정 분야 특화 리팩터링
    • 스캇 엠블러와 프라모드 사달게 “리팩토링 데이터베이스”
    • 엘리엇 러스티 해롤드 “리팩토링 HTML”
    • 마이클 페더스 “레거시 코드 활용 전략”
    • 제이 필즈와 셰인 하비 “Refactoring: Ruby Edition”

참고