티스토리 뷰
지금까지는 프로그램 요소를 생성/제거하거나 이름은 변경하는 리팩터링을 다뤘다.
여기에 더해 요소를 다른 컨텍스트(클래스나 모듈 등)로 옮기는 일 역시 리팩터링의 중요한 축이다.
- 다른 클래스나 모듈로 함수나 필드를 옮길 때
- 8.1 함수 옮기기
- 8.2 필드 옮기기
- 문장을 함수 안이나 바깥으로 옮길 때
- 8.3 문장을 함수로 옮기기
- 8.4 문장을 호출한 곳으로 옮기기
- 같은 함수 안에서 옮길 때는
- 8.6 문장 슬라이드 하기
- 한 덩어리의 문장들이 기존 함수와 같은 일을 할 때
- 8.5 인라인 코드를 함수 호출로 바꾸기
- 반복문이 단 하나의 일만 수행하도록 보장하는
- 8.7 반복문 쪼개기
- 반복문을 완전히 없애 버리는
- 8.8 반복문을 파이프라인으로 바꾸기
8.1 함수 옮기기 (Move Function)
개요
- 좋은 소프트웨어 설계의 핵심은 모듈화가 얼마나 잘 되어 있느냐를 뜻하는 모듈성이다
- 모듈성이란 프로그램을 수정할때 해당 기능과 깊이 관련된 작은 일부만 이해해도 가능하게 해주는 능력이다
- 서로 연관된 요소들을 묶고, 요소 사이의 연결 관계를 쉽게 찾고 이해할 수 있도록 함수 옮기기 리팩터링이 필요함
적용시점
- 예를 들어 어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소들을 더 많이 참조한다면 B로 옮겨줘야 함
- 함수들의 역할과 연관성을 살펴보고 클래스로 묶기6.9, 클래스 추출하기7.5 등의 해결방식을 함께 고려해야 함
효과
- 함수를 적절한 위치로 이동하여 모듈성을 높인다
before
class Account {
get overdraftCharge() {...}
after
class AccountType {
get overdraftCharge() {...}
8.2 필드 옮기기 (Move Field)
개요
- 주어진 문제에 적합한 데이터 구조를 활용하면 동작 코드는 자연스럽게 단순하고 직관적으로 짜여진다
- 하지만 이는 어려운 일이다. 초기에는 합리적이었던 설계가 다음 주가 되면 잘못된것으로 판명나는 경우가 있다
- 따라서, 적절하지 않은 데이터 구조를 깨닫게 되면 수정하는 리팩터링이 필요함
적용시점
- 현재 데이터 구조가 적절치 않음을 깨닫게 되면 곧바로 수정해야 함
효과
- 고치지 않으면 남겨진 흠들은 우리 머릿속을 혼란스럽게 만들고 훗날 작성하게 될 코드를 더욱 복잡하게 만들어 버림
before
class Customer {
get plan() {return this._plan;}
get discountRate() {return this._discountRate;}
after
class Customer {
get plan() {return this._plan;}
get discountRate() {return this.plan.discountRate;}
8.3 문장을 함수로 옮기기 (Move Statements into Function)
개요
- 중복 제거는 코드를 건강하게 관리하는 가장 효과적인 방법 중 하나이다
- 특정 함수를 호출하는 코드 앞뒤에 반복되는 코드들을 함수로 옮기는 리팩터링임
- 혹시 나중에 동작을 여러 변형들로 나눠야 하는 순간이 오면 반대 리팩터링을 진행한다
적용시점
- 특정 함수를 호출하는 코드가 나올 때마다 그 앞뒤에 똑같은 코드가 추가로 실행되는 모습이 보이는 경우
- 문장들이 피호출 함수의 일부라는 확신이 있는 경우만 진행함
효과
- 반복되는 부분에서 무언가 수정할 일이 생겼을 때 단 한 곳만 수정하면 됨
before
result.push(`<p>title: ${person.photo.title}</p>`);
result.concat(photoData(person.photo));
function photoData(aPhoto) {
return [
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.date.toDateString()}</p>`,
];
}
after
result.concat(photoData(person.photo));
function photoData(aPhoto) {
return [
`<p>title: ${aPhoto.title}</p>`,
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.date.toDateString()}</p>`,
];
}
8.4 문장을 호출한 곳으로 옮기기 (Move Statements to Callers)
개요
- 함수는 추상화의 기본 빌딩 블록이지만 그 경계를 항상 올바르게 긋기가 만만치 않다
- 코드베이스의 기능 범위가 달라지면 추상화의 경계도 움직이게 되며, 특정 동작을 다시 호출자로 옮겨야 하는 경우도 있다
적용시점
- 여러 곳에서 사용하던 기능이 일부 호출자에게는 다르게 동작하도록 바뀌어야 하는 경우
- 만약, 호출자와 호출 대상의 경계를 완전히 다시 그어야 할 정도라면 아래 순서처럼 리팩터링하여 더 적합한 경계를 설정한다
- 함수 인라인하기6.2
- 문장 슬라이드8.6
- 함수 추출하기6.1
효과
- 달라지는 동작을 호출자로 옮긴 뒤에는 필요할 때마다 독립적으로 수정할 수 있게 됨
before
emitPhotoData(outStream, person.photo);
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
outStream.write(`<p>location: ${photo.location}</p>\n`);
}
after
emitPhotoData(outStream, person.photo);
outStream.write(`<p>location: ${person.photo.location}</p>\n`);
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
}
8.5 인라인 코드를 함수 호출로 바꾸기 (Replace Inline Code with Function Call)
개요
- 이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견하면 함수로 대체하는 리팩터링
적용시점
- 이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견할 때
- 예외적으로 우연히 비슷한 코드가 만들어진 경우에는 적용하면 안된다 (목적이 다른 코드)
효과
- 함수 추출하기6.1 와 동일하며 차이점은 오직 인라인 코드를 대체할 함수가 이미 존재하느냐 여부이다
before
let appliesToMass = false;
for(const s of states) {
if (s === "MA") appliesToMass = true;
}
after
appliesToMass = states.includes("MA");
8.6 문장 슬라이드 하기 (Slide Statements)
개요
- 관련된 코드들이 가까이 모여 있다면 이해하기 더 쉽다. 대표적으로 변수를 선언하고 사용하는 부분이다
- 관련 코드끼리 모으는 작업은 다른 리팩터링의 준비 단계로 자주 행해진다
적용시점
- 주로 함수 추출하기6.1 의 준비 작업으로 행해진다
효과
- 함수를 이해하기 쉽게 한다
- 또한, 코드들이 모여 있어야만 함수 추출이 가능해진다
before
const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
after
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
let charge;
8.7 반복문 쪼개기 (Split Loop)
개요
- 그저 두 일을 한꺼번에 처리할 수 있다는 이유로 한 반복문에서 두 가지 일을 수행하는 경우가 있다
- 하지만 이렇게 하면 반복문을 수정할 때마다 두 가지 일 모두를 잘 이해하고 진행해야하는 문제가 있다
- 서로 다른 일들이 한 함수에서 이뤄지고 있다는 신호일 수 있고, 반복문 쪼개기와 함수 추출하기를 연이어 수행하는 일이 잦다
적용시점
- 반복문에서 두 가지 일을 수행하는 경우
- 반복문을 두 번 실행해야 하므로 이 리팩터링을 불편해 하는 프로그래머도 많지만 리팩터링과 최적화를 구분 하자
- 최적화는 코드를 깔끔하게 정리한 이후에 수행하자
- 반복문을 두 번 실행하는게 병목이라 밝혀지면 그때 다시 하나로 합치기는 식은 죽 먹기다
- 실제 긴 리스트를 반복하더라도 병목으로 이어지는 경우는 매우 드물다
- 오히려 반복문 쪼개기를 통해 다른 최적화를 적용할 가능성이 높아진다
효과
- 반복문을 분리하면 수정할 동작 하나만 이해하면 된다
before
let averageAge = 0;
let totalSalary = 0;
for (const p of people) {
averageAge += p.age;
totalSalary += p.salary;
}
averageAge = averageAge / people.length;
after
let totalSalary = 0;
for (const p of people) {
totalSalary += p.salary;
}
let averageAge = 0;
for (const p of people) {
averageAge += p.age;
}
averageAge = averageAge / people.length;
8.8 반복문을 파이프라인으로 바꾸기 (Replace Loop with Pipeline)
개요
- 컬렉션을 순회할때 반복문이 아닌 컬렉션 파이프라인을 사용할 수 있도록 언어는 발전했다
- 이를 이용하면 처리 과정을 일련의 연산으로 표현할 수 있다
적용시점
- 파이프라인으로 교체 가능한 컬렉션 반복문이 있을 경우 (반복문 쪼개기가 선행되어야 함)
효과
- 논리를 파이프라인으로 표현하면 이해하기 훨씬 쉬워진다
before
const names = [];
for (const i of input) {
if (i.job === "programmer")
names.push(i.name);
}
after
const names = input
.filter(i => i.job === "programmer")
.map(i => i.name)
;
참고
http://www.yes24.com/Product/Goods/89649360
https://refactoring.com/catalog
'소프트웨어공학, CS > 리팩터링 2판' 카테고리의 다른 글
[리팩터링 2판] 11장 API 리팩터링 (0) | 2021.05.16 |
---|---|
[리팩터링 2판] 10장 조건부 로직 간소화 (0) | 2021.04.18 |
[리팩터링 2판] 9장 데이터 조직화 (0) | 2021.04.04 |
[리팩터링 2판] 7장 캡슐화 (0) | 2021.03.13 |
[리팩터링 2판] 6장 기본적인 리팩터링 (0) | 2021.03.04 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 마틴파울러
- Debug It! 실용주의 디버깅
- SSL
- aws fargate
- 그림으로 배우는 HTTP & Network
- 일 잘하는 사람은 단순하게 말합니다
- amazon aurora
- Refactoring
- 리팩터링이란
- 위임
- 질의함수
- 조건부 로직
- amazon vpc
- 제어플래그
- 박소연
- 리팩토링
- 지시의 언어
- 디버깅
- Debug
- 코드악취
- 매개변수화
- HTTP
- 코드스멜
- 그림으로 공부하는 IT 인프라 구조
- AWS
- Debugging
- 안심 첫 문장
- https
- 일잘러
- 변경함수
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함