😀 책에서 기억하고 싶은 내용을 써보세요.
리팩토링 전
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
리팩토링 후
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return defaultOwnerData;}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}
배경
- 리팩터링은 결국 프로그램의 요소를 조작하는 일이다.
함수는 데이터보다 다루기가 수월하다.
- 함수를 사용한다는 건 대체로 호출한다는 뜻이고, 함수의 이름을 바꾸거나 다른 모듈로 옮기기는 어렵지 않다. 여차하면 기존 함수를 그대로 둔 채 전달함수로 활용할 수도 있기 때문이다.
- 즉 예전 코드들은 변함없이 기존 함수를 호출하고, 이 기존 함수가 새로 만든 함수를 호출하는 식이다.
- 이런 전달 함수를 오래 남겨둘 일은 별로 없지만 리팩터링 작업을 간소화하는데 큰 역할을 한다.
반대로 데이터는 함수보다 다루기가 까다롭다.
- 데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다.
- 짧은 함수 안의 임시 변수처럼 유효범위가 아주 좁은 데이터는 어려울 게 없지만, 유효범위가 넓어질수록 다루기 어려워진다.
- 전역 데이터가 골칫거리인 이유도 여기에 있다.
- 그래서 접근할 수 있는 범위가 넓은 데이터를 옮길 때는 먼저 그 데이터로의 접근을 독점하는 함수를 만드는 식으로 캡슐화하는 것이 가장 좋은 방법일 때가 많다.
- 데이터 재구성이라는 어려운 작업을 함수 재구성이라는 더 단순한 작업으로 변환하는 것
- 저자는 유효범위가 함수 하나보다 넓은 가변 데이터는 모두 이런 식으로 캡슐화해서 그 함수를 통해서만 접근하게 만드는 습관이 있다고 한다.
- 데이터의 유효범위가 넓을수록 캡슐화해야 한다.
- 레거시 코드를 다룰 때는 이런 변수를 참조하는 코드를 추가하거나 변경할 때마다 최대한 캡슐화한다. 그래야 자주 사용하는 데이터에 대한 결합도가 높아지는 일을 막을 수 있다.
- 객체 지향에서 객체의 데이터를 항상 private으로 유지해야 한다고 그토록 강조하는 이유가 바로 여기에 있다.
- 저자는 public 필드를 발견할 때마다 캡슐화해서 가시 범위를 제한한다고 한다.
- 불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다.
- 데이터가 변경될 일이 없어서 갱신 전 검증 같은 추가 로직이 자리할 공간을 마련할 필요가 없기 때문이다.
- 불변 데이터는 옮길 필요 없이 그냥 복제하면 된다.
절차
- 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
- 정적 검사를 수행한다.
- 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트한다.
- 변수의 접근 범위를 제한한다.
- 변수로의 직접 접근을 막을 수 없을 때도 있다. 그럴 때는 변수 이름을 바꿔서 테스트해보면 해당 변수를 참조하는 곳을 쉽게 찾아낼 수 있다.
- 테스트한다.
- 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.
값 캡슐화하기
const owner1 = defaultOwner();
assert.equal("파울러", owner1.lastName, "처음 값 확인");
const owner2 = defaultOwner();
owner2.lastName = "파슨스";
assert.equal("파슨스", owner1.lastName, "owner2를 변경한 후"); // 성공할까?
- 기본 캡슐화 기법은 데이터 항목을 참조하는 부분만 캡슐화한다.
변수뿐 아니라 변수에 담긴 내용을 변경하는 행위까지 제어할 수 있게 캡슐화하고 싶을 때
- 가장 간단한 방법은 그 값을 바꿀 수 없게 만드는 것
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return Object.assign({}, defaultOwnerData);}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}
- 저자는 주로 게터가 데이터의 복제본을 반환하도록 수정하는 식으로 처리한다고 한다.
- 특히 리스트에 이 기법을 많이 적용한다.
- 데이터의 복제본을 반환하면 클라이언트는 게터로 얻은 데이터를 변경할 수 있지만 원본에는 아무 영향을 주지 못한다.
주의할 점
공유 데이터(원본)를 변경하기를 원하는 클라이언트가 있을 수 있다.
- 이럴 때는 저자는 문제가 될만한 부분을 테스트로 찾는다.
- 아니면 아예 변경할 수 없게 만들수도 있다.
- ⇒ 이를 위한 좋은 방법이 레코드 캡슐화하기다.
- 레코드 캡슐화하기
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를 만들어보니 불편하다고 느꼈습니다. ⇒ 자바가 객체지향적으로 사용하기 정말 편한 언어라는 것을 다시 알게 되었습니다.
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- 데이터의 사용 범위가 넓은 것을 어떻게 알 수 있을까?
- 우선 데이터를 사용한다면 캡슐화를 고려해서 코딩을 해야 겠다고 생각했습니다.
반응형