티스토리 뷰
프로덕트 엔지니어의 일은 고객이 원하는 기능을 고객이 원하는 시점에 전달하는 것이다.
일정을 지키지 못하더라도 100% 완벽한 기능 구현하는 것보다 아무리 급해도 항상 80% ~ 90% 정도의 소프트웨어를 일정 내 개발할 수 있는 방법이 가장 좋은 방법이다.
그 방법을 가장 잘 지키는 개발자들의 공통점은 본인만의 기준과 원칙이 있으며, 그 기준과 원칙으로 빠르게 개발 결정을 한다는 것이다.
선택의 순간마다 고민하는 개발자보다는 원칙에 따라 빠르게 결정하고 중요한 것만 고민하는 개발자가 좋은 개발자라고 생각할 수 있다.
제어할 수 없는 것에 의존하지 않기
도서 "실용중의 프로그래머"에서는 자신의 힘응로 제어할 수 없는 속성에 의존하지 말라는 글이 있다. 절대 변하지 않을 것이라고 믿고 의존했던 속성들이, 나중에는 변경이 발생하여 문제가 될 수 있다.
예를 들면, 고객의 개인정보를 주요키로 결정하였지만, 개인정보를 수집하지 말라는 정부의 지침으로 인해 대부분의 시스템의 주요키를 변경해야 하는 문젲가 발생하는 것처럼 말이다.
그렇기 때문에, 외부에서 전달 받은 값은 절대 주요키로 선택하지 않아야 한다는 것을 고민해볼 필요가 있다. 또한, SQL에서 만들어주는 "password()", "encrypt()" 등은 본인이 만드는 것이 아니기 때문에 배제하고 본인이 만드는 값들을 활용해 영향을 덜 받도록 해야 한다.
제어할 수 없는 것에 의존할 수록 변화에 쉽게 흔들리는 소프트웨어가 만들어질 수 있다.
제어할 수 없는 코드의 예는 다음과 같다.
export class Order {
private _amount: number;
discount() {
const now = LocalDateTime.now();
if (now.dayOfWeek() === DayOfWeek.SUNDAY) {
this._amount = this.amount * 0.9;
}
}
}
위 코드는, 일요일 날 결제를 하면 금액을 할인해주는 코드이다. 그러나, 여기서 제어할 수 없는 부분은 바로 "LocalDateTime.now()"이다. 왜냐하면, 오늘의 날짜를 자동으로 받아오기 때문에 만약 오늘이 일요일이 아니라면 위의 코드를 테스트해볼 수 없게 된다. 즉, 우리가 날짜를 제어할 수 없기 때문에 문제가 발생하는 것이다.
it('일요일에는 주문 금액이 10% 할인된다', () => {
const sut = new Order(10_000);
sut.discount();
expect(sut.amount).toBe(9_000);
}
위 처럼, 테스트 코드를 작성해 실행하면 일요일이 아닐 경우, 당연히 테스트는 실패한다.
물론, "Jest"의 모킹에서 날짜 라이브러리인 "js-joda"를 활용하면 가능은 하다.
it('[Mock] 일요일에는 주문 금액이 10% 할인된다', () => {
jest.mock('@js-joda/core');
LocalDateTime.now = jest
.fn()
.mockReturnValue(LocalDateTime.of(2023, 3, 26));
const sut = new Order(10_000);
sut.discount();
expect(sut.amount).toBe(9_000);
}
그러나, 위의 코드도 문제가 있다.
만약, 날짜 라이브러리 (js-joda)가 교체가 되거나, 테스트 프레임워크 (Jest)가 교체가 된다면 위 처럼 작성한 모든 테스트 코드를 하나씩 수정해주어야 한다.
이것을 우리는 제어할 수 있는 코드로 변경해야 한다.
export class Order {
private _amount: number;
discount(now = LocalDateTime.now()) {
if (now.dayOfWeek() === DayOfWeek.SUNDAY) {
this._amount = this.amount * 0.9;
}
}
}
기존 코드는 변수에 오늘 날짜를 받아서 사용했기 때문에, 날짜를 제어할 수 없었다. 그러나 위 코드로 변경을 하고 나면, 날짜를 인자로 받기 때문에 만약, 파라미터에 특정 날짜를 넣었다면 그 특정 날짜를 사용하고 아니라면 오늘 날짜를 사용하게 된다.
즉, 우리가 날짜를 제어할 수 있게 되는 것이다. 사용해야 할 값을 안에서 사용하는 것이 아니라 바깥으로 밀어내어 사용하는 것이다.
it('일요일에는 주문 금액이 10% 할인된다', () => {
const sut = new Order(10_000);
sut.discount(LocalDateTime.of(2023, 3, 26));
expect(sut.amount).toBe(9_000);
}
이렇게 코드를 수정하면, 일요일에 성공하는지 월요일에 실패하는지 확인이 쉽게 가능해진다.
전염되는 제어할 수 없는 코드
위에서는 제어할 수 없는 코드를 확인했다면, 이제는 제어할 수 없는 코드 때문에 소프트웨어가 얼마나 견고해지지 않는 경우를 확인한다.
export async function payCourses(courses: Course[]) {
for (const course of courses) {
const payAmount = course.amount * course.discountPercent;
if (payAmount > 100) {
await requestPg(course.titme, payAmount);
}
}
Modal.open(`${courses.length} 개 강의가 결제되었습니다.`);
}
위 코드는, 여러 강의들 중 결제 금액 계산 결과가 100원이 넘는 건들만 골라서 PG 결제를 하는 예제이다.
반복문을 돌며, 조건이 맞으면 "requestPg" API가 실행이 되는 것이다.
그리고 위의 코드를 하나의 함수는 하나의 기능을 하도록 리팩토링을 해보도록 하자.
export async function payCourses(courses: Course[]) {
await payAll(courses);
Modal.open(`${courses.length} 개 강의가 결제되었습니다.`);
}
export async function payAll(courses: Course[]) {
for (const course of courses) {
await pay(course);
}
}
export async function pay(course: Course) {
const payAmount = course.amount * course.discountPercent;
if (payAmount > 100) {
await requestPg(course.titme, payAmount);
}
}
위 처럼 각 기능을 하는 3개의 함수로 리팩토링을 했을 때, 문제가 존재한다. "async await"이라는 외부 API를 호출하는 키워드가 모든 함수에 들어가 있다. 클린코드의 원칙에 따라 하나의 함수가 하나의 기능만 하도록 수정했는데 결과적으로 모든 함수는 외부 API 모킹을 해야지만 성공하는 코드가 되는 것이다.
그 이유는, pay 함수의 외부 API를 호출하는 "await requestPg(course.titme, payAmount)"로 이 코드에 의존하고 있는 모든 코드는 제어할 수 없는 코드가 되버린다.
여기서 다시 설계를 생각해봐야 한다.
우리가 제어할 수 없는 것은 외부 서비스와 Modal이며, 제어할 수 있는 것은 결제 금액 계산과 100원 이상 걸러내는 것이다.
위의 내용을 구분해서 다시 리팩토링을 하게 된다면 다음과 같다.
export async function payCourses(courses: Course[]) {
const courseAmounts = getCourseAmounts(courses);
for (const courseAmount of courseAmounts) {
await requestPg(courseAmount.title, courseAmount.billingAmount);
}
}
export async function getCourseAmounts(courses: Course[]) {
return courses
.map(c => getCourseAmount(c))
.filter(c => c.billingAmount > 100);
}
export async function getCourseAmount(course: Course) {
return {
billingAmount: course.amount * course.discountPercent,
title: course.title,
}
}
우리가 제어할 수 없는 외부 API를 호출하는 "requsetPg"와 "Modal" 기능을 담은 payAll 함수와 그 두 개를 제외하여 우리가 제어할 수 있는 코드로 구성된 getCourseAmounts 함수와 getCourseAmount 함수로 수정하여 단위 테스트가 가능해진다.
다시 한 번, 제어할 수 없는 코드는 순수하지 않은 함수 혹은 객체를 말한다.
결과적으로, UI 레이어와 데이터는 제어하기 어렵지만, 순수 함수들이 모여있는 비즈니스 로직은 제어하기 쉽다.
그렇기 때문에, 제어가 어려운 코드는 UI 레이어나 데이터에 밀어 넣어서 격리할 영역을 만들고, 반대로 비즈니스 로직에는 제어 가능한 코드로 최대한 늘려나가야 할 영역으로 만들어야 한다.
'백엔드 > Node' 카테고리의 다른 글
데이터베이스에서 동시 수정을 방지하기 (0) | 2023.05.21 |
---|---|
[MongoDB] DB Error MongooseServerSelectionError (0) | 2023.04.04 |
Node에서 Serverless 생성하고 설정하기 (0) | 2023.03.28 |
Export Default & Export (0) | 2021.09.29 |
Next() 메소드는 무엇이지? (0) | 2021.08.09 |
- Total
- Today
- Yesterday
- PostechAppleDeveloperAcademy
- 프론트엔드 챌린지
- 조코딩과함께
- LottieFiles
- Frontend
- 자바스크립트
- React
- 포스텍애플아카데미
- 고민한 부분
- node
- 그룹인터뷰후기
- 설명회느낌점
- Singleton
- 깃허브 Merge
- 신입개발자가 준비해야 할 것들
- javascript
- 포스텍애플디벨로퍼아카데미
- 개발 이력서 지원 팁
- 원티드 프리온보딩 챌린지
- 코딩테스트 대비
- if(kakao)dev2022
- 스프링
- DB Error MongooseServerSelectionError
- 개발자이력서꿀팁
- Express
- 원티드 프리온보딩
- 최종추가합격
- #포스텍애플디벨로퍼아카데미
- 싱글톤
- Default Branch
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |