😀 책에서 기억하고 싶은 내용을 써보세요.

 

배경

데이터 항목 여러 개가 이 함수에서 저 함수로 몰려다니는 경우를 자주 본다.

저자는 이런 데이터 무리를 발견하면 데이터 구조 하나로 모아주곤 한다.

  • 데이터 뭉치를 데이터 구조로 묶으면 데이터 사이의 관계가 명확해진다는 이점을 얻는다.
  • 함수가 이 데이터 구조를 받게 하면 매개변수 수가 줄어든다.
  • 같은 데이터 구조를 사용하는 모든 함수가 원소를 참조할 때 항상 똑같은 이름을 사용하기 때문에 일관성도 높여준다.

 

 

절차

  1. 적당한 데이터 구조가 아직 마련되어 있지 않다면 새로 만든다.
    • 개인적으로 클래스로 만드는 걸 선호한다. 나중에 동작까지 함께 묶기 좋기 때문이다. 저자는 주로 데이터 구조를 값 객체(Value Object)로 만든다고 한다.
  2. 테스트한다.
  3. 함수 선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다.
  4. 테스트한다.
  5. 함수 호출 시 새로운 데이터 구조 인스턴스를 넘기도록 수정한다. 하나씩 수정할 때마다 테스트
  6. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다.
  7. 다 바꿨다면 기존 매개변수를 제거하고 테스트한다.

 

예시

온도 측정값 배열에서 정상 작동 범위를 벗어난 것이 있는지 검사하는 코드(예시)

const station = { name: "ZB1",
				readings: [
                            {temp: 47, time: "2016-11-10 09:10"},
                            {temp: 53, time: "2016-11-10 09:20"},
                            {temp: 58, time: "2016-11-10 09:30"},
                            {temp: 53, time: "2016-11-10 09:40"},
                            {temp: 51, time: "2016-11-10 09:50"},
                        ]
};

 

정상 범위를 벗어난 측정값을 찾는 함수

function readingOutsideRange(station, min, max) {
	return station.readings
			.filter(r => r.temp < min || r.temp > max);
}

 

호출문

alerts = readingsOutsideRange(station,
                            operatingPlan.temperatureFloor, // 최저 온도
                            operatingPlan.temperatureCeiling); // 최고 온도

operatingPlan의 데이터 항목 두 개를 쌍으로 가져와서 readingOutsideRange()로 전달한다.

그리고 operatingPlan은 범위의 시작과 끝 이름을 readingOutsideRange()와 다르게 표현한다.

이와 같은 범위라는 개념은 객체 하나로 묶어 표현하는 게 나은 대표적인 예다

 

 

class NumberRange {
	constructor(min, max) {
		this._data = {min: min, max: max};
	}
	get min() {return this._data.min;}
	get max() {return this._data.max;}
}

먼저 묶은 데이터를 표현하는 클래스부터 선언한다.

 

function readingsOutsideRange(station, min, max, range) {
	return station.readings
        .filter(r => r.temp < min || r.temp > max);
}

새로 만든 객체를 readingsOutsideRange()의 매개변수로 추가하도록 함수 선언을 바꾼다.

 

// 호출문
alerts = readingsOutsideRange(station,
                            operatingPlan.temperatureFloor,
                            operatingPlan.temperatureCeiling,
                            null);

자바스크립트라면 호출문을 예전 상태로 둬도 되지만, 다른 언어를 사용할 때는

위와 같이 새 매개변수 자리에 null을 적어둔다.

 

const range = new NumberRange(operatingPlan.temperatureFloor,
                            operatingPlan.temperatureCeiling);
alerts = readingsOutsideRange(station,
                            operatingPlan.temperatureFloor,
                            operatingPlan.temperatureCeiling,
                            range);

이제 온도 범위를 객체 형태로 전달하도록 호출문을 하나씩 바꾼다.

바꾼 후 테스트를 한다.

 

 

function readingsOutsideRange(station, min, max, range) { 	// <---- max 매개변수 제거
	return station.readings
		.filter(r => r.temp < min || r.temp > range.max);
}
// 호출문
const range = new NumberRange(operatingPlan.temperatureFloor,
                            operatingPlan.temperatureCeiling);
alerts = readingsOutsideRange(station,
                            operatingPlan.temperatureFloor,
                           	operatingPlan.temperatureCeiling,		// <--- 매개변수 제거
                            range);

이제 기존 매개변수를 사용하는 부분을 변경한다. 최댓값부터 바꾼다.

바꾼 후 한번 더 테스트를 한다.

 

 

function readingsOutsideRange(station, min, range) {		// <--- min 매개변수 제거
	return station.readings
			.filter(r => r.temp < range.min || r.temp > range.max);
}
// 호출문
const range = new NumberRange(operatingPlan.temperatureFloor,
                            operatingPlan.temperatureCeiling);
alerts = readingsOutsideRange(station,
                            operatingPlan.temperatureFloor,		// <--- 매개변수 제거
                            range);

다음 매개변수도 제거한다.

 

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 스프링을 사용하면서 DTO로 데이터를 묶어서 전달하는 일을 했습니다.
    • 이 방법이 매개변수를 더 깔끔하고 추상개념을 격상된다는 것을 알게 되었습니다.
  • DTO던 VO던 결국 데이터를 전송하는 객체를 만들어 전송하는 개념이 중요하다는 걸 알게 되었습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 공부를 하면서 자바를 사용하면서만 매개변수를 객체로 전달하는 것을 해봤습니다. 다른 언어에서는 어떤 방식으로 매개변수에 객체를 담아 전달하는지 궁금합니다.
    • 자바스크립트는 예시로 나와있지만 실제 프로젝트에서 어떻게 사용하는지 궁금합니다.

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

리팩터링 전

let a = height * width;

리팩터링 후

let area = height * width;

 

 

배경

  • 명확한 프로그래밍의 핵심은 이름짓기다.
    • 변수는 프로그래머가 하려는 일에 관해 많은 것을 설명해준다.
    • 단, 이름을 잘 지었을 때만 그렇다.

 

  • 특히 이름의 중요성은 그 사용 범위에 영향을 많이 받는다.
    • 한 줄짜리 람다식에서 사용하는 변수는 대체로 쉽게 파악할 수 있다.
    • 맥락으로부터 변수의 목적을 명확히 알 수 있어서 한 글자로 된 이름을 짓기도 한다.
    • 자바스크립트와 같은 동적 타입 언어라면 이름 앞에 타입을 드러내기도 한다.

 

  • 함수 호출 한 번으로 끝나지 않고 값이 영속되는 필드라면 이름에 더 신경 써야 한다.

 

절차

  1. 폭넓게 쓰이는 변수라면 변수 캡슐화하기를 고려한다.
  2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서, 하나씩 변경한다.
    • 다른 코드베이스에서 참조하는 변수는 외부에 공개된 변수이므로 이 리팩터링을 적용할 수 없다.
    • 변수 값이 변하지 않는다면 다른 이름으로 본제본을 만들어서 하나씩 점진적으로 변경한다. 하나씩 바꿀 때마다 테스트한다.
  3. 테스트한다.

 

 

예시

  • 변수 이름 바꾸기의 가장 간단한 예는 임시 변수나 인수처럼 유효범위가 함수 하나로 국한된 변수다.
    • 그저 변수를 참조하는 코드를 찾아서 하나씩 바꾸면 되며, 다 바꾼 뒤에는 테스트 진행

 

함수 밖에서도 참조할 수 있는 변수라면 조심해야 한다.

  • 코드베이스 전체에서 두루 참조할 수도 있다.
let tpHd = "untitled";

result += `<h1>${tpHd}</h1>`;  // 변수를 읽기만 하는 참조

tpHd = obj['articleTitle'];    // 값을 수정하는 참조

저자는 이럴 때 주로 변수 캡슐화로 처리한다고 한다.

 

result += `<h1>${title()}</h1>`;

setTitle(obj['articleTitle']);

function title()       {return tpHd;}  // tpHd 변수의 게터
function setTitle(arg) {tpHd = arg;}   // tpHd 변수의 세터

캡슐화 후에는 변수 이름을 바꿔도 된다.

 

let _title = "untitled";

function title() {return _title;}
function setTitle(arg) {_title = arg;}

 

 

예시: 상수 이름 바꾸기

상수의 이름은 캡슐화하지 않고도 복제방식으로 점진적으로 바꿀 수 있다.

const cpyNm = "애크미 구스베리";

먼저 원본의 이름을 바꾼 후, 원본의 원래 이름(기존 이름)과 같은 복제본을 만든다.

 

const companyName = "애크미 구스베리";
const cpyNm = companyName;
  • 이제 기존 이름(복제본)을 참조하는 코드들을 새 이름으로 점진적으로 바꿀 수 있다.
    • 다 바꿨다면 복제본을 삭제한다.

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 상당히 짧은 내용의 챕터였지만 코딩을 하면서 어렵다고 느낀 리팩터링 방법입니다.
  • 이름은 신중하게 지어놓지 않은 코드들을 보고 정말 고통을 받은 기억이 있습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 변수를 이상하게 만드는 사람과는 어떻게 협업을 할까?
  • 심하게 축약된 변수명은 어떻게 할까?

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

리팩토링 전

let defaultOwner = {firstName: "마틴", lastName: "파울러"};

리팩토링 후

let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return defaultOwnerData;}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}

 

 

배경

  • 리팩터링은 결국 프로그램의 요소를 조작하는 일이다.

 

함수는 데이터보다 다루기가 수월하다.

  • 함수를 사용한다는 건 대체로 호출한다는 뜻이고, 함수의 이름을 바꾸거나 다른 모듈로 옮기기는 어렵지 않다. 여차하면 기존 함수를 그대로 둔 채 전달함수로 활용할 수도 있기 때문이다.
    • 즉 예전 코드들은 변함없이 기존 함수를 호출하고, 이 기존 함수가 새로 만든 함수를 호출하는 식이다.
    • 이런 전달 함수를 오래 남겨둘 일은 별로 없지만 리팩터링 작업을 간소화하는데 큰 역할을 한다.

 

반대로 데이터는 함수보다 다루기가 까다롭다.

  • 데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다.
  • 짧은 함수 안의 임시 변수처럼 유효범위가 아주 좁은 데이터는 어려울 게 없지만, 유효범위가 넓어질수록 다루기 어려워진다.

 

  • 전역 데이터가 골칫거리인 이유도 여기에 있다.
  • 그래서 접근할 수 있는 범위가 넓은 데이터를 옮길 때는 먼저 그 데이터로의 접근을 독점하는 함수를 만드는 식으로 캡슐화하는 것이 가장 좋은 방법일 때가 많다.
    • 데이터 재구성이라는 어려운 작업을 함수 재구성이라는 더 단순한 작업으로 변환하는 것

 

  • 저자는 유효범위가 함수 하나보다 넓은 가변 데이터는 모두 이런 식으로 캡슐화해서 그 함수를 통해서만 접근하게 만드는 습관이 있다고 한다.
    • 데이터의 유효범위가 넓을수록 캡슐화해야 한다.
    • 레거시 코드를 다룰 때는 이런 변수를 참조하는 코드를 추가하거나 변경할 때마다 최대한 캡슐화한다. 그래야 자주 사용하는 데이터에 대한 결합도가 높아지는 일을 막을 수 있다.

 

  • 객체 지향에서 객체의 데이터를 항상 private으로 유지해야 한다고 그토록 강조하는 이유가 바로 여기에 있다.
    • 저자는 public 필드를 발견할 때마다 캡슐화해서 가시 범위를 제한한다고 한다.

 

  • 불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다.
    • 데이터가 변경될 일이 없어서 갱신 전 검증 같은 추가 로직이 자리할 공간을 마련할 필요가 없기 때문이다.
    • 불변 데이터는 옮길 필요 없이 그냥 복제하면 된다.

 

 

절차

  1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
  2. 정적 검사를 수행한다.
  3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트한다.
  4. 변수의 접근 범위를 제한한다.
    • 변수로의 직접 접근을 막을 수 없을 때도 있다. 그럴 때는 변수 이름을 바꿔서 테스트해보면 해당 변수를 참조하는 곳을 쉽게 찾아낼 수 있다.
  5. 테스트한다.
  6. 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.

 

값 캡슐화하기

const owner1 = defaultOwner();
assert.equal("파울러", owner1.lastName, "처음 값 확인");
const owner2 = defaultOwner();
owner2.lastName = "파슨스";
assert.equal("파슨스", owner1.lastName, "owner2를 변경한 후");  // 성공할까?
  • 기본 캡슐화 기법은 데이터 항목을 참조하는 부분만 캡슐화한다.

 

 

변수뿐 아니라 변수에 담긴 내용을 변경하는 행위까지 제어할 수 있게 캡슐화하고 싶을 때

  1. 가장 간단한 방법은 그 값을 바꿀 수 없게 만드는 것
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return Object.assign({}, defaultOwnerData);}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}
  • 저자는 주로 게터가 데이터의 복제본을 반환하도록 수정하는 식으로 처리한다고 한다.
  • 특히 리스트에 이 기법을 많이 적용한다.
  • 데이터의 복제본을 반환하면 클라이언트는 게터로 얻은 데이터를 변경할 수 있지만 원본에는 아무 영향을 주지 못한다.

 

주의할 점

공유 데이터(원본)를 변경하기를 원하는 클라이언트가 있을 수 있다.

  • 이럴 때는 저자는 문제가 될만한 부분을 테스트로 찾는다.
  • 아니면 아예 변경할 수 없게 만들수도 있다.
  • ⇒ 이를 위한 좋은 방법이 레코드 캡슐화하기다.

 

  1. 레코드 캡슐화하기
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return new Person(defaultOwnerData);}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}

class Person {
	constructor(data) {
		this._lastName = data.lastName;
		this._firstName = data.firstName;
	}

get lastName() {return this._lastName;}
get firstName() {return this._firstName;}
// 다른 속성도 이런 식으로 처리한다.
  • 이렇게 하면 defaultOwnerData의 속성을 다시 대입하는 연산은 모두 무시된다.
    • 이런 변경을 감지하거나 막는 구체적인 방법은 언어마다 다르므로 사용하는 언어에 맞는 방법으로 처리

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 캡슐화의 정의와 왜 사용하는지 정확하게 인지를 하지 못했는데 책을 읽고 이해가 되었습니다.
    • 그리고 코딩을 하면서 자연스럽게 캡슐화를 하고 있었다는 것을 알게되었습니다.
  • 자바언어 인텔리제이 IDE에서 사용하면서 getter setter를 쉽게 정의 할 수 있었습니다.
    • 자바스크립트로 getter setter를 만들어보니 불편하다고 느꼈습니다. ⇒ 자바가 객체지향적으로 사용하기 정말 편한 언어라는 것을 다시 알게 되었습니다.

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 데이터의 사용 범위가 넓은 것을 어떻게 알 수 있을까?
    • 우선 데이터를 사용한다면 캡슐화를 고려해서 코딩을 해야 겠다고 생각했습니다.

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

배경

  • 함수는 프로그램을 작은 부분으로 나누는 주된 수단이다.
  • 함수 선언은 각 부분이 서로 맞물이는 방식을 표현하며, 실질적으로 소프트웨어 시스템의 구성 요소를 조립하는 연결부 역할을 한다.
    • 건축과 마찬가지로 소프트웨어도 이러한 연결분에 상당히 의존한다.
    • 연결부를 잘 정의하면 시스템에 새로운 부분을 추가하기가 쉬워지는 반면, 잘못 정의하면 지속적인 방해 요인으로 작용하여 소프트웨어 동작을 파악하기 어려워지고 요구사항이 바뀔 때 적절히 수정하기 어렵게 한다.
    • 다행히 소프트웨어는 소프트하기 때문에 연결부를 수정할 수 있다.
  • 이러한 연결부에 가장 중요한 요소는 함수의 이름이다.
    • 이름이 좋으면 함수의 구현 코드를 살펴볼 필요 없이 호출문만 보고도 무슨 일을 하는지 파악할 수 있다.
  • 이름이 잘못된 함수를 발견하면 더 나은 이름이 떠오르는 즉시 바꾸라는 명령으로 받아들인다.
  • 좋은 이름을 떠올리는 데 효과적인 방법이 하나 있다.
    • 바로 주석을 이용해 함수의 목적을 설명해 보는 것이다. 그러다 보면 주석이 멋진 이름으로 바뀌어 되돌아올 때가 있다.
  • 함수의 매개변수도 마찬가지다.
    • 매개변수는 함수가 외부 세계와 어우러지는 방식을 정의한다.
    • 매개변수는 함수를 사용하는 문맥을 설정한다.

 

절차

함수 선언 바꾸기는 ‘간단한 절차' 만으로 충분할 때도 많지만, 더 세분화된 ‘마이그레이션 절차' 가 훨씬 적합한 경우도 많다. 따라서 리팩터링을 할 때는 먼저 변경 사항을 살펴보고 함수 선언과 호출문들을 단번에 고칠 수 있을지 가늠해본다.

 

 

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
  2. 메서드 선언을 원하는 형태로 바꾼다.
  3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
  4. 테스트한다.

변경할 게 둘 이상이면 나눠서 처리하는 편이 나을 때가 많다. 따라서 이름 변경과 매개변수 추가를 모두 하고 싶다면 각각을 독립적으로 처리하자(그러다 문제가 생기면 작업을 되돌리고 ‘마이그레이션 절차'를 따른다.)

 

 

마이그레이션 절차

  1. 이어지는 추출 단계를 수월하게 만들어야 한다면 함수의 본문을 적절히 리팩터링한다.
  2. 함수 본문을 새로운 함수로 추출한다. → 새로 만들 함수 이름이 기존 함수와 같다면 일단 검색하기 쉬운 이름을 임시로 붙여둔다.
  3. 추출한 함수에 매개변수를 추가해야 한다면 ‘간단한 절차'를 따라 추가한다.
  4. 테스트한다.
  5. 기존 함수를 인라인한다.
  6. 이름을 임시로 붙여뒀다면 함수 선언 바꾸기를 한 번 더 적용해서 원래 이름으로 되돌린다.
  7. 테스트한다.

다형성을 구현한 클래스, 즉 상속 구조 속에 있는 클래스의 메서드를 변경할 때는 다형 관계인 다른 클래스들에도 변경이 반영되어야 한다. 이때, 상황이 복잡하기 때문에 간접 호출 방식으로 우회(혹은 중간 단계로 활용) 하는 방법도 쓰인다. 먼저 원하는 형태의 메서드를 새로 만들어서 원래 함수를 호출하는 전달메서드로 활용하는 것이다.

단일 상속 구조라면 전달 메서드를 슈퍼클래스에 정의하면 해결된다.

 

 

예시: 함수 이름 바꾸기(간단한 절차)

function circum(radius) {
	return 2 * Math.PI * radius;
}

리팩터링 후

function circumference(radius) {
	return 2 * Math.PI * radius;
}
  • 정적 타입 언어와 뛰어난 IDE의 조합이라면 함수 이름 바꾸기를 자동으로 처리할 수 있고, 그 과정에서 오류가 날 가능성도 거의 없다.

 

예시: 함수 이름 바꾸기(마이그레이션 절차)

function circum(radius) {
	return 2 * Math.PI * radius;
}

[2] 먼저 함수 본문 전체를 새로운 함수로 추출한다.

 

리팩터링 후 1

function circum(radius) {
	return circumference(radius);
}

function circumference(radius) {
	return 2 * Math.PI * radius;
}

[4] 수정한 코드를 테스트 한 뒤 [5] 예전 함수를 인라인한다. 그러면 예전 함수를 호출하는 부분이 모두 새 함수를 호출하도록 바뀐다. [7] 하나를 변경할 때마다 테스트하면서 한 번에 하나씩 처리하자.

  • 리팩터링 대상은 대부분 직접 수정할 수 있는 코드지만, 함수 선언 바꾸기만큼은 공개된 API, 다시 말해 직접 고칠 수 없는 외부 코드가 사용하는 부분을 리팩터링하기에 좋다.

 

예시: 매개변수 추가하기

상황: 도서 관리 프로그램의 Book 클래스에 예약 기능이 구현되어 있다고 하자.

addReservation(customer) {
	this._reservations.push(customer);
}

추가 요구사항: 예약 시 우선순위 큐를 지원

 

addReservatiom(customer) {
	this.zz_addReservation(customer);
}

zz_addReservation(customer) {
	this._reservations.push(customer);
}

[2] 먼저 addReservation()의 본문을 새로운 함수로 추출한다.

 

addReservation(custoemr) {
	this.zz_addReservation(customer, false);
}

zz_addReservation(customer, isPriority) {
	this._reservations.push(customer);
}

[3] 그런 다음 새 함ㅅ두의 선언문과 호출문에 원하는 매개변수를 추가한다.

 

zz_addReservation(customer, isPriority) {
	assert(isPriority === true || isPriority === false);
	this._reservations.push(customer);
}
  • 저자는 자바스크립트로 프로그래밍한다면, 호출문을 변경하기 전에 어서션을 추가하여 호출 하는 곳에서 새로 추가한 매개변수를 실제로 사용하는지 확인한다. 이렇게 해두면 호출문을 수정하는 과정에서 실수로 새 매개변수를 바뜨린 부분을 찾는 데 도움된다.

[5] 이제 기존 함수를 인라인하여 호출 코드들이 새 함수를 이용하도록 고친다. 호출문은 한번에 하나씩 변경한다.

[6] 다 고쳤다면 새 함수의 이름을 기존 함수의 이름으로 바꾼다.

 

예시: 매개변수를 속성으로 바꾸기

상황: 고객이 뉴잉글랜드에 살고 있는지 확인하는 함수가 있다고 하자.

function inNewEngland(aCustomer) {
	return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(aCustomer.address.state);
}

다음은 이 함수를 호출하는 코드 중 하나다.

 

// 호출문...
const newEnglanders = someCustomers.filter(c => inNewEngland(c));

inNewEngland() 함수는 고객이 거주하는 주 이름을 보고 뉴잉글랜드에 사는지 판단한다.

저자는 이 함수가 주 식별 코드를 매개변수로 받도록 리팩터링 할 것이라고 한다.

⇒ 그러면 고객에 대한 의존성이 제거되어 더 넓은 문맥에 활용할 수 있기 때문이다.

 

 

리팩터링 후 1

function inNewEngland(aCustomer) {
	const stateCode = aCustomer.address.state;
	return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}

[1] 저자는 함수 선언을 바꿀 때 함수 추출부터 하는 편이라고 한다.

하지만 이번 코드는 함수 본문을 살짝 리팩터링해두면 이후 작업이 더 수월해질 터라 우선 매개변수로 사용할 코드를 변수로 추출해둔다.

 

 

리팩터링 후 2

function inNewEngland(aCustomer) {
	const stateCode = aCustomer.address.state;
	return xxNEWinNewEngland(stateCode);
}

function xxNEWinNewEngland(stateCode) {
 return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}

[2] 함수 추출하기로 새 함수를 만든 것

 

 

리팩터링 후 3

function inNewEngland(aCustomer) {
	return xxNEWinNewEngland(aCustomer.address.state);
}

새 함수의 이름을 나중에 기존 함수 이름으로 바꾸기 쉽도록 검색하기 좋은 이름을 붙여둔다.

그런 다음 위와 같이 기존 함수 안에 변수로 추출해둔 입력 매개변수를 인라인한다.

 

// 호출문...
const newEnglanders = someCustomers.filter(c => xxNEWinNewEngland(c.address.state);

[5] 함수 인라인하기로 기존 함수의 본문을 호출문들에 집어넣는다. 실질적으로 기존 함수 호출문을 새 함수 호출문으로 교체하는 셈이다. 이 작업은 한 번에 하나씩 처리한다.

 

 

리팩터링 후 4

// 호출문...
const newEnglanders = someCustomers.filter(c => inNewEngland(c.address.state));
// 최상위...
function inNewEngland(stateCode) {
	return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 생각해보면 자바 혹은 C#등 다양한 언어들에 이미 구현된 함수의 함수명은 상당히 이해하기 쉬웠습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 아직 리팩터링에 대해 모르는게 많다는 걸 알게되었고 이해가 안되는 부분이 꽤 있었습니다.
반응형

 

 

 

😀 책에서 기억하고 싶은 내용을 써보세요.

let basePrice = anOrder.basePrice;
return (basePrice > 1000);

 

리팩터링 후

return anOrder.basePrice > 1000;

 

 

 

배경

  • 변수는 함수 안에서 표현식을 가리키는 이름으로 쓰이며, 대체로 긍정적인 효과를 준다.
    • 하지만 그 이름이 원래 표현식과 다를 바 없을 때도 있다.
    • 또 변수가 주변 코드를 리팩터링 하는 데 방해가 되기도 한다.

이럴 때는 그 변수를 인라인 하는 것이 좋다.

 

 

 

절차

  1. 대입문의 우변(표현식)에서 부작용이 생기지는 않는지 확인한다.
  2. 변수가 불변으로 선언되지 않았다면 불변으로 만든 후 테스트한다.
  3. → 이렇게 하면 변수에 값이 단 한 번만 대입되는지 확인할 수 있다.
  4. 이 변수를 가장 처음 사용하는 코드를 찾아서 대입문 우변의 코드로 바꾼다.
  5. 테스트한다.
  6. 변수를 사용하는 부분을 모두 교체할 때까지 이 과정을 반복한다.
  7. 변수 선언문과 대입문을 지운다.
  8. 테스트한다.

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 변수 인라인하기는 인텔리제이 IDE를 사용해서 쉽게 리팩터링이 가능했습니다.
  • 리팩터링을 한다는 것이 어떻게보면 상당히 사소한 것부터 시작 할 수 있다는 것을 알게되었습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 없음

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

배경

  • 표현식이 너무 복잡해서 이해하기 어려울 때가 있다.
    • 이럴 때 지역 변수를 활용하면 표현식을 쪼개 관리하기 더 쉽게 만들 수 있다.
    • 복잡한 로직을 구성하는 단계마다 이름을 붙일 수 있어서 코드의 목적을 훨씬 명확하게 드러낼 수 있다.
  • 이 과정에서 추가한 변수는 디버깅에 도움된다.
    • 디버거에 중단점을 지정하거나 상태를 출력하는 문장을 추가할 수 있기 때문이다.

 

절차

  1. 추출하려는 표현식에 부작용은 없는지 확인한다.
  2. 불변 변수를 하나 선언하고 이름을 붙일 표현식의 복제본을 대입한다.
  3. 원본 표현식을 새로 만든 변수로 교체한다.
  4. 테스트한다.
  5. 표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다. 하나 교체할 때마다 테스트 한다.

 

예시

function price(order) {
	// 가격(price) 기본 가격 - 수량 할인 + 배송비
	
	return order.quantity * order.itemPrice -
		Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
		Math.min(order.quantity * order.itemPrice * 0.1, 100);
}

 

리팩터링 후 1

function price(order) {
	// 가격(price) 기본 가격 - 수량 할인 + 배송비
	const basePrice = order.quantity * order.itemPrice;
	return order.quantity * order.itemPrice -
		Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
		Math.min(order.quantity * order.itemPrice * 0.1, 100);
}

2 이 로직을 이해했다면 기본 가격을 담을 변수를 만들고 적절한 이름을 지어준다.

 

 

리팩터링 후 2

function price(order) {
	// 가격(price) 기본 가격 - 수량 할인 + 배송비
	const basePrice = order.quantity * order.itemPrice;
	return **basePrice** -
		Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
		Math.min(order.quantity * order.itemPrice * 0.1, 100);
}

3 이 변수를 실제로 사용해야 하므로 원래 표현식에서 새로 만든 변수에 해당하는 부분을 교체한다.

 

 

리팩터링 후 3

function price(order) {
	// 가격(price) 기본 가격 - 수량 할인 + 배송비
	const basePrice = order.quantity * order.itemPrice;
	return **basePrice** -
		Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
		Math.min(**basePrice** * 0.1, 100);
}

5 방금 교체한 표현식이 쓰이는 부분이 더 있다면 마찬가지로 새 변수를 사용하도록 수정한다.

 

 

최종 리팩터링 완료

const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500)
													* order.itemPrice * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;

2 ~ 5 마지막으로 수량 할인, 배송비도 똑같이 처리한다. 다 수정했다면 주석은 지워도 된다.

 

 

예시: 클래스 안에서

class Order {
	constructor(aRecord) {
		this._data = aRecord;
	}
	
	get quantity() {return this._data.quantity;}
	get itemPrice() {return this._data.itemPrice;}
	
	get price() {
		return this.quantity * this.itemPrice - 
			Math.max(0, this.quantity - 500 * this.itemPrice * 0.05 +
			Math.min(this.quantity * this.itemPrice * 0.1, 100);
	}
}

이번에도 추출하려는 이름은 같다. 하지만 그 이름이 가격을 계산하는 price() 메서드의 범위를 넘어.

주문을 표현하는 Order 클래스 전체에 적용된다. 이처럼 클래스 전체에 영향을 줄 때 저자는 변수가 아닌 메서드로 추출하는 편이라고 한다.

 

 

리팩터링 후

class Order {
	constructor(aRecord) {
		this._data = aRecord;
	}
	
	get quantity() {return this._data.quantity;}
	get itemPrice() {return this._data.itemPrice;}
	
	get price() {
		return this.basePrice - this.quantityDiscount + this.shipping;
	}

	get basePrice()        {return this.quantity * this.itemPrice;}
	get quantityDiscount() {return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05;}
	get shipping()         {return Math.min(this.basePrice * 0.1, 100);}
}
  • 여기서 객체의 엄청난 장점을 볼 수 있다.
    • 객체는 특정 로직과 데이터를 외부와 공유하려 할 때 공유할 정보를 설명해주는 적당한 크기의 문맥이 되어준다.
    • 덩치가 큰 클래스에서 공통 동작을 별도 이름으로 뽑아내서 추상화해두면 그 객체를 다룰 때 쉽게 활용할 수 있어서 매우 유용하다.

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 확실히 변수로 추출을 하면 좀 더 직관적으로 볼 수 있다고 느꼈습니다.
  • 상당히 간결하지만 한줄에 모든걸 담은 로직은 가독성이 낮았습니다.
  • 결국에 변수명을 잘 지어야 빛을 발하는 리팩터링 방법 같습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 추상화의 정의는 책으로 여러번 봤지만 실제로 활용하는 방법을 아직 잘 모르겠습니다.

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

리팩터링 전

function getRating(driver) {
	return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFileLateDeliveries(driver) {
	return driver.numberOfLateDeliveries > 5;
}

 

리팩터링 후

function getRating(driver) {
	return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}

 

 

배경

  • 이 책은 분명히 드러나는 이름의 짤막한 함수를 이용하기를 권한다.
    • 그래야 코드가 명료해지고 이해하기 쉬워지기 때문이다.
    • 하지만 함수 본문이 이름만큼 명확한 경우도 있다. 또는 함수 본문 코드를 이름만큼 깔끔하게 리팩터링할 때도 있다. ⇒ 이럴 때는 그 함수를 제거한다.
    • 간접 호출은 유용할 수도 있지만 쓸데없는 간접 호출은 거슬릴 뿐이다.
  • 리팩터링 과정에서 잘못 추출된 함수들도 다시 인라인한다.
    • 잘못 추출된 함수들을 원래 함수로 합친 다음, 필요하면 원하는 형태로 다시 추출
  • 간접 호출을 너무 과하게 쓰는 코드도 흔한 인라인 대상이다.
    • 가령 다른 함수로 단순히 위임하기만 하는 함수들이 너무 많아서 위임 관계가 복잡하게 얽혀 있으면 인라인해버린다.

 

절차

  1. 다형 메서드(polymorphic method)인지 확인한다. ⇒ 서브클래스에서 오버라이드하는 메서드는 인라인하면 안 된다.
  2. 인라인할 함수를 호출하는 곳을 모두 찾는다.
  3. 각 호출문을 함수 본문으로 교체한다.
  4. 하나씩 교체할 때마다 테스트한다. ⇒ 인라인 작업을 한 번에 처리할 필요는 없다. 인라인하기가 까다로운 부분이 있다면 일단 남겨두고 여유가 생길 때마다 틈틈이 처리한다.
  5. 함수 정의(원래 함수)를 삭제한다.

 

 

예시

function rating(aDriver) {
	return moreThanFiveLateDeliveries(aDriver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(aDriver) {
	return aDriver.numberOfLateDeliveries > 5;
}

호출되는 함수의 반환문을 그대로 복사해서 호출하는 함수의 호출문을 덮어쓰면 끝이다.

리팩터링 후

function rating(aDriver) {
	return aDriver.numberOfLateDeliveries > 5 ? 2 : 1;
}

 

 

예시2

function reportLines(aCustomer) {
	const lines = [];
	gatherCustomerData(lines, aCustomer);
	return lines;
}

function gatherCustomerData(out, aCustomer) {
	out.push(["name", aCustomer.name]);
	out.push(["location", aCustomer.location]);
}
  • 단순히 잘라 붙이는 식으로는 gatherCustomerData()를 reportLines()로 인라인할 수 없다.
  • 먼저 여러 문장을 호출한 곳으로 옮기기를 적용해서 첫 문장부터 시작해보자.

 

function reportLines(aCustomer) {
	const lines = [];
	lines.push(["name", aCustomer.name]);
	gatherCustomerData(lines, aCustomer);
	return lines;
}

function gatherCustomerData(out, aCustomer) {
	~~out.push(["name", aCustomer.name]);~~
	out.push(["location", aCustomer.location]);
}

나머지 문장도 같은 식으로 처리한다.

 

function reportLines(aCustomer) {
	const lines = [];
	lines.push(["name", aCustomer.name]);
	lines.push(["location"], aCustomer.location]);
	return lines;
}
  • 여기서 핵심은 항상 단계를 잘게 나눠서 처리하는 데 있다.
  • 평소 저자의 스타일대로 함수를 작게 만들어뒀다면 인라인을 단번에 처리할 수 있을 때가 많다. 그러다 상황이 복잡해지면 다시 한 번에 한 문장씩 처리한다.
  • 한 문장을 처리하는 데도 얼마든지 복잡해질 수 있다. ⇒ 이럴 때는 더 정교한 리팩터링인 문장을 호출한 곳으로 옮기기로 작업을 더 잘게 나눈다.

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 상황에 맞게 잘게 나누거나 한 문장씩 처리할 수도 있다는 것을 알게 되었습니다.
    • 여기서 한 문장씩 처리하는 과정을 거칠 때 전 단계에서 잘게 나눠져 있어야 처리가 편하다.

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 다형 메서드란?
    • 다형성을 개념을 보면 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미합니다.
    • 자바에서 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.
    • 다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다.
    • 다형성을 활용하면 기능을 확장하거나, 객체를 변경해야할 때 타입 변경 없이 객체 주입만으로 수정이 일어나게 할 수 있다
    • 상속을 사용한다면 중복되는 코드까지 제거할 수 있으므로 더욱 객체 지향 설계와 가까워질 수 있다.

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

  • 저자가 가장 많이 사용하는 리팩터링은 ‘**함수 추출하기’**와 ‘**변수 추출하기’**다. 이 두 리팩터링을 반대로 진행하는 ‘함수 인라인하기‘ 와 ‘변수 인라인하기' 도 자주 사용한다.
  • 추출은 결국 이름 짓기이며, 코드 이해도가 높아지다 보면 이름을 바꿔야 할 때가 많다.
  • 함수 선언 바꾸기는 함수의 이름을 변경할 때 많이 쓰인다.
    • 함수의 인수를 추가하거나 제거할 때도 이 리팩터링을 적용한다.
  • 바꿀 대상이 변수라면 ‘변수 이름 바꾸기’ 를 사용하는데, 이는 ‘변수 캡슐화하기’ 와 관련이 깊다.
  • 자주 함께 뭉쳐 다니는 인수들은 ‘매개변수 객체 만들기’ 를 적용해 객체 하나로 묶으면 편리할 때가 많다.

 

6.1 함수 추출하기(Extract Function)

function printOwing(invoice) {
	printBanner();
	let outstanding = calculateOutstanding();

	// 세부 사항 출력
	console.log(`고객명: ${invoice.customer}`);
	console.log(`채무액: ${outstanding}`);
}
function printOwing(invoice) {
	printBanner();
	let outstanding = calculateOutstanding();
	printDetails();
	
	function printDetails(outstanding) {
		console.log(`고객명: ${invoice.customer}`);
		console.log(`채무액: ${outstanding}`);
	}
}
  • 코드를 언제 독립된 함수로 묶어야 하는지?
    • 길이를 기준으로 삼는 것
    • 재사용성을 기준으로 삼는 것
    • 저자는 ‘목적과 구현을 분리’ 하는 방식을 합리적이라고 생각하고 있다. ⇒ 이렇게 해두면 나중에 코드를 다시 맇을 때 함수의 목적이 눈에 확 들어온다. ⇒ 본문 코드(그 함수가 목적을 이루기 위해 구체적으로 수행하는 일)에 대해서는 더 이상 신경 쓸 일이 거의 없다.
  • 저자의 경험상 함수 안에 들어갈 코드가 대여섯 줄을 넘어갈 때부터 슬슬 냄새를 풍기기 시작했고, 단 한 줄짜리 함수를 만드는 일도 적지 않았다.
  • 함수를 짧게 만들면 함수 호출이 많아져서 성능이 느려질까 걱정하는 사람도 있다.
    • 저자가 젊던 시절에는 간혹 문제가 되긴 했지만 요즘은 그런 일이 거의 없다.
    • 함수가 짧으면 캐싱하기 더 쉽기 때문에 컴파일러가 최적화하는 데 유리할 때가 많다.
    • 성능 최적화에 대해서는 항상 일반 지침을 따르도록 하자.
  • 이러한 짧은 함수의 이점은 이름을 잘 지어야만 발휘되므로 이름 짓기에 특별히 신경 써야 한다.
    • 이름을 잘 짓기까지는 어느 정도 훈련이 필요하다.

 

절차

  1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다(’어떻게’가 아닌 ‘무엇을’ 하는지가 드러나야 한다).
  2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다.
  3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.
  4. 변수를 다 처리했다면 컴파일한다.
  5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다.(즉, 추출한 함수로 일을 위임한다.)
  6. 테스트한다.
  7. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다. 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.(인라인 코드를 함수 호출로 바꾸기)

 

 

예시: 유효범위를 벗어나는 변수가 없을 때

리팩터링 전

function printOwing(invoice) {
	let outstanding = 0;

	console.log("*****************");
	console.log("*** 고객 채무 ***");
	console.log("*****************");

	// 매해결 채무(outstanding)를 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}

	// 마감일(dueDate)을 기록한다.
	const today = Clock.today;
	invoice.dueDate = new Date(today.getFullyear(), today.getMonth(),
													 today.getDate() + 30);

	// 세부 사항을 출력한다.
	console.log(`고객명: ${invoice.customer}`);
	console.log(`채무액: ${outstanding}`);
	console.log(`마감일: ${invoice.dueDate.toLocalDateString()}`);
}

리팩터링 후

function printOwing(invoice) {
	let outstanding = 0;
	
	printBanner(); // <-- 배너 출력 로직을 함수로 추출

	// 미해결 채무(outstanding)를 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount
	}
	
	...

	// 세부 사항을 출력한다.
	console.log(`고객명: ${invoice.customer}`);
	console.log(`채무액: ${outstanding}`);
	console.log(`마감일: ${invoice.dueDate.toLocalDateString()}`);

	function printBanner() {
		console.log("*****************");
		console.log("*** 고객 채무 ***");
		console.log("*****************");
	}
}

리팩터링 후 2

function printOwing(invoice) {
	let outstanding = 0;
	
	printBanner();
	
	....

	printDetails();  // <-- 세부 사항 출력 로직을 함수로 추출
	
	function printDetails() {
		console.log("*****************");
		console.log("*** 고객 채무 ***");
		console.log("*****************");
	}

}

 

 

예시: 지역 변수를 사용할 때

function printOwing(invoice) {
	let outstanding = 0;

	printBanner();

	// 미해결 채무(outstanding)를 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}

	// 마감일(dueDate)을 기록한다.
	const today = Clock.today;
	invoice.dueDate = new Date(today.getFullYear(), today.getMonth(),
														 today.getDate() + 30);
	
	// 세부 사항을 출력한다.
	console.log(`고객명: ${invoice.customer}`);
	console.log(`채무액: ${outstanding}`);
	console.log(`마감일: ${invoice.dueDate.toLocalDateString()}`);
}
function printOwing(invoice) {
	let outstanding = 0;

	printBanner();

	// 미해결 채무(outstanding)를 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}

	// 마감일(dueDate)을 기록한다.
	const today = Clock.today;
	invoice.dueDate = new Date(today.getFullYear(), today.getMonth(),
														 today.getDate() + 30);

	printDetails(invoice, outstanding); // <--- 앞의 예와 달리 지역 변수를 매개변수로 전달
}

function printDetails(invoice, outstanding) {
	console.log(`고객명: ${invoice.customer}`);
	console.log(`채무액: ${outstanding}`);
	console.log(`마감일: ${invoice.dueDate.toLocalDateString()}`);
}
  • 지역 변수와 관련하여 가장 간단한 경우는 변수를 사용하지만 다른 값을 다시 대입하지는 않을 때다. 이 경우에는 지역 변수들을 그냥 매개변수로 넘기면 된다.
function printOwing(invoice) {
	let outstanding = 0;
	
	printBanner();
	
	// 미해결 채무(outstanding)를 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}

	recordDueDate(invoice);   // <--- 마감일 설정 로직을 함수로 추출
	printDetails(invoice, outstanding);
}

function recordDueDate(invoice) {
	const today = Clock.today;
	invoice.dueDate = new Date(today.getFullYear(), today.getMonth(),
														 today.getDate() + 30);
}
  • 지역 변수가 (배열, 레코드, 객체와 같은) 데이터 구조라면 똑같이 매개변수로 넘긴 후 필드 값을 수정 할 수 있다.

 

 

예시: 지역 변수의 값을 변경할 때

지역 변수에 값을 대입하게 되면 문제가 복잡해진다.

⇒ 만약 매개변수에 값을 대입하는 코드를 발견하면 곧바로 그 변수를 쪼개서 임시 변수를 새로 하나 만들어 그 변수에 대입하게 한다.

만약 변수가 초기화 되는 지점과 실제로 사용되는 지점이 떨어져 있다면 문장 슬라이드하기를 활용하여 변수 조작을 모두 한곳에 처리하도록 모아두면 편하다.

function printOwing(invoice) {
	let outstanding = 0;
	
	printBanner();

	// 미해결  채무(outstanding)을 계산한다.
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}
	
	recordDueDate(invoice);
	printDetails(invoice, outstanding);
}
  • 앞 예시에서 수행한 리팩터링들은 모두 간단하게 단번에 처리했지만, 이번에는 단계를 나눠서 진행
  • 선언문을 변수가 사용되는 코드 근처로 슬라이드한다.
function printOwing(invoice) {
	printBanner();

	// 미해결 채무(outstanding)를 계산한다.

	let outstanding = 0;  // <--- 맨 위에 있던 선언문을 이 위치로 이동
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}

	recordDueDate(invoice);
	printDetails(invoice, outstanding);
}
  1. 그런 다음 추출할 부분을 새로운 함수로 복사한다.
function printOwing(invoice) {
	printBanner();
	
	// 미해결 채무(outstanding)를 계산한다.
	let outstanding = 0;
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}
	recordDueDate(invoice);
	printDetails(invoice, outstanding);
}

function calculateOutstanding(invoice) {
	let outstanding = 0; // <--- 추출할 코드 복사
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}
	return outstanding;  // <--- 수정된 값 반환
}
  1. outstanding의 선언문을 추출할 코드 앞으로 옮겼기 때문에 매개변수로 전달하지 않아도 된다. 추출한 코드에서 값이 변경된 변수는 outstanding뿐이다.
  2. 내 자바스크립트 환경은 컴파일해도 아무런 값을 출력하지 않는다. 4 단계는 스킵
  3. 추출한 코드의 원래 자리를 새로 뽑아낸 함수를 호출하는 문장으로 교체한다. 추출한 함수에서 새 값을 반환하니, 이 값을 원래 변수에 저장한다.
function printOwing(invoice) {
	printBanner();
	let outstanding = calculateOutstanding(invoice);  // <-- 함수 추출 완료. 추출한 함수가 반환한 값을 원래 변수에 저장한다.
	recordDueDate(invoice);
	printDetails(invoice, outstanding);
}

function calculateOutstanding(invoice) {
	let outstanding = 0;
	for (const o of invoice.orders) {
		outstanding += o.amount;
	}
	return outstanding;
}

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 저자처럼 상당한 실력의 개발자도 단계를 나눠서 리팩터링 한다는 것을 알게되었습니다.
  • 리팩터링을 제가 예전에 작성했던 코드를 바탕으로 진행해야 겠다고 생각했습니다.
    • 실제로도 이렇게 수정하면서 리팩터링을 실천하고 있습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 책에서는 최적화를 할 때 하지마라와 아직 하지 마라 고 했는데 이유가 궁금합니다.
    • 최적화를 하면서 코드가 더러워지면 오히려 마이너스가 되서 그렇지 않을까 생각했습니다.
  • 실제로 서비스중인 코드를 리팩터링 할려면?
    • 이에대한 내용을 확인해보니 해당 회사의 담당 서비스에서 업무를 3년 이상해도 진행하기 힘들다고 한다.
    • 섣불리 리팩터링을 하다가 잘 돌아가는 서비스가 망가질 수 있기 때문에 상당히 위험한 행동이라고 한다.

 

 

반응형

 

 

😀 책에서 기억하고 싶은 내용을 써보세요.

5.1 리팩터링 설명 형식

  • 가장 먼저 이름이 나온다. 이름은 리팩터링 용어를 구축하는 데 중요하다. 책 전반에서 해당 리팩터링을 이 이름으로 지칭한다. 같은 기법을 다르게 부르는 경우도 있기 때문에 그중 흔한 이름도 함께 소개한다.

  • 다음으로 리팩터링의 핵심 개념을 간략히 표현한 개요(개념도 + 코드 예시)가 나온다. 원하는 리팩터링을 찾을 때 도움이 될 것이다.
    • 개요는 리팩터링의 개념이나 수행 과정을 설명하기 위해서가 아니라 나중에 다시 찾아볼 때 이 기법이 어떤 것인지 쉽게 떠올리기 위한 것이다.

  • 그 다음에 나오는 배경은 해당 리팩터링 기법이 왜 필요한지와 그 기법을 적용하면 안 되는 상황을 설명한다.
  • 이어서 나오는 절차는 리팩터링하는 과정을 단계별로 제시한다.
    • 오랜만에 적용하는 리팩터링의 구체적인 단계를 잊지 않도록 개인 노트에 기록해둔 것이다. 그래서 압축된 표현이 많고, 단계를 왜 그렇게 구성했는지에 대한 설명이 없을 때도 많다.
    • 리팩터링을 안전하게 수행하려면 단계를 최대한 잘게 나누고 각 단계마다 테스트해야 한다.
    • 상황이 난해할수록 단계를 잘게 나누자.

  • 마지막의 예시는 해당 리팩터링 기법을 실제로 적용하는 간단한 예와 그 효과를 보여준다.

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 테스트 코드를 체크리스트처럼 사용한다는 점에서 공감이 많이 되었습니다.
    • 내가 구현한 혹은 리팩터링을 한 코드를 체크리스트로 점검을 하는 것 같다고 느꼈습니다.
  • 본격적으로 리팩터링을 시작하기 전 카탈로그라 내용이 짧았다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 없음

 

 

 

반응형

 

😀 책에서 기억하고 싶은 내용을 써보세요.

  • 리팩터링을 제대로 하려면 불가피하게 저지르는 실수를 잡아주는 견고한 테스트 스위트가 뒷받침돼야 한다.

4.1 자가 테스트 코드의 가치

  • 프로그래머는 대부분의 시간을 디버깅에 쓴다.
    • 버그 수정 자체는 금방 끝나지만 버그를 찾는 여정이 고통스럽다.
  • 자가 테스트 코드 자체뿐 아니라 테스트를 자주 수행하는 습관도 버그를 찾는 강력한 도구가 된다.
  • 테스트를 작성하기 가장 좋은 시점은 프로그래밍을 시작하기 전이다.
    • 테스트를 작성하다 보면 원하는 기능을 추가하기 위해 무엇이 필요한지 고민하게 된다.
    • 구현보다 인터페이스에 집중하게 된다는 장점도 있다(무조건 좋은 일이다.)
    • 코딩이 완료되는 시점을 정확하게 판달할 수 있다.
    • 테스트를 모두 통과한 시점이 바로 코드를 완성한 시점이다.
  • 리팩터링에는 테스트가 필요하다. 그러니 리팩터링하고 싶다면 테스트를 반드시 작성해야 한다.
  •  

4.3 첫 번째 테스트

describe('province', function() {
	if('shortfall', function() {
		const asia = new Province(sampleProvinceData()); // 1️⃣ 픽스처 설정
		assert.equal(asia.shortfall, 5);   // 2️⃣ 검증
	}
}
  • 모카 프레임워크를 사용해서 테스트 코드 작성
    • 1️⃣ 첫 번째 단계에서는 테스트에 필요한 데이터와 객체를 픽스처(fixture: 고정장치)를 설정한다. 이 예시에서는 샘플 지역 정보로부터 생성한 Province 객체를 픽스터로 설정했다.
    • 2️⃣두 번째 단계에서는 이 픽스처의 속성들을 검증하는데, 여기서는 주어진 초깃값에 기초하여 생산 부족분을 정확히 계산했는지 확인한다.
  • 실패해야 할 상황에서는 반드시 실패하게 만들자.
    • 수 많은 테스트를 실행했음에도 실패하는 게 없다면 테스트가 내 의도와는 다른 방식으로 코드를 다루는 건 아닌지 불안해진다. 그래서 각각의 테스트가 실패하는 모습을 최소한 한 번씩은 직접 확인해본다.
  • 자주 테스트하라. 작성 중인 코드는 최소한 몇 분 간격으로 테스트하고,
    적어도 하루에 한 번은 전체 테스트를 돌려보자.

 

4.4테스트 추가하기

  • 테스트는 위험 요인을 중심으로 작성해야 한다!
    • 테스트의 목적은 어디까지나 현재 혹은 향후에 발생하는 버그를 찾는 데 있다. 따라서 단순히 필드를 읽고 쓰기만 하는 접근자는 테스트할 필요가 없다. 이런 코드는 너무 단순해서 버그가 숨어들 가능성도 별로 없다.
  • 완벽하게 만드느라 테스트를 수행하지 못하느니, 불완전한 테스트라도 작성해 실행하는 게 낫다.
describe('province', function() {
	const asia = new Province(sampleProvinceData());  // 이렇게 하면 안 된다.
	if('shortfall', function() {
		expect(asia.shortfall).equal(5);
	});
	if('profit', function() {
		expect(asia.profit).equal(230);
	});
}
  • 주석에 적은 것처럼 나는(저자는) 절대로 이렇게 하지 않는다.
    • 일시적인 효과는 있게지만, 테스트 관련 버그 중 가장 지저분한 유형인 ‘테스트끼리 상호작용하게 하는 공유 픽스처’를 생성하는 원인이 된다.
    • 자바스크립트에서 const 키워드는 asia 객체의 ‘내용’이 아니라 asia를 가리키는 참조가 상수임을 뜻한다.
describe('province', function() {
	let asia;
	beforeEach(function() {
		expect(asia.shortfall).equal(5);
	});
	if('profit', function() {
		expect(asia.profit).equal(230);
	});
}
  • beforeEach 구문은 각각의 테스트 바로 전에 실행되어 asia를 초기화하기 때문에 모든 테스트가 자신만의 새로운 asia를 사용하게 된다.
  • 개별 테스트를 실행할 때마다 픽스처를 새로 만들면 모든 테스트를 독립적으로 구성할 수 있어서, 결과를 예측할 수 없어 골치를 썩는 사태를 예방할 수 있다.

 

4.6 경계 조건 검사하기

  • 문제가 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 테스트하자.
  • 어차피 모든 버그를 잡아낼 수는 없다고 생각하여 테스트를 작성하지 않는다면 대다수의 버그를 잡을 수 있는 기회를 날리는 셈이다.
  • 아무리 테스트해도 버그 없는 완벽한 프로그램을 만들 수는 없다는 말은 많이 들어봤을 것이다. 맞는 말이지만, 테스트가 프로그래밍 속도를 높여준다는 사실에는 변함이 없다.
  • 테스트가 모든 버그를 걸러주지는 못할지라도, 안심하고 리팩터링할 수 있는 보호막은 되어준다. 그리고 리팩터링을 하면서 프로그램을 더욱 깊이 이해하게 되어 더 많은 버그를 찾게 된다.

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 테스트 코드의 중요성은 명저라고 불리는 프로그래밍 책에서는 꼭 나오는 것 같습니다.
  • 테스트 코드를 직접 작성하면서 마음이 좀 더 안정되는 기분을 느꼈습니다.
    • 테스트를 통해 빠른 피드백을 얻고 검증을 할 수 있다.
  • Junit에서 @BeforeEach라는 어노테이션이 있는데 자바스크립트 테스트코드에도 비슷한게 있는 걸 보고 신기하다고 느꼈습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 테스트에 회의적인 팀원을 만나면 어떻게 해야 할까?
  • 단위 테스트란?
    • 코드의 작은 영역만을 대상으로 빠르게 실행되도록 설계된 테스트다.
    • 단위 테스트는 자가 테스트 코드의 핵심이자, 자가 테스트 시스템은 대부분 단위 테스트가 차지한다.

 

 

반응형

+ Recent posts