├── .gitignore ├── 01_리팩터링-첫-번째-예시 ├── 01.리펙터링-첫-번째-예시_lcuid │ └── app │ │ ├── REAME.md │ │ ├── data │ │ ├── invocies.json │ │ └── plays.json │ │ ├── example │ │ ├── change_1-4.js │ │ ├── change_1-6.js │ │ └── origin.js │ │ ├── index.html │ │ ├── main.js │ │ ├── module │ │ ├── createStatement.js │ │ └── statement.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── test │ │ ├── createStatement.test.js │ │ └── statement.test.js │ │ ├── util.js │ │ └── yarn.lock ├── 01_리팩터링-첫번째-예시_SHUZAN │ ├── 01_리팩터링-첫번째-예시_SHUZAN.md │ ├── app │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── babel.config.json │ │ ├── jest.config.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ └── chp1 │ │ │ │ ├── 1-4-statement.js │ │ │ │ ├── 1-6-createStatementData.js │ │ │ │ ├── 1-6-statement.js │ │ │ │ ├── 1-8-createStatementData.js │ │ │ │ ├── 1-8-statement.js │ │ │ │ ├── data │ │ │ │ ├── invocies.json │ │ │ │ └── plays.json │ │ │ │ └── statement-original.js │ │ └── test │ │ │ └── ch1 │ │ │ ├── 1-4-statement.test.js │ │ │ ├── 1-6-statement.test.js │ │ │ ├── 1-8-statement.test.js │ │ │ ├── statement-original.test.js │ │ │ └── test-result.js │ └── test.png ├── 01_리팩터링-첫번째-예시_Tony │ ├── 0_서문 │ │ ├── 0_서문.md │ │ └── 0_서문.ts │ ├── 1_첫번째-예시 │ │ ├── 1-6_final │ │ │ ├── createStatementData.ts │ │ │ ├── statement.ts │ │ │ └── type.ts │ │ ├── 1-8 │ │ │ ├── createStatementData.ts │ │ │ ├── statement.ts │ │ │ └── type.ts │ │ ├── 1_리팩터링 첫번째 예시.md │ │ ├── index.html │ │ ├── statement.ts │ │ ├── statement_1-4.ts │ │ └── statement_1-6.ts │ └── tsconfig.json ├── 01_리팩터링-첫번째-예시_hyeonmin │ ├── Chapter1_첫_번째_예시_hyeonmin.md │ ├── data │ │ ├── invoices.json │ │ └── plays.json │ └── srcs │ │ ├── 1-1_statement.js │ │ ├── 1-4_statement.js │ │ ├── 1-6_statement.js │ │ ├── createStatementData.js │ │ ├── package.json │ │ └── statement.js ├── 01_리팩터링_첫_번째_예시_Minwoo │ ├── README.md │ ├── invoices.json │ ├── main.py │ ├── performance_calculator.py │ ├── plays.json │ ├── statement.py │ ├── statement_data.py │ └── tests.py ├── 01_리팩터링_첫번째-예시_kiwi │ ├── 1-1_statement.js │ ├── 1-4_statement.js │ ├── 1-6-src │ │ ├── createStatementData.js │ │ └── statement.js │ ├── 1-6_statement.js │ ├── README.md │ └── data │ │ ├── invoices.json │ │ └── plays.json └── a ├── 02_리팩터링-원칙 ├── 02_리팩터링-원칙_SHUZAN.md ├── 02_리팩터링-원칙_lucid.md ├── 02_리팩터링-원칙_tony.md ├── 02_리팩터링_원칙_Minwoo.md ├── 02_리팩터링_원칙_hyeonmin.md ├── 02_리팩터링_원칙_kiwi.md └── a ├── 03_코드에서-나는-악취 ├── 03_코드에서-나는-악취_SHUZAN.md ├── 03_코드에서-나는-악취_Tony.md ├── 03_코드에서-나는-악취_hyeonmin.md ├── 03_코드에서-나는-악취_kiwi.md ├── 03_코드에서-나는-악취_lucid.md ├── 03_코드에서_나는_악취_Minwoo.md └── a ├── 04_테스트-구축하기 ├── 04_테스트-구축하기_Minwoo │ ├── producer.py │ ├── province.py │ ├── review.md │ └── tests.py ├── 04_테스트-구축하기_SHUZAN │ ├── 04_테스트-구축하기_SHUZAN.md │ └── app │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ └── Province.js │ │ └── test │ │ └── province.test.js ├── 04_테스트-구축하기_lucid │ ├── .eslintrc.js │ ├── README.md │ ├── dist │ │ └── main.js │ ├── index.html │ ├── jest.config.js │ ├── main.css │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── Producer.ts │ │ ├── Province.ts │ │ ├── index.test.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock ├── 04_테스트-구축하기_tony │ ├── .babelrc │ ├── 4장-정리_tony.md │ ├── 4장.test.ts │ ├── Province.ts │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── 환경설정.md ├── 04_테스트_구축하기_hyeonmin │ ├── 4장_테스트_구축하기_정리.md │ └── app │ │ ├── .babelrc │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ └── province.js │ │ └── test │ │ └── province.test.js ├── 04_테스트_구축하기_kiwi │ ├── 04_테스트_구축하기_kiwi.md │ └── app │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ └── Province.js │ │ └── test │ │ └── province.test.js └── a ├── 05_리팩터링-카탈로그-보는-법 ├── 05_리팩터링-카탈로그-보는-법_Tony.md ├── 05_리팩터링-카탈로그-보는-법_hyeonmin.md ├── 05_리팩터링-카탈로그-보는-법_lucid.md └── a ├── 06_기본적인-리팩터링 ├── 06_기본적인-리팩터링_Tony │ ├── 6_11_after.js │ ├── 6_11_before.js │ ├── 6장-정리.md │ └── image │ │ ├── classCopyTest.png │ │ └── classCopyTest1.png ├── 06_기본적인-리팩터링_hyeonmin │ ├── 06_기본적인_리팩터링_hyeonmin.md │ └── src │ │ ├── 6-1-1.js │ │ ├── 6-1-2.js │ │ ├── 6-2-1.js │ │ ├── 6-2-2.js │ │ ├── 6-3-1.js │ │ ├── 6-3-2.js │ │ ├── 6-5-1.js │ │ ├── 6-5-2.js │ │ ├── 6-5-3.js │ │ ├── 6-5-4.js │ │ ├── 6-6-1.js │ │ └── 6-6-1_defaultOwner.js ├── 06_기본적인-리팩터링_kiwi │ ├── 06_기본적인_리팩터링_kiwi.md │ └── src │ │ ├── 6.1example.js │ │ ├── 6.2example.js │ │ ├── 6.3example.js │ │ ├── 6.4example.js │ │ ├── 6.5example.js │ │ ├── 6.6example.js │ │ ├── 6.7example.js │ │ ├── 6.8example.js │ │ └── 6.9example.js ├── 06_기본적인-리팩터링_lucid │ ├── .eslintrc.js │ ├── README.md │ ├── index.html │ ├── jest.config.js │ ├── main.css │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── 6-1 │ │ │ ├── 6-1-1.ts │ │ │ ├── 6-1-2.ts │ │ │ ├── 6-1-3.ts │ │ │ └── 6-1.test.ts │ │ └── 6-3 │ │ │ └── 6-3.ts │ ├── tsconfig.json │ └── webpack.config.js ├── 06_기본적인_리팩터링_Minwoo.md ├── 06_기본적인_리팩터링_SHUZAN │ ├── 06_기본적인_리팩터링_SHUZAN.md │ ├── sample │ │ ├── 6-1-함수추출하기.js │ │ ├── 6-10-여러함수를변환함수로묶기.js │ │ ├── 6-2-함수인라인하기.js │ │ ├── 6-3-변수추출하기.js │ │ ├── 6-4-변수인라인하기.js │ │ ├── 6-5-함수선언바꾸기.js │ │ ├── 6-6-변수캡슐화하기.js │ │ ├── 6-7-변수이름바꾸기.js │ │ ├── 6-8-매개변수객체만들기.js │ │ └── 6-9-여러함수를클래스로묶기.js │ └── test │ │ ├── .babelrc │ │ ├── .gitIgnore │ │ ├── 6-10-여러함수를변환함수로묶기.test.js │ │ ├── CombineFunctionsIntoClass-Client1.js │ │ ├── CombineFunctionsIntoClass-Client1.test.js │ │ ├── CombineFunctionsIntoClass-Client2.js │ │ ├── CombineFunctionsIntoClass-Client2.test.js │ │ ├── CombineFunctionsIntoClass-Client3.js │ │ ├── CombineFunctionsIntoClass-Client3.test.js │ │ ├── CombineFunctionsIntoClass.js │ │ ├── babel.config.json │ │ └── jest.config.json └── a ├── 07_캡슐화 ├── 07_캡슐화_Minwoo.md ├── 07_캡슐화_SHUZAN │ ├── 07_캡슐화_SHUZAN.md │ └── sample │ │ ├── 7-1-레코드캡슐화하기.js │ │ ├── 7-2-컬렉션캡슐화하기.js │ │ ├── 7-3-기본형을객체로바꾸기.js │ │ ├── 7-4-임시변수를질의함수로바꾸기.js │ │ ├── 7-5-클래스추출하기.js │ │ ├── 7-6-클래스인라인하기.js │ │ ├── 7-7-위임숨기기.js │ │ ├── 7-8-중개자제거하기.js │ │ └── 7-9-알고리즘교체하기.js ├── 07_캡슐화_Tony │ ├── 07_캡슐화_Tony.md │ └── image │ │ ├── 대단한것은_아니지만_엄청난것.png │ │ └── 완전하게_복사는_안되네.png ├── 07_캡슐화_kiwi │ └── 07_캡슐화_kiwi.md ├── 07_캡슐화_lucid │ └── README.md └── a ├── 08_기능-이동 ├── 08_기능-이동_SHUZAN │ ├── README.md │ └── sample │ │ ├── 01-함수옮기기.ts │ │ ├── 01-함수옮기기2.ts │ │ ├── 02-필드옮기기.js │ │ ├── 03-문장을함수로옮기기.js │ │ ├── 05-인라인코드를함수호출로바꾸기.js │ │ ├── 07-반복문쪼개기.js │ │ └── 07-반복문파이프라인으로바꾸기.js ├── 08_기능_이동_Minwoo.md ├── 08_기능이동_Tony │ └── 08_기능이동_Tony.md ├── 08_기능이동_kiwi │ ├── .babelrc │ ├── .gitignore │ ├── 08_기능이동_kiwi.md │ ├── 8-1-move_function │ │ ├── move_function_1.js │ │ ├── move_function_2.js │ │ ├── move_function_3.js │ │ └── test.js │ ├── 8-2-move_field │ │ ├── move_field_1.js │ │ ├── move_field_2.js │ │ └── test.js │ ├── 8-3-move_statements_into_function │ │ ├── move_s_into_function1.js │ │ ├── move_s_into_function2.js │ │ └── test.js │ ├── 8-4-move_statements_to_callers │ │ └── move_s_to_callers.js │ ├── 8-5_Repalce_inline_code_function_call │ │ ├── replace_inline_code.js │ │ ├── replace_inline_code2.js │ │ └── test.js │ ├── 8-6_slide_statements │ │ ├── slide_statements.js │ │ ├── slide_statements2.js │ │ └── test.js │ ├── 8-7-slit_Loop │ │ ├── slit_Loop.js │ │ ├── slit_Loop2.js │ │ └── test.js │ ├── 8-8_replace_loop_with_pipeline │ │ ├── replace_loop_with_pipeline.js │ │ ├── replace_loop_with_pipeline2.js │ │ └── test.js │ ├── 8-9-Remove_dead_code │ │ └── remove_dead_code.js │ └── package.json ├── 08_기능이동_lucid │ ├── 01-1-1.js │ ├── 01-1-2.js │ ├── 01-2-1.js │ ├── 01-2-2.js │ ├── 6-1-1.js │ ├── 6-1-2.js │ ├── 7-1-after.js │ ├── 7-1-after2.js │ ├── 7-1-before.js │ ├── 8-1-before.js │ ├── Account.js │ ├── REAME.md │ ├── app.js │ └── main.js └── a ├── 09_데이터-조직화 ├── 09_데이터-조직화_Tony │ ├── 09_데이터-조직화_Tony.md │ ├── Map하나보장.png │ └── 매개변수_호출자.png ├── 09_데이터-조직화_hyeonmin │ ├── 09_데이터-조직화_hyeonmin.md │ └── src │ │ ├── 1-1-after.js │ │ ├── 1-1-before.js │ │ ├── 1-2-after.js │ │ ├── 1-2-before.js │ │ ├── 2-after.js │ │ ├── 2-before.js │ │ ├── 3-1-after.js │ │ ├── 3-1-before.js │ │ ├── 3-2-after.js │ │ ├── 3-2-before.js │ │ ├── 4-after.js │ │ ├── 4-before.js │ │ ├── 5-after.js │ │ └── 5-before.js ├── 09_데이터-조직화_kiwi │ └── 09_데이터-조직화_kiwi.md ├── 09_데이터-조직화_lucid │ ├── 1 │ │ ├── after.js │ │ └── before.js │ ├── 2 │ │ ├── after.js │ │ └── before.js │ ├── 3 │ │ ├── after.js │ │ └── before.js │ └── README.md ├── 09_데이터_조직화_Minwoo.md └── a ├── 10_조건부-로직-간소화 ├── 10_조건부-로직-간소화_Hyeonmin │ ├── README.md │ └── src │ │ ├── 01-after.js │ │ ├── 01-before.js │ │ ├── 02-1-after.js │ │ ├── 02-1-before.js │ │ ├── 03-1-after.js │ │ ├── 03-1-before.js │ │ ├── 03-2.js │ │ ├── 04-1.js │ │ ├── 04-2.js │ │ ├── 05-1.js │ │ ├── 06.js │ │ └── 07.js ├── 10_조건부-로직-간소화_SHUZAN │ ├── README.md │ └── sample │ │ ├── .gitignore │ │ ├── 01-조건문분해하기.js │ │ ├── 02-1조건식통합하기.js │ │ ├── 02-2조건식통합하기.js │ │ ├── 03-1-중첩조건문을보호구문으로바꾸기.js │ │ ├── 03-2-중첩조건문-조건반대로만들기js │ │ ├── 04-조건부로직-다형성으로바꾸기.js │ │ ├── 05-조건부로직-변형동작다형성.js │ │ ├── 06-1-특이케이스추가하기.js │ │ ├── 06-2-특이케이스추가하기.js │ │ ├── 06-3-특이케이스추가하기.js │ │ ├── 07-어서션추가하기.js │ │ ├── 08-제어플래그-탈출문으로바꾸기.js │ │ ├── package-lock.json │ │ └── package.json ├── 10_조건부-로직-간소화_lucid │ ├── 1 │ │ ├── advance.js │ │ ├── after.js │ │ └── before.js │ ├── 2 │ │ ├── after-2.js │ │ ├── after.js │ │ └── before.js │ ├── 3 │ │ ├── after.js │ │ └── before.js │ ├── 4 │ │ ├── after-2.js │ │ ├── after.js │ │ └── before.js │ ├── 6 │ │ ├── after.js │ │ └── before.js │ ├── 7 │ │ ├── advance.js │ │ ├── after-1.js │ │ ├── after-2.js │ │ └── before.js │ ├── README.md │ ├── package-lock.json │ └── package.json ├── 10_조건부_로직_간소화_Minwoo.md └── a ├── 11_API-리팩터링 ├── 11_API-리팩터링_Hyeonmin │ ├── README.md │ └── src │ │ ├── 01.js │ │ ├── 02-1.js │ │ ├── 02-2.js │ │ ├── 03-1.js │ │ ├── 03-2.js │ │ ├── 04-1.js │ │ ├── 04-2.js │ │ ├── 05.js │ │ ├── 06.js │ │ ├── 07.js │ │ ├── 09.js │ │ ├── 10.js │ │ ├── 11.js │ │ ├── 12.js │ │ └── 8.js ├── 11_API-리팩터링_Tony │ ├── 11_11.ts │ ├── 11_12.ts │ └── 11_summary.md ├── 11_API-리팩터링_lucid │ ├── 1 │ │ ├── after.js │ │ └── before.js │ ├── 2 │ │ ├── after.js │ │ └── before.js │ ├── 3.플래그인수제거하기 │ │ ├── after.js │ │ └── before.js │ ├── 4.객체통째로넘기기 │ │ └── before.js │ ├── 5.매개변수를 질의 함수로 바꾸기 │ │ ├── after.js │ │ └── before.js │ └── README.md ├── 11_API_리팩터링_Minwoo.md ├── 11_API_리팩터링_SHUZAN │ ├── README.md │ └── sample │ │ ├── 01-질의함수와-변경함수-분리하기.js │ │ ├── 02-함수-매개변수화하기.js │ │ ├── 03-플래그-인수-제거하기.js │ │ ├── 04-객체-통째로-넘기기.js │ │ ├── 05-매개변수를-질의함수로-바꾸기.js │ │ ├── 06-질의함수를-매개변수로-바꾸기.js │ │ ├── 07-세터제거하기.js │ │ ├── 08-생성자를 팩터리함수로 바꾸기.js │ │ ├── 09-함수를-명령으로-바꾸기.js │ │ ├── 10-명령을-함수로-바꾸기.js │ │ ├── 11-수정된-값-반환하기.js │ │ ├── 12-오류코드를-예외로-바꾸기.js │ │ └── 13-예외를-사전확인으로-바꾸기.ts ├── 11_API_리팩터링_kiwi │ └── README.md └── a ├── 12_상속-다루기 ├── 12_상속-다루기_lucid │ ├── 11.슈퍼클래스위임으로 바꾸기 │ │ ├── advance.ts │ │ ├── after.ts │ │ └── before.ts │ ├── 1_메서드올리기 │ │ ├── after.js │ │ └── before.js │ ├── 2.필드올리기 │ │ ├── after.ts │ │ └── before.ts │ ├── 3.생성자_본문_올리기 │ │ ├── after.ts │ │ ├── after2.ts │ │ ├── before.ts │ │ └── before2.ts │ ├── 6.타입코드를서브클래스로바꾸기 │ │ ├── advance-after.ts │ │ ├── advance-before.ts │ │ ├── after.ts │ │ ├── after2.ts │ │ └── before.ts │ ├── 7.서브클래스제거하기 │ │ ├── after.ts │ │ └── before.ts │ ├── 8.슈퍼클래스호출하기 │ │ ├── after.ts │ │ └── before.ts │ └── README.md ├── 12_상속_다루기_Minwoo.md ├── a └── hyeonmin │ ├── README.md │ └── src │ ├── 01.js │ ├── 02.js │ ├── 03-1.js │ ├── 03-2.js │ ├── 06-1.js │ ├── 06-2.js │ ├── 07.js │ ├── 08.js │ ├── 10-1-special.js │ └── 10-1.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /**/node_modules/ 3 | .vscode 4 | coverage -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/data/invocies.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": "BigCo", 3 | "performances": [ 4 | { 5 | "playID": "halmet", 6 | "audience": 55 7 | }, 8 | { 9 | "playID": "asLike", 10 | "audience": 35 11 | }, 12 | { 13 | "playID": "othello", 14 | "audience": 40 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/data/plays.json: -------------------------------------------------------------------------------- 1 | { 2 | "halmet": { "name": "Halmet", "type": "tragedy"}, 3 | "asLike": {"name": "As You Like It", "type": "comedy"}, 4 | "othello": { "name": "Othello", "type": "tragedy"} 5 | } -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 7 | 8 |총액: ${krw(data.totalAmount)}
` 14 | result += `적립 포인트: ${data.totalVolumeCredit}점
` 15 | return result; 16 | } 17 | 18 | function krw(aNumber) { 19 | return new Intl.NumberFormat('ko-KR', 20 | { 21 | style: "currency", currency: "KRW", 22 | minimumFractionDigits: 2 23 | }).format(aNumber); 24 | } 25 | 26 | function htmlStatement(invoice, plays) { 27 | return renderHtml(createStatement(invoice, plays)) 28 | } 29 | 30 | 31 | module.exports = { 32 | htmlStatement 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "node main.js", 8 | "dev": "lite-server", 9 | "test": "jest --watch" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "jest": "^27.3.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/test/createStatement.test.js: -------------------------------------------------------------------------------- 1 | const {createStatement} = require("../module/createStatement"); 2 | 3 | const invoices = require('../data/invocies.json') 4 | const plays = require('../data/plays.json') 5 | 6 | const expectResult = { 7 | "customer": "BigCo", 8 | "performances": [ 9 | { 10 | "amount": 65000, 11 | "volumeCredit": 25, 12 | "audience": 55, 13 | "play": {"name": "Halmet", "type": "tragedy"}, 14 | "playID": "halmet" 15 | }, 16 | { 17 | "amount": 49000, 18 | "volumeCredit": 12, 19 | "audience": 35, 20 | "play": {"name": "As You Like It", "type": "comedy"}, 21 | "playID": "asLike" 22 | }, 23 | { 24 | "amount": 50000, 25 | "volumeCredit": 10, 26 | "audience": 40, 27 | "play": {"name": "Othello", "type": "tragedy"}, 28 | "playID": "othello" 29 | }], 30 | "totalAmount": 164000, 31 | "totalVolumeCredit": 47 32 | } 33 | 34 | describe('createStatement test', () => { 35 | 36 | test('calculate origin statement', () => { 37 | const result = createStatement(invoices, plays); 38 | expect(result).toEqual(expectResult) 39 | }) 40 | 41 | }) -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/test/statement.test.js: -------------------------------------------------------------------------------- 1 | const invoices = require('../data/invocies.json') 2 | const plays = require('../data/plays.json') 3 | 4 | const {statement: originStatement} = require('../example/origin'); 5 | const {statement: changeStatement} = require('../example/change_1-6'); 6 | 7 | const expectResult = 8 | "\t청구 내역 (고객명: BigCo)\n" + 9 | "\tHalmet: ₩650.00 (55석)\n" + 10 | "\tAs You Like It: ₩490.00 (35석)\n" + 11 | "\tOthello: ₩500.00 (40석)\n" + 12 | "\t총액: ₩1,640.00\n" + 13 | "\t적립 포인트: 47점\n" 14 | 15 | describe('calculate amount, credit', () => { 16 | 17 | test('calculate origin statement', () => { 18 | const result = originStatement(invoices, plays); 19 | expect(result).toEqual(expectResult) 20 | }) 21 | 22 | test('calculate example-2 statement', () => { 23 | const result = changeStatement(invoices, plays); 24 | expect(result).toEqual(expectResult) 25 | }) 26 | 27 | }) -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/util.js: -------------------------------------------------------------------------------- 1 | const useDebugger = false; 2 | 3 | const log = (...args) => useDebugger ? console.log(args) : null; 4 | 5 | module.exports = { 6 | log 7 | } -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["transform-es2015-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | clover.xml 4 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/babel.config.json: -------------------------------------------------------------------------------- 1 | //ref:https://babeljs.io/docs/en/configuration 2 | { 3 | "presets": ["@babel/preset-env"] 4 | } 5 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectCoverage": true, 3 | "moduleFileExtensions": ["js", "mjs"], 4 | "transform": { 5 | "^.+\\.js$": "babel-jest", 6 | "^.+\\.mjs$": "babel-jest" 7 | }, 8 | "testRegex": "((\\.|/*.)(test))\\.js?$" 9 | } 10 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-dh", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "author": "dh", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.16.0", 13 | "@babel/preset-env": "^7.16.4", 14 | "@types/jest": "^27.0.3", 15 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 16 | "jest": "^27.3.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/src/chp1/data/invocies.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": "BigCo", 3 | "performances": [ 4 | { 5 | "playID": "halmet", 6 | "audience": 55 7 | }, 8 | { 9 | "playID": "asLike", 10 | "audience": 35 11 | }, 12 | { 13 | "playID": "othello", 14 | "audience": 40 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/src/chp1/data/plays.json: -------------------------------------------------------------------------------- 1 | { 2 | "halmet": { "name": "Halmet", "type": "tragedy" }, 3 | "asLike": { "name": "As You Like It", "type": "comedy" }, 4 | "othello": { "name": "Othello", "type": "tragedy" } 5 | } 6 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/test/ch1/1-4-statement.test.js: -------------------------------------------------------------------------------- 1 | const invoices = require("../../src/chp1/data/invocies.json"); 2 | const plays = require("../../src/chp1/data/plays.json"); 3 | import { statement } from "../../src/chp1/1-4-statement.js"; 4 | import result from "./test-result.js"; 5 | 6 | describe("[Chpate1.4ver] check refactoring result", () => { 7 | test("statement test result", () => { 8 | const statementResult = statement(invoices, plays); 9 | expect(statementResult).toBe(result); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/test/ch1/1-6-statement.test.js: -------------------------------------------------------------------------------- 1 | const invoices = require("../../src/chp1/data/invocies.json"); 2 | const plays = require("../../src/chp1/data/plays.json"); 3 | import { statement } from "../../src/chp1/1-6-statement.js"; 4 | import result from "./test-result.js"; 5 | 6 | describe("[Chpate1.6ver] check refactoring result", () => { 7 | test("statement test result", () => { 8 | const statementResult = statement(invoices, plays); 9 | expect(statementResult).toBe(result); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/test/ch1/1-8-statement.test.js: -------------------------------------------------------------------------------- 1 | const invoices = require("../../src/chp1/data/invocies.json"); 2 | const plays = require("../../src/chp1/data/plays.json"); 3 | import { statement } from "../../src/chp1/1-8-statement.js"; 4 | import result from "./test-result.js"; 5 | 6 | describe("[Chpate1.8ver] check refactoring result", () => { 7 | test("statement test result", () => { 8 | const statementResult = statement(invoices, plays); 9 | expect(statementResult).toBe(result); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/test/ch1/statement-original.test.js: -------------------------------------------------------------------------------- 1 | const invoices = require("../../src/chp1/data/invocies.json"); 2 | const plays = require("../../src/chp1/data/plays.json"); 3 | import { statement } from "../../src/chp1/statement-original.js"; 4 | import result from "./test-result.js"; 5 | 6 | describe("[before start refactoring ver] check refactoring result", () => { 7 | test("statement test result", () => { 8 | const statementResult = statement(invoices, plays); 9 | expect(statementResult).toBe(result); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/app/test/ch1/test-result.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 진행이 필요한 test case 3 | * [x] original statement.js - 리팩토링 시작 전 4 | * [x] 1-4 version statement.js - 1차 리팩토링 5 | * [x] 1-6 version statement.js - 2차 리팩토링(다형성 버전) 6 | */ 7 | 8 | export default `청구 내역 (고객명: BigCo) 9 | Halmet: $650.00 (55석) 10 | As You Like It: $580.00 (35석) 11 | Othello: $500.00 (40석) 12 | 총액 : $1,730.00 13 | 적립 포인트 : 47점 14 | `; 15 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_SHUZAN/test.png -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_Tony/0_서문/0_서문.md: -------------------------------------------------------------------------------- 1 | # 리팩터링 2판 - 들어가며 2 | 3 | ### 다루는 내용 4 | 5 | - 1장 : 간단한 예제로 알아보는 리팩터링 6 | - 2장 : 리팩터링의 일반원칙, 정의, 당위성 7 | - 리팩터링할 때 생기는 문제점 8 | - 3장 : 악취를 찾는 방법과 제거하는 방법 9 | - 4장 : 테스트 작성 방법 10 | 11 | 1~4장 : 리팩터링 실습 12 | 나머지장 : 카탈로그 개념 - 그때 그때 찾아보는 부분 13 | 14 | 자바스크립트로 되어있지만 타입스크립트로 변환해서 해보고 싶다. 15 | 16 | ### 리팩터링을 지속적으로 하는 것의 의미 17 | 18 | - 처음부터 완벽한 설계를 갖추기 보다는 개발을 진행하면서 지속적으로 설계한다. 19 | 20 | ### Typescript의 getter와 setter 21 | 22 | ```typescript 23 | class Person { 24 | private _name: string; 25 | get name() { 26 | return this._name; 27 | } // 게터 28 | set name(name: string) { 29 | this._name = name; 30 | } 31 | constructor(name: string) { 32 | this._name = name; 33 | } 34 | } 35 | 36 | class Person { 37 | // 위와 같은 코드 38 | get name() { 39 | return this._name; 40 | } // 게터 41 | set name(name: string) { 42 | this._name = name; 43 | } 44 | 45 | constructor(private _name: string) { 46 | // private _name 이 아니라 _name 인 경우 setter를 호출하기 때문에 무한 루프에 빠짐 47 | } 48 | } 49 | 50 | const tony = new Person('tony'); 51 | console.log(tony.name); // getter가 호출 됨 52 | tony.name = '태환'; // setter가 호출 됨 53 | console.log(tony.name); // setter로 변경된 name 출력 54 | ``` 55 | 56 | ### typescript 환경 설정 57 | 58 | - node, npm 설치 59 | - npm install -g typescript 60 | - npm i -g ts-node 61 | - npm i -g @types/node 62 | - tsc --init 63 | - tsc --init까진 필요없을 것 같음 64 | 65 | #### typescript 파일 실행 66 | 67 | - ts-node 파일명.ts 68 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_Tony/0_서문/0_서문.ts: -------------------------------------------------------------------------------- 1 | { 2 | class Person { 3 | // 위와 같은 코드 4 | get name() { 5 | return this._name; 6 | } // 게터 7 | set name(name: string) { 8 | this._name = name; 9 | } 10 | 11 | constructor(private _name: string) { 12 | // private _name 이 아니라 _name 인 경우 setter를 호출하기 때문에 무한 루프에 빠짐 13 | } 14 | } 15 | 16 | const tony = new Person('tony'); 17 | console.log(tony.name); // getter가 호출 됨 18 | tony.name = '태환'; // setter가 호출 됨 19 | console.log(tony.name); // setter로 변경된 name 출력 20 | } 21 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_Tony/1_첫번째-예시/1-6_final/type.ts: -------------------------------------------------------------------------------- 1 | export type Performance = { 2 | playID: 'hamlet' | 'asLike' | 'othello'; 3 | audience: number; 4 | }; 5 | 6 | export type Invoice = { 7 | customer: string; 8 | performances: Performance[]; 9 | }; 10 | 11 | export type PlayDetail = { 12 | name: string; 13 | type: 'tragedy' | 'comedy'; 14 | }; 15 | 16 | export type Plays = { 17 | hamlet: PlayDetail; 18 | asLike: PlayDetail; 19 | othello: PlayDetail; 20 | }; 21 | 22 | export type StatementPerformance = Performance & { 23 | play: PlayDetail; 24 | amount: number; 25 | volumeCredits: number; 26 | }; 27 | 28 | export type StatementData = { 29 | customer: string; // customer?: string; 로 선언했던 것을 statementData에 직접 선언하므로서 undefined를 잡지 않아도 됨 30 | performances: StatementPerformance[]; // Performance[]; 31 | totalAmount: number; 32 | totalVolumeCredits: number; 33 | }; 34 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_Tony/1_첫번째-예시/1-8/type.ts: -------------------------------------------------------------------------------- 1 | export type Performance = { 2 | playID: 'hamlet' | 'asLike' | 'othello'; 3 | audience: number; 4 | }; 5 | 6 | export type Invoice = { 7 | customer: string; 8 | performances: Performance[]; 9 | }; 10 | 11 | export type PlayDetail = { 12 | name: string; 13 | type: 'tragedy' | 'comedy'; 14 | }; 15 | 16 | export type Plays = { 17 | hamlet: PlayDetail; 18 | asLike: PlayDetail; 19 | othello: PlayDetail; 20 | }; 21 | 22 | export type StatementPerformance = Performance & { 23 | play: PlayDetail; 24 | amount: number; 25 | volumeCredits: number; 26 | }; 27 | 28 | export type StatementData = { 29 | customer: string; // customer?: string; 로 선언했던 것을 statementData에 직접 선언하므로서 undefined를 잡지 않아도 됨 30 | performances: StatementPerformance[]; // Performance[]; 31 | totalAmount: number; 32 | totalVolumeCredits: number; 33 | }; 34 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_Tony/1_첫번째-예시/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |연극 | 14 |좌석 수 | 15 |금액 | 16 |
---|---|---|
Hamlet | 20 |55 | 21 |$650.00 | 22 |
As You Like It | 26 |35 | 27 |$580.00 | 28 |
Othello | 32 |40 | 33 |$500.00 | 34 |
총액 : $1,730.00
37 |적립 포인트 : 47점
38 | 39 | 40 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_hyeonmin/Chapter1_첫_번째_예시_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter1. 첫 번째 예시 2 | 3 | 4 | 5 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/1-a444598a907947f58e1f2e61ff3637b7) 6 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_hyeonmin/data/invoices.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": "BigCo", 3 | "performances": [ 4 | { 5 | "playID": "halmet", 6 | "audience": 55 7 | }, 8 | { 9 | "playID": "as-like", 10 | "audience": 35 11 | }, 12 | { 13 | "playID": "othello", 14 | "audience": 40 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_hyeonmin/data/plays.json: -------------------------------------------------------------------------------- 1 | { 2 | "halmet": { "name": "Halmet", "type": "tragedy" }, 3 | "as-like": { "name": "As You Like It", "type": "comedy" }, 4 | "othello": { "name": "Othello", "type": "tragedy" } 5 | } 6 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링-첫번째-예시_hyeonmin/srcs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-chapter-1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "statement.js", 6 | "scripts": { 7 | "start": "node statement.js" 8 | }, 9 | "type": "module", 10 | "author": "", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫_번째_예시_Minwoo/invoices.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "customer": "BigCo", 4 | "performances": [ 5 | { 6 | "playID": "hamlet", 7 | "audience": 55 8 | }, 9 | { 10 | "playID": "as-like", 11 | "audience": 35 12 | }, 13 | { 14 | "playID": "othello", 15 | "audience": 40 16 | } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫_번째_예시_Minwoo/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from statement import statement 4 | 5 | 6 | def main(): 7 | invoice_doc = "" 8 | 9 | with open("plays.json") as play_f, open("invoices.json") as invoice_f: 10 | plays = json.load(play_f) 11 | invoices = json.load(invoice_f) 12 | 13 | for invoice in invoices: 14 | invoice_doc += statement(invoice, plays) 15 | 16 | print(invoice_doc, end="") 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫_번째_예시_Minwoo/plays.json: -------------------------------------------------------------------------------- 1 | { 2 | "hamlet": { "name": "Hamlet", "type": "tragedy" }, 3 | "as-like": { "name": "As You Like It", "type": "comedy" }, 4 | "othello": { "name": "Othello", "type": "tragedy" } 5 | } 6 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫_번째_예시_Minwoo/statement_data.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | from functools import reduce 3 | 4 | from performance_calculator import create_performance_calculator 5 | 6 | 7 | def create_statement_data(invoice: dict, plays: dict) -> dict: 8 | result = {} 9 | result["customer"] = invoice["customer"] 10 | result["performances"] = [ 11 | enrich_performance(perf, plays) for perf in invoice["performances"] 12 | ] 13 | result["total_amount"] = get_total_amount(result) 14 | result["total_volume_credits"] = get_total_volume_credits(result) 15 | return result 16 | 17 | 18 | def enrich_performance(performance: dict, plays: dict) -> dict: 19 | calculator = create_performance_calculator( 20 | performance, get_play_for(performance, plays) 21 | ) 22 | result = copy(performance) # Shallow copy 23 | result["play"] = calculator.play 24 | result["amount"] = calculator.get_amount() 25 | result["volume_credits"] = calculator.get_volume_credits() 26 | return result 27 | 28 | 29 | def get_play_for(performance: dict, plays: dict) -> dict: 30 | return plays[performance["playID"]] 31 | 32 | 33 | def get_total_amount(data: dict) -> int: 34 | return reduce(lambda total, p: total + p["amount"], data["performances"], 0) 35 | 36 | 37 | def get_total_volume_credits(data: dict) -> int: 38 | return reduce( 39 | lambda total, p: total + p["volume_credits"], data["performances"], 0 40 | ) 41 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫번째-예시_kiwi/data/invoices.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": "BigCo", 3 | "performances": [ 4 | { 5 | "playID": "halmet", 6 | "audience": 55 7 | }, 8 | { 9 | "playID": "as-like", 10 | "audience": 35 11 | }, 12 | { 13 | "playID": "othello", 14 | "audience": 40 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01_리팩터링_첫번째-예시_kiwi/data/plays.json: -------------------------------------------------------------------------------- 1 | { 2 | "halmet": { "name": "Halmet", "type": "tragedy" }, 3 | "as-like": { "name": "As You Like It", "type": "comedy" }, 4 | "othello": { "name": "Othello", "type": "tragedy" } 5 | } 6 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/01_리팩터링-첫-번째-예시/a -------------------------------------------------------------------------------- /02_리팩터링-원칙/02_리팩터링_원칙_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter2. 리팩터링 원칙 2 | 3 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/2-f54ec96b0fc74556912c0b0b098c449f) -------------------------------------------------------------------------------- /02_리팩터링-원칙/02_리팩터링_원칙_kiwi.md: -------------------------------------------------------------------------------- 1 | ### Chapter2. 리팩터링 원칙 2 | 3 | [학습 내용 정리 링크](https://www.notion.so/2-eef191aff0bf408caf1e2c829269ccd8). 4 | 5 | -------------------------------------------------------------------------------- /02_리팩터링-원칙/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/02_리팩터링-원칙/a -------------------------------------------------------------------------------- /03_코드에서-나는-악취/03_코드에서-나는-악취_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter3. 코드에서 나는 악취 2 | 3 | 4 | 5 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/3-1571f141e9b045f4afca25cf12be115e) 6 | -------------------------------------------------------------------------------- /03_코드에서-나는-악취/03_코드에서_나는_악취_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 03. 코드에서 나는 악취 2 | 3 | - 본 챕터는 여러 code snippet을 참조하는 것이 더 이해하기 쉽습니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [코드에서 나는 악취](https://mwjjeongdev.notion.site/Chapter-03-03966db9fb314b17adc548d19db598d3) 6 | -------------------------------------------------------------------------------- /03_코드에서-나는-악취/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/03_코드에서-나는-악취/a -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_Minwoo/producer.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | 4 | class Producer: 5 | def __init__(self, province, data): 6 | self._province = province 7 | self._cost = data["cost"] 8 | self._name = data["name"] 9 | self._production = data.get("production", 0) 10 | 11 | @property 12 | def name(self): 13 | return self._name 14 | 15 | @property 16 | def cost(self): 17 | return self._cost 18 | 19 | @cost.setter 20 | def cost(self, value): 21 | self._cost = int(value) 22 | 23 | @property 24 | def production(self): 25 | return self._production 26 | 27 | @production.setter 28 | def production(self, amount: Union[str, int]): 29 | if type(amount) == str: 30 | new_production = int(amount) if amount.isdigit() else 0 31 | else: 32 | new_production = amount 33 | self._province.total_production += new_production - self._production 34 | self._production = new_production 35 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_Minwoo/review.md: -------------------------------------------------------------------------------- 1 | # Chapter 04. 테스트 구축하기 2 | 3 | ## Introduction 4 | 5 | - 본 문서에는 주로 책에서 나온 일반적인 이론 또는 조언들을 정리하는데 초점을 두었다. 6 | - Javascript에 한정되는 내용들은 정리 내용에서 제외하였다. 7 | 8 | ## 자가 테스트 코드의 가치 9 | 10 | - 리팩터링을 제대로 하려면 견고한 테스트 스위트가 뒷받침되어야 한다. 11 | - 좋은 테스트를 작성하는 일은 개발 효율을 높인다. 12 | - **모든 테스트를 완전히 자동화하고 그 결과까지 스스로 검사하게 하자.** 13 | - 잘 작성된 테스트를 자주 수행하는 습관은 디버깅의 효율을 극도로 높일 수 있다. 14 | - 테스트를 작성하기 가장 좋은 시점은 프로그래밍을 시작하기 전이다. 15 | - _테스트 주도 개발 (Test-Driven Development)_ 16 | 17 | ## 테스트 작성 시의 조언 18 | 19 | - 실패해야 할 상황에서는 반드시 실패하게 만들자. 20 | - 자주 테스트하자. 21 | 22 | - 작성 중인 코드는 최소 몇 분 간격으로 테스트 23 | - 적어도 하루에 한 번은 전체 테스트 24 | 25 | - **테스트는 위험 요인을 중심으로 작성하자** 26 | 27 | - 테스트의 목적은 현재 혹은 향후 발생할 수 있는 버그를 찾는 것이다. 28 | - 단순한 Getter/Setter는 보통 테스트를 할 필요 없다. 29 | - 모든 경우에 대해 완벽하게 테스트를 작성할 필요 없다. 30 | 31 | - **공유 픽스쳐의 사용은 피하자.** 32 | 33 | - 개별 테스트마다 픽스처를 새로 구성해야 모든 테스트를 독립적으로 구성할 수 있다. 34 | 35 | - 의도된 동작 외에 **경계 조건**에 대해서도 꼭 검사하자. 36 | 37 | - 문제 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 테스트하자. 38 | - 의식적으로 프로그램을 망가뜨리기. 39 | - 경계 조건에 대응하는 동작은 겉보기 동작이 아니다. 40 | - 리팩터링으로 인해 이 동작이 변하는지는 신경 쓸 필요 없다. 41 | 42 | - **수확 체감 법칙** 43 | 44 | - 테스트를 너무 많이 작성하면 오히려 의욕이 떨어진다. 45 | - 위험한 부분에 집중하자. 46 | 47 | - 리팩터링을 하는 동안에도 꾸준히 테스트를 보강하자. 48 | 49 | ## 그 외의 조언 50 | 51 | - 버그 리포트를 받으면 가장 먼저 그 버그를 드러내는 단위 테스트부터 작성하자. 52 | - 리팩터링 후 테스트 결과가 모두 통과된 것만으로 리팩터링 과정에서 생겨난 버그가 없다 확신할 수 있다면 좋은 테스트 스위트이다. 53 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_SHUZAN/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_SHUZAN/app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_SHUZAN/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-2nd-edition_04ch_shuzan", 3 | "version": "1.0.0", 4 | "description": "", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "test": "mocha --watch --recursive --require babel-core/register" 10 | }, 11 | "author": "shuzan", 12 | "license": "ISC", 13 | "dependencies": { 14 | "babel-preset-es2015": "^6.24.1", 15 | "babel-register": "^6.26.0", 16 | "eslint": "^5.12.1", 17 | "mocha": "^5.2.0", 18 | "chai": "^4.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | }, 7 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended'], 8 | plugins: ['prettier', '@typescript-eslint'], 9 | rules: { 10 | 'prettier/prettier': [ 11 | 'error', 12 | { 13 | singleQuote: true, 14 | semi: true, 15 | useTabs: false, 16 | tabWidth: 2, 17 | printWidth: 130, 18 | bracketSpacing: true, 19 | arrowParens: 'avoid', 20 | }, 21 | ], 22 | }, 23 | parserOptions: { 24 | parser: '@typescript-eslint/parser', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |${person.name}
`); 4 | result.push(renderPhoto(person.photo)); 5 | result.push(emitPhotoData(person.photo)); 6 | return result.join("\n"); 7 | } 8 | 9 | function photoDiv(aPhoto) { 10 | return ["title: ${p.title}
`, 17 | `location: ${p.location}
`, 18 | `date: ${p.date.toDateString()}
`, 19 | ].join("\n"); 20 | } 21 | 22 | function renderPhoto(aPhoto) { 23 | return ""; 24 | } 25 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/05-인라인코드를함수호출로바꾸기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | let appliesToMass = false; 4 | const states = ["MA", "JJ"]; 5 | for (const s of states) { 6 | if (s === "MA") appliesToMass = true; 7 | } 8 | } 9 | //리팩토링 후 10 | 11 | { 12 | const states = ["MA", "JJ"]; 13 | const appliesToMass = states.includes["MA"]; 14 | } 15 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/07-반복문쪼개기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | let average = 0; 4 | let totalSalary = 0; 5 | const people = [ 6 | { age: 30, salary: 4000 }, 7 | { age: 40, salary: 7000 }, 8 | { age: 24, salary: 2800 }, 9 | { age: 37, salary: 4600 }, 10 | { age: 27, salary: 3200 }, 11 | ]; 12 | for (const p of people) { 13 | average += p.age; 14 | totalSalary += p.salary; 15 | } 16 | } 17 | 18 | //리팩토링 후 19 | { 20 | let average = 0; 21 | let totalSalary = 0; 22 | const people = [ 23 | { age: 30, salary: 4000 }, 24 | { age: 40, salary: 7000 }, 25 | { age: 24, salary: 2800 }, 26 | { age: 37, salary: 4600 }, 27 | { age: 27, salary: 3200 }, 28 | ]; 29 | for (const p of people) { 30 | average += p.age; 31 | } 32 | for (const p of people) { 33 | totalSalary += p.salary; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/07-반복문파이프라인으로바꾸기.js: -------------------------------------------------------------------------------- 1 | const input = [ 2 | { job: "programmer", name: "a" }, 3 | { job: "programmer", name: "b" }, 4 | { job: "teacher", name: "c" }, 5 | ]; 6 | 7 | // 리팩토링 전 8 | { 9 | const names = []; 10 | for (const i of input) { 11 | if (i.job === "programmer") names.push(i.name); 12 | } 13 | } 14 | 15 | // 리팩토링 후 16 | { 17 | const names = input.filter((i) => i.job === "programmer").map((i) => i.name); 18 | } 19 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능_이동_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 08. 기능 이동 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 08 정리 문서](https://mwjjeongdev.notion.site/Chapter-08-3191c7ec9e7e4f9882377fdc6babf26c) 6 | - [Chapter 08 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter08) 7 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-1-move_function/move_function_1.js: -------------------------------------------------------------------------------- 1 | module.exports = function trackSummary(points) { 2 | const totalTime = calculateTime(); 3 | const totalDistance = calculateDistance(); 4 | const pace = totalTime / 60 / totalDistance; 5 | 6 | return { 7 | time: totalTime, 8 | distance: totalDistance, 9 | pace: pace, 10 | }; 11 | 12 | // 총 거리 계산 13 | function calculateDistance() { 14 | let result = 0; 15 | for (let i = 1; i < points.length; i++) { 16 | result += distance(points[i - 1], points[i]); 17 | } 18 | 19 | return result; 20 | } 21 | 22 | // 두 지점의 거리 계산 23 | function distance(p1, p2) { 24 | const EARTH_RADIUS = 3959; // mile 25 | const dLat = radians(p2.lat) - radians(p1.lat); 26 | const dLon = radians(p2.lon) - radians(p1.lon); 27 | const a = 28 | Math.pow(Math.sin(dLat / 2), 2) + 29 | Math.cos(radians(p2.lat)) * 30 | Math.cos(radians(p1.lat)) * 31 | Math.pow(Math.sin(dLon / 2), 2); 32 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 33 | 34 | return EARTH_RADIUS * c; 35 | } 36 | 37 | // 라디안 값으로 변환 38 | function radians(degrees) { 39 | return (degrees * Math.PI) / 180; 40 | } 41 | 42 | // 총 시간 계산 43 | function calculateTime(params) {} 44 | } 45 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-1-move_function/move_function_3.js: -------------------------------------------------------------------------------- 1 | module.exports = function trackSummary(points) { 2 | const totalTime = calculateTime(); 3 | const pace = totalTime / 60 / totalDistance(points); 4 | 5 | return { 6 | time: totalTime, 7 | distance: totalDistance(points), 8 | pace: pace, 9 | }; 10 | 11 | function totalDistance() { 12 | let result = 0; 13 | 14 | for (let i = 1; i < points.length; i++) { 15 | result += distance(points[i - 1], points[i]); 16 | } 17 | 18 | return result; 19 | } 20 | 21 | // 두 지점의 거리 계산 22 | function distance(p1, p2) { 23 | // 하버사인 공식은 다음 링크 참고 24 | // http://www.movable-type.co.uk/scrips/latlong.html 25 | const EARTH_RADIUS = 3959; // mile 26 | const dLat = radians(p2.lat) - radians(p1.lat); 27 | const dLon = radians(p2.lon) - radians(p1.lon); 28 | const a = 29 | Math.pow(Math.sin(dLat / 2), 2) + 30 | Math.cos(radians(p2.lat)) * 31 | Math.cos(radians(p1.lat)) * 32 | Math.pow(Math.sin(dLon / 2), 2); 33 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 34 | 35 | return EARTH_RADIUS * c; 36 | } 37 | 38 | // 라디안 값으로 변환 39 | function radians(degrees) { 40 | return (degrees * Math.PI) / 180; 41 | } 42 | 43 | function calculateTime(params) {} 44 | 45 | } -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-1-move_function/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const trackSummary = require("./move_function_3"); 3 | 4 | function sampleProvinceData(){ 5 | return [ 6 | { 7 | lat: 12, 8 | lon: 12 9 | }, 10 | { 11 | lat: 13, 12 | lon: 13 13 | } 14 | ] 15 | } 16 | 17 | describe("move_function", function () { 18 | const result = trackSummary(sampleProvinceData()) 19 | 20 | it("distance", function () { 21 | assert.equal(result.distance, 96.56681074723605); 22 | }); 23 | it("time", function () { 24 | assert.equal(result.time, undefined); 25 | }); 26 | it("pace", function () { 27 | assert.equal(result.pace, NaN); 28 | }); 29 | }) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-2-move_field/move_field_1.js: -------------------------------------------------------------------------------- 1 | module.exports = class Customer { 2 | constructor(name, discountRate) { 3 | this._name = name; 4 | this._discountRate = discountRate; 5 | this._contract = new CustomerContract("2021-12-29"); 6 | } 7 | 8 | get discountRate() { 9 | return this._discountRate; 10 | } 11 | 12 | becomePreferred() { 13 | this._discountRate += 0.03; 14 | 15 | // 다른 멋진 일들 16 | } 17 | 18 | applyDiscount(amount) { 19 | return amount.subtract(amount.multiply(this._discountRate)); 20 | } 21 | } 22 | 23 | class CustomerContract { 24 | constructor(startDate) { 25 | this._startDate = startDate; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-2-move_field/move_field_2.js: -------------------------------------------------------------------------------- 1 | module.exports = class Customer { 2 | constructor(name, discountRate) { 3 | this._name = name; 4 | this._contract = new CustomerContract("2021-12-29"); 5 | this._setDiscountRate(discountRate); 6 | } 7 | 8 | get discountRate() { 9 | return this._contract.discountRate; 10 | } 11 | 12 | _setDiscountRate(aNumber) { 13 | this._contract.discountRate = aNumber; 14 | } 15 | 16 | becomePreferred() { 17 | this._setDiscountRate(this.discountRate + 0.03); 18 | 19 | // some code 20 | } 21 | 22 | applyDiscount(amount) { 23 | return amount.subtract(amount.multiply(this.discountRate)); 24 | } 25 | } 26 | 27 | class CustomerContract { 28 | constructor(startDate, discountRate) { 29 | this._startDate = startDate; 30 | this._discountRate = discountRate; 31 | } 32 | 33 | get discountRate() { 34 | return this._discountRate; 35 | } 36 | 37 | set discountRate(arg) { 38 | this._discountRate = arg; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-2-move_field/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const Customer = require("./move_field_1"); 3 | const Customer2 = require("./move_field_2"); 4 | 5 | 6 | function sampleCustomer(){ 7 | return new Customer("kiwi", 3) 8 | } 9 | function sampleCustomer2(){ 10 | return new Customer2("kiwi", 3) 11 | } 12 | 13 | describe("move_field", function () { 14 | const result = sampleCustomer() 15 | 16 | it("name", function () { 17 | assert.equal(result._name, 'kiwi'); 18 | }); 19 | it("discountRate", function () { 20 | assert.equal(result._discountRate, 3); 21 | }); 22 | it("contract:", function () { 23 | assert.equal(result._contract._startDate, '2021-12-29'); 24 | }); 25 | }) 26 | 27 | describe("move_field2", function () { 28 | const result = sampleCustomer2() 29 | 30 | it("name", function () { 31 | assert.equal(result._name, 'kiwi'); 32 | }); 33 | it("discountRate", function () { 34 | assert.equal(result._contract._discountRate, 3); 35 | }); 36 | it("contract:", function () { 37 | assert.equal(result._contract._startDate, '2021-12-29'); 38 | }); 39 | }) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-3-move_statements_into_function/move_s_into_function1.js: -------------------------------------------------------------------------------- 1 | module.exports = function renderPerson(outStream, person) { 2 | const result = []; 3 | 4 | result.push(`${person.name}
`); 5 | result.push(`제목: ${person.photo.title}
`);//. <- 제목 출력 6 | result.push(emitPhotoData(person.photo)); 7 | 8 | return result.join('\n'); 9 | } 10 | 11 | function photoDiv(p) { 12 | return ['제목: ${p.title}
`, emitPhotoData(p), '위치: ${aPhoto.location}
`); 21 | result.push(`날짜: ${aPhoto.date}
`); 22 | 23 | return result.join('\n'); 24 | } 25 | 26 | const person = { 27 | name: "case1", 28 | photo : { 29 | title: "title", 30 | location : "seoul", 31 | date: "2021-12-29" 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-3-move_statements_into_function/move_s_into_function2.js: -------------------------------------------------------------------------------- 1 | module.exports = function renderPerson(outStream, person) { 2 | const result = []; 3 | 4 | result.push(`${person.name}
`); 5 | result.push(`제목: ${person.photo.title}
`); 6 | result.push(emitPhotoData(person.photo)); 7 | 8 | return result.join('\n'); 9 | } 10 | 11 | function photoDiv(aPhoto) { 12 | return ['제목: ${aPhoto.title}
`, 18 | `위치: ${aPhoto.location}
`, 19 | `날짜: ${aPhoto.date}
`, 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-3-move_statements_into_function/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const renderPerson = require("./move_s_into_function1"); 3 | const renderPerson2 = require("./move_s_into_function2"); 4 | 5 | 6 | function sampleRenderPerson(){ 7 | return { 8 | name: "case1", 9 | photo : { 10 | title: "title", 11 | location : "seoul", 12 | date: "2021-12-29" 13 | } 14 | } 15 | } 16 | 17 | 18 | describe("move_statement", function () { 19 | const result = renderPerson(1, sampleRenderPerson()) 20 | 21 | it("distance", function () { 22 | assert.equal(result, 'case1
\n제목: title
\n위치: seoul
\n날짜: 2021-12-29
'); 23 | }); 24 | }) 25 | 26 | describe("move_statement2", function () { 27 | const result = renderPerson2(1, sampleRenderPerson()) 28 | 29 | it("distance", function () { 30 | assert.equal(result, 'case1
\n' + 31 | '제목: title
\n' + 32 | '제목: title
,위치: seoul
,날짜: 2021-12-29
'); 33 | }); 34 | } 35 | ) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-5_Repalce_inline_code_function_call/replace_inline_code.js: -------------------------------------------------------------------------------- 1 | const states = ['a', 'b', 'MA'] 2 | 3 | let appliesToMass = false; 4 | 5 | for (const s of states) { 6 | if (s === 'MA') { 7 | appliesToMass = true; 8 | } 9 | } 10 | module.exports = appliesToMass -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-5_Repalce_inline_code_function_call/replace_inline_code2.js: -------------------------------------------------------------------------------- 1 | const states = ['a', 'b', 'MA'] 2 | 3 | 4 | let appliesToMass = states.includes('MA'); 5 | 6 | module.exports = appliesToMass -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-5_Repalce_inline_code_function_call/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const appliesToMass = require("./replace_inline_code2"); 3 | 4 | 5 | describe("replace_inline_code", function () { 6 | const result = appliesToMass 7 | 8 | it("ok_case", function () { 9 | assert.equal(result, true); 10 | }); 11 | }) 12 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-6_slide_statements/slide_statements.js: -------------------------------------------------------------------------------- 1 | 2 | let result = false; 3 | const availableResources = [1,2]; 4 | const allocatedResources = [] 5 | 6 | 7 | if (availableResources.length === 0) { 8 | result = createResource(); 9 | allocatedResources.push(result); 10 | } else { 11 | result = availableResources.pop(); 12 | allocatedResources.push(result); 13 | } 14 | 15 | module.exports = { 16 | result : result, 17 | allocatedResources: allocatedResources 18 | } 19 | 20 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-6_slide_statements/slide_statements2.js: -------------------------------------------------------------------------------- 1 | 2 | let result = false; 3 | const availableResources = [1,2]; 4 | const allocatedResources = [] 5 | 6 | if (availableResources.length === 0) { 7 | result = createResource(); 8 | } else { 9 | result = availableResources.pop(); 10 | } 11 | 12 | allocatedResources.push(result); 13 | 14 | module.exports = { 15 | result : result, 16 | allocatedResources: allocatedResources 17 | } 18 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-6_slide_statements/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const result = require("./slide_statements2"); 3 | 4 | 5 | describe("slide_statements", function () { 6 | 7 | it("result", function () { 8 | assert.equal(result.result, 2); 9 | }); 10 | it("allocatedResources", function () { 11 | assert.equal(result.result, [2]); 12 | }); 13 | }) 14 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-7-slit_Loop/slit_Loop.js: -------------------------------------------------------------------------------- 1 | const people = [{ 2 | age : 20, 3 | salary :2000 4 | }, 5 | { 6 | age : 10, 7 | salary :3000 8 | }, 9 | { 10 | age : 15, 11 | salary :4000 12 | } 13 | ] 14 | 15 | let youngest = people[0] ? people[0].age : Infinity; 16 | let totalSalary = 0; 17 | 18 | for (const p of people) { 19 | if (p.age < youngest) { 20 | youngest = p.age; 21 | } 22 | totalSalary += p.salary; 23 | } 24 | 25 | module.exports = `최연소: ${youngest}, 총 급여: ${totalSalary}`; -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-7-slit_Loop/slit_Loop2.js: -------------------------------------------------------------------------------- 1 | const people = [{ 2 | age : 20, 3 | salary :2000 4 | }, 5 | { 6 | age : 10, 7 | salary :3000 8 | }, 9 | { 10 | age : 15, 11 | salary :4000 12 | } 13 | ] 14 | 15 | 16 | function youngestAge() { 17 | return Math.min(...people.map((p) => p.age)); 18 | } 19 | 20 | function totalSalary() { 21 | return people.reduce((total, p) => total + p.salary, 0); 22 | } 23 | 24 | function example() { 25 | return `최연소: ${youngestAge()}, 총 급여: ${totalSalary()}`; 26 | } 27 | 28 | module.exports = example(); -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-7-slit_Loop/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const result = require("./slit_Loop2"); 3 | 4 | 5 | describe("slit_Loop", function () { 6 | 7 | it("result", function () { 8 | assert.equal(result, '최연소: 10, 총 급여: 9000'); 9 | }); 10 | }) 11 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-8_replace_loop_with_pipeline/replace_loop_with_pipeline.js: -------------------------------------------------------------------------------- 1 | function acquireData(input) { 2 | const lines = input.split('\n'); 3 | let firstLine = true; 4 | const result = []; 5 | 6 | for (const line of lines) { 7 | if (firstLine) { 8 | firstLine = false; 9 | 10 | continue; 11 | } 12 | 13 | if (line.trim() === '') { 14 | continue; 15 | } 16 | 17 | const record = line.split(','); 18 | 19 | if (record[1].trim() === 'India') { 20 | result.push({ city: record[0].trim(), phone: record[2].trim() }); 21 | } 22 | } 23 | 24 | return result; 25 | } 26 | 27 | const data = `office, country, telephone 28 | Chicago, USA, +1 312 373 1000 29 | Beijing, China, +86 4008 900 505 30 | Bangalore, India, +91 80 4064 9570 31 | Proto Alergre, Brazil, +55 51 3079 3550 32 | Chennai, India, +91 44 660 44766` 33 | 34 | module.exports = acquireData(data) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-8_replace_loop_with_pipeline/replace_loop_with_pipeline2.js: -------------------------------------------------------------------------------- 1 | function acquireData(input) { 2 | const lines = input.split('\n'); 3 | 4 | const result = lines 5 | .slice(1) 6 | .filter((line) => line.trim !== '') 7 | .map((line) => line.split(',')) 8 | .filter((record) => record[1].trim() === 'India') 9 | .map((record) => ({ city: record[0].trim(), phone: record[2].trim() })); 10 | 11 | return result; 12 | } 13 | const data = `office, country, telephone 14 | Chicago, USA, +1 312 373 1000 15 | Beijing, China, +86 4008 900 505 16 | Bangalore, India, +91 80 4064 9570 17 | Proto Alergre, Brazil, +55 51 3079 3550 18 | Chennai, India, +91 44 660 44766` 19 | 20 | module.exports = acquireData(data) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-8_replace_loop_with_pipeline/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | const result = require("./replace_loop_with_pipeline2"); 3 | 4 | 5 | describe("slit_Loop", function () { 6 | 7 | it("result0", function () { 8 | assert.equal(result[0].city, 'Bangalore'); 9 | assert.equal(result[0].phone, '+91 80 4064 9570'); 10 | assert.equal(result[1].city, 'Chennai'); 11 | assert.equal(result[1].phone, '+91 44 660 44766'); 12 | }); 13 | }) 14 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/8-9-Remove_dead_code/remove_dead_code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 리팩토링 전 3 | */ 4 | if (false) { 5 | doSomethingThatUsedToMatter(); 6 | } 7 | 8 | 9 | /** 10 | * 리팩토링 후 11 | */ -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_kiwi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-2nd-edition_08", 3 | "version": "1.0.0", 4 | "description": "", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "test": "mocha --watch --recursive --require babel-core/register" 10 | }, 11 | "author": "kiwi", 12 | "license": "ISC", 13 | "dependencies": { 14 | "babel-preset-es2015": "^6.24.1", 15 | "babel-register": "^6.26.0", 16 | "eslint": "^5.12.1", 17 | "mocha": "^5.2.0", 18 | "chai": "^4.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/01-1-1.js: -------------------------------------------------------------------------------- 1 | 2 | // 함수에서 중청함수 calculateDistance()를 최상위로 옮겨서 추적 거리를 다른 정보화 독립적을고 계산하고 싶다 3 | 4 | // 3. 가장 먼저 할 을 이 함수로 최상위로 복사한다는것이다 5 | 6 | function trackSummary(points) { 7 | const totalTime = calculateTime() 8 | const totalDistance = calculateDistance(points); 9 | const pace = totalTime / 60 / totalDistance; 10 | 11 | return { 12 | time: totalTime, 13 | distance: totalDistance, 14 | pace 15 | } 16 | 17 | function calculateTime() {} 18 | function calculateDistance() { 19 | return top_calculateDistance(points); 20 | } 21 | } 22 | 23 | function top_calculateDistance(points) { // 최상위로 복사하면서 새로운 임시 이름을 지워줌 24 | let result = 0; 25 | for (let i = 0; i < points.length; i++) { 26 | result += distance(points[i-1],points[i]); 27 | } 28 | return result; 29 | 30 | // points 와, distance, 가 정적 분석기에서 떠오를거다 31 | 32 | function distance(p1, p2) { 33 | const EARTH_RADIUS = 3959; 34 | const dLat = radians(p2.lat) - radians(p1.lat); 35 | const dLon = radians(p2.lon) - radians(p1.lon); 36 | const a = Math.pow(Math.sin(dLat / 2), 2) 37 | + Math.cos(radians(p2.lat)) 38 | * Math.cos(radians(p1.lat)) 39 | * Math.pow(Math.min(dLon / 2), 2); 40 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 41 | return EARTH_RADIUS * c; 42 | } 43 | 44 | function radians(degrees) { 45 | return degrees * Math.PI / 180; 46 | } 47 | } 48 | // 6 이제 변경하는 함수를 호출해보자( 직접 말고 간접으로) -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/01-1-2.js: -------------------------------------------------------------------------------- 1 | function trackSummary(points) { 2 | const totalTime = calculateTime() 3 | const pace = totalTime / 60 / totalDistance(points); 4 | 5 | return { 6 | time: totalTime, 7 | distance: totalDistance(points), 8 | pace 9 | } 10 | 11 | function calculateTime() {} 12 | } 13 | function totalDistance(points) { // 최상위로 복사하면서 새로운 임시 이름을 지워줌 14 | let result = 0; 15 | for (let i = 0; i < points.length; i++) { 16 | result += distance(points[i-1],points[i]); 17 | } 18 | return result; 19 | } 20 | // 6 이제 변경하는 함수를 호출해보자( 직접 말고 간접으로) 21 | // points 와, distance, 가 정적 분석기에서 떠오를거다 22 | function distance(p1, p2) { 23 | const EARTH_RADIUS = 3959; 24 | const dLat = radians(p2.lat) - radians(p1.lat); 25 | const dLon = radians(p2.lon) - radians(p1.lon); 26 | const a = Math.pow(Math.sin(dLat / 2), 2) 27 | + Math.cos(radians(p2.lat)) 28 | * Math.cos(radians(p1.lat)) 29 | * Math.pow(Math.min(dLon / 2), 2); 30 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 31 | return EARTH_RADIUS * c; 32 | } 33 | function radians(degrees) { 34 | return degrees * Math.PI / 180; 35 | } 36 | 37 | 38 | // distance radians 함수도 totalDistance 안에 어떤것에도 의존하지 않을경우 최상위로 옮기자 39 | // 중첩 함수는 숨겨진데이터끼리 상호 의존할수 있으니 되도록 만들지 말자 40 | 41 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/01-2-1.js: -------------------------------------------------------------------------------- 1 | class Account { 2 | get bankCharge(){ 3 | let result = 4.5; 4 | if (this._daysOverdrawn > 0) result +=this.overdraftCharge; 5 | return result; 6 | } 7 | get overdraftCharge(){ 8 | if (this.type._isPremium){ 9 | const baseCharge = 10; 10 | if (this.daysOverdrawn <= 7) 11 | return baseCharge; 12 | else 13 | return baseCharge + (this.daysOverdrawn -7) * 0.85; 14 | } 15 | else 16 | return this.daysOverdrawn * 1.75; 17 | } 18 | } 19 | 20 | 21 | // overdraftCharge 를 계좌 종류 클래스인 AccountType 으로 옮기느게 자연스럽다 -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/01-2-2.js: -------------------------------------------------------------------------------- 1 | class Account { 2 | get bankCharge(){ 3 | let result = 4.5; 4 | if (this._daysOverdrawn > 0) result += this.overdraftCharge; 5 | return result; 6 | } 7 | get overdraftCharge() { // 위임 메소드 8 | return this.type.overedraftCharge(this.daysOverdrawn) 9 | } 10 | } 11 | 12 | 13 | // TODO::overdraftCharge 를 계좌 종류 클래스인 AccountType 으로 옮기느게 자연스럽다 14 | class AccountType { 15 | get bankCharge() { 16 | let result = 4.5; 17 | if (this._daysOverdrwawn > 0) 18 | result += this.type.overdraftCharge(this.daysOverdrawn); 19 | return result; 20 | } 21 | overdraftCharge(account){ 22 | if (this.isPremium){ 23 | const baseCharge = 10; 24 | if (account.daysOverdrawn <= 7) 25 | return baseCharge; 26 | else 27 | return baseCharge + (account.daysOverdrawn - 7) * 0.85; 28 | } 29 | else 30 | return account.daysOverdrawn * 1.75; 31 | } 32 | } 33 | 34 | // -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/6-1-1.js: -------------------------------------------------------------------------------- 1 | const pricingPlan = retirvePricingPlan(); 2 | const order = retreiveOrder(); 3 | const baseCharge = pricingPlan.base; 4 | let charge; 5 | const chargePerUnit = pricingPlan.unit; 6 | const units = order.units; 7 | let discount; 8 | charge = baseCharge + units * chargePerUnit; 9 | let dicountableUntis = Math.max(units - pricingPlan.discountThreshold, 0); 10 | discount = discountUnits * princingPlan.discountFactor; 11 | if (order.isRepeat) discount += 20; 12 | charge = charge - discount; 13 | chargeOrder(charge); 14 | 15 | function a() { 16 | if (availableResources.length === 0){ 17 | result = createResource(); 18 | allocateResources.push(result); 19 | } else{ 20 | result = availableResources.pop(); 21 | allocateResources.push(result); 22 | } 23 | return result 24 | 25 | 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/6-1-2.js: -------------------------------------------------------------------------------- 1 | const order = retreiveOrder(); 2 | const pricingPlan = retirvePricingPlan(); // 위와 첫번째의 위치를 바꿔주었다(명렬지의 분리, 아무 부수효과 없음을 확인해야 함수 위치를 바꿔줄수있다) 3 | const baseCharge = pricingPlan.base; 4 | let charge; 5 | const chargePerUnit = pricingPlan.unit; 6 | const units = order.units; 7 | charge = baseCharge + units * chargePerUnit; 8 | let discountableUnits = Math.max(units - pricingPlan.discountThreshold, 0); 9 | 10 | let discount; // step 1 자신을 참조하는 첫 번째 코드 바로 앞까지 언제든지 이동 가능 11 | discount = discountableUnits * pricingPlan.discountFactor; 12 | if (order.isRepeat) discount += 20; // 여기부터 슬라이드 하고 싶지만. discount chargeOrder 가 둘다해서 마음대로 움직일수없다. 13 | charge = charge - discount; 14 | chargeOrder(charge); 15 | 16 | 17 | function a() { 18 | let result; 19 | if (availableResources.length === 0){ 20 | result = createResource(); 21 | } else{ 22 | result = availableResources.pop(); 23 | } 24 | allocateResources.push(result); // 중복된 로직이 있을경우 꺼내오자. 25 | 26 | return result 27 | 28 | } 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/7-1-after.js: -------------------------------------------------------------------------------- 1 | const a = (people) => { 2 | let youngest = people[0] ? people[0].age : Infinity; 3 | let totalSalary = 0; 4 | for (const p of people){ 5 | if (p.age < youngest) youngest = p.age; 6 | // totalSalary += p.salary; step: 2. q부수효과가 있는 코드 한쪽에 남기고 제거 7 | } 8 | 9 | for (const p of people){ // step: 1 반복문을 복사한다. 10 | totalSalary += p.salary; 11 | } 12 | return `최연소 ${youngest}, 총 급여: ${totalSalary}` 13 | } 14 | 15 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/7-1-after2.js: -------------------------------------------------------------------------------- 1 | const a = (people) => { 2 | const youngest = getYoungestAge(); 3 | const totalSalary = totalSalary(); 4 | 5 | return `최연소 ${youngest}, 총 급여: ${totalSalary}` 6 | 7 | function getYoungestAge() { 8 | let youngest = people[0] ? people[0].age : Infinity; 9 | for (const p of people){ 10 | if (p.age < youngest) youngest = p.age; 11 | // totalSalary += p.salary; step: 2. q부수효과가 있는 코드 한쪽에 남기고 제거 12 | } 13 | return youngest; 14 | } 15 | 16 | 17 | function getTotalSalary(){ 18 | let result = 0; 19 | for (const p of people){ 20 | result += p.salary; 21 | } 22 | return result; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/7-1-before.js: -------------------------------------------------------------------------------- 1 | const a = (people) => { 2 | let youngest = people[0] ? people[0].age : Infinity; 3 | let totalSalary = 0; 4 | for (const p of people){ 5 | if (p.age < youngest) youngest = p.age; 6 | totalSalary += p.salary; 7 | } 8 | return `최연소 ${youngest}, 총 급여: ${totalSalary}` 9 | } 10 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/8-1-before.js: -------------------------------------------------------------------------------- 1 | function acquireData(input) { 2 | const lines = input.split('\n') 3 | 4 | let firstLint = true; 5 | const result = []; 6 | for (const line of lines) { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/Account.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | class Account { 4 | constructor(number, type, interestRate) { 5 | this._number = number; 6 | this._type = type; 7 | assert(interestRate === this._type.interestRate); 8 | this._interestRate = interestRate; 9 | } 10 | 11 | get interRate() { return this._interestRate; } 12 | } 13 | 14 | class AccountType { 15 | constructor(nameString, type) { 16 | this._name = nameString; 17 | this._type = type; 18 | } 19 | get interestRate(){ return this._type._interestRate } 20 | } 21 | // 이자율을 공유를 해야한다 -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/app.js: -------------------------------------------------------------------------------- 1 | function renderPerson(outStream, person) { 2 | outStream.write(`${outStream.name}
`); 3 | renderPhoto(outStream, person.photo); 4 | emitPhotoData(outStream, person.photo); 5 | } 6 | 7 | function listRecentPhoto(outStream, photos) { 8 | photos 9 | .filter(p => p.date > recentDateCutoff()) 10 | .forEach(p => { 11 | outStream.write("제목: ${photo.title}
\n`); 19 | outStream.write(`날짜: ${photo.date.toDateString()}
\n`); 20 | outStream.write(`위치: ${photo.location}
\n`); 21 | } -------------------------------------------------------------------------------- /08_기능-이동/08_기능이동_lucid/main.js: -------------------------------------------------------------------------------- 1 | class CustomerContract { 2 | constructor(startDate, discountRate) { 3 | this._startDate = startDate; 4 | this._discoiuntRate = discountRate; 5 | } 6 | 7 | get discountRate() { return this._discoiuntRate } 8 | set discountRate(arg) { return this._discoiuntRate = arg } 9 | } 10 | 11 | // 1. 가장 먼저 할 일은 이 필드를 캡슐화하는 것이다 12 | class Customer { 13 | constructor(name, discountRate) { 14 | this._name = name; 15 | this._contract = new CustomerContract(dateToday(), this.discountRate) 16 | // 힐인율을 수정하는 세터를 만들고 싶지 않아. 세터 속성이 아니라 메서드를 만들었다 17 | this._setDiscountRate = discountRate; 18 | } 19 | // 자바스크립를 사용하고 잇어서 소스 필드를 미리 선언할 필요는 없었다. 20 | get discountRate() { return this._contract.discountRate; } 21 | _setDiscountRate(aNumber) { this._contract.discountRate = aNumber; } // 캡슐화 22 | 23 | becomePreferred(){ 24 | this._setDiscountRate(this._contract.discountRate + 0.03); 25 | // 남은일들 26 | } 27 | applyDiscount(amount){ 28 | return amount.subtract(amount.multiply(this.discountRate)); 29 | } 30 | } -------------------------------------------------------------------------------- /08_기능-이동/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/08_기능-이동/a -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_Tony/Map하나보장.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/09_데이터-조직화/09_데이터-조직화_Tony/Map하나보장.png -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_Tony/매개변수_호출자.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/09_데이터-조직화/09_데이터-조직화_Tony/매개변수_호출자.png -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/09_데이터-조직화_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter9. 데이터 조직화 2 | 3 | 4 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/9-5b5008ad1ad9479ba49e69abe092f391) 5 | -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/1-1-after.js: -------------------------------------------------------------------------------- 1 | /* 2 | const distanceTravelled = (scenario, time) => { 3 | let result 4 | let acc = scenario.primaryForce / scenario.mass // (a = F / m) 5 | let primaryTime = Math.min(time, scenario.delay) 6 | result = 0.5 * acc * primaryTime ** 2 7 | let secondaryTime = time - scenario.delay 8 | if (secondaryTime > 0) { 9 | let primaryVelocity = acc * scenario.delay 10 | acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass 11 | result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime ** 2 12 | } 13 | return result 14 | } 15 | */ 16 | const scenario = { 17 | primaryForce: 100, 18 | secondaryForce: 10, 19 | mass: 10, 20 | delay: 40, 21 | } 22 | 23 | const distanceTravelled = (scenario, time) => { 24 | const primaryAcceleration = scenario.primaryForce / scenario.mass // (a = F / m) 25 | let primaryTime = Math.min(time, scenario.delay) 26 | let result = 0.5 * primaryAcceleration * primaryTime ** 2 27 | let secondaryTime = time - scenario.delay 28 | if (secondaryTime > 0) { 29 | let primaryVelocity = primaryAcceleration * scenario.delay 30 | let secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass 31 | result += primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime ** 2 32 | } 33 | return result 34 | } 35 | 36 | console.log(distanceTravelled(scenario, 100)) // 51800 -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/1-1-before.js: -------------------------------------------------------------------------------- 1 | const distanceTravelled = (scenario, time) => { 2 | let result 3 | let acc = scenario.primaryForce / scenario.mass // (a = F / m) 4 | let primaryTime = Math.min(time, scenario.delay) 5 | result = 0.5 * acc * primaryTime ** 2 6 | let secondaryTime = time - scenario.delay 7 | if (secondaryTime > 0) { 8 | let primaryVelocity = acc * scenario.delay 9 | // ! acc 변수에 값이 두 번 대입된다. => 역할이 두 개라는 신호 10 | acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass 11 | result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime ** 2 12 | } 13 | return result 14 | } -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/1-2-after.js: -------------------------------------------------------------------------------- 1 | const discount = (inputValue, quantity) => { 2 | let result = inputValue; 3 | if (inputValue > 50) result = result - 2 4 | if (quantity > 100) result = result - 1 5 | return result 6 | } -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/1-2-before.js: -------------------------------------------------------------------------------- 1 | const discount = (inputValue, quantity) => { 2 | if (inputValue > 50) inputValue = inputValue - 2 3 | if (quantity > 100) inputValue = inputValue - 1 4 | return inputValue 5 | } -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/2-after.js: -------------------------------------------------------------------------------- 1 | class Organization { 2 | constructor(data) { 3 | this._title = data.title 4 | this._country = data.country 5 | } 6 | get title() { 7 | return this._title 8 | } 9 | set title(aString) { 10 | this._title = aString 11 | } 12 | get country() { 13 | return this._country 14 | } 15 | set country(aCountry) { 16 | this._country = aCountry 17 | } 18 | } 19 | const organization = new Organization({ title: '애크미 구스베리', country: 'GB' }) 20 | 21 | console.log(organization.name); 22 | console.log(organization.title); -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/2-before.js: -------------------------------------------------------------------------------- 1 | class Organization { 2 | constructor(data) { 3 | this._name = data.name 4 | this._country = data.country 5 | } 6 | get name() { 7 | return this._name 8 | } 9 | set name(aString) { 10 | this._name = aString 11 | } 12 | get country() { 13 | return this._country 14 | } 15 | set country(aCountry) { 16 | this._country = aCountry 17 | } 18 | } 19 | const organization = new Organization({ name: '애크미 구스베리', country: 'GB' }) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/3-1-after.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | class ProductionPlan { 4 | _adjustments = [] 5 | _production = 0 6 | 7 | get production() { 8 | return this._adjustments 9 | .reduce((total, adjustment) => total + adjustment.amount, 0); 10 | } 11 | 12 | applyAdjustment(anAdjustment) { 13 | this._adjustments.push(anAdjustment) 14 | } 15 | } 16 | 17 | const products = new ProductionPlan() 18 | products.applyAdjustment({ name: '사과', amount: 10 }) 19 | products.applyAdjustment({ name: '바나나', amount: 20 }) 20 | 21 | console.log(products.production) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/3-1-before.js: -------------------------------------------------------------------------------- 1 | class ProductionPlan { 2 | _adjustments = [] 3 | _production = 0 4 | 5 | get production() { 6 | assert 7 | return this._production 8 | } 9 | applyAdjustment(anAdjustment) { 10 | this._adjustments.push(anAdjustment) 11 | this._production += anAdjustment.amount 12 | } 13 | } 14 | 15 | const products = new ProductionPlan() 16 | products.applyAdjustment({ name: '사과', amount: 10 }) 17 | products.applyAdjustment({ name: '바나나', amount: 20 }) 18 | 19 | console.log(products.production) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/3-2-after.js: -------------------------------------------------------------------------------- 1 | class ProductionPlan { 2 | constructor(production) { 3 | this._initialProduction = production; 4 | this._adjustments = []; 5 | } 6 | get production() { 7 | return this._adjustments 8 | .reduce((total, adjustment) => total + adjustment.amount, this._initialProduction); 9 | } 10 | 11 | applyAdjustment(anAdjustment) { 12 | this._adjustments.push(anAdjustment) 13 | this._production += anAdjustment.amount 14 | } 15 | } 16 | 17 | const products = new ProductionPlan(0) 18 | products.applyAdjustment({ name: '사과', amount: 10 }) 19 | products.applyAdjustment({ name: '바나나', amount: 20 }) 20 | 21 | console.log(products.production) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/3-2-before.js: -------------------------------------------------------------------------------- 1 | class ProductionPlan { 2 | 3 | constructor(production) { 4 | this._production = production; 5 | this._adjustments = []; 6 | } 7 | get production() { 8 | return this._production 9 | } 10 | applyAdjustment(anAdjustment) { 11 | this._adjustments.push(anAdjustment) 12 | this._production += anAdjustment.amount 13 | } 14 | } 15 | 16 | const products = new ProductionPlan(0) 17 | products.applyAdjustment({ name: '사과', amount: 10 }) 18 | products.applyAdjustment({ name: '바나나', amount: 20 }) 19 | 20 | console.log(products.production) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/4-after.js: -------------------------------------------------------------------------------- 1 | class TelephoneNumber { 2 | constructor(areaCode, number) { 3 | this._areaCode = areaCode; 4 | this._number = number; 5 | } 6 | get areaCode() { 7 | return this._areaCode 8 | } 9 | get number() { 10 | return this._number 11 | } 12 | 13 | equals(other) { 14 | if (!(other instanceof TelephoneNumber)) return false; 15 | return this.areaCode === other.areaCode && this.number === other.number; 16 | } 17 | } 18 | 19 | class Person { 20 | constructor() { 21 | this._telephoneNumber = new TelephoneNumber() 22 | } 23 | get officeAreaCode() { 24 | return this._telephoneNumber.areaCode 25 | } 26 | set officeAreaCode(arg) { 27 | this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber); 28 | } 29 | get officeNumber() { 30 | return this._telephoneNumber.number 31 | } 32 | set officeNumber(arg) { 33 | this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg); 34 | } 35 | } 36 | 37 | const p = new Person(); 38 | p.officeAreaCode = '031'; 39 | p.officeNumber = '123-4321'; 40 | console.log(p.officeAreaCode, p.officeNumber) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/4-before.js: -------------------------------------------------------------------------------- 1 | class TelephoneNumber { 2 | constructor() { 3 | this._areaCode 4 | this._number 5 | } 6 | get areaCode() { 7 | return this._areaCode 8 | } 9 | set areaCode(arg) { 10 | this._areaCode = arg 11 | } 12 | get number() { 13 | return this._number 14 | } 15 | set number(arg) { 16 | this._number = arg 17 | } 18 | } 19 | 20 | class Person { 21 | constructor() { 22 | this._telephoneNumber = new TelephoneNumber() 23 | } 24 | get officeAreaCode() { 25 | return this._telephoneNumber.areaCode 26 | } 27 | set officeAreaCode(arg) { 28 | this._telephoneNumber.areaCode = arg 29 | } 30 | get officeNumber() { 31 | return this._telephoneNumber.number 32 | } 33 | set officeNumber(arg) { 34 | this._telephoneNumber.number = arg 35 | } 36 | } 37 | 38 | const p = new Person() 39 | p.officeAreaCode = '312' 40 | p.officeNumber = '555-0142' 41 | console.log(p.officeAreaCode, p.officeNumber) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/5-after.js: -------------------------------------------------------------------------------- 1 | let _repositoryData; 2 | initialize(); 3 | 4 | function initialize() { 5 | _repositoryData = {}; 6 | _repositoryData.customers = new Map(); 7 | } 8 | 9 | function registerCustomer(id) { 10 | if (! _repositoryData.customers.has(id)){ 11 | _repositoryData.customers.set(id, new Customer(id)); 12 | } 13 | return findCustomer(id); 14 | } 15 | 16 | function findCustomer(id) { 17 | return _repositoryData.customers.get(id); 18 | } 19 | 20 | class Customer { 21 | constructor(id) { 22 | this._id = id; 23 | this._testData; 24 | } 25 | get id() { 26 | return this._id; 27 | } 28 | 29 | get testData() { 30 | return this._testData; 31 | } 32 | 33 | set testData(arg) { 34 | this._testData = arg; 35 | } 36 | } 37 | 38 | class Order { 39 | constructor(data) { 40 | this._number = data.number 41 | this._customer = registerCustomer(data.customer); 42 | } 43 | get customer() { 44 | return this._customer 45 | } 46 | } 47 | 48 | const o1 = new Order({ number: 1, customer: 'a' }); 49 | const o2 = new Order({ number: 2, customer: 'a' }); 50 | console.log(o1.customer.testData); 51 | o2.customer.testData = 'hello'; 52 | console.log(o1.customer.testData); -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_hyeonmin/src/5-before.js: -------------------------------------------------------------------------------- 1 | class Customer { 2 | constructor(id) { 3 | this._id = id 4 | } 5 | get id() { 6 | return this._id 7 | } 8 | } 9 | 10 | class Order { 11 | constructor(data) { 12 | this._number = data.number 13 | this._customer = new Customer(data.customer) 14 | } 15 | get customer() { 16 | return this._customer 17 | } 18 | } 19 | 20 | const o = new Order({ number: 1, customer: 'a' }) 21 | console.log(o.customer.id) -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_kiwi/09_데이터-조직화_kiwi.md: -------------------------------------------------------------------------------- 1 | ### 09 데이터 조직화 2 | 3 | ### [학습 내용 정리 Notion Page](https://www.notion.so/9-256d9cd2d2c549618857ef275f295c0f) 4 | -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/1/after.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param scenario{{primaryForce: number, mass: number, delay: number, secondaryForce: number }} 3 | * @param time{number} 4 | * */ 5 | function distanceTraveled(scenario, time) { 6 | let result; 7 | // TODO:: 01 기존 acc 역활이 다른데 두번이나 연산되어있다. acc => primaryAcceleration 8 | // TODO:: 01 상수로 변경했다. 그 이후 테스트한다. 원래 중복 사용하던 변수는 선언해준다 9 | const primaryAcceleration = scenario.primaryForce / scenario; 10 | let primaryTime = Math.min(time, scenario.delay) ; 11 | let secondaryTime = time - scenario.delay; 12 | result = 0.5 * primaryAcceleration * primaryTime; // 전파된 거리 13 | 14 | if (secondaryTime > 0){ 15 | let primaryVelocity = primaryAcceleration * scenario.delay; 16 | let secondAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // TODO:: 01 acc 기존으로 선언한다 02. 다시 어울리는 이름으로 상세히 변경해준다(acc => second 17 | result += primaryVelocity * secondaryTime + 0.5 * secondAcceleration * secondaryTime * secondaryTime; 18 | } 19 | return result; 20 | 21 | } 22 | 23 | /** 24 | * javascript 는 call by value 여서 인자를 바꿔도 호출자에게 문제가없다!! 25 | * */ 26 | 27 | 28 | function discount(originInputValue, quantity) { // 먼저 처음 매개변수 이름을 바꾼다 29 | let result = originInputValue; // 기존에 있는값을 주입하고, 다시 변경한다 inputValue => result 30 | if (result > 50) result = result - 2; 31 | if (quantity > 100) result = result -1; 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/1/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param scenario{{primaryForce: number, mass: number, delay: number, secondaryForce: number }} 3 | * @param time{number} 4 | * */ 5 | function distanceTraveled(scenario, time) { 6 | let result; 7 | let acc = scenario.primaryForce / scenario; // 힘 질량 8 | let primaryTime = Math.min(time, scenario.delay); 9 | let secondaryTime = time - scenario.delay; 10 | result = 0.5 * acc * primaryTime; // 전파된 거리 11 | 12 | if (secondaryTime > 0){ 13 | let primaryVelocity = acc * scenario.delay; 14 | acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; 15 | result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime; 16 | } 17 | return result; 18 | 19 | } 20 | 21 | function discount(inputValue, quantity) { 22 | if (inputValue > 50) inputValue = inputValue - 2; 23 | if (quantity > 100) inputValue = inputValue -1; 24 | return inputValue; 25 | 26 | } -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/2/after.js: -------------------------------------------------------------------------------- 1 | // TODO:: name => title 로 바꾼다. 2 | // 1. 기존의 _name => title 로 바꾼다 3 | class Organ { 4 | /** 5 | * @param data {{ country: name, title?: name}} 6 | * */ 7 | constructor(data) { 8 | this._title = data.title ? data.title : data.name ; // TODO:: 2 title 및 name 이 허용될수 있게 전체를 바꾼다 9 | this._country = data.country; 10 | } 11 | get title() { return this._title } // TODO:: 4 최종적으로 get, set 일므을 바꿔준다 12 | set title(aString) { this._title = aString } 13 | 14 | } 15 | 16 | const org = new Organ({ title: '1', country: 'korea'}) // TODO:: 3 2번까지의 테스트가 마무리되었으면 호출하는쪽에서 변경해준다 17 | 18 | const name = org.name // 호출 되는쪽에서 호출이 되어지는데 -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/2/before.js: -------------------------------------------------------------------------------- 1 | // class Organ { 2 | // 3 | // /** 4 | // * @param data {{ name: string, country: name}} 5 | // * */ 6 | // constructor(data) { 7 | // this._name = data.name; 8 | // this._country = data.country; 9 | // } 10 | // get name() { return this._name } 11 | // set name(aString) { this._name = aString } 12 | // 13 | // } 14 | // 15 | // const org = new Organ({ name: '1', country: 'korea'}) 16 | 17 | 18 | // TODO:: IDE 에서 변화가 없는걸확인하기 위해 변경해준다 -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/3/after.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | 3 | // Generate an AssertionError to compare the error message later: 4 | const { message } = new assert.AssertionError({ 5 | actual: 1, 6 | expected: 2, 7 | operator: 'strictEqual' 8 | }); 9 | 10 | 11 | class ProductionPlan { 12 | constructor() { 13 | this._adjustment = []; 14 | // this._calculateProduction = 0; // TODO:: 3 필요없는것도 추가로 뺀다 15 | //this._production = 0; // TODO:: 3 필요없는것도 추가로 뺀다 16 | } 17 | 18 | get production() { 19 | // assert.strictEqual(this._production === this.calculateProduction, true) // TODO:: 1)확인해줫다 20 | // return this._production 21 | return this._adjustment.reduce((sum, a) => sum + a.amount, 0) // TODO:: 2) 기존따론 뺀 필드 계싼식을 넣는다 22 | } 23 | 24 | get adjustment() { // 조정 25 | return this._adjustment 26 | } 27 | 28 | // get calculateProduction() {return this._adjustment.reduce((sum, a) => sum + a.amount, 0);} // TODO:: 3 필요없는것도 추가로 뺀다 29 | 30 | applyAdjustment(anAdjustment) { // assert 를 추가하자 31 | this._adjustment.push(anAdjustment); 32 | // this._production += anAdjustment.amount; // TODO:: 3 필요없는것도 추가로 뺀다 33 | } 34 | } 35 | 36 | const productionPlan = new ProductionPlan() 37 | productionPlan.applyAdjustment({ amount: 2 }) 38 | const production = productionPlan.production 39 | 40 | console.log(production) 41 | -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터-조직화_lucid/3/before.js: -------------------------------------------------------------------------------- 1 | class ProductionPlan { 2 | private _adjustment: string[]; 3 | 4 | get production() { return this._production } 5 | get adjustment() { return this._adjustment } 6 | 7 | applyAdjustment(anAdjustment){ // adjustment 를 적용하는 과정에서 직접 관련이 없는 누적 값 Production 까지 갱신했다. 8 | this._adjustment.push(anAdjustment); 9 | this._production += anAdjustment.amount; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /09_데이터-조직화/09_데이터_조직화_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 09. 데이터 조직화 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 09 정리 문서](https://mwjjeongdev.notion.site/Chapter-09-986ae0d05b964d39bd59752200d8323f) 6 | - [Chapter 09 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter09) 7 | -------------------------------------------------------------------------------- /09_데이터-조직화/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/09_데이터-조직화/a -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/README.md: -------------------------------------------------------------------------------- 1 | # Chapter10. 조건부 로직 간소화 2 | 3 | 4 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/10-3d1289cea7e74ff1ac961366da0dc252) 5 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/01-after.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs'); 2 | 3 | const plan = { 4 | summerStart: dayjs("2021-07-01"), 5 | summerEnd: dayjs("2021-08-31"), 6 | summerRate: 1000, 7 | regularRate: 1100, 8 | regularServiceCharge: 100, 9 | }; 10 | 11 | const getCharge = (quantity, aDate) => { 12 | const charge = summer() ? summerCharge() : regularCharge(); 13 | return charge; 14 | 15 | function regularCharge() { 16 | return quantity * plan.regularRate + plan.regularServiceCharge; 17 | } 18 | function summerCharge() { 19 | return quantity * plan.summerRate; 20 | } 21 | function summer() { 22 | return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd) 23 | } 24 | }; 25 | 26 | console.log(getCharge(10, dayjs("2021-06-29"))); 27 | console.log(getCharge(10, dayjs("2021-08-15"))); 28 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/01-before.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs'); 2 | 3 | const plan = { 4 | summerStart: dayjs('2021-07-01'), 5 | summerEnd: dayjs('2021-08-31'), 6 | summerRate: 1000, 7 | regularRate: 1100, 8 | regularServiceCharge: 100, 9 | } 10 | 11 | const getCharge = (quantity, aDate) => { 12 | if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) return quantity * plan.summerRate 13 | return quantity * plan.regularRate + plan.regularServiceCharge 14 | } 15 | console.log(getCharge(10, dayjs('2021-06-29'))) 16 | console.log(getCharge(10, dayjs('2021-08-15'))) -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/02-1-after.js: -------------------------------------------------------------------------------- 1 | const disabilityAmount = anEmployee => { 2 | if (isNotEligibleForDisability()) return 0; 3 | // 장애 수당 계산 4 | 5 | function isNotEligibleForDisability() { 6 | return ((anEmployee.seniority < 2) 7 | || (anEmployee.monthsDisabled > 12) 8 | || (anEmployee.isPartTime)); 9 | } 10 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/02-1-before.js: -------------------------------------------------------------------------------- 1 | const disabilityAmount = anEmployee => { 2 | if (anEmployee.seniority < 2) return 0 3 | if (anEmployee.monthsDisabled > 12) return 0 4 | if (anEmployee.isPartTime) return 0 5 | // 장애 수당 계산 6 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/03-1-after.js: -------------------------------------------------------------------------------- 1 | const payAmount = employee => { 2 | // 가변 변수 제거하기 3 | 4 | // 예외 케이스들은 모두 보호 구문으로 처리 5 | if (employee.isSeperated) return { amount: 0, reasonCode: 'SEP' }; 6 | if (employee.isRetired) return { amount: 0, reasonCode: 'RET' }; 7 | 8 | // 급여 계산 로직 처리 9 | const result = { amount: 100, reasonCode: 'WRK' }; 10 | return result; 11 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/03-1-before.js: -------------------------------------------------------------------------------- 1 | const payAmount = employee => { 2 | let result; 3 | 4 | if (employee.isSeperated) result = { amount: 0, reasonCode: 'SEP' } 5 | else { 6 | if (employee.isRetired) result = { amount: 0, reasonCode: 'RET' } 7 | else { 8 | result = { amount: 100, reasonCode: 'WRK' } 9 | } 10 | } 11 | return result 12 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/03-2.js: -------------------------------------------------------------------------------- 1 | const adjustCapital = anInstrument => { 2 | /* 3 | 1. 바깥쪽 조건문부터 조건을 역으로 설정하여 추출, 보호 구문으로. 4 | 2. 예외 케이스들을 모두 보호 구문으로 변경 5 | 3. 동일한 결과를 반환하므로, 조건문 통합 적용 6 | */ 7 | 8 | if ((anInstrument.capital <= 0) 9 | || (anInstrument.interestRate <= 0) 10 | || (anInstrument.duration <= 0)) return 0; 11 | 12 | const result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor; 13 | return result; 14 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/06.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | class Customer { 4 | _discountRate 5 | applyDiscount(number) { 6 | if (!this._discountRate) return number 7 | const discountedPrice = number - this._discountRate * number; 8 | return Math.max(discountedPrice, 0); 9 | } 10 | set discountRate(number) { 11 | assert(number === null || number >= 0) 12 | this._discountRate = number 13 | } 14 | } 15 | 16 | const aCustomer = new Customer(); 17 | aCustomer.discountRate = 0.2; 18 | console.log(aCustomer.applyDiscount(2000)); -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_Hyeonmin/src/07.js: -------------------------------------------------------------------------------- 1 | const sendAlert = () => console.warn("악당을 찾았소"); 2 | 3 | const checkForMiscreants = (people) => { 4 | if (people.some(p => ["조커", "사루만"].includes(p))) sendAlert(); 5 | }; 6 | 7 | checkForMiscreants([ 8 | "슈퍼맨", 9 | "배트맨", 10 | "아이언맨", 11 | "사루만", 12 | "블랙위도우", 13 | "조커", 14 | "스파이더맨", 15 | ]); 16 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/01-조건문분해하기.js: -------------------------------------------------------------------------------- 1 | // JavaScript 날짜 관련 경량 라이브러리 2 | const dayjs = require("dayjs"); 3 | 4 | const plan = { 5 | summerStart: dayjs("2022-07-01"), 6 | summerEnd: dayjs("2022-08-31"), 7 | summerRate: 2000, //여름시즌 8 | regularRate: 1000, //비수기 9 | regularServiceCharge: 100, 10 | }; 11 | 12 | const getCharge = (quantity, aDate) => { 13 | const isSummer = () => !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd); 14 | const summerCharge = () => quantity * plan.summerRate; 15 | const reugularCharge = () => quantity * plan.regularRate + plan.regularServiceCharge; 16 | 17 | return isSummer() ? summerCharge() : reugularCharge(); 18 | }; 19 | 20 | console.log(getCharge(10, dayjs("2022-03-29"))); //10100 21 | console.log(getCharge(10, dayjs("2022-08-15"))); //20000 22 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/02-1조건식통합하기.js: -------------------------------------------------------------------------------- 1 | const disabilityAmount = (anEmployee) => { 2 | //장애수당 적용 여부 확인 3 | const isNotEligibleForDisability = () => { 4 | anEmployee.seniority < 2 || anEmployee.monthsDisabled > 12 || anEmployee.isPartTime; 5 | }; 6 | if (isNotEligibleForDisability()) return 0; 7 | }; 8 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/02-2조건식통합하기.js: -------------------------------------------------------------------------------- 1 | const func = (anEmployee) => { 2 | if (anEmployee.onVacation && anEmployee.seniority > 10) return 1; 3 | return 0.5; 4 | }; 5 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/03-1-중첩조건문을보호구문으로바꾸기.js: -------------------------------------------------------------------------------- 1 | //early return 패턴을 활용하여 보호구문으로 바꾸기 2 | //ref : https://medium.com/swlh/return-early-pattern-3d18a41bba8 3 | 4 | const payAmount = (employee) => { 5 | //퇴사O 6 | if (employee.isSeperated) return { amount: 0, reasonCode: "SEP" }; 7 | //은퇴O 8 | if (employee.isRetired) return { amount: 0, reasonCode: "RET" }; 9 | // 재직O - 급여 계산 로직 10 | return { amount: 100, reasonCode: "WRK" }; 11 | }; 12 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/03-2-중첩조건문-조건반대로만들기js: -------------------------------------------------------------------------------- 1 | const adjustCapital = (anInstrument) => { 2 | if (anInstrument.capital <= 0 || anInstrument.interestRate <= 0 || anInstrument.duration <= 0) return 0; 3 | return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor; 4 | }; 5 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/07-어서션추가하기.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | class Customer { 4 | _discountRate; 5 | applyDiscount(number) { 6 | if (!this._discountRate) return number; 7 | console.log(assert(this._discountRate >= 0)); 8 | assert(this._discountRate >= 0); 9 | return number - this._discountRate * number; 10 | } 11 | set discountRate(number) { 12 | assert(number === null || number >= 0); 13 | this._discountRate = number; 14 | } 15 | } 16 | 17 | const customer = new Customer(); 18 | // assert 조건 미충족시 -> code - 'ERR_ASSERTION 19 | // customer.discountRate = -0.2; 20 | customer.discountRate = 0.2; 21 | console.log(customer.applyDiscount(3)); 22 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/08-제어플래그-탈출문으로바꾸기.js: -------------------------------------------------------------------------------- 1 | const sendAlert = () => console.warn("악당 발견😎"); 2 | 3 | const checkForMiscreants = (people) => { 4 | if (people.some((p) => ["조커", "사루만"].includes(p))) sendAlert(); 5 | }; 6 | checkForMiscreants(["슈퍼맨", "배트맨", "아이언맨", "사루만", "블랙위도우", "조커", "스파이더맨"]); 7 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "sample", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7" 13 | } 14 | }, 15 | "node_modules/dayjs": { 16 | "version": "1.10.7", 17 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", 18 | "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" 19 | } 20 | }, 21 | "dependencies": { 22 | "dayjs": { 23 | "version": "1.10.7", 24 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", 25 | "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_SHUZAN/sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "01-조건문분해하기.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/1/advance.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const summer = () => !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd); // 리팩토링 4 | 5 | function summerCharge(): number { 6 | return quantity * plan.summerRate; 7 | } 8 | 9 | const regularCharge = () => quantity * plan.regularRate + plan.regularServiceCharge; 10 | 11 | const charge = summer() ? summerCharge() : regularCharge(); 12 | console.log(charge) -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/1/after.js: -------------------------------------------------------------------------------- 1 | let charge = 0; 2 | 3 | function summer() { 4 | return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd); 5 | } // 리팩토링 6 | 7 | function summerCharge() { 8 | return quantity * plan.summerRate; 9 | } 10 | 11 | const regularCharge = () => quantity * plan.regularRate + plan.regularServiceCharge; 12 | 13 | if (summer()) { 14 | charge = summerCharge(); 15 | } else { 16 | charge = regularCharge(); 17 | } 18 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/1/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {{ isBefore: function, isAfter: function }} aDate 3 | * */ 4 | 5 | /** 6 | * @typedef {{ summerStart, summerEnd, summerRate: number, regularServiceCharge: number, regularRate: number }} plan 7 | * @typedef {number} quantity 8 | * */ 9 | 10 | let charge = 0; 11 | if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) { 12 | charge = quantity * plan.summerRate; 13 | } else { 14 | charge = quantity * plan.regularRate + plan.regularServiceCharge; 15 | } 16 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/2/after-2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {{ seniority: number, monthDisabled: number,isPartTime: boolean}} anEmployee 3 | * */ 4 | function disabilityAmount(anEmployee) { 5 | const isNotEligibleForDisability = () => anEmployee.seniority < 2 || anEmployee.monthDisabled > 12 || anEmployee.isPartTime; 6 | 7 | if (isNotEligibleForDisability()) 8 | return 0; 9 | } 10 | 11 | function funcAnd(anEmployee) { 12 | if((anEmployee.onVacation) && (anEmployee.seniority > 10)) return 1; 13 | 14 | return 0.5; 15 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/2/after.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {{ seniority: number, monthDisabled: number,isPartTime: boolean}} anEmployee 3 | * */ 4 | 5 | function disabilityAmount(anEmployee) { 6 | if (anEmployee.seniority < 2 || anEmployee.monthDisabled > 12 || anEmployee.isPartTime) return 0; 7 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/2/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {{ seniority: number, monthDisabled: number,isPartTime: boolean, onVacation: boolean}} anEmployee 3 | * */ 4 | 5 | function disabilityAmount(anEmployee) { 6 | if (anEmployee.seniority < 2) return 0; 7 | if (anEmployee.monthDisabled > 12) return 0; 8 | if (anEmployee.isPartTime) return 0; 9 | } 10 | 11 | /** 12 | * 13 | * */ 14 | function funcAnd(anEmployee) { 15 | if (anEmployee.onVacation){ 16 | if (anEmployee.seniority > 10) 17 | return 1; 18 | return 0.5; 19 | } 20 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/3/after.js: -------------------------------------------------------------------------------- 1 | function someFinalComputation() { 2 | return {amount: 500, reasonCode: ''}; 3 | } 4 | 5 | /** 6 | * @param {{ isSeparated: boolean, isRetired: boolean }} employee 7 | * */ 8 | function payAmount(employee) { 9 | let result = { amount: 0, reasonCode: '' }; 10 | if (employee.isRetired){ 11 | result = { amount: 0, reasonCode: 'SEP'} 12 | } else{ 13 | if (employee.isRetired){ 14 | result = { amount: 0, reasonCode: "REP"} 15 | } else { 16 | // 급여 계산 로직 17 | // lorem 18 | result = someFinalComputation() 19 | } 20 | } 21 | return result; 22 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/3/before.js: -------------------------------------------------------------------------------- 1 | function someFinalComputation() { 2 | return {amount: 500, reasonCode: ''}; 3 | } 4 | 5 | /** 6 | * @param {{ isSeparated: boolean, isRetired: boolean }} employee 7 | * */ 8 | function payAmount(employee) { 9 | let result = {amount: 0, reasonCode: ''}; 10 | if (employee.isRetired) return {amount: 0, reasonCode: 'SEP'} 11 | if (employee.isRetired) return {amount: 0, reasonCode: "REP"} 12 | // 급여 계산 로직 13 | // lorem 14 | return someFinalComputation(); 15 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/4/after.js: -------------------------------------------------------------------------------- 1 | class Bird { 2 | /** 3 | * @param {Bird} birdObj 4 | * */ 5 | constructor(birdObj) { 6 | Object.assign(this, birdObj) 7 | } 8 | 9 | get plumage(){ 10 | switch (this.type){ 11 | case '유럽제비': 12 | return '보통이다;' 13 | case '아프리카제비': 14 | return this.totalCount > 2 ? '지쳣다' : '보통이다'; 15 | case '노르웨이 파랑앵무': 16 | return this.voltage > 100 ? '그열렷다' : '예쁘다' 17 | default: 18 | return '알수없다' 19 | } 20 | } 21 | 22 | get airSpeedVelocity(){ 23 | switch (this.type){ 24 | case '유럽제비': 25 | return 35 26 | case '아프리카제비': 27 | return 40 -2 * this.totalCount; 28 | case '노르웨이 파랑앵무': 29 | return this.isNailed ? 0 : 10 + this.voltage / 10; 30 | default: 31 | return null; 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * @param {Bird} bird; 38 | * */ 39 | 40 | function plumage(bird) { 41 | return new Bird(bird).plumage; 42 | } 43 | 44 | /** 45 | * @param {Bird} bird 46 | * */ 47 | function airSpeedVelocity(bird) { 48 | return new Bird(bird).airSpeedVelocity; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/4/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {object} Bird 3 | * @property {string} name 4 | * @property {'유럽제비'|'아프리카제비'|'노르웨이 파랑앵무'} type 5 | * @property {number} totalCount 6 | * @property {number} voltage 7 | * @property {boolean} isNailed 8 | * 9 | * * */ 10 | 11 | /** 12 | * @param {Bird} bird 13 | * */ 14 | function plumage(bird) { 15 | switch (bird.type){ 16 | case '유럽제비': 17 | return '보통이다;' 18 | case '아프리카제비': 19 | return bird.totalCount > 2 ? '지쳣다' : '보통이다'; 20 | case '노르웨이 파랑앵무': 21 | return bird.voltage > 100 ? '그열렷다' : '예쁘다' 22 | default: 23 | return '알수없다' 24 | } 25 | 26 | } 27 | 28 | /** 29 | * @param {Bird} bird 30 | * */ 31 | function airSpeedVelocity(bird) { 32 | switch (bird.type){ 33 | case '유럽제비': 34 | return 35 35 | case '아프리카제비': 36 | return 40 -2 * bird.totalCount; 37 | case '노르웨이 파랑앵무': 38 | return bird.isNailed ? 0 : 10 + bird.voltage / 10; 39 | default: 40 | return null; 41 | } 42 | 43 | } 44 | 45 | /** 46 | * @param {Bird[]} birds 47 | * */ 48 | function plumAges(birds) { 49 | return new Map(birds.map(b => [b.name], plumage)) 50 | } 51 | 52 | 53 | /** 54 | * @param {Bird[]} birds 55 | * */ 56 | function speeds(birds){ 57 | return new Map(birds.map(b => [b.name, airSpeedVelocity(b)])) 58 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/6/after.js: -------------------------------------------------------------------------------- 1 | 2 | class Customer { 3 | constructor(aDiscountRate) { 4 | console.assert(aDiscountRate === null || aDiscountRate >= 0 , `discount Rate 는 ${this.discountRate} 가 아닌 양수이어야야합니다`); 5 | 6 | this.discountRate = aDiscountRate; 7 | } 8 | 9 | applyDiscount(aNumber) { 10 | if (!this.discountRate) { 11 | return aNumber; 12 | } 13 | console.assert(this.discountRate >= 0 , `discount Rate 는 ${this.discountRate} 가 아닌 양수이어야야합니다`); 14 | return (this.discountRate) ? aNumber - (this.discountRate * aNumber) : aNumber; 15 | } 16 | } 17 | 18 | const customer = new Customer(-5); 19 | 20 | const discountValue = customer.applyDiscount(-2) 21 | console.log(discountValue) -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/6/before.js: -------------------------------------------------------------------------------- 1 | 2 | // 양수라는 가정이 잇으니 코드에는 문제 없다 3 | class Customer { 4 | constructor() { 5 | this.discountRate = null; 6 | } 7 | applyDiscount(aNumber){ 8 | return (this.discountRate) ? aNumber - (this.discountRate * aNumber) : aNumber; 9 | } 10 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/7/advance.js: -------------------------------------------------------------------------------- 1 | const people = ['조커', '사루만', '간달프'] 2 | 3 | const sendAlert = () => { 4 | } 5 | 6 | const checkForMiscreants = (people) => { 7 | if (people.some(p => ['조커','사루만'].includes(p))) sendAlert(); 8 | // TODO:: 근사한 집합 연산 지원 9 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/7/after-1.js: -------------------------------------------------------------------------------- 1 | const people = ['조커', '사루만', '간달프'] 2 | 3 | const sendAlert = () => { 4 | } 5 | 6 | const checkForMiscreants = (people) => { 7 | 8 | let found = false; 9 | for (const p of people) { 10 | if (!found) { 11 | if (p === '조커') { 12 | sendAlert(); 13 | found = true 14 | } 15 | if (p === '사루만') { 16 | sendAlert(); 17 | found = true 18 | } 19 | } 20 | } 21 | } 22 | 23 | // TODO:: 1. 함수추출하기: 밀접한 함수만 추출한다 24 | checkForMiscreants(people); -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/7/after-2.js: -------------------------------------------------------------------------------- 1 | const people = ['조커', '사루만', '간달프'] 2 | 3 | const sendAlert = () => { 4 | } 5 | 6 | 7 | const checkForMiscreants = (people) => { 8 | // 반복문을 순회하지 않게 리턴을 시킨다 9 | // let found = false; 10 | for (const p of people) { 11 | if (p === '조커') { 12 | sendAlert(); 13 | return; 14 | } 15 | if (p === '사루만') { 16 | sendAlert(); 17 | return; 18 | } 19 | } 20 | } 21 | checkForMiscreants(people); 22 | 23 | // the nice 한 코드 24 | 25 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/7/before.js: -------------------------------------------------------------------------------- 1 | const people = ['조커', '사루만', '간달프'] 2 | const sendAlert = () => {} 3 | 4 | let found = false; 5 | for (const p of people) { 6 | if (!found) { 7 | if (p === '조커'){ 8 | sendAlert(); 9 | found = true 10 | } 11 | if (p === '사루만'){ 12 | sendAlert(); 13 | found = true 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부-로직-간소화_lucid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "jsdoc": "^3.6.7" 4 | }, 5 | "scripts": { 6 | "docs": "node_modules/.bin/jsdoc" 7 | }, 8 | "dependencies": { 9 | "docdash": "^1.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/10_조건부_로직_간소화_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 10. 조건부 로직 간소화 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 10 정리 문서](https://mwjjeongdev.notion.site/Chapter-10-fc27437e2dc64c5c914b7b8efe6a2821) 6 | - [Chapter 10 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter10) 7 | -------------------------------------------------------------------------------- /10_조건부-로직-간소화/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/10_조건부-로직-간소화/a -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/README.md: -------------------------------------------------------------------------------- 1 | # Chapter11. API 리팩터링 2 | 3 | 4 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/11-API-5b3a517b93324db09970733b1a31da74) 5 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/01.js: -------------------------------------------------------------------------------- 1 | const setOffAlarms = () => { 2 | console.warn('악당을 찾았소'); 3 | } 4 | 5 | const findMiscreant = people => people.find(p => p === '조커' || p === '사루만'); 6 | const alertForMiscreant = people => { 7 | if (findMiscreant(people)) { 8 | setOffAlarms(); 9 | } 10 | } 11 | 12 | const people = ['슈퍼맨', '배트맨', '아이언맨', '사루만', '블랙위도우', '조커', '스파이더맨']; 13 | // const people = ['슈퍼맨', '배트맨', '아이언맨', '블랙위도우', '스파이더맨']; 14 | const found = findMiscreant(people); 15 | alertForMiscreant(people); -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/02-1.js: -------------------------------------------------------------------------------- 1 | const raise = (aPerson, factor) => { 2 | aPerson.salary = aPerson.salary.multiply(1 + factor); 3 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/02-2.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 대역(band)을 다루는 세 함수의 로직이 상당히 비슷하므로, 매개변수화 함수로 통합 3 | - 통합 시에는, 먼저 대상 함수 중 하나를 골라 매개변수를 추가. (다른 함수들까지 고려) 4 | */ 5 | 6 | const usd = aNumber => 7 | new Intl.NumberFormat('en-US', { 8 | style: 'currency', 9 | currency: 'USD', 10 | minimumFractionDigits: 2, 11 | }).format(aNumber / 100) 12 | 13 | const withinBand = (usage, bottom, top) => { 14 | return (usage > bottom ? Math.min(usage, top) - bottom : 0); 15 | } 16 | const baseCharge = usage => { 17 | if (usage < 0) return usd(0) 18 | const amount = 19 | withinBand(usage, 0, 100) * 0.03 20 | + withinBand(usage, 100, 200) * 0.05 21 | + withinBand(usage, 200, Infinity) * 0.07 22 | return usd(amount) 23 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/03-1.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 배송일자를 계산하는 함수에 대한 예시 3 | - isRush에 따라 호출되는 함수가 실행할 로직이 변경 => 전형적인 플래그 인수 4 | - 호출하는 쪽에서 넘겨주는 불리언 값이 무엇인지 명확하지 못하다. 5 | */ 6 | 7 | class Place { 8 | plusDays(time) { 9 | const d = new Date("2021-07-08T10:00:00.000Z"); 10 | d.setHours(d.getHours() + time); 11 | return d; 12 | } 13 | } 14 | class Order { 15 | deliveryState; 16 | placedOn; 17 | constructor(deliveryState) { 18 | this.deliveryState = deliveryState; 19 | this.placedOn = new Place(); 20 | } 21 | } 22 | 23 | const rushDeliveryDate = (anOrder) => { 24 | let deliveryTime; 25 | if (["MA", "CT"].includes(anOrder.deliveryState)) deliveryTime = 1; 26 | else if (["NY", "NH"].includes(anOrder.deliveryState)) deliveryTime = 2; 27 | else deliveryTime = 3; 28 | return anOrder.placedOn.plusDays(1 + deliveryTime); 29 | } 30 | 31 | const regularDeliveryDate = (anOrder) => { 32 | let deliveryTime; 33 | if (["MA", "CT", "NY"].includes(anOrder.deliveryState)) deliveryTime = 2; 34 | else if (["ME", "NH"].includes(anOrder.deliveryState)) deliveryTime = 3; 35 | else deliveryTime = 4; 36 | return anOrder.placedOn.plusDays(2 + deliveryTime); 37 | } 38 | 39 | console.log(rushDeliveryDate(new Order("MA"))); 40 | console.log(rushDeliveryDate(new Order("NH"))); 41 | console.log(regularDeliveryDate(new Order("CT"))); 42 | console.log(regularDeliveryDate(new Order("ME"))); 43 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/04-1.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 실내온도 모니터링 시스템: 일일 최저, 최고 기온이 난방 계획에서 정한 범위를 벗어나는지 확인 3 | - 최저, 최고 기온을 뽑아내어 인수를 건네는 대신 범위 객체를 통째로 건넬 수도 있다. 4 | */ 5 | class TemperatureRange { 6 | high; 7 | low; 8 | constructor(low, high) { 9 | this.high = high; 10 | this.low = low; 11 | } 12 | } 13 | class Room { 14 | daysTempRange; 15 | constructor(min, max) { 16 | this.daysTempRange = new TemperatureRange(min, max); 17 | } 18 | } 19 | const room = new Room(22, 24); 20 | 21 | class HeatingPlan { 22 | _temperatureRange; 23 | constructor(low, high) { 24 | this._temperatureRange = new TemperatureRange(low, high); 25 | } 26 | withinRange(aNumberRange) { 27 | return ( 28 | (aNumberRange.low >= this._temperatureRange.low) && 29 | (aNumberRange.high <= this._temperatureRange.high) 30 | ); 31 | } 32 | } 33 | 34 | const client = () => { 35 | const plan = new HeatingPlan(21, 25); 36 | if (!plan.withinRange(room.daysTempRange)) { 37 | console.log("방 온도가 지정 범위를 벗어났습니다."); 38 | } else { 39 | console.log("적정 온도입니다."); 40 | } 41 | }; 42 | client(); 43 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/04-2.js: -------------------------------------------------------------------------------- 1 | class TemperatureRange { 2 | high; 3 | low; 4 | constructor(low, high) { 5 | this.high = high; 6 | this.low = low; 7 | } 8 | } 9 | class Room { 10 | daysTempRange; 11 | constructor(min, max) { 12 | this.daysTempRange = new TemperatureRange(min, max); 13 | } 14 | } 15 | 16 | class HeatingPlan { 17 | _temperatureRange; 18 | constructor(low, high) { 19 | this._temperatureRange = new TemperatureRange(low, high); 20 | } 21 | withinRange({ low, high }) { 22 | return ( 23 | low >= this._temperatureRange.low && high <= this._temperatureRange.high 24 | ); 25 | } 26 | } 27 | 28 | const client = () => { 29 | const plan = new HeatingPlan(21, 25); 30 | const room = new Room(22, 24); 31 | const isWithinRange = plan.withinRange(room.daysTempRange); 32 | if (!isWithinRange) { 33 | console.log("방 온도가 지정 범위를 벗어났습니다."); 34 | } else { 35 | console.log("적정 온도입니다."); 36 | } 37 | }; 38 | client(); 39 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/05.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 다른 리팩터링을 수행한 뒤 특정 매개변수가 더는 필요 없어졌을 때가 있는데, 바로 이번 리팩터링을 적용하는 가장 흔한 사례. 3 | - 함수 간소화를 위해, 임시 변수를 질의 함수로 바꾸기 적용 4 | - 매개변수를 참조하는 코드를 모두 함수 호출로 변경 5 | - 함수 선언 바꾸기로 매개변수 제거 6 | */ 7 | 8 | class Order { 9 | quantity; 10 | itemPrice; 11 | constructor() {} 12 | 13 | get finalPrice() { 14 | const basePrice = this.quantity * this.itemPrice; 15 | return this.discountedPrice(basePrice); 16 | } 17 | 18 | get discountLevel() { 19 | return (this.quantity > 100) ? 2 : 1; 20 | } 21 | 22 | discountedPrice(basePrice) { 23 | switch (this.discountLevel) { 24 | case 1: 25 | return basePrice * 0.95; 26 | case 2: 27 | return basePrice * 0.9; 28 | default: 29 | return basePrice; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/06.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 실내온도 제어 시스템: 사용자는 온도조절기로 온도 설정 가능, 목표 온도는 난방 계획에서 정한 범위에서만 선택. 3 | - targetTemperature() 메서드가 전역 객체인 thermostat에 의존하는 것이 신경쓰임 4 | - 그러니, 이 전역 객체에 건네는 질의 메서드를 매개변수로 옮겨서 의존성을 끊어보자. 5 | - 6 | */ 7 | 8 | const thermostat = { 9 | selectedTemperature: 25, 10 | currentTemperature: 27, 11 | }; 12 | 13 | class HeatingPlan { 14 | #max; 15 | #min; 16 | get targetTemperature() { 17 | return this.targetTemperature(thermostat.selectedTemperature); 18 | } 19 | 20 | targetTemperature(selectedTemperature) { 21 | if (selectedTemperature > this.#max) return this.#max; 22 | else if (selectedTemperature < this.#min) return this.#min; 23 | else return selectedTemperature; 24 | } 25 | } 26 | 27 | const temperatureController = () => { 28 | const setToHeat = () => {}; 29 | const setToCool = () => {}; 30 | const setOff = () => {}; 31 | 32 | const heatingPlan = new HeatingPlan(); 33 | if (heatingPlan.targetTemperature > thermostat.currentTemperature) 34 | setToHeat(); 35 | else if (heatingPlan.targetTemperature < thermostat.currentTemperature) 36 | setToCool(); 37 | else setOff(); 38 | }; 39 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/07.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 사람의 속성 중 이름은 객체를 생성한 뒤라도 변경될 수 있겠지만, id는 그러면 안 된다. 3 | - 이 의도를 명확히 알리기 위해 id 세터를 제거해보자. 4 | */ 5 | 6 | class Person { 7 | name; 8 | id; 9 | 10 | constructor(id) { 11 | this.id = id; 12 | } 13 | get name() { 14 | return this.name; 15 | } 16 | set name(name) { 17 | this.name = name; 18 | } 19 | get id() { 20 | return this.id; 21 | } 22 | } 23 | const martin = new Person("1234"); 24 | martin.name = "Martin"; 25 | console.log(martin); -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/09.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 건강보험 애플리케이션에서 사용하는 점수 계산 함수 3 | - 복잡한 함수를 잘게 쪼개 명령 객체로 리팩터링하는 과정 4 | 5 | 1. 빈 클래스 생성 6 | 2. 함수를 해당 클래스로 이동 7 | 3. 명령이 받는 인수들은 생성자로 이동 8 | (execute 메서드는 매개변수를 받지 않도록 하는 것을 추천) 9 | 4. 모든 지역변수를 필드로 변경 10 | 5. 함수 추출하기로 계산 로직 구조화하기 11 | */ 12 | class Scorer { 13 | constructor(candidate, medicalExam, scoringGuide) { 14 | this._candidate = candidate; 15 | this._medicalExam = medicalExam; 16 | this._scoringGuide = scoringGuide; 17 | } 18 | execute() { 19 | this._result = 0; 20 | this._healthLevel = 0; 21 | this._highMedicalRiskFlag = false; 22 | if (this._medicalExam.isSmoker) { 23 | this._healthLevel += 10; 24 | this._highMedicalRiskFlag = true; 25 | } 26 | this.scoreSmoking(); 27 | this._certificationGrade = "regular"; 28 | this._result -= Math.max(this._healthLevel - 5, 0); 29 | return this._result; 30 | } 31 | 32 | scoreSmoking() { 33 | if (this._scoringGuide.stateWithLowCertification(this._candidate.originState)) { 34 | this._certificationGrade = "low"; 35 | this._result -= 5; 36 | } 37 | } 38 | } 39 | 40 | const scoringGuide = { 41 | stateWithLowCertification: (state) => state === "CA" || state === "ME", 42 | }; 43 | console.log(new Scorer({ originState: "CA" }, { isSmoker: true }, scoringGuide).execute()); 44 | console.log(new Scorer({ originState: "NY" }, { isSmoker: false }, scoringGuide).execute()); 45 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/10.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 복잡하지 않은 작은 명령 객체를 함수로 변환하는 예제 3 | - 명령 클래스가 간단한 편이므로 함수로 대체한다. 4 | 5 | 1. 이 클래스를 생성하고 호출하는 코드를 함께 함수로 추출 6 | 2. 명령의 실행함수가 호출하는 보조 메서드들 각각을 인라인 7 | 3. 생성자가 받던 모든 매개변수를 charge() 메서드로 옮긴다 (함수 선언 바꾸기) 8 | 4. charge() 메서드에서 필드 대신 건네받은 매개변수를 사용하도록 수정 9 | 5. 다 됐으면 최상위 charge() 함수로 인라인 10 | 6. 죽은 코드 제거하기 11 | */ 12 | 13 | function charge(customer, usage, provider) { 14 | const baseCharge = customer.baseRate * usage; 15 | return baseCharge + provider.connectionCharge; 16 | } 17 | const customer = { baseRate: 100 }; 18 | const usage = 1000; 19 | const provider = { connectionCharge: 50 }; 20 | const monthCharge = charge(customer, usage, provider); 21 | console.log(monthCharge); 22 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/11.js: -------------------------------------------------------------------------------- 1 | /* 2 | - GPS 위치 목록으로 다양한 계산을 수행하는 코드 3 | - 이번 리팩터링에선 고도 상승분(Ascent) 계산만을 고려한다. 4 | 5 | 1. 먼저 totalAscent 값을 반환하고, 호출한 곳에서 변수에 대입하게 고친다. 6 | 2. 계산을 수행하는 함수의 반환 값을 수정하고, 반환 값을 담을 지역변수를 선언 7 | 3. 이 계산이 변수 선언과 동시에 수행되도록 하고, const를 붙여 불변으로 만든다. 8 | */ 9 | 10 | const calculateAscent = () => { 11 | let result = 0; 12 | for (let i = 1; i < points.length; i++) { 13 | const verticalChange = points[i].elevation - points[i - 1].elevation 14 | result += verticalChange > 0 ? verticalChange : 0 15 | } 16 | return result; 17 | } 18 | const calculateTime = () => {} 19 | const calculateDistance = () => {} 20 | 21 | let points = [] 22 | let totalTime = 0 23 | let totalDistance = 0 24 | const totalAscent = calculateAscent(); 25 | calculateTime() 26 | calculateDistance() 27 | const pace = totalTime / 60 / totalDistance -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/12.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 전역 테이블에서 배송지의 배송 규칙을 알아내는 코드 3 | - 이 코드는 국가 정보(Country)가 유효한지를 이 함수 호출 전에 다 검증했다고 가정한다. 4 | 5 | 1. 최상위에 예외 핸들러를 갖춘다. 6 | */ 7 | class ShippingRules { 8 | data 9 | constructor(data) { 10 | this.data = data 11 | } 12 | } 13 | const countryData = { 14 | shippingRules: { 15 | US: 10, 16 | CA: 7, 17 | }, 18 | } 19 | const errorList = [] 20 | 21 | class OrderProcessingError extends Error { 22 | code 23 | constructor(errorCode) { 24 | super(`주문 처리 오류${errorCode}`) 25 | this.code = errorCode 26 | } 27 | get name() { 28 | return 'OrderProcessingError' 29 | } 30 | } 31 | 32 | const localShippingRules = country => { 33 | const data = countryData.shippingRules[country] 34 | if (data) return new ShippingRules(data) 35 | throw new OrderProcessingError(-23) 36 | } 37 | const calculateShippingCosts = order => { 38 | // 관련 없는 코드 39 | const shippingRules = localShippingRules(order.country) 40 | // 관련 없는 코드 41 | } 42 | const execute = order => { 43 | try { 44 | calculateShippingCosts(order) 45 | } catch (err) { 46 | if (err instanceof OrderProcessingError) errorList.push({ order, errorCode: err.code }) 47 | else throw err 48 | // 예외 처리 로직 49 | } 50 | } 51 | 52 | execute({ country: 'US' }) 53 | execute({ country: 'CA' }) 54 | execute({ country: 'KO' }) 55 | 56 | console.log(errorList) -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Hyeonmin/src/8.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 직원의 유형을 다루는 예제 3 | 4 | 1. 팩터리 함수 만들기 5 | 2. 생성자를 호출하는 곳을 팩터리 함수로 교체 6 | 3. 함수에 문자열 리터럴을 전달하는 악취 제거 7 | */ 8 | 9 | class Employee { 10 | #name 11 | #typeCode 12 | constructor(name, typeCode) { 13 | this.#name = name 14 | this.#typeCode = typeCode 15 | } 16 | get name() { 17 | return this.#name 18 | } 19 | get type() { 20 | return this.#typeCode 21 | } 22 | static get legalTypeCodes() { 23 | return { 24 | E: 'Engineer', 25 | M: 'Manager', 26 | S: 'Salesperson', 27 | } 28 | } 29 | } 30 | 31 | function createEmployee(name, typeCode) { 32 | return new Employee(name, typeCode); 33 | } 34 | 35 | function createEngineer(name) { 36 | return new Employee(name, 'E'); 37 | } 38 | 39 | const client1 = () => { 40 | const document = { name: '재남', empType: 'M', leadEngineer: '로이' } 41 | const candidate = createEmployee(document.name, document.empType) 42 | const leadEngineer = createEngineer(document.leadEngineer) 43 | return { candidate: candidate.name, leadEngineer: leadEngineer.name } 44 | } 45 | 46 | console.log(client1()) -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Tony/11_11.ts: -------------------------------------------------------------------------------- 1 | type Point = { 2 | elevation: number; 3 | }; 4 | 5 | type Points = Point[]; 6 | // const points = [{ elevation: 5 }, { elevation: 4 }, { elevation: 2 }]; 7 | const points = [ 8 | { elevation: 1 }, 9 | { elevation: 2 }, 10 | { elevation: 5 }, 11 | { elevation: 12 }, 12 | ]; 13 | const totalAscent = calculateAscent(points); 14 | 15 | function calculateAscent(points: Point[]) { 16 | let result = 0; 17 | for (let i = 1; i < points.length; i++) { 18 | const verticalChange = points[i].elevation - points[i - 1].elevation; 19 | result += verticalChange > 0 ? verticalChange : 0; 20 | } 21 | return result; 22 | } 23 | 24 | console.log("totalAscent", totalAscent); 25 | 26 | const totalAscent1 = points.reduce((accumulatedPoint, currentPoint, index) => { 27 | console.log("accumulatedPoint before", accumulatedPoint); 28 | console.log("currentPoint", currentPoint); 29 | const verticalChange = currentPoint.elevation - accumulatedPoint.elevation; 30 | accumulatedPoint.elevation = verticalChange > 0 ? verticalChange : 0; 31 | console.log("accumulatedPoint after", accumulatedPoint); 32 | return currentPoint; 33 | }).elevation; 34 | 35 | console.log("totalAscent1", totalAscent1); 36 | // reduce 결론 : index가 0부터가 아닌 previous가 있는 1부터 시작한다. 37 | // parameter의 첫 번째 인자는 전에 return한 것이다. 38 | // reduce로 구하는 방법은 잘 모르겠다 그냥 이렇게 복잡한 것은 for문이 짱이다. 39 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_Tony/11_12.ts: -------------------------------------------------------------------------------- 1 | const data = "data"; 2 | 3 | function test() { 4 | if (data) { 5 | return data; 6 | } else { 7 | // throw new OrderProcessingError(-23); 8 | throw new Error("-23"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/1/after.js: -------------------------------------------------------------------------------- 1 | function setOffAlarms() {} 2 | 3 | // TODO:: 1) 첫 단계는 함수를 복제하고 질의 목적에 맞는 이름짓기다. 4 | function findMicsreant(peoples) { 5 | for (const people of peoples) { 6 | if (people === '조커'){ 7 | // setOffAlarms() TODO:: 2) 새 질의 함수에서 부수효과를 낳은 부분을 제거한다 8 | return '조커' 9 | } 10 | if (people === '사루만'){ 11 | // setOffAlarms(); 12 | return '사루만'; 13 | } 14 | } 15 | return '' 16 | 17 | } 18 | 19 | const alertForMicreant = (peoples) => { 20 | for (const people of peoples) { 21 | setOffAlarms(); 22 | } 23 | } 24 | 25 | const people = ['조커','사루만','알라딘'] 26 | const found = findMicsreant(people) 27 | alertForMicreant(people); -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/1/before.js: -------------------------------------------------------------------------------- 1 | function setOffAlarms() { 2 | 3 | } 4 | 5 | function alertForMicsreant(peoples) { 6 | for (const people of peoples) { 7 | if (people === '조커'){ 8 | setOffAlarms(); 9 | return '조커' 10 | } 11 | if (people === '사루만'){ 12 | setOffAlarms(); 13 | return '사루만'; 14 | } 15 | } 16 | return '' 17 | 18 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/2/after.js: -------------------------------------------------------------------------------- 1 | // 예시1 2 | function raise(aPerson, factor) { 3 | aPerson.salary = aPerson.salary.multiply(1 + factor); 4 | } 5 | 6 | 7 | 8 | function usd(number) { 9 | return undefined; 10 | } 11 | 12 | /** 13 | * 가변적으로 함수를 쓸수 잇게 바꾼다 14 | * */ 15 | function withinBand(usage, bottom, top) { 16 | if (usage > bottom) { 17 | return Math.min(usage, top) - bottom; 18 | } 19 | 20 | return 0; 21 | } 22 | 23 | 24 | function baseCharge(usage) { 25 | if (usage < 0) return usd(0); 26 | const amount = withinBand(usage, 0, 100) * 0.03 + withinBand(usage, 100, 200) * 0.05 + withinBand(usage, 200, Infinity) * 0.87; 27 | return usd(amount); 28 | } 29 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/2/before.js: -------------------------------------------------------------------------------- 1 | // 예시 1 2 | function tenPercentRaise(aPerson) { 3 | aPerson.salary = aPerson.salary.multiply(1.1); 4 | } 5 | 6 | function fivePercentRaise(aPerson) { 7 | aPerson.salary = aPerson.salary.multiply(1.05); 8 | } 9 | 10 | // 예시 2 11 | 12 | function usd(number) { 13 | return undefined; 14 | } 15 | 16 | function bottomBand(usage) { 17 | return Math.min(usage, 100) 18 | } 19 | 20 | function middleBand(usage) { 21 | return usage > 100 ? Math.min(usage, 200) - 100 : 0; 22 | } 23 | 24 | function topBand(usage) { 25 | return usage > 200 ? usage - 200 : 0; 26 | } 27 | 28 | function baseCharge(usage) { 29 | if (usage < 0) return usd(0); 30 | const amount = bottomBand(usage) * 0.03 + middleBand(usage) * 0.05 + topBand(usage) * 0.87; 31 | return usd(amount); 32 | 33 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/3.플래그인수제거하기/after.js: -------------------------------------------------------------------------------- 1 | const aShipment = { 2 | deliveryDate: null, 3 | placeOn: { 4 | pluDays: (total) => total + 1 5 | } 6 | } 7 | 8 | function rushDeliveryDate(anOrder) { 9 | let deliveryTime; 10 | if (["MA","CT"].includes(anOrder.deliveryState)) deliveryTime = 1; 11 | else if(["NY","NM"].includes(anOrder.deliveryState)) deliveryTime = 2 12 | else deliveryTime = 3 13 | return anOrder.placeOn.pluDays(1 + deliveryTime); 14 | } 15 | 16 | function regularDeliveryDate(anOrder) { 17 | let deliveryTime; 18 | if (["MA","CT", 'NY'].includes(anOrder.deliveryState)) deliveryTime = 1; 19 | else if(["ME","NH"].includes(anOrder.deliveryState)) deliveryTime = 2 20 | else deliveryTime = 4 21 | return anOrder.placeOn.pluDays(2 + deliveryTime); 22 | } 23 | 24 | function deliveryDate(anOrder, isRush) { 25 | if (isRush) return rushDeliveryDate(anOrder) 26 | regularDeliveryDate(anOrder); 27 | } 28 | 29 | 30 | const anOrder = { 31 | deliveryState: 'MA' 32 | } 33 | // aShipment.deliveryDate = deliveryDate(anOrder, true) 34 | aShipment.deliveryDate = rushDeliveryDate(anOrder) // 플래그가있는거 말고 바로 즉석함수를 호출하자 35 | 36 | // 복잡해질때는 또 계산식을빼서 플래그로 구하는 방법을 하자 -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/3.플래그인수제거하기/before.js: -------------------------------------------------------------------------------- 1 | const aShipment = { 2 | deliveryDate: null, 3 | placeOn: { 4 | pluDays: (total) => total + 1 5 | } 6 | } 7 | 8 | function deliveryDate(anOrder, isRush) { 9 | if (isRush){ 10 | let deliveryTime; 11 | if (["MA","CT"].includes(anOrder.deliveryState)) deliveryTime = 1; 12 | else if(["NY","NM"].includes(anOrder.deliveryState)) deliveryTime = 2 13 | else deliveryTime = 3 14 | return anOrder.placeOn.pluDays(1 + deliveryTime); 15 | } 16 | // b... 17 | let deliveryTime; 18 | if (["MA","CT", 'NY'].includes(anOrder.deliveryState)) deliveryTime = 1; 19 | else if(["ME","NH"].includes(anOrder.deliveryState)) deliveryTime = 2 20 | else deliveryTime = 4 21 | return anOrder.placeOn.pluDays(2 + deliveryTime); 22 | 23 | } 24 | 25 | 26 | const anOrder = { 27 | deliveryState: 'MA' 28 | } 29 | aShipment.deliveryDate = deliveryDate(anOrder, true) -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/4.객체통째로넘기기/before.js: -------------------------------------------------------------------------------- 1 | const low = aRoom.daysTempRange.low; 2 | const high = aRoom.daysTempRange.high; 3 | 4 | const func = (aPlan) => { 5 | if (!aPlan.withinRange(low, high)) { 6 | 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/5.매개변수를 질의 함수로 바꾸기/after.js: -------------------------------------------------------------------------------- 1 | class Order { 2 | constructor(price, quantity) { 3 | this._price = price; 4 | this._quantity = quantity; 5 | } 6 | 7 | get price() { return this._price } 8 | get quantity () { return this._quantity } 9 | 10 | get basePrice() { 11 | return this.quantity * this.itemPrice; 12 | } 13 | 14 | get discountLevel(){ 15 | return this.quantity > 100 ? 2 : 1; 16 | } 17 | 18 | discountPrice() { 19 | switch (this.discountLevel){ 20 | case 1: return this.basePrice * 0.95; 21 | case 2: return this.basePrice * 0.9; 22 | } 23 | } 24 | 25 | get finalPrice(){ 26 | return this.basePrice - this.discountPrice 27 | } 28 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API-리팩터링_lucid/5.매개변수를 질의 함수로 바꾸기/before.js: -------------------------------------------------------------------------------- 1 | class Order { 2 | get finalPrice(){ 3 | const basePrice = this.quantity * this.itemPrice; 4 | const discountLevel = this.quantity > 100 ? 2 : 1; 5 | return this.discountPrice(basePrice, discountLevel); 6 | 7 | } 8 | 9 | discountPrice(basePrice, discountLevel) { 10 | switch (discountLevel){ 11 | case 1: return basePrice * 0.95; 12 | case 2: return basePrice * 0.9; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 11. API 리팩터링 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 11 정리 문서](https://mwjjeongdev.notion.site/Chapter-11-API-eb8b19013ac44ce2aa6bf41e46144531) 6 | - [Chapter 11 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter11) 7 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/01-질의함수와-변경함수-분리하기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | function getTotalOutstandingAndSendBill() { 4 | const result = customer.invoices.reduce((total, each) => each.amount + total, 0); 5 | sendBill(); 6 | return result; 7 | } 8 | 9 | function sendBill() { 10 | emailGateway.send(formatBill(customer)); 11 | } 12 | } 13 | //리팩토링 후 - 부수효과 제거 14 | { 15 | function getTotalOutstandingAndSendBill() { 16 | const result = customer.invoices.reduce((total, each) => each.amount + total, 0); 17 | } 18 | 19 | function sendBill() { 20 | emailGateway.send(formatBill(customer)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/02-함수-매개변수화하기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | function tenPercentRaise(aPerson) { 4 | aPerson.salary = aPerson.salary.multiply(1.1); 5 | } 6 | 7 | function fivePercentRaise(aPerson) { 8 | aPerson.salary = aPerson.salary.multiply(1.05); 9 | } 10 | } 11 | //리팩토링 후 - 다른 값만 매개변수 처리 12 | { 13 | function raise(aPerson, factor) { 14 | aPerson.salary = aPerson.salary.multiply(1 + factor); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/03-플래그-인수-제거하기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | function setDimension(name, value) { 4 | if (name === "height") { 5 | this._height = value; 6 | return; 7 | } 8 | if (name === "width") { 9 | this._width = value; 10 | return; 11 | } 12 | } 13 | } 14 | //리팩토링 후 - 함수 자체 분리 15 | { 16 | function setHeight(value) { 17 | this._height = value; 18 | } 19 | 20 | function setWidth(value) { 21 | this._width = value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/04-객체-통째로-넘기기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | const low = aRoom.daysTempRange.low; 4 | const high = aRoom.daysTempRange.high; 5 | if (aPlan.withinRange(low, high)) { 6 | console.log("with in range 👍"); 7 | } 8 | } 9 | //리팩토링 후 - 객체 통째로 넘김 10 | { 11 | if (aPlan.withinRange(aRoom.daysTempRange)) { 12 | console.log("with in range 👍"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/05-매개변수를-질의함수로-바꾸기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | availableVacation(anEmployee, anEmployee.grade); 4 | 5 | function availableVacation(anEmployee, grade) { 6 | // 연휴계산 logic 7 | } 8 | } 9 | //리팩토링 후 10 | { 11 | availableVacation(anEmployee); 12 | 13 | function availableVacation(anEmployee) { 14 | const grade = anEmployee.grade; 15 | // 연휴계산 logic 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/06-질의함수를-매개변수로-바꾸기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | const aPlan = {}; 4 | const thermostat = { currentTemperature }; 5 | function targetTemperature(aPlan) { 6 | currentTemperature = thermostat.currentTemperature; 7 | //logic 8 | } 9 | targetTemperature(aPlan); 10 | } 11 | //리팩토링 후 - 거북한 참조를 매개변수로 수정 12 | { 13 | const aPlan = {}; 14 | const thermostat = { currentTemperature }; 15 | function targetTemperature(aPlan, currentTemperature) { 16 | //logic 17 | } 18 | targetTemperature(aPlan, thermostat.currentTemperature); 19 | } 20 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/07-세터제거하기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | class Person { 4 | constructor(name) { 5 | this._name = name; 6 | } 7 | get name() { 8 | return this._name; 9 | } 10 | set name(aString) { 11 | this._name = aString; 12 | } 13 | } 14 | } 15 | //리팩토링 후 - 세터제거 16 | { 17 | class Person { 18 | constructor(name) { 19 | this._name = name; 20 | } 21 | get name() { 22 | return this._name; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/08-생성자를 팩터리함수로 바꾸기.js: -------------------------------------------------------------------------------- 1 | class Employee {} 2 | const document = { leadEngineer: "Dahye" }; 3 | 4 | //리팩토링 전 5 | { 6 | const leadEngineer = new Employee(document.leadEngineer, "E"); 7 | } 8 | 9 | //리팩토링 후 10 | { 11 | const createEngineer = (engineer) => new Employee(engineer, "E"); 12 | const leadEngineer = createEngineer(document.leadEngineer); 13 | } 14 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/09-함수를-명령으로-바꾸기.js: -------------------------------------------------------------------------------- 1 | class Score { 2 | /** 3 | * [지역 변수 필드로 바꾸기] 4 | * - 복잡한 함수 잘게 나누기 위한 목적 5 | * - 함수 상태를 모두 명령 객체로 옮긴다 6 | * -> 함수가 사용하던 변수를 유효범위에 구애받지 않고 !함수 추출하기! 리팩토링 적용할 수 있다. 7 | * 8 | * => 명령을 중첩 함수처럼 다룰 수 있게 된다. 9 | * 10 | */ 11 | constructor(candidate, medicalExam, scoringGuide) { 12 | this._candidate = candidate; 13 | this._medicalExam = medicalExam; 14 | this._scoringGuide = scoringGuide; 15 | } 16 | excute() { 17 | this._result = 0; 18 | this._healthLevel = 0; 19 | this._highMedicalRiskFlag = false; 20 | 21 | this.scoreSmoking(); 22 | 23 | let certificationGrade = "regular"; 24 | if ( 25 | this._scoringGuide.stateWithLowCertification(this._candidate.originState) 26 | ) { 27 | certificationGrade = "low"; 28 | this._result -= 5; 29 | } 30 | 31 | this._result -= Math.max(this._healthLevel - 5, 0); 32 | return this._result; 33 | } 34 | 35 | //함수추출하기 36 | scoreSmoking() { 37 | if (this._medicalExam.isSmoker) { 38 | this._healthLevel += 10; 39 | this._highMedicalRiskFlag = true; 40 | } 41 | } 42 | } 43 | 44 | const score = (candidate, medicalExam, scoringGuide) => { 45 | return new Score(candidate, medicalExam, scoringGuide).excute(); 46 | }; 47 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/10-명령을-함수로-바꾸기.js: -------------------------------------------------------------------------------- 1 | const charge = (customer, usage, provider) => { 2 | const baseCharge = customer * usage; //생성자와 메서드 호출 인라인 3 | return baseCharge + provider; 4 | }; 5 | 6 | //호출자 7 | const monthCharge = charge(customer, usage, provider).charge; 8 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/11-수정된-값-반환하기.js: -------------------------------------------------------------------------------- 1 | //리팩토링 전 2 | { 3 | let points = []; 4 | let totalAscent = 0; 5 | 6 | calculateAscent(); 7 | 8 | function calculateAscent() { 9 | for (let i = 1; i < points.length; i++) { 10 | const verticalChange = points[i].elevation - points[i - 1].elevation; 11 | totalAscent += verticalChange > 0 ? verticalChange : 0; 12 | } 13 | } 14 | } 15 | 16 | //리팩토링 후 - 갱신 사실을 밖으로 알리기 17 | //- 변수 갱신하는 함수의 수정된 값을 반환 18 | //-> 호출자 코드를 읽을 때 변수 갱신 될 것임을 인지하게 한다. 19 | { 20 | let points = []; 21 | const totalAscent = calculateAscent(); 22 | 23 | function calculateAscent() { 24 | let result = 0; 25 | for (let i = 1; i < points.length; i++) { 26 | const verticalChange = points[i].elevation - points[i - 1].elevation; 27 | result += verticalChange > 0 ? verticalChange : 0; 28 | } 29 | return totalAscent; 30 | } 31 | function calculateTime() {} 32 | function calculateDistance() {} 33 | } 34 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/12-오류코드를-예외로-바꾸기.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Case - country가 불일치 3 | */ 4 | const orderData = { 5 | country: "KO", 6 | }; 7 | 8 | const countryData = { 9 | shippingRules: { 10 | KOR: 82, 11 | }, 12 | }; 13 | 14 | class ShippingRules { 15 | constructor(data) { 16 | this.data = data; 17 | } 18 | } 19 | 20 | //[예외를 구분할 식별 방법] 21 | class OrderProcessingError extends Error { 22 | constructor(errorCode) { 23 | super(`주문 처리 오류 ${errorCode}`); 24 | this.code = errorCode; 25 | } 26 | get name() { 27 | return "OrderProcessingError"; 28 | } 29 | } 30 | 31 | const errorList = []; 32 | 33 | //[예외 핸들러] 34 | // - 콜스택 상위에 해당 예외처리 할 예외 핸들러 작성 35 | // - 처음에는 모든 예외를 던진다. 36 | // - 적절 처리하는 핸들러 존재하면, 지금 콜스택도 처리하도록 확장한다. 37 | try { 38 | calculateShippingCosts(orderData); 39 | } catch (e) { 40 | console.log(e); //{ [OrderProcessingError: 주문 처리 오류 -23] code: -23 } 41 | if (e instanceof OrderProcessingError) { 42 | errorList.push({ order: orderData, errorCode: e.code }); 43 | } 44 | throw e; 45 | } 46 | 47 | function localShippingRules(country) { 48 | const data = countryData.shippingRules[country]; 49 | if (data) return new ShippingRules(data); 50 | else throw new OrderProcessingError(-23); //오류 대신 예외 던지기 51 | } 52 | function calculateShippingCosts(order) { 53 | // ... 54 | localShippingRules(order.country); 55 | // ... 56 | } 57 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_SHUZAN/sample/13-예외를-사전확인으로-바꾸기.ts: -------------------------------------------------------------------------------- 1 | class ResourcePool