TIL 클린코드 - 6장. 객체와 자료 구조
TIL (Today I Learned)
2022.05.04
오늘 읽은 범위
6장. 객체와 자료 구조
책에서 기억하고 싶은 내용을 써보세요.
- 변수를 비공개로 정의하는 이유는 남들이 변수에 의존하지 않게 만들고 싶어서다.
- 자료 추상화
- 변수를 private으로 선언하더라도 각 값마다 get함수와 set함수를 제공한다면 구현을 외부로 노출하는 셈이다.
- 변수 사이에 함수라는 계층을 넣는다고 구현이 저절로 감춰지지는 않는다. 구현을 감추려면 추상화가 필요하다. 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스이다.
- 자료를 세세하게 공개하기 보다는 추상적인 개념으로 표현하는 편이 좋다.
- 자료/객체 비대칭
- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
- 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.
- 복잡한 시스템을 짜다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이때는 클래스와 객체 지향 기법이 가장 적합하다.
- 반면, 새로운 자료 타입이 아니라 새로운 함수가 필요한 경우도 생긴다. 이때는 절차적인 코드와 자료 구조가 좀 더 적합하다.
디미터 법칙
- 디미터 법칙은 잘 알려진 휴리스틱(직관적으로 판단)으로, 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다.
객체에서 허용된 메소드가 반환하는 객체의 메서드는 호출하면 안 된다.
// 나쁜 예 - 기차 충돌(train wreck) final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); // 좋은 예 Options opts = ctxt.getOptions(); File scratchDir = opts.getScratchDir(); final String ouputDir = scratchDir.getAbsolutePath(); /* ctxt, Options, ScratchDir이 객체라면 내부 구조를 숨겨야 하므로 디미터 법칙을 위반하고, 자료 구조라면 내부 구조를 노출하므로 디미터 법칙이 적용되지 않는다. */ // 더 좋은 예 - 조회 함수를 이용하지 않음 final String outputDir = ctxt.options.scratchDir.absolutePath;- ctxt가 객체라면 뭔가를 하라고 말해야지 속을 드러내라고 말하면 안 된다.
// 디미터 법칙을 위반하지 않는 좋은 예시 BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName); // 내부 구조를 드러내지 않으며 모듈에서 해당 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없다- 자료 전달 객체
- 자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다. 이런 자료 구조체를 때로는 자료 전달 객체(Data Transfer Object, DTO)
- 데이터베이스와 통신하거나 소켓에서 받은 메시지의 구문을 분석할 때 유용하다.
- 활성 레코드
- 활성 레코드는 DTO의 특수한 형태이다. 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조지만, 대개 save나 find와 같은 탐색 함수도 제공한다. 활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한 결과다.
오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요
- 예전에 잠깐 spring framework로 백엔드 API를 개발한 경험이 있는데, 객체와 자료 구조의 의미를 제대로 모르고 잡종 구조(절반은 객체, 절반은 자료구조)로 개발했던 것 같다.
- 자바 코드로 예시를 보여주고 있지만 기차 충돌 예시는 모든 코드에 적용되는 예시인 것 같다.
- 나는 주로 자바스크립트와 타입스크립트를 사용하기 때문에 Clean Code concepts adapted for JavaScript - 한글 번역판 을 보고 공부했고 아래 내용은 연관된 내용만 가져온 것이다.
getter와 setter를 사용하세요
JavaScript는 인터페이스와 타입을 가지고있지 않고 이러한 패턴을 적용하기가 힘듭니다. 왜냐하면 public이나 private같은 키워드가 없기 때문이죠. 그렇기 때문에 getter 및 setter를 사용하여 객체의 데이터에 접근하는 것이 객체의 속성을 찾는 것보다 훨씬 낫습니다. “왜요?”라고 물으실 수도 있겠습니다. 왜 그런지에 대해서 몇 가지 이유를 두서없이 적어봤습니다.
- 객체의 속성을 얻는 것 이상의 많은 것을 하고싶을 때, 코드에서 모든
접근자를 찾아 바꾸고 할 필요가 없습니다. set할때 검증로직을 추가하는 것이 코드를 더 간단하게 만듭니다.- 내부용 API를 캡슐화 할 수 있습니다.
getting과setting할 때 로그를 찾거나 에러처리를 하기 쉽습니다.- 서버에서 객체 속성을 받아올 때 lazy load 할 수 있습니다.
안좋은 예:
function makeBankAccount() {
// ...
return {
// ...
balance: 0,
};
}
const account = makeBankAccount();
account.balance = 100;
좋은 예:
function makeBankAccount() {
// private으로 선언된 변수
let balance = 0;
// 아래 return을 통해 public으로 선언된 "getter"
function getBalance() {
return balance;
}
// 아래 return을 통해 public으로 선언된 "setter"
function setBalance(amount) {
// ... balance를 업데이트하기 전 검증로직
balance = amount;
}
return {
// ...
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);
객체에 비공개 멤버를 만드세요
클로저를 이용하면 가능합니다. (ES5 이하에서도)
안좋은 예:
const Employee = function (name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined
좋은 예:
function makeEmployee(name) {
return {
getName() {
return name;
},
};
}
const employee = makeEmployee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe