|
| 1 | +# 03. 액션과 계산, 데이터의 차이를 알기 |
| 2 | + |
| 3 | +- [액션, 계산, 데이터](#액션-계산-데이터) |
| 4 | +- [어디에나 적용할 수 있는 액션, 계산, 데이터](#어디에나-적용할-수-있는-액션-계산-데이터) |
| 5 | +- [새로 만드는 코드에 함수형 사고 적용하기](#새로-만드는-코드에-함수형-사고-적용하기) |
| 6 | +- [이미 있는 코드에 함수형 사고 적용하기](#이미-있는-코드에-함수형-사고-적용하기) |
| 7 | + |
| 8 | +## 액션, 계산, 데이터 |
| 9 | + |
| 10 | + |
| 11 | +| 액션 | 계산 | 데이터 | |
| 12 | +| --- | --- | --- | |
| 13 | +| 실행 시점과 횟수에 의존<br/>외부 세계에 영향을 주거나 받는 것<br/>언제 실행 (순서), 얼마나 실행 (반복) | 입력으로 출력을 계산<br/>외부 세계에 영향 주거나 받지 않음<br/>실행 시점이나 횟수에 의존하지 않음 | 이벤트에 대한 사실<br/>일어난 일에 대한 결과<br/>의미 있게 처리한 입력 장치로 얻은 정보 | |
| 14 | +| 부수 효과, 순수하지 않은 함수 | 순수 함수, 수학 함수 | | |
| 15 | +| 이메일 보내기, DB 읽기 | 최댓값 찾기, 이메일 주소가 올바른지 확인 | 사용자가 입력한(이벤트) 이메일 주소(사실)<br/>은행 API로 읽은(이벤트) 달러 수량 (사실) | |
| 16 | + |
| 17 | +### 🍭 액션 |
| 18 | + |
| 19 | +- 사용하기 어려우나 소프트웨어를 실행하려는 가장 중요한 이유임 |
| 20 | +- 액션을 잘 사용하기 위한 방법 |
| 21 | + - 가능한 한 적게 사용. 가급적 대신 계산 사용 |
| 22 | + - 가능한 한 작게 만듦. 액션과 관련 없는 코드(결정, 계획과 관련된 부분)는 모두 제거 |
| 23 | + - 외부 세계와 상호작용하는 것 제한. 내부에 계산과 데이터만 있고 가장 바깥에 액션이 있는 게 이상적. 18장 어니언 아키텍처 참고 |
| 24 | + - 액션이 호출 시점에 의존하는 것을 제한 |
| 25 | + |
| 26 | +### 🍭 계산 |
| 27 | + |
| 28 | +- 참조 투명(referentially transparant)함: 계산을 호출하는 코드를 계산 결과로 바꿀 수 있음 |
| 29 | +- 장점 (액션과 비교했을 때) |
| 30 | + - 테스트 용이 - 외부에 영향 주지 않음 → 가급적 액션보다 계산 사용하는 게 좋음 |
| 31 | + - 기계적 분석 쉬움 - 정벅 분석에서 자동화된 분석은 중요 |
| 32 | + - 조합하기 좋음 - 더 큰 계산 만들 수 있음. high order 계산 사용. |
| 33 | + - 계산 쓰면 걱정하지 않아도 됨 |
| 34 | + 1. 동시에 실행되는 것 |
| 35 | + 2. 과거에 실행됐던 것이나 미래에 실행할 것 |
| 36 | + 3. 실행 횟수 |
| 37 | +- 단점 |
| 38 | + - 실행 전에 어떤 일이 일어날지 알 수 없음. 블랙박스 |
| 39 | + |
| 40 | +### 🍭 데이터 |
| 41 | + |
| 42 | +- 불변성 |
| 43 | + - 카피 온 라이트 copy on write - 변경할 때 복사본 만듦 |
| 44 | + - 방어적 복사 defensive copy - 보관하려는 데이터의 복사본 만듦 |
| 45 | +- 장점: 데이터 자체로 할 수 있는 일이 없음 → 데이터 그대로 이해 가능 |
| 46 | + - 직렬화: 직렬화된 데이터는 전송하거나 데이터에 저장했다가 읽기 쉬움 |
| 47 | + - 동일성 비교 용이 |
| 48 | + - 자유로운 해석 |
| 49 | +- 단점 |
| 50 | + - 해석이 반드시 필요 |
| 51 | + |
| 52 | +<br /> |
| 53 | + |
| 54 | +## 어디에나 적용할 수 있는 액션, 계산, 데이터 |
| 55 | + |
| 56 | +1. 액션, 계산, 데이터는 어디에나 적용 가능 |
| 57 | +2. 액션 안에는 계산, 데이터, 또 다른 액션이 숨어있을 수도 있음 - 액션을 더 작은 것으로 나누고 나누는 것을 언제 멈춰야 할지 아는 게 중요 |
| 58 | +3. 계산은 더 작은 계산과 데이터로 나누고 연결 가능 |
| 59 | +4. 데이터는 데이터만 조합 가능 - 데이터는 다른 영향 주지 않음 → 데이터 찾는 일을 먼저 해야 |
| 60 | +5. 계산은 때로 ‘머릿속에서’ 일어남 - 사고 과정에 녹아있는 계산 존재 (장을 보는데 무엇을 사야 할지) → 결정, 계획은 계산일 가능성이 높음 |
| 61 | + |
| 62 | +<br /> |
| 63 | + |
| 64 | + |
| 65 | +## 새로 만드는 코드에 함수형 사고 적용하기 |
| 66 | + |
| 67 | +- 명세 |
| 68 | + - 친구 10명 이상 추천하면 더 좋은 쿠폰 보내줌 |
| 69 | + - 친구 10명을 추천하면 best 쿠폰, 모든 사용자는 good 쿠폰, 사용자에게 전달하지 않는 bad 쿠폰 존재 |
| 70 | + - 이메일 DB - 이메일, 추천 수 |
| 71 | + - 쿠폰 DB - 쿠폰, 등급 |
| 72 | +- 쿠폰 보내는 과정 |
| 73 | + |
| 74 | + 1. 데이터베이스에서 구독자 가져오기 |
| 75 | + 2. 데이터베이스에서 쿠폰 목록 가져오기 |
| 76 | + 3. 보내야 할 이메일 목록 만들기 |
| 77 | + 4. 이메일 전송하기 |
| 78 | + |
| 79 | + ```tsx |
| 80 | + // 1. 데이터베이스에서 구독자 가져오기 - 액션 |
| 81 | + // 데이터베이스에서 가져온 구독자 - 데이터 |
| 82 | + var subscribe = { |
| 83 | + |
| 84 | + rec_count: 16, |
| 85 | + }; |
| 86 | + |
| 87 | + // 쿠폰 등급은 문자열 - 데이터베이스 테이블의 rank 열 값과 동일 |
| 88 | + var rank1 = "best"; |
| 89 | + var rank2 = "good"; |
| 90 | + |
| 91 | + // 구독자가 받을 쿠폰 등급을 결정하는 함수 - 계산 |
| 92 | + function subCouponRank(subscriber) { |
| 93 | + if (subscriber.rec_count >= 10) { |
| 94 | + return "best"; |
| 95 | + } else { |
| 96 | + return "good"; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + // 2. 데이터베이스에서 쿠폰 목록 가져오기 - 액션 |
| 101 | + // 데이터베이스에서 가져온 쿠폰 - 데이터 |
| 102 | + var coupon = { |
| 103 | + code: "10PERCENT", |
| 104 | + rank: "bad", |
| 105 | + }; |
| 106 | + |
| 107 | + // 특정 등급의 쿠폰 목록을 선택하는 함수 - 계산 |
| 108 | + /** |
| 109 | + * @param {coupons} Object 전체 쿠폰 목록 |
| 110 | + * @param {rank} string 선택할 등급 |
| 111 | + */ |
| 112 | + function selectCouponsByRank(coupons, rank) { |
| 113 | + var ret = []; |
| 114 | + for (var c = 0; c < coupons.length; c++) { |
| 115 | + var coupon = coupons[c]; |
| 116 | + // 쿠폰이 주어진 등급에 맞으면 쿠폰 코드를 배열에 넣음 |
| 117 | + if (coupon.rank === rank) { |
| 118 | + ret.push(coupon.code); |
| 119 | + } |
| 120 | + return ret; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + // 3. 보내야 할 이메일 목록 만들기 |
| 125 | + // 이메일 - 데이터 |
| 126 | + var message = { |
| 127 | + |
| 128 | + |
| 129 | + subject: "Your weekly coupons inside", |
| 130 | + body: "Here are your coupons...", |
| 131 | + }; |
| 132 | + |
| 133 | + // 구독자가 받을 이메일 목록 계획 - 계산 |
| 134 | + /** |
| 135 | + * @param {subscriber} Object 구독자 |
| 136 | + * @param {goods} Object good 쿠폰 목록 |
| 137 | + * @param {bests} Object bad 쿠폰 목록 |
| 138 | + */ |
| 139 | + function emailForSubscriber(subscriber, goods, bests) { |
| 140 | + var rank = subCouponRank(subscriber); |
| 141 | + if (rank === "best") { |
| 142 | + // 등급 결정 |
| 143 | + return { |
| 144 | + // 이메일 만들어 리턴 |
| 145 | + |
| 146 | + |
| 147 | + subject: "Your best weekly coupons inside", |
| 148 | + body: "Here are your the best coupons: " + bests.join(", "), |
| 149 | + }; |
| 150 | + } else { |
| 151 | + return { |
| 152 | + |
| 153 | + |
| 154 | + subject: "Your good weekly coupons inside", |
| 155 | + body: "Here are your the good coupons: " + goods.join(", "), |
| 156 | + }; |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + // 보낸 이메일 목록 준비 - 계산 |
| 161 | + function emailsForSubscribers(subscribers, goods, bests) { |
| 162 | + var emails = []; |
| 163 | + for (var s = 0; s < subscribers.length; s++) { |
| 164 | + var subscriber = subscribers[s]; |
| 165 | + var email = emailForSubscriber(subscriber, goods, bests); |
| 166 | + emails.push(email); |
| 167 | + } |
| 168 | + return emails; |
| 169 | + } |
| 170 | + |
| 171 | + // 4. 이메일 전송하기 |
| 172 | + // 이메일 보내기 - 액션: 앞에서 이미 계획한 목록 순회하며 보내기 |
| 173 | + function sendIssue() { |
| 174 | + var coupons = fetchCouponsFromDB(); |
| 175 | + var goodCoupons = selectCouponsByRank(coupons, "good"); |
| 176 | + var bestCoupons = selectCouponsByRank(coupons, "best"); |
| 177 | + // 최적화하려면 구독자 목록을 페이지네이션으로 잘라서 가져오면 됨 |
| 178 | + var subscribers = fetchSubscribersFromDB(); |
| 179 | + var emails = emailsForSubscribers(subscribers, goodCoupons, bestCoupons); |
| 180 | + for (var e = 0; e < emails.length; e++) { |
| 181 | + var email = emails[e]; |
| 182 | + emailSystem.send(email); |
| 183 | + } |
| 184 | + } |
| 185 | + ``` |
| 186 | + |
| 187 | +<br /> |
| 188 | + |
| 189 | + |
| 190 | +## 이미 있는 코드에 함수형 사고 적용하기 |
| 191 | + |
| 192 | +자회사에 수수료 보내기 |
| 193 | + |
| 194 | +```tsx |
| 195 | +// 2. 액션을 호출함 -> 함수 전체가 액션 |
| 196 | +function figurePayout(affiliate) { |
| 197 | + var owed = affiliate.sales * affiliate.commission; |
| 198 | + if (owed > 100) { |
| 199 | + // 100달러 이하면 송금 안 함 |
| 200 | + sendPayout(affiliate.bank_code, owed); // 1. 액션 |
| 201 | + } |
| 202 | +} |
| 203 | + |
| 204 | +// 4. 액션을 호출함 -> 함수 전체가 액션 |
| 205 | +function affiliatePayout(affiliates) { |
| 206 | + for (var a = 0; a < affiliates.length; a++) { |
| 207 | + figurePayout(affiliates[a]); // 3. 액션 함수를 호출 -> 액션 |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +// 6. 액션을 호출함 -> 함수 전체가 액션 |
| 212 | +function main(affiliates) { |
| 213 | + affiliatePayout(affiliates); // 5. 액션 함수를 호출 -> 액션 |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +⇒ 액션을 호출하는 함수 또한 액션이 되기에 액션 조심해서 사용해야 |
0 commit comments