refactoring

변수 캡슐화하기, 이름바꾸기, 매개변수 객체 만들기

단점이없어지고싶은개발자 2022. 8. 1. 00:54
반응형

리팩토링 188p ~ 201p

 

변수 캡슐화하기(Encapsulate Variable)

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

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

리팩토링은 결국 프로그램의 요소를 조작하는 일이다. 함수는 데이터보다 다루기가 수월하다.

함수를 사용한다는 것은 호출한다는 뜻이고, 함수의 이름을 바꾸거나 다른 모듈로 옮기기는 어렵지 않다. 여차하면 기존 함수를 그대로 둔 채 전달 함수로 활용할수도 있기 때문이다. 

즉, 예전 코드들은 변함없이 기존 함수를 호출하고, 이 기존 함수가 새로 만든 함수를 호출하는 식이다.

 

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

 

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

이렇께 되면 데이터 변경 전 검증이나 변경 후 추가 로직을 쉽게 끼워 넣을 수 있다.

 

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

 

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

 

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

 

절차

1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수를 만든다.

2. 정적 검사를 수행한다.

3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트한다.

4. 변수의 접근 범위를 제한한다.

5. 테스트한다.

6. 변수 값인 레코드라면 레코드 캡슐화하기를 적용할지 고려한다.

 

위와 같은 예시를 생각해보자

//전역 변수에 중요한 데이터가 담겨져있다.
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
//데이터라면 다음과 같이 참조하는 코드가 있을 것이다.
spaceship.owner = defaultOwner;
//갱신하는 코드 역시 있을 것이다.
defaultOwner = {firstName: "레베카", lastName: "파슨스"};
//1. 기본적인 캡슐화를 위해 가장 먼저 데이터를 읽고 쓰는 함수부터 정의한다.
function getDefaultOwner() {return defaultOwner;}
function setDefaultOwner(arg) {defaultOwner = arg;}

//3. 그런다음 defaultOwner를 참조하는 코드를 찾아서 방금 만던 get함수를 호출하도록 고친다.
spaceship.owner = getDefaultOwner();
//대입문은 set함수로 바꾼다.
setDefualtOwner({firstName: "레베카", lastName: "파슨스"});
//하나씩 바꿀 때마다 테스트한다.
//4. 모든 참조를 수정했다면 이제 변수의 가시 범위를 제한한다.

방금 위 예시에서 데이터 구조로의 참조를 캡슐화하면, 그 구조로의 접근이나 구조 자체를 다시 대입하는 행위는 제어할 수 있다. 하지만 필드 값을 변경하는 일은 제어할 수 없다.

const owner1 = defualtOwner();
assert.equal("파울러", owner1.lastName, "처음값 확인");
const owner2 = defualtOwner();
owner2.lastName = "파슨스";
assert.equal("파슨스", owner1.lastName, "owner2를 변경한 후");

기본 캡슐화 기법은 데이터 항목을 참조하는 부분만 캡슐화한다. 

저자는 get함수가 데이터의 복제본을 반환하도록 수정하는 식으로 처리한다고 한다.

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

 

변수 이름 바꾸기(Rename Variable)

let a = height * width;
//
let area = height * width;

명확한 프로그래밍의 핵심은 이름짓기다. 변수는 프로그래머가 하려는 일에 관해 많은 것을 설명해준다. 단, 이름을 잘 지었을 때만 그렇다. 변수 이름을 통한 맥락으로부터 변수의 목적을 명확히 알 수 있어서 한 글자로 된 이름을 짓기도 한다. 

 

절차

1. 폭넓게 쓰이는 변수라면 변수 캡슐화하기를 고려한다.

2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서, 하나씩 변경한다.

3. 테스트한다.

 

매개변수 객체 만들기

function  amountInvoiced(startDate, endDate) {...}
function  amountReceived(startDate, endDate) {...}
function  amountOverdue(startDate, endDate) {...}

//------------------------------------------------
function  amountInvoiced(aDateRange) {...}
function  amountReceived(aDateRange) {...}
function  amountOverdue(aDateRange) {...}

 

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

하지만 근본적으로 데이터 구조에 담길 데이터에 공통으로 적용되는 동작을 추출해서 함수로 만드는 것이다. 이 과정에서 새로 만든 데이터 구조가 문제 영역을 훨씬 간결하게 표현하는 새로운 추상개념으로 격상되면서, 코드의 개념적인 그림을 다시 그릴수도 있다.

 

절차

1. 적당한 데이터 구조가 아직 마련되어 있지 않다면 새로 만든다.

2. 테스트한다.

3. 함수 선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다.

4. 테스트한다.

5. 함수 호출 시 새로운 데이터 구조 인스턴스를 넘기도록 수정한다. 하나씩 수정할 때마다 테스트한다.

6. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다.

7. 다 바꿨다면 기존 매개변수를 제거하고 테스트한다.

반응형