본문 바로가기
책/파이브 라인스 오브 코드

6장 - 데이터 보호

by jeounpar 2024. 2. 26.

getter & setter를 사용하지 말 것

OOP의 특징중 하나인 캡슐화를 배울 때 getter와 setter는 private 필드를 다루기 위한 메서드로 함께 배운다.

but, 객체의 필드에 대한 getter가 존재하는 순간 캡슐화를 해제하고 불변속성을 전역적으로 만들게 된다.

-> 객체를 얻은 어느 곳에서나 getter와 setter를 호출할 수 있게되며 예상치 못한 에러를 발생시킬 수 있다.

 

pull-based 아키텍쳐

- 데이터를 한 곳으로 가져와 연산을 함

 

push-based 아키텍쳐

- 데아터를 가져오는 대신 인자로 데이터를 전달함

 

pull-based 아키텍쳐 예제

class Website {
	constructor(private url: string) {}
	getUrl() {
		return this.url;
	}
}

class User {
	constructor(private username: string) {}
	getUsername() {
		return this.username;
	}
}

class BlogPost {
	constructor(private author: User, private id: string) {}
	getId() {
		return this.id;
	}
	getAuthor() {
		return this.author;
	}
}

function generatePostLink(website: Website, post: BlogPost) {
	let url = website.getUrl();
	let user = post.getAuthor();
	let name = user.getUsername();
	let postId = post.getId();
	return url + name + postId;
}

generatePostLink 라는 메서드에서 getter를 통해 연산에 필요한 모든 데이터를 불러오는 모습을 볼 수 있다.

 

push-based 아키텍쳐 예제

class Website {
	constructor(private url: string) {}
	generateLink(name: string, id: string) {
		return this.url + name + id;
	}
}

class User {
	constructor(private username: string) {}
	generateLink(website: Website, id: string) {
		return website.generateLink(this.username, id);
	}
}

class BlogPost {
	constructor(private author: User, private id: string) {}
	generateLink(website: Website) {
		return this.author.generateLink(website, this.id);
	}
}

function generatePostLink(website: Website, post: BlogPost) {
	return post.generateLink(website);
}

메서드의 인자로 데이터를 전달해서 각 클래스에서 데이터를 처리후 반환하고 있다.

 

공통 접사를 사용하지 말 것

계좌에서 돈을 인출하는 예제를 살펴보자.

function accountDeposit(to: string, amount: number) {
	let accountId = database.find(to);
	database.updateOne(accountId, { $inc: { balance: amount } });
}

function accountTransfer(amount: number, from: string, to: string) {
	accountDeposit(from, -amount);
	accountDeposit(to, amount);
}

deposit을 직접 호출하는 행위는 좋아보이지 않는다.

deposit을 Account 클래스에 넣어두고, 접근제어자 private을 걸어두어 직접 deposit을 호출하지 못하게 하면 코드는 다음과 같아진다.

class Account {
	private deposit(to: string, amount: number) {
		let accountId = database.find(to);
		database.updateOne(accountId, { $inc: { balance: amount } });
	}

	transfer(amount: number, from: string, to: string) {
		this.deposit(from, -amount);
		this.deposit(to, amount);
	}
}

이렇게 리팩터링을 진행하면 '메서드는 한 가지 작업만 해야 한다'는 단일 책임 원칙을 지키게 된다.

 

순서 강제화

A-> B -> C 순서로 실행되어야 하는 작업이 있다고 할 때,

각 작업이 메서드로 이루어져 있어 개발자가 메서드를 잘못된 순서로 호출한다면 에러가 발생할 수 있다.

이를 생성자를 사용해서 순서를 강제할 수 있다.

class Transfer {
	constructor(from: string, private amount: number) {
		this.depositHelper(from, -this.amount);
	}
	private depositHelper(to: string, amoubt: number) {
		let accountId = database.find(to);
		database.updateOne(accountId, { $inc: { balance: amount } });
	}
	deposit(to: string) {
		this.depositHelper(to, this.amount);
	}
}

Transfer(송금)클래스를 위와 같이 작성하게 되면 출금 없이 입금이 이루어질 수 없다는 것을 보증할 수 있지만,

수취인(to)을 인자로 deposit을 호출하는 것을 잊어버리면 돈이 그냥 사라질 수 있다.

따라서 입금도 반드시 발생하도록 이 클래스를 다른 클래스로 감쌀 수도 있다.