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

리팩토링 전

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를 만들어보니 불편하다고 느꼈습니다. ⇒ 자바가 객체지향적으로 사용하기 정말 편한 언어라는 것을 다시 알게 되었습니다.

 

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

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

 

 

반응형

+ Recent posts