├── .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 | 리팩터링-첫 번째 예시 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/main.js: -------------------------------------------------------------------------------- 1 | 2 | const invoices = require('./data/invocies.json') 3 | const plays = require('./data/plays.json') 4 | 5 | const {statement: originStatement } = require('./example/origin') 6 | const {log} = require("./util"); 7 | const {htmlStatement} = require("./module/statement"); 8 | 9 | log( 10 | '함수에 들어가는 데이터입니다', 11 | {invoices, plays} 12 | ) 13 | 14 | const billingMessage = originStatement(invoices, plays); 15 | // document.body.append(htmlStatement(invoices,plays)); 16 | -------------------------------------------------------------------------------- /01_리팩터링-첫-번째-예시/01.리펙터링-첫-번째-예시_lcuid/app/module/statement.js: -------------------------------------------------------------------------------- 1 | const {createStatement} = require("./createStatement"); 2 | 3 | function renderHtml(data) { 4 | let result = `

청구내역 (고객명: ${data.customer}

`; 5 | result += ""; 6 | result += "" 7 | 8 | for (let perf of data.performances) { 9 | result += ` ` 10 | result += `
`; 11 | } 12 | result += "
연극좌석 수금액
${perf.play.name}(${perf.audience}석)${krw(perf.amount)}
"; 13 | result += `

총액: ${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 | Document 8 | 9 | 10 |

청구 내역 (고객명: BigCo)

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
연극좌석 수금액
Hamlet55$650.00
As You Like It35$580.00
Othello40$500.00
36 |

총액 : $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 | 코로나 세계 현황 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/main.css: -------------------------------------------------------------------------------- 1 | /* browser default stylesheet reset */ 2 | html, 3 | body { 4 | width: 100%; 5 | height: 100%; 6 | line-height: 1; 7 | font-family: 'Exo 2', sans-serif; 8 | } 9 | 10 | body, 11 | ol, 12 | p, 13 | h1, 14 | h3, 15 | span, 16 | div { 17 | margin: 0; 18 | padding: 0; 19 | border: 0; 20 | font-size: 100%; 21 | font-weight: 500; 22 | font: inherit; 23 | font-family: 'Exo 2', sans-serif; 24 | } 25 | ol { 26 | list-style-type: none; 27 | } 28 | 29 | *, 30 | *:after, 31 | *:before { 32 | box-sizing: border-box; 33 | font-family: 'Exo 2', sans-serif; 34 | } 35 | 36 | /* util stylesheets */ 37 | .flex { 38 | display: flex; 39 | } 40 | .column { 41 | flex-direction: column; 42 | } 43 | .justify-center { 44 | justify-content: center; 45 | } 46 | .align-center { 47 | align-items: center; 48 | } 49 | 50 | /* common stylesheets */ 51 | body { 52 | background: #4d4646; 53 | color: white; 54 | } 55 | main { 56 | padding: 0 1rem; 57 | } 58 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "province-app", 3 | "version": "1.0.0", 4 | "description": "최종 프로젝트 폴더입니다", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "build": "webpack", 8 | "watch": "webpack -w", 9 | "serve": "lite-server", 10 | "test": "jest --watch" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@types/jest": "^27.0.3", 16 | "jest": "^27.4.3" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.15.4", 20 | "@babel/preset-env": "^7.15.4", 21 | "@babel/preset-typescript": "^7.15.0", 22 | "@typescript-eslint/eslint-plugin": "^4.30.0", 23 | "@typescript-eslint/parser": "^4.30.0", 24 | "eslint": "^7.32.0", 25 | "eslint-plugin-prettier": "^4.0.0", 26 | "lite-server": "^2.6.1", 27 | "prettier": "^2.3.2", 28 | "ts-jest": "^27.1.0", 29 | "ts-loader": "^9.2.5", 30 | "typescript": "^4.4.2", 31 | "webpack": "^5.52.0", 32 | "webpack-cli": "^4.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/src/Producer.ts: -------------------------------------------------------------------------------- 1 | import Province from "./Province"; 2 | 3 | interface IProducer { 4 | name: string; 5 | cost: number; 6 | province: Province; 7 | production: number; 8 | } 9 | 10 | export interface ProducerData { 11 | name: string; 12 | cost: number; 13 | production: number; 14 | } 15 | 16 | class Producer implements IProducer { 17 | private readonly _name: string; 18 | province: Province; 19 | 20 | private _cost :number; 21 | private _production: number; 22 | 23 | constructor(province: Province, data: any) { 24 | this._name = data.name 25 | this._cost = data.cost; 26 | this._production = data.production || 0; 27 | this.province = province; 28 | } 29 | 30 | get name(){ return this._name } 31 | get cost(){ return this._cost } 32 | set cost(arg) { this._cost = arg } 33 | get production(){ return this._production } 34 | set production(arg){ 35 | const newProduction = Number.isNaN(arg) ? 0: arg; 36 | this.province.totalProduction += newProduction - this._production; 37 | this._production = newProduction; 38 | } 39 | 40 | } 41 | 42 | export default Producer; -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/src/index.ts: -------------------------------------------------------------------------------- 1 | const sampleProvinceData = () => { 2 | return { 3 | name: 'Asia', 4 | producers: [ 5 | { name: 'Byzantium', cost: 10, production: 9}, 6 | { name: 'Attalia', cost: 12, production: 10}, 7 | { name: 'Sinope', cost: 10, production: 6} 8 | ], 9 | demand: 30, 10 | price: 20 11 | } 12 | }; 13 | 14 | 15 | export { 16 | sampleProvinceData 17 | } -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "ES5", 5 | "module": "es6", 6 | "outDir": "./built", 7 | "moduleResolution": "Node", 8 | "noImplicitAny": true, 9 | "lib": [ 10 | "ES2015", 11 | "DOM", 12 | "DOM.Iterable" 13 | ], 14 | "types": ["jest", "node"], 15 | "allowUnusedLabels": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | // "typeRoots": ["./node_modules/@types", "types"] 19 | "strict": true, 20 | "strictFunctionTypes": true 21 | }, 22 | "include": [ 23 | "./src/**/*" 24 | ], 25 | "exclude": [ 26 | "node_modules" 27 | ] 28 | } -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_lucid/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/index.ts', 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.ts/, 8 | use: 'ts-loader', 9 | }, 10 | ], 11 | }, 12 | resolve: { 13 | extensions: ['.ts', '.js'], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_tony/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-typescript", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_tony/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jest": "^27.4.3", 4 | "typescript": "^4.5.2" 5 | }, 6 | "name": "test", 7 | "version": "1.0.0", 8 | "main": "index.js", 9 | "devDependencies": { 10 | "@babel/preset-env": "^7.16.4", 11 | "@babel/preset-typescript": "^7.16.0", 12 | "@types/jest": "^27.0.3" 13 | }, 14 | "scripts": { 15 | "test": "jest", 16 | "test:watch": "jest --watch", 17 | "test:coverage": "jest --coverage" 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "description": "" 22 | } 23 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트-구축하기_tony/환경설정.md: -------------------------------------------------------------------------------- 1 | # 4장 테스트 환경 세팅 2 | 3 | - 폴더 이동 4 | - cd 04\_테스트-구축하기/04\_테스트-구축하기\_tony 5 | 6 | ### typescript 7 | 8 | #### 설치 9 | 10 | - node, npm 설치 11 | - npm install -g typescript 12 | - npm i -g ts-node 13 | - npm i -g @types/node 14 | - tsc --init 15 | 16 | #### 설정 17 | 18 | tsconfig.json에 type 설정에 아래와 같이 추가 19 | 20 | ```json 21 | "types": [ 22 | "jest", 23 | "node" 24 | ] 25 | ``` 26 | 27 | - https://stackoverflow.com/questions/54139158/cannot-find-name-describe-do-you-need-to-install-type-definitions-for-a-test 28 | 29 | ### jest 30 | 31 | #### 테스트 라이브러리 : jest 설치 32 | 33 | - npm install --save-dev jest 34 | - npm install --save-dev @types/jest 35 | 36 | #### typescript with jest 37 | 38 | - npm i -D @babel/preset-typescript @babel/preset-env 39 | - .babelrc에 아래 옵션 추가 40 | - https://velog.io/@njh7799/typescript-jest%EB%A1%9C-test%ED%95%A0-%EB%95%8C-import-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0 41 | 42 | ```json 43 | { 44 | "presets": ["@babel/preset-typescript", "@babel/preset-env"] 45 | } 46 | ``` 47 | 48 | #### package.json - script 49 | 50 | ```json 51 | "scripts": { 52 | "test": "jest", 53 | "test:watch": "jest --watch", 54 | "coverage": "jest --coverage" 55 | }, 56 | ``` 57 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_hyeonmin/4장_테스트_구축하기_정리.md: -------------------------------------------------------------------------------- 1 | # Chapter4. 테스트 구축하기 2 | 3 | 4 | 5 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/4-a639d84a7d8042a7b45d8429bc56e15c) 6 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_hyeonmin/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_hyeonmin/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "hyeonmin Week4 TestCode", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "test": "mocha --watch --recursive --require babel-core/register" 10 | }, 11 | "author": "hyeonmin", 12 | "license": "ISC", 13 | "dependencies": { 14 | "babel-preset-es2015": "^6.24.1", 15 | "babel-register": "^6.26.0", 16 | "chai": "^4.3.4", 17 | "eslint": "^8.4.1", 18 | "mocha": "^9.1.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_kiwi/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_kiwi/app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /04_테스트-구축하기/04_테스트_구축하기_kiwi/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-2nd-edition_04_kiwi", 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 | -------------------------------------------------------------------------------- /04_테스트-구축하기/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/04_테스트-구축하기/a -------------------------------------------------------------------------------- /05_리팩터링-카탈로그-보는-법/05_리팩터링-카탈로그-보는-법_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter5. 리팩터링 카탈로그 보는 법 2 | 3 | 4 | 5 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/5-32f14c4e98cc46de936d2cf864951a2c) 6 | -------------------------------------------------------------------------------- /05_리팩터링-카탈로그-보는-법/05_리팩터링-카탈로그-보는-법_lucid.md: -------------------------------------------------------------------------------- 1 | # 05. 리팩터링 카탈로그 보는 법 2 | 3 | ## 5-1 리팩터링 설명 형식 4 | 1. 이름이 먼저나옴 5 | 2. 개요(개념도 + 코드 예시) 6 | 3. 배경: Why 7 | 4. 절차: 리팩터링 하는 과정 8 | 5. 예시 및 효과 9 | 10 | ## 05. 리팩터링 기법 선정 기준 11 | 12 | - 완벽 X 13 | - 유용한걸 담음 14 | - 직관적인 리펙터링은 생략함(문장 슬라이드는 이번에 추가함) 15 | - 논리적으로 존재하지만 본이니 사용하지 않거나, 비슷한 케이스 생략(변수 캡슐화하기) 16 | -------------------------------------------------------------------------------- /05_리팩터링-카탈로그-보는-법/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/05_리팩터링-카탈로그-보는-법/a -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_Tony/6_11_before.js: -------------------------------------------------------------------------------- 1 | // product 추론 2 | const product = { 3 | discountThreshold: 5, // 5개 이상 초과 구매 시 할인 적용 4 | basePrice: 10000, 5 | discountRate: 0.05, // 할인율 5% 6 | }; 7 | 8 | // shippingMethod 추론 9 | const shippingMethod = { 10 | discountThreshold: 30000, // basePrice(구매액 = 가격 * 수량)이 30000원 이상이면 배송할인 적용 11 | discountedFee: 0, // 3000원 이상 구매 시 배송비 무료 12 | feePerCase: 2500, // 개당 배송비 2500원 13 | }; 14 | 15 | // 계산이 두 단계로 이루어짐 16 | function priceOrder(product, quantity, shippingMethod) { 17 | const basePrice = product.basePrice * quantity; 18 | const discount = 19 | Math.max(quantity - product.discountThreshold, 0) * // 많이 사면(product.discountThreshold 초과) 할인이 됨 20 | product.basePrice * 21 | product.discountRate; 22 | const shippingPerCase = // 개당 배송비 23 | basePrice > shippingMethod.discountThreshold // 일정가격(3만원) 이상 구매 시 할인된 배송비 적용 24 | ? shippingMethod.discountedFee // 할인된 배송비 : 무료 25 | : shippingMethod.feePerCase; // 아니면 개당 배송비 적용 : 2500원 26 | const shippingCost = quantity * shippingPerCase; // 배송비 = 수량 * 배송비 27 | const price = basePrice - discount + shippingCost; // 최종 가격 = 기본 가격 - 할인된 가격 - 배송비 28 | return price; 29 | } 30 | 31 | console.log(priceOrder(product, 3, shippingMethod)); 32 | 33 | // node 6_11_before.js 34 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_Tony/image/classCopyTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/06_기본적인-리팩터링/06_기본적인-리팩터링_Tony/image/classCopyTest.png -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_Tony/image/classCopyTest1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/06_기본적인-리팩터링/06_기본적인-리팩터링_Tony/image/classCopyTest1.png -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/06_기본적인_리팩터링_hyeonmin.md: -------------------------------------------------------------------------------- 1 | # Chapter6. 기본적인 리팩터링 2 | 3 | 4 | 5 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/6-f739264812ca43199e92179cb8f3be83) 6 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-1-2.js: -------------------------------------------------------------------------------- 1 | const printOwing = (invoice) => { 2 | // 배너출력 3 | printBanner(); 4 | 5 | // 미해결 채무 계산 6 | const outstanding = calculateOutstanding(invoice) 7 | // 마감일 기록 8 | recordDueDate(invoice); 9 | // 세부 사항 출력 10 | printDetails(invoice, outstanding); 11 | }; 12 | 13 | function accumulateValues(values) { 14 | return values.reduce((total, value) => total + value, 0); 15 | } 16 | 17 | function calculateOutstanding(invoice) { 18 | return accumulateValues(invoice.orders.map(order => order.amount)); 19 | } 20 | 21 | function recordDueDate(invoice) { 22 | const today = new Date(); 23 | invoice.dueDate = new Date( 24 | today.getFullYear(), 25 | today.getMonth(), 26 | today.getDate() + 30 27 | ); 28 | } 29 | 30 | function printBanner() { 31 | console.log("******************"); 32 | console.log("**** 고객채무 ****"); 33 | console.log("******************"); 34 | } 35 | 36 | function printDetails(invoice, outstanding) { 37 | console.log(`고객명: ${invoice.customer}`); 38 | console.log(`채무액: ${outstanding}`); 39 | console.log(`마감일: ${invoice.dueDate?.toLocaleString()}`); 40 | } 41 | 42 | printOwing({ 43 | customer: "hyeonmin", 44 | orders: [ 45 | { name: "사채", amount: 100 }, 46 | { name: "대출", amount: 1000 }, 47 | ], 48 | }); -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-2-1.js: -------------------------------------------------------------------------------- 1 | const moreThanFiveLateDeliveries = aDriver => aDriver.numberOfLateDeliveries > 5 2 | const rating = aDriver => (moreThanFiveLateDeliveries(aDriver) ? 2 : 1) 3 | // const rating = aDriver => (aDriver.numberOfLateDeliveries > 5 ? 2 : 1) 4 | 5 | const DriverA = { name: 'A', numberOfLateDeliveries: 10 } 6 | const DriverB = { name: 'B', numberOfLateDeliveries: 4 } 7 | 8 | console.log(DriverA.name, rating(DriverA)) 9 | console.log(DriverB.name, rating(DriverB)) -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-2-2.js: -------------------------------------------------------------------------------- 1 | // const gatherCustomerData = (out, aCustomer) => { 2 | // out.push(['name', aCustomer.name]) 3 | // out.push(['location', aCustomer.location]) 4 | // } 5 | // const reportLines = aCustomer => { 6 | // const lines = [] 7 | // gatherCustomerData(lines, aCustomer) 8 | // return lines 9 | // } 10 | 11 | const reportLines = aCustomer => { 12 | const lines = []; 13 | lines.push(['name', aCustomer.name]); 14 | lines.push(['location', aCustomer.location]); 15 | return lines; 16 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-3-1.js: -------------------------------------------------------------------------------- 1 | const price = (order) => { 2 | const basePrice = order.quantity * order.itemPrice; 3 | const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; 4 | const shipping = Math.min(basePrice * 0.1, 100); 5 | return ( 6 | // 가격(price) = 기본 가격 - 수량 할인 + 배송비 7 | basePrice - quantityDiscount + shipping 8 | ); 9 | }; 10 | 11 | const orderA = { 12 | itemPrice: 600, 13 | quantity: 3, 14 | } 15 | const orderB = { 16 | itemPrice: 8000, 17 | quantity: 2, 18 | } 19 | 20 | console.log(price(orderA)) 21 | console.log(price(orderB)) -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-3-2.js: -------------------------------------------------------------------------------- 1 | class Order { 2 | constructor(aRecord) { 3 | this._data = aRecord; 4 | } 5 | get quantity() { 6 | return this._data.quantity; 7 | } 8 | get itemPrice() { 9 | return this._data.itemPrice; 10 | } 11 | get price() { 12 | return ( 13 | this.basePrice - 14 | this.quantityDiscount + 15 | this.shipping 16 | ); 17 | } 18 | 19 | get basePrice() { 20 | return this.quantity * this.itemPrice; 21 | } 22 | get quantityDiscount() { 23 | return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05; 24 | } 25 | get shipping() { 26 | return Math.min(this.basePrice * 0.1, 100); 27 | } 28 | } 29 | 30 | const orderA = new Order({ 31 | itemPrice: 600, 32 | quantity: 3, 33 | }); 34 | const orderB = new Order({ 35 | itemPrice: 8000, 36 | quantity: 2, 37 | }); 38 | 39 | console.log(orderA.price); 40 | console.log(orderB.price); 41 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-5-1.js: -------------------------------------------------------------------------------- 1 | function circum(radius) { 2 | return 2 * Math.PI * radius; 3 | } 4 | 5 | function circumference(radius) { 6 | return 2 * Math.PI * radius; 7 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-5-2.js: -------------------------------------------------------------------------------- 1 | function circum(radius) { 2 | return 2 * Math.PI * radius; 3 | } 4 | 5 | function circum(radius) { 6 | return circumference(radius); 7 | } 8 | 9 | function circumference(radius) { 10 | return 2 * Math.PI * radius; 11 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-5-3.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | class Book { 4 | _reservations = []; 5 | 6 | get reservation() { 7 | return this._reservations 8 | } 9 | 10 | addReservation(customer) { 11 | this.zz_addReservation(customer, 1); 12 | } 13 | 14 | zz_addReservation(customer, isPriority) { 15 | assert(isPriority === true || isPriority === false); 16 | this._reservations.push(customer) 17 | } 18 | } 19 | 20 | const bookcafe = new Book() 21 | bookcafe.addReservation({ name: 'roy' }) 22 | bookcafe.addReservation({ name: 'jay' }) 23 | console.log(bookcafe.reservation) -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-5-4.js: -------------------------------------------------------------------------------- 1 | const inNewEngland = aCustomer => { 2 | return xxNEWinNewEngland(aCustomer.address.state); 3 | } 4 | 5 | function inNewEngland(stateCode) { 6 | return ['MA', 'CT', 'ME', 'VT', 'NH', 'RI'].includes(stateCode); 7 | } 8 | 9 | const newEnglanders = someCustomers.filter(c => inNewEngland(c.address.state)); -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-6-1.js: -------------------------------------------------------------------------------- 1 | let defaultOwner = { firstName: '마틴', lastName: '파울러' } 2 | 3 | const spaceship = { 4 | owner: defaultOwner, 5 | } 6 | 7 | const getDefaultOwner = () => defaultOwner 8 | const setDefaultOwner = arg => { 9 | defaultOwner = arg 10 | } 11 | 12 | spaceship.owner = getDefaultOwner(); 13 | setDefaultOwner({ firstName: '레베카', lastName: '파슨스' }); 14 | 15 | console.log(spaceship.owner); 16 | console.log(defaultOwner); -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_hyeonmin/src/6-6-1_defaultOwner.js: -------------------------------------------------------------------------------- 1 | let defaultOwnerData = { firstName: '마틴', lastName: '파울러' } 2 | 3 | export const defaultOwner = () => (new Person(defaultOwnerData)) 4 | export const sertDefaultOwner = arg => { 5 | defaultOwnerData = arg 6 | } 7 | 8 | class Person { 9 | constructor(data) { 10 | this._lastName = data.lastName; 11 | this._firstName = data.firstName; 12 | } 13 | get lastName() {return this._lastName;} 14 | get firstName() {return this._firstName;} 15 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.2example.js: -------------------------------------------------------------------------------- 1 | //before 2 | function getRaintg(driver) { 3 | return moreThanFiveLateDeilveries(driver) ? 2: 1; 4 | } 5 | function moreThanFiveLateDeilveries(dvr) { 6 | return dvr.numberOFLateDeliveries > 5; 7 | } 8 | 9 | 10 | //after 11 | function getRaintg(driver) { 12 | return driver.numberOFLateDeliveries > 5 ? 2: 1; 13 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.3example.js: -------------------------------------------------------------------------------- 1 | const pad2digit = digit => digit.length >= 2 ? digit : `0${digit}` 2 | 3 | //before 4 | const timerFormat = value => `${pad2digit(`${parseInt(value / 60)}`)}:${pad2digit(`${value % 60}`)}` 5 | 6 | 7 | //after 8 | const timerFormat = value => { 9 | const _minutes = parseInt(value / 60) 10 | const minutes = pad2digit(`${_minutes}`) 11 | const seconds = pad2digit(`${value % 60}`) 12 | 13 | return `${minutes}:${seconds}` 14 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.4example.js: -------------------------------------------------------------------------------- 1 | const pad2digit = digit => digit.length >= 2 ? digit : `0${digit}` 2 | 3 | //before 4 | function a() { 5 | const isEnd = next > -1; 6 | return isEnd; 7 | } 8 | 9 | //after 10 | function a() { 11 | return next > -1; 12 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.5example.js: -------------------------------------------------------------------------------- 1 | 2 | //before 3 | const getLoanInfo = async (encryptParam) => { 4 | const { validationCode } = await checkValidation({ encryptParam }); 5 | handleInvalid(VALID_CODE[validationCode]); 6 | }; 7 | 8 | //after 9 | const getLoanInfo = async (encryptParam) => await nfValidation(encryptParam); 10 | 11 | const nfValidation = async (encryptParam) => { 12 | const { validationCode } = await checkValidation({ encryptParam }); 13 | handleInvalid(VALID_CODE[validationCode]); 14 | }; -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.7example.js: -------------------------------------------------------------------------------- 1 | 2 | //before 3 | const a = width * height; 4 | 5 | 6 | //after 7 | const area = width * height; 8 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_kiwi/src/6.8example.js: -------------------------------------------------------------------------------- 1 | 2 | //before 3 | const station = { 4 | name: 'ZB1', 5 | readings: [ 6 | {temp: 47, time: "2016-11-10 09:10"}, 7 | {temp: 53, time: "2016-11-10 09:20"}, 8 | {temp: 58, time: "2016-11-10 09:30"}, 9 | {temp: 53, time: "2016-11-10 09:40"}, 10 | {temp: 51, time: "2016-11-10 09:50"} 11 | ] 12 | } 13 | 14 | const operationPlan = { 15 | temperatureFloor: 50, 16 | temperatureCeiling: 55 17 | } 18 | 19 | const readingsOutsideRange = (station, min, max) => { 20 | return station.readings.filter(r => r.temp < min || r.temp > max) 21 | } 22 | 23 | const alerts = readingsOutsideRange(station, operationPlan.temperatureFloor, operationPlan.temperatureCeiling) 24 | 25 | //after 26 | class NumberRange { 27 | constructor(min, max) { 28 | this._data = {min, max} 29 | } 30 | 31 | get min() {return this._data.min} 32 | get max() {return this._data.max} 33 | 34 | contains(arg) {return arg >= this.min && arg <= this.max} 35 | } 36 | 37 | const readingsOutsideRange = (station, range) => { 38 | return station.readings.filter(r => !range.contains(r.temp)) 39 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_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 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 코로나 세계 현황 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/main.css: -------------------------------------------------------------------------------- 1 | /* browser default stylesheet reset */ 2 | html, 3 | body { 4 | width: 100%; 5 | height: 100%; 6 | line-height: 1; 7 | font-family: 'Exo 2', sans-serif; 8 | } 9 | 10 | body, 11 | ol, 12 | p, 13 | h1, 14 | h3, 15 | span, 16 | div { 17 | margin: 0; 18 | padding: 0; 19 | border: 0; 20 | font-size: 100%; 21 | font-weight: 500; 22 | font: inherit; 23 | font-family: 'Exo 2', sans-serif; 24 | } 25 | ol { 26 | list-style-type: none; 27 | } 28 | 29 | *, 30 | *:after, 31 | *:before { 32 | box-sizing: border-box; 33 | font-family: 'Exo 2', sans-serif; 34 | } 35 | 36 | /* util stylesheets */ 37 | .flex { 38 | display: flex; 39 | } 40 | .column { 41 | flex-direction: column; 42 | } 43 | .justify-center { 44 | justify-content: center; 45 | } 46 | .align-center { 47 | align-items: center; 48 | } 49 | 50 | /* common stylesheets */ 51 | body { 52 | background: #4d4646; 53 | color: white; 54 | } 55 | main { 56 | padding: 0 1rem; 57 | } 58 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "province-app", 3 | "version": "1.0.0", 4 | "description": "최종 프로젝트 폴더입니다", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "build": "webpack", 8 | "watch": "webpack -w", 9 | "serve": "lite-server", 10 | "test": "jest --watch" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@types/jest": "^27.0.3", 16 | "jest": "^27.4.3" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.15.4", 20 | "@babel/preset-env": "^7.15.4", 21 | "@babel/preset-typescript": "^7.15.0", 22 | "@typescript-eslint/eslint-plugin": "^4.30.0", 23 | "@typescript-eslint/parser": "^4.30.0", 24 | "eslint": "^7.32.0", 25 | "eslint-plugin-prettier": "^4.0.0", 26 | "lite-server": "^2.6.1", 27 | "prettier": "^2.3.2", 28 | "ts-jest": "^27.1.0", 29 | "ts-loader": "^9.2.5", 30 | "typescript": "^4.4.2", 31 | "webpack": "^5.52.0", 32 | "webpack-cli": "^4.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-1/6-1-1.ts: -------------------------------------------------------------------------------- 1 | // 예시: 유효범위를 벗어나는 변수가 없을때 2 | function printOwing(invoice: any) { 3 | let outstanding = 0; 4 | printBanner(); 5 | 6 | // 미해결 채무(outstanding) 계산한다 7 | for (const o of invoice.orders){ 8 | outstanding += o.amount; 9 | } 10 | 11 | // 마감일(dueDate)를 기록한다 12 | const today = Clock.toDay(); 13 | invoice.newDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30); 14 | 15 | printDetail(); 16 | 17 | function printBanner(){ 18 | console.log('************') 19 | console.log('*** 고객 채무 ****') 20 | console.log('************') 21 | } 22 | function printDetail(){ 23 | // 세부 사항을 출력한다 24 | console.log(`고객명: ${invoice.customer}`) 25 | console.log(`채무액: ${outstanding}`) 26 | console.log(`마감일: ${invoice.dueDate.toLocaleString()}`) 27 | } 28 | } 29 | 30 | class Clock { 31 | 32 | constructor() { 33 | 34 | } 35 | static toDay(){ 36 | return new Date() 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-1/6-1-2.ts: -------------------------------------------------------------------------------- 1 | // 예시: 지역 변수를 사용할때 2 | 3 | function printOwing2(invoice: any) { 4 | let outstanding = 0; 5 | printBanner(); 6 | 7 | // 미해결 채무(outstanding) 계산한다 8 | for (const o of invoice.orders){ 9 | outstanding += o.amount; 10 | } 11 | 12 | 13 | 14 | recordDate(invoice) 15 | printDetail(invoice, outstanding); 16 | 17 | function printBanner(){ 18 | console.log('************') 19 | console.log('*** 고객 채무 ****') 20 | console.log('************') 21 | } 22 | 23 | } 24 | 25 | function printDetail2(invoice: any, outstanding: number){ 26 | // 세부 사항을 출력한다 27 | console.log(`고객명: ${invoice.customer}`) 28 | console.log(`채무액: ${outstanding}`) 29 | console.log(`마감일: ${invoice.dueDate.toLocaleString()}`) 30 | 31 | } 32 | 33 | function recordDate2(invoice: any){ 34 | // 마감일(dueDate)를 기록한다 35 | const today = Clock.toDay(); 36 | invoice.newDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30); 37 | 38 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-1/6-1-3.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 지연 변수의 값을 변경할때 4 | * 지역 변수에 값을 대입할시 문제가 복잡해짐, 5 | * 6 | * */ 7 | 8 | function printOwing3(invoice: any) { 9 | printBanner(); 10 | 11 | 12 | const outstanding = calcOutstanding(invoice); 13 | recordDate(invoice); 14 | printDetail(invoice, outstanding); 15 | 16 | function printBanner(){ 17 | console.log('************') 18 | console.log('*** 고객 채무 ****') 19 | console.log('************') 20 | } 21 | 22 | function calcOutstanding(invoice: any){ 23 | // 미해결 채무(outstanding) 계산한다 24 | let result = 0; // 맨 위에 있던 선언문을 이 위치로 이동(먼저 선언되는 변수가 사용되는 코드 근처로 슬라이드한다) 25 | for (const o of invoice.orders){ 26 | result += o.amount; 27 | } 28 | return result 29 | } 30 | 31 | } 32 | 33 | function printDetail(invoice: any, outstanding: number){ 34 | // 세부 사항을 출력한다 35 | console.log(`고객명: ${invoice.customer}`) 36 | console.log(`채무액: ${outstanding}`) 37 | console.log(`마감일: ${invoice.dueDate.toLocaleString()}`) 38 | 39 | } 40 | 41 | function recordDate(invoice: any){ 42 | // 마감일(dueDate)를 기록한다 43 | const today = Clock.toDay(); 44 | invoice.newDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30); 45 | } 46 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-1/6-1.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-1/6-1.test.ts -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-3/6-3.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/src/6-3/6-3.ts -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "ES5", 5 | "module": "es6", 6 | "outDir": "./built", 7 | "moduleResolution": "Node", 8 | "noImplicitAny": true, 9 | "lib": [ 10 | "ES2015", 11 | "DOM", 12 | "DOM.Iterable" 13 | ], 14 | "types": ["jest", "node"], 15 | "allowUnusedLabels": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | // "typeRoots": ["./node_modules/@types", "types"] 19 | "strict": true, 20 | "strictFunctionTypes": true 21 | }, 22 | "include": [ 23 | "./src/**/*" 24 | ], 25 | "exclude": [ 26 | "node_modules" 27 | ] 28 | } -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인-리팩터링_lucid/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/index.ts', 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.ts/, 8 | use: 'ts-loader', 9 | }, 10 | ], 11 | }, 12 | resolve: { 13 | extensions: ['.ts', '.js'], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 06. 기본적인 리팩터링 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [기본적인 리팩터링](https://mwjjeongdev.notion.site/Chapter-06-2e3acd41f67b4a7fb60d166bb3162b21) 6 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-1-함수추출하기.js: -------------------------------------------------------------------------------- 1 | function printBanner(console) { 2 | console.log("***********************"); 3 | console.log("**** Customer Owes ****"); 4 | console.log("***********************"); 5 | } 6 | 7 | function printDetails(invoice, outstanding, console) { 8 | console.log(`name: ${invoice.customer}`); 9 | console.log(`amount: ${outstanding}`); 10 | console.log(`due: ${invoice.dueDate.toLocaleDateString("en-US")}`); 11 | } 12 | 13 | function recordDueDate(clock, invoice) { 14 | const today = clock.today; 15 | invoice.dueDate = new Date( 16 | today.getFullYear(), 17 | today.getMonth(), 18 | today.getDate() + 30, 19 | ); 20 | } 21 | 22 | function calculateOutstanding(invoice) { 23 | let result = 0; 24 | for (const o of invoice.orders) { 25 | result += o.amount; 26 | } 27 | return result; 28 | } 29 | 30 | export function printOwing(invoice, clock) { 31 | printBanner(); 32 | const outstanding = calculateOutstanding(invoice); 33 | recordDueDate(clock, invoice); 34 | printDetails(invoice, outstanding); 35 | } 36 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-10-여러함수를변환함수로묶기.js: -------------------------------------------------------------------------------- 1 | { 2 | const reading = { customer: "ivan", quantity: 10, month: 5, year: 2017 }; 3 | //클라이언트1 4 | { 5 | const aReading = acquireReading(); 6 | const baseCharge = baseRate(aReading.month, aReading.year) * quantity; 7 | } 8 | { 9 | //클라이언트2 10 | const aReading = acquireReading(); 11 | const baseCharge = 12 | baseRate(aReading.month, aReading.year) * aReading.quantity; 13 | const taxableCharge = Math.max(0, base - taxThreshold(aReading.year)); 14 | } 15 | { 16 | //클라이언트3 17 | const aReading = acquireReading(); 18 | const basicChargeAmount = calculateBaseCharge(aReading); 19 | function calculateBaseCharge(aReading) { 20 | return baseRate(aReading.month, aReading.year); 21 | } 22 | } 23 | } 24 | //--------------------------------------------------------------------- 25 | { 26 | function enrichReading(original) { 27 | //입력객체 복사하여 반환 28 | const result = JSON.parse(JSON.stringify(original)); 29 | return result; 30 | } 31 | 32 | const rawReading = acquireReading(); 33 | const aReading = enrichReading(rawReading); 34 | 35 | function enrichReading(original) { 36 | let result = JSON.parse(JSON.stringify(original)); 37 | result.baseCharge = calculateBaseCharge(result); 38 | result.basicChargeAmount = calculateBaseCharge(result); 39 | //미가공 측정값에 기본 소비량을 부가 정보로 붙인다 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-2-함수인라인하기.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [함수 인라인] 3 | * - 간접 호출이 과하면 인라인 대상이 된다. 4 | */ 5 | 6 | //과한 간접 호출.version 7 | { 8 | function getRating(driver) { 9 | return driver.numberOfLateDeliveries > 5; 10 | } 11 | 12 | function moreThanFiveLateDeliveries() { 13 | return driver.numberOfLateDeliveries > 5; 14 | } 15 | } 16 | 17 | //인라인.version 18 | { 19 | function getRating(driver) { 20 | return driver.numberOfLateDeliveries > 5; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-3-변수추출하기.js: -------------------------------------------------------------------------------- 1 | //예시 2 | { 3 | function price(order) { 4 | const basePrice = order.quantity * order.itemPrice; 5 | const quantityDiscount = 6 | Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; 7 | const shipping = Math.min(basePrice * 0.01, 100); 8 | return basePrice - quantityDiscount + shipping; 9 | } 10 | } 11 | 12 | //예시 Class version 13 | { 14 | class Order { 15 | constructor(aRecord) { 16 | this._data = aRecord; 17 | } 18 | get quantity() { 19 | return this._data.quantity; 20 | } 21 | get itemPrice() { 22 | return this._data.itemPrice; 23 | } 24 | get price() { 25 | return this.basePrice - this.quantityDiscount - this.shipping; 26 | } 27 | get basePrice() { 28 | return this.quantity * this.itemPrice; 29 | } 30 | get quantityDiscount() { 31 | return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05; 32 | } 33 | get shipping() { 34 | return Math.min(this.basePrice - 0.1, 100); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-4-변수인라인하기.js: -------------------------------------------------------------------------------- 1 | //인라인 Before 2 | { 3 | let basePrice = anOrder.basePrice; 4 | return basePrice > 1000; 5 | } 6 | //인라인 After 7 | { 8 | return basePrice > 1000; 9 | } 10 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-6-변수캡슐화하기.js: -------------------------------------------------------------------------------- 1 | //default data 2 | { 3 | let defaultOwner = { firstName: "Martin", lastName: "Fowler" }; 4 | } 5 | //읽고 쓰는 함수를 정의 6 | { 7 | let defaultOwner = { firstName: "Martin", lastName: "Fowler" }; 8 | 9 | function getDefaultOwner() { 10 | return defaultOwner; 11 | } 12 | function setDefaultOwner(arg) { 13 | defaultOwner = arg; 14 | } 15 | } 16 | //값 캡슐화하기 17 | { 18 | function getDefaultOwner() { 19 | return Object.assign({}, defaultOwner); 20 | } 21 | function setDefaultOwner(arg) { 22 | defaultOwner = arg; 23 | } 24 | } 25 | 26 | // 레코드 캡슐화하기 27 | { 28 | class Person { 29 | constructor(data) { 30 | this._lastName = data.lastName; 31 | this._firstName = data.firstName; 32 | } 33 | get lastName() { 34 | return this._lastName; 35 | } 36 | get firstName() { 37 | return this._firstName; 38 | } 39 | } 40 | 41 | function getDefaultOwner() { 42 | return Object.assign({}, defaultOwner); 43 | } 44 | function setDefaultOwner(arg) { 45 | defaultOwner = arg; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/sample/6-7-변수이름바꾸기.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 예시 : 변수 바꾸기 3 | * - 캡슐화하기 4 | */ 5 | { 6 | let tpHd = "untitle"; 7 | let result = `

${tpHd}

`; 8 | } 9 | 10 | { 11 | let _title = ""; 12 | let result = `

${title()}

`; 13 | setTitle("article title"); 14 | function title() { 15 | return _title; 16 | } 17 | function setTitle(arg) { 18 | _title = arg; 19 | } 20 | let result = `

${tpHd}

`; 21 | } 22 | 23 | /** 24 | * 예시 : 상수 이름 바꾸기 25 | * - 점진적으로 바꾼다. 26 | */ 27 | { 28 | const cpNm = "daji Company"; 29 | } 30 | { 31 | const cpNm = "daji Company"; 32 | const companyName = cpNm; 33 | } 34 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["transform-es2015-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/.gitIgnore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | clover.xml 4 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/6-10-여러함수를변환함수로묶기.test.js: -------------------------------------------------------------------------------- 1 | import { acquireReading, Reading } from "./CombineFunctionsIntoClass"; 2 | 3 | const rawReading = acquireReading(); 4 | const aReading = new Reading(rawReading); 5 | 6 | export const basicChargeAmount = aReading.baseCharge; 7 | 8 | // describe("[Chpate6.9ver] check reading unchanged", () => { 9 | // test("check reading unchanged", () => { 10 | // const baseReading = { 11 | // customer: "ivan", 12 | // quantity: 10, 13 | // month: 5, 14 | // year: 2017, 15 | // }; 16 | // const oracle = JSON.parse(JSON.stringify(baseReading)); 17 | // function enrichReading(original) { 18 | // let result = JSON.parse(JSON.stringify(original)); 19 | // result.baseCharge = calculateBaseCharge(result); 20 | // result.basicChargeAmount = calculateBaseCharge(result); 21 | // //미가공 측정값에 기본 소비량을 부가 정보로 붙인다 22 | // return result; 23 | // } 24 | // function calculateBaseCharge(aReading) { 25 | // return baseRate(aReading.month, aReading.year); 26 | // } 27 | // enrichReading(baseReading); 28 | // // assert.equal(baseReading, oracle); 29 | // expect(baseReading).toBe(oracle); 30 | // }); 31 | // }); 32 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client1.js: -------------------------------------------------------------------------------- 1 | import {acquireReading, Reading} from "./CombineFunctionsIntoClass"; 2 | 3 | const rawReading = acquireReading(); 4 | const aReading = new Reading(rawReading); 5 | 6 | 7 | export const baseCharge = aReading.baseCharge; -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client1.test.js: -------------------------------------------------------------------------------- 1 | import { baseCharge } from "./CombineFunctionsIntoClass-Client1"; 2 | 3 | describe("CombineFunctionsIntoClass-Client1.js", () => { 4 | it("baseCharge", () => { 5 | expect(baseCharge).to.equal(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client2.js: -------------------------------------------------------------------------------- 1 | import { acquireReading, Reading } from "./CombineFunctionsIntoClass"; 2 | 3 | const rawReading = acquireReading(); 4 | const aReading = new Reading(rawReading); 5 | export const taxableCharge = aReading.taxableCharge; 6 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client2.test.js: -------------------------------------------------------------------------------- 1 | import { taxableCharge } from "./CombineFunctionsIntoClass-Client2"; 2 | 3 | describe("CombineFunctionsIntoClass-Client2.js", () => { 4 | it("taxableCharge", () => { 5 | expect(taxableCharge).to.equal(0.9); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client3.js: -------------------------------------------------------------------------------- 1 | import {acquireReading, Reading} from "./CombineFunctionsIntoClass"; 2 | 3 | const rawReading = acquireReading(); 4 | const aReading = new Reading(rawReading); 5 | 6 | export const basicChargeAmount = aReading.baseCharge; 7 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass-Client3.test.js: -------------------------------------------------------------------------------- 1 | import { basicChargeAmount } from "./CombineFunctionsIntoClass-Client3"; 2 | 3 | describe("CombineFunctionsIntoClass-Client3.js", () => { 4 | it("basicChargeAmount", () => { 5 | expect(basicChargeAmount).to.equal(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/CombineFunctionsIntoClass.js: -------------------------------------------------------------------------------- 1 | const reading = { customer: "ivan", quantity: 10, month: 5, year: 2017 }; 2 | 3 | export function acquireReading() { 4 | return reading; 5 | } 6 | 7 | //a dumb implementation just to make things to hang together 8 | export function baseRate(month, year) { 9 | if (year === 2017 && month === 5) return 0.1; 10 | return 0.2; 11 | } 12 | 13 | export function taxThreshold(year) { 14 | return 0.1; 15 | } 16 | 17 | export class Reading { 18 | constructor(data) { 19 | this._customer = data.customer; 20 | this._quantity = data.quantity; 21 | this._month = data.month; 22 | this._year = data.year; 23 | } 24 | 25 | get customer() { 26 | return this._customer; 27 | } 28 | get quantity() { 29 | return this._quantity; 30 | } 31 | get month() { 32 | return this._month; 33 | } 34 | get year() { 35 | return this._year; 36 | } 37 | 38 | get baseCharge() { 39 | return baseRate(this.month, this.year) * this.quantity; 40 | } 41 | 42 | get taxableCharge() { 43 | return Math.max(0, this.baseCharge - taxThreshold(reading.year)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/babel.config.json: -------------------------------------------------------------------------------- 1 | //ref:https://babeljs.io/docs/en/configuration 2 | { 3 | "presets": ["@babel/preset-env"] 4 | } 5 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/06_기본적인_리팩터링_SHUZAN/test/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 | -------------------------------------------------------------------------------- /06_기본적인-리팩터링/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/06_기본적인-리팩터링/a -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 07. 캡슐화 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 07 정리 문서](https://mwjjeongdev.notion.site/Chapter-07-eee6c1cafc924bb4bcc13f4219b90dfb) 6 | - [Chapter 07 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter07) 7 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-2-컬렉션캡슐화하기.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name) { 3 | this._name = name; 4 | this._course = []; 5 | } 6 | get name() { 7 | return this._name; 8 | } 9 | get course() { 10 | return this._course.slice(); // 복제본 제공하기 11 | } 12 | } 13 | 14 | class Course { 15 | constructor(name, isAdvanced) { 16 | this._name = name; 17 | this._isAdvanced = isAdvanced; 18 | } 19 | get name() { 20 | return this._name; 21 | } 22 | get isAdvanced() { 23 | return this._isAdvanced; 24 | } 25 | 26 | addCourse(course) { 27 | this._courses.push(course); 28 | } 29 | 30 | removeCourse( 31 | aCourse, 32 | fnIfAbsent = () => { 33 | throw new RangeError(); 34 | }, 35 | ) { 36 | const index = this._courses.indexOf(aCourse); 37 | if (index === -1) fnIfAbsent(); 38 | else this._courses.splice(index, 1); 39 | } 40 | } 41 | 42 | // Person이 제공하는 수업 컬렉션 수업 정보는 얻는다. 43 | const aPerson = new Person(); 44 | const numAdvancedCourses = aPerson.course.filter((c) => c.isAdvanced).length; 45 | 46 | /** 47 | * 필드가 접근자 메소드로 보호 받고 있으나 48 | * 세터를 이용해서 컬렉션을 수정 할 수 있다. 49 | * 하위 코드 방식으로 컬렉션을 마음대로 수정할 수 있다. 50 | * => 캡슐화된 Setter로 접근하도록 한다. 51 | */ 52 | const basicCourseNames = readBasicCourse(fileName); 53 | 54 | for (const name of basicCourseNames(filename)) { 55 | aPerson.addCourse(new Course(name, false)); 56 | } 57 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-4-임시변수를질의함수로바꾸기.js: -------------------------------------------------------------------------------- 1 | class Order { 2 | constructor(quantity, item) { 3 | this._quantity = quantity; 4 | this._item = item; 5 | } 6 | get price() { 7 | return this.basePrice * this.discountFactor; 8 | } 9 | get basePrice() { 10 | return this._quantity * this._item.price; 11 | } 12 | get discountFactor() { 13 | var discountFactor = 0.98; 14 | if (this.basePrice > 1000) discountFactor -= 0.03; 15 | return discountFactor; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-5-클래스추출하기.js: -------------------------------------------------------------------------------- 1 | export class Person { 2 | constructor() { 3 | this._telephoneNumber = new TelephoneNumber(areaCode, number); 4 | } 5 | 6 | get name() { 7 | return this._name; 8 | } 9 | set name(arg) { 10 | this._name = arg; 11 | } 12 | get telephoneNumber() { 13 | return this._telephoneNumber.toString(); 14 | } 15 | get officeAreaCode() { 16 | return this._telephoneNumber.areaCode; 17 | } 18 | set officeAreaCode(arg) { 19 | this._telephoneNumber.areaCode = arg; 20 | } 21 | get officeNumber() { 22 | return this._telephoneNumber.number; 23 | } 24 | set officeNumber(arg) { 25 | this._telephoneNumber.number = arg; 26 | } 27 | } 28 | 29 | class TelephoneNumber { 30 | constructor() {} 31 | 32 | get areaCode() { 33 | return this._areaCode; 34 | } 35 | set areaCode(arg) { 36 | this._areaCode = arg; 37 | } 38 | get number() { 39 | return this._number; 40 | } 41 | set number(arg) { 42 | this._number = arg; 43 | } 44 | get officeAreaCode() { 45 | return this._officeAreaCode; 46 | } 47 | set officeAreaCode(arg) { 48 | this._officeAreaCode = arg; 49 | } 50 | 51 | toString() { 52 | return `(${this.areaCode}) ${this.number}`; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-6-클래스인라인하기.js: -------------------------------------------------------------------------------- 1 | //인라인하기 전 (Before Refactor) 2 | { 3 | export class Person { 4 | constructor() { 5 | this._telephoneNumber = new TelephoneNumber(areaCode, number); 6 | } 7 | get officeAreaCode() { 8 | return this._telephoneNumber.areaCode; 9 | } 10 | set officeAreaCode(arg) { 11 | this._telephoneNumber.areaCode = arg; 12 | } 13 | get officeNumber() { 14 | return this._telephoneNumber.number; 15 | } 16 | set officeNumber(arg) { 17 | this._telephoneNumber.number = arg; 18 | } 19 | } 20 | 21 | class TelephoneNumber { 22 | constructor() {} 23 | 24 | get officeAreaCode() { 25 | return this._officeAreaCode; 26 | } 27 | set officeAreaCode(arg) { 28 | this._officeAreaCode = arg; 29 | } 30 | } 31 | } 32 | 33 | //인라인 후 (After Refactor) 34 | { 35 | export class Person { 36 | constructor() { 37 | this._telephoneNumber = new TelephoneNumber(areaCode, number); 38 | } 39 | get officeAreaCode() { 40 | return this._officeAreaCode; 41 | } 42 | set officeAreaCode(arg) { 43 | this._officeAreaCode = arg; 44 | } 45 | get officeNumber() { 46 | return this._officeNumber; 47 | } 48 | set officeNumber(arg) { 49 | this._officeNumber = arg; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-7-위임숨기기.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name) { 3 | this._name = name; 4 | } 5 | get name() { 6 | return this._name; 7 | } 8 | get department() { 9 | return this._department; 10 | } 11 | //위임 메소드를 통해서 의존성을 줄인다. 12 | get manager() { 13 | return this._department.manager; 14 | } 15 | set department(arg) { 16 | this._department = arg; 17 | } 18 | } 19 | 20 | class Departement { 21 | constructor() {} 22 | get chargeCode() { 23 | return this._chargeCode; 24 | } 25 | set chargeCode(arg) { 26 | this._chargeCode = arg; 27 | } 28 | get manager() { 29 | return this._manager; 30 | } 31 | set manager(arg) { 32 | return (this._manager = arg); 33 | } 34 | } 35 | 36 | const aPerson = new Person(); 37 | /** 38 | * 기존 읽기 코드에서 접근자 제거 39 | */ 40 | //const manager = aPerson.department.manager; 41 | 42 | const manager = aPerson.manager; 43 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-8-중개자제거하기.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 7.7 위임 숨기기에서 위임 메서드를 없애면 됨. 3 | */ 4 | 5 | class Person { 6 | constructor(name) { 7 | this._name = name; 8 | } 9 | get name() { 10 | return this._name; 11 | } 12 | get department() { 13 | return this._department; 14 | } 15 | 16 | set department(arg) { 17 | this._department = arg; 18 | } 19 | } 20 | 21 | class Departement { 22 | constructor() {} 23 | get chargeCode() { 24 | return this._chargeCode; 25 | } 26 | set chargeCode(arg) { 27 | this._chargeCode = arg; 28 | } 29 | get manager() { 30 | return this._manager; 31 | } 32 | set manager(arg) { 33 | return (this._manager = arg); 34 | } 35 | } 36 | 37 | const aPerson = new Person(); 38 | const manager = aPerson.department.manager; 39 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_SHUZAN/sample/7-9-알고리즘교체하기.js: -------------------------------------------------------------------------------- 1 | //알고리즘 교체 전 2 | { 3 | const foundPerson = (people) => { 4 | for (let i = 0; i < people.length; i++) { 5 | const person = people[i]; 6 | if (person === "Don") return "Don"; 7 | if (person === "Jhon") return "Jhon"; 8 | if (person === "Kent") return "Kent"; 9 | return ""; 10 | } 11 | }; 12 | } 13 | //알고리즘 교체 후 14 | { 15 | const foundPerson = (people) => { 16 | const candidates = ["Done", "Jhon", "Kent"]; 17 | return people.find((p) => cadidates.indexOf(p)) || ""; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_Tony/image/대단한것은_아니지만_엄청난것.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/07_캡슐화/07_캡슐화_Tony/image/대단한것은_아니지만_엄청난것.png -------------------------------------------------------------------------------- /07_캡슐화/07_캡슐화_Tony/image/완전하게_복사는_안되네.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/07_캡슐화/07_캡슐화_Tony/image/완전하게_복사는_안되네.png -------------------------------------------------------------------------------- /07_캡슐화/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/07_캡슐화/a -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/01-함수옮기기.ts: -------------------------------------------------------------------------------- 1 | { 2 | const trackSummary = (points: number[]): object => { 3 | //(내부로 함수 옮김) - 사용하는 컨텍스트 안으로 4 | const distance = (p1: number, p2: number): number => Math.abs(p1 - p2); 5 | const calculateTime = (): number => 10000; 6 | 7 | //(중첩 함수를 최상위로 올리기) 8 | //- const로 선언해서 function이 아닌 호이스팅으로 인한 메모리 참조가 불가하여, 미리 위에 선언해둠. 9 | //-> 최상위로 복사하면서 새로운 임시 이름 지어줌 10 | const calculateDistance = (points: number[]) => { 11 | let result = 0; 12 | for (let i = 1; i < points.length; i++) { 13 | result += distance(points[i - 1], points[i]); 14 | } 15 | return result; 16 | }; 17 | 18 | const totalTime: number = calculateTime(); //숫자 19 | const pace = totalTime / 60 / calculateDistance(points); 20 | 21 | return { 22 | time: totalTime, 23 | distance: calculateDistance(points), 24 | pace: pace, 25 | }; 26 | }; 27 | console.log(trackSummary([30, 250, 150, 550, 660])); 28 | } 29 | 30 | 31 | { 32 | 33 | 34 | 35 | 36 | } -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/01-함수옮기기2.ts: -------------------------------------------------------------------------------- 1 | class Account { 2 | public type: AccountType; 3 | public _daysOverdrawn: number; 4 | 5 | constructor(accountType: AccountType, daysOverdrawn: number) { 6 | this.type = accountType; 7 | this._daysOverdrawn = daysOverdrawn; 8 | } 9 | 10 | get bankCharge() { 11 | let result = 4.5; 12 | if (this._daysOverdrawn > 0) 13 | result += this.type.overdraftCharge(this.daysOverdrawn); //인라인하기 -> 위임 메소드 삭제가능 14 | return result; 15 | } 16 | 17 | overdraftCharge() { 18 | //위임메소드 - 정리된 메소드를 호출 19 | return this.type.overdraftCharge(this.daysOverdrawn); 20 | } 21 | 22 | get daysOverdrawn() { 23 | return this._daysOverdrawn; 24 | } 25 | } 26 | 27 | class AccountType { 28 | public _type: string; 29 | constructor(type: string) { 30 | this._type = type; 31 | } 32 | get isPremium() { 33 | return this._type === "Premium"; 34 | } 35 | //기능을 복사한 후 정리 36 | overdraftCharge(daysOverdrawn: number) { 37 | if (this.isPremium) { 38 | const baseCharge = 10; 39 | if (daysOverdrawn <= 7) return baseCharge; 40 | else return baseCharge + (daysOverdrawn - 7) * 0.85; 41 | } else return daysOverdrawn * 1.75; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /08_기능-이동/08_기능-이동_SHUZAN/sample/03-문장을함수로옮기기.js: -------------------------------------------------------------------------------- 1 | function renderPerson(person) { 2 | const result = []; 3 | result.push(`

${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 ["
", emitPhotoData(aPhoto), "
"].join("\n"); 11 | } 12 | 13 | //함수추출 + emitPhotoData 함수 인라인 + 함수이름바꾸기 14 | function emitPhotoData(p) { 15 | return [ 16 | `

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), '
'].join( 13 | '\n', 14 | ); 15 | } 16 | 17 | function emitPhotoData(aPhoto) { 18 | const result = []; 19 | 20 | result.push(`

위치: ${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 ['
', emitPhotoData(aPhoto), `
`].join('\n'); 13 | } 14 | 15 | function emitPhotoData(aPhoto) { 16 | return [ 17 | `

제목: ${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("
\n"); 12 | emitPhotoData(outStream, p); 13 | outStream.write("
\n"); 14 | }); 15 | } 16 | 17 | function emitPhotoData(outStream, photo) { 18 | 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 { 2 | private available: T[] = []; 3 | private allocated: T[] = []; 4 | 5 | get() { 6 | let result; 7 | if (this.isEmpty) { 8 | result = Resource.create(); 9 | this.allocated.push(result); 10 | } else { 11 | result = this.available.pop(); 12 | this.allocated.push(result); 13 | } 14 | return result; 15 | } 16 | 17 | isEmpty() { 18 | this.available.length === 0; 19 | } 20 | } 21 | 22 | class Resource { 23 | static create() { 24 | return new Resource(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /11_API-리팩터링/11_API_리팩터링_kiwi/README.md: -------------------------------------------------------------------------------- 1 | # Chapter11. API 리팩터링 2 | 3 | 4 | #### [학습 내용 정리 Notion Page](https://www.notion.so/Chapter-11-5b4498bfac774543a7a0d1b4952238bf) 5 | -------------------------------------------------------------------------------- /11_API-리팩터링/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/11_API-리팩터링/a -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/11.슈퍼클래스위임으로 바꾸기/after.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 물리적인 스크롤과 논리적인 카탈로그 아이템에는 차이가 있다. 3 | * 석화병 치료법 적어 놓은 스크롤은 사본이 여러개에도, 카탈로그 아이템은 하나이다. 1대 N의 관계다 4 | * */ 5 | class CatalogItem { 6 | get title(): any { 7 | return this._title; 8 | } 9 | 10 | get id(): any { 11 | return this._id; 12 | } 13 | 14 | hasTag(arg) { 15 | return this._tags.includes(arg) 16 | } 17 | 18 | 19 | private _id: any; 20 | private _title: any; 21 | private _tags: any; 22 | 23 | constructor(id, title, tags) { 24 | this._id = id; 25 | this._title = title; 26 | this._tags = tags 27 | } 28 | } 29 | 30 | /** 정기 세척 이력이 필요했다. */ 31 | class Scroll { 32 | private _lastCleaned: any; 33 | private readonly _catalogItem: CatalogItem; 34 | 35 | constructor(id, title, tags, dateLastCleaned) { 36 | this._catalogItem = new CatalogItem(id, title, tags); 37 | this._lastCleaned = dateLastCleaned; 38 | } 39 | 40 | get id() { 41 | return this._catalogItem.id; 42 | } 43 | get title() { 44 | return this._catalogItem.title; 45 | } 46 | 47 | hasTag(arg) { 48 | return this._catalogItem.hasTag(arg); 49 | } 50 | 51 | get catalogItem(): CatalogItem { 52 | return this._catalogItem; 53 | } 54 | 55 | needCleaning(targetDate){ 56 | const threshold = this.hasTag('revered' ) ? 700 : 1500; 57 | return this.daySinceLastCleaning(targetDate) > threshold 58 | } 59 | 60 | private daySinceLastCleaning(targetDate: any) { 61 | return this._lastCleaned.until(targetDate, 'DAYS') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/11.슈퍼클래스위임으로 바꾸기/before.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 고대 스크롤(두루마리) 보관 오뢔된 도서관에 컨설팅, 이미 카탈로그로 분류돼있고 id, title, tags 복수개로 구성되었다. 3 | * */ 4 | class CatalogItem { 5 | get title(): any { 6 | return this._title; 7 | } 8 | 9 | get id(): any { 10 | return this._id; 11 | } 12 | 13 | hasTag(arg) { 14 | return this._tags.includes(arg) 15 | } 16 | 17 | 18 | private _id: any; 19 | private _title: any; 20 | private _tags: any; 21 | 22 | constructor(id, title, tags) { 23 | this._id = id; 24 | this._title = title; 25 | this._tags = tags 26 | } 27 | } 28 | 29 | /** 정기 세척 이력이 필요했다. */ 30 | class Scroll extends CatalogItem { 31 | private _lastCleaned: any; 32 | constructor(id, title, tags, dateLastCleaned) { 33 | super(id, title, tags) 34 | this._lastCleaned = dateLastCleaned; 35 | } 36 | 37 | needCleaning(targetDate){ 38 | const threshold = this.hasTag('reverd' ) ? 700 : 1500; 39 | return this.daySinceLastCleaning(targetDate) > threshold 40 | } 41 | 42 | private daySinceLastCleaning(targetDate: any) { 43 | return this._lastCleaned.until(targetDate, 'DAYS') 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/1_메서드올리기/after.js: -------------------------------------------------------------------------------- 1 | class Party { 2 | constructor() { 3 | } 4 | 5 | get monthlyCost() { 6 | return 30; 7 | } 8 | 9 | 10 | /** 구현을 바로하면 좋겟지만 놓칠수도 있다*/ 11 | // get annualCost() { 12 | // return this.monthlyCost * 12; 13 | // } 14 | get annualCost(){ 15 | return new Error('구현하세요') 16 | } 17 | 18 | } 19 | 20 | 21 | class Employee extends Party {} 22 | 23 | class Department extends Party { 24 | } 25 | 26 | 27 | const employee = new Employee(); 28 | const department = new Department(); 29 | 30 | console.log( 31 | employee.annualCost, department.annualCost, 32 | employee.annualCost === department.annualCost ? '동일합니다' : '불일치합니다' 33 | ) -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/1_메서드올리기/before.js: -------------------------------------------------------------------------------- 1 | class Party { 2 | constructor() { 3 | } 4 | 5 | get monthlyCost() { 6 | return 30; 7 | } 8 | } 9 | 10 | 11 | class Employee extends Party { 12 | get annualCost() { 13 | return this.monthlyCost * 12; 14 | } 15 | } 16 | 17 | class Department extends Party { 18 | get annualCost() { 19 | return this.monthlyCost * 12; 20 | } 21 | } 22 | 23 | 24 | const employee = new Employee(); 25 | const department = new Department(); 26 | 27 | console.log( 28 | employee.annualCost, department.annualCost, 29 | employee.annualCost === department.annualCost ? '동일합니다' : '불일치합니다' 30 | ) -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/2.필드올리기/after.ts: -------------------------------------------------------------------------------- 1 | class Employee { 2 | protected name: string; 3 | constructor(name: string) { 4 | this.name = name 5 | } 6 | } 7 | 8 | class SalesPerson extends Employee { 9 | // constructor(name: string) { 10 | // super(name); 11 | // } 12 | get firstName(){ 13 | return this.name.charAt(0); 14 | } 15 | } 16 | 17 | class Engineer extends Employee{ 18 | readonly _skill: 'typescript' | 'java' | 'cpp' 19 | 20 | constructor(name: string, skill) { 21 | super(name); // 생성자 호출시 무조건 super 로 호출한다. 부모를 22 | this._skill = skill; 23 | } 24 | get lastName(){ 25 | return this.name.slice(1, this.name.length); 26 | } 27 | 28 | get skill(){ 29 | return this._skill; 30 | } 31 | } 32 | 33 | const salesPerson = new SalesPerson('lucid') 34 | const engineer = new Engineer('paul', 'typescript') 35 | 36 | console.log(engineer.lastName, salesPerson.firstName) -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/2.필드올리기/before.ts: -------------------------------------------------------------------------------- 1 | class Employee {} 2 | class SalesPerson extends Employee { 3 | private name: string; 4 | } 5 | 6 | class Engineer extends Employee{ 7 | private name: string; 8 | } 9 | 10 | const salesPerson = new SalesPerson() 11 | const engineer = new Engineer() -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/3.생성자_본문_올리기/after.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Employee { 4 | private _name: any; 5 | constructor(name) { 6 | this._name = name; 7 | } 8 | 9 | get name() { 10 | return this._name; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/3.생성자_본문_올리기/after2.ts: -------------------------------------------------------------------------------- 1 | class Employee { 2 | protected _name: string; 3 | constructor(name) { 4 | this._name = name; 5 | } 6 | get name() { 7 | return this._name; 8 | } 9 | get isPrivileged() { return true } 10 | 11 | /** 12 | * 하위에서 호출하고 테스트후 상위 클래스에 옮긴다. 13 | * */ 14 | protected finishConstruction(){ 15 | if (this.isPrivileged) this.assignCar(); 16 | } 17 | protected assignCar() {} 18 | 19 | 20 | } 21 | 22 | class Manager extends Employee { 23 | constructor(name, grade) { 24 | super(name); 25 | this._grade = grade; 26 | this.finishConstruction(); 27 | } 28 | 29 | /** 1. 생성자에서 메서드로 함수추출한다 */ 30 | // finishConstruction(){ 31 | // if (this.isPrivileged) this.assignCar(); 32 | // } 33 | 34 | get grade(): any { 35 | return this._grade; 36 | } 37 | 38 | set grade(value: any) { 39 | this._grade = value; 40 | } 41 | private _grade: any; 42 | 43 | } 44 | 45 | const employee = new Employee('hello'); 46 | 47 | // employee.assignCar('a') 호출이 안된다 하위클래스에서만 생성되게 할수 있다 48 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/3.생성자_본문_올리기/before.ts: -------------------------------------------------------------------------------- 1 | class Party {} 2 | 3 | class Employee extends Party { 4 | private _id: number; 5 | private _name: string; 6 | private _monthlyCost: number; 7 | 8 | constructor(name, id, monthlyCost) { 9 | super(); 10 | this._id = id; 11 | this._name = name; 12 | this._monthlyCost = monthlyCost 13 | } 14 | } 15 | 16 | class Department extends Party { 17 | private _name: string; 18 | private _staff: string; 19 | 20 | constructor(name, staff) { 21 | super(); 22 | this._name = name; 23 | this._staff = staff; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/3.생성자_본문_올리기/before2.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Employee { 4 | protected _name: string; 5 | constructor(name) { 6 | this._name = name; 7 | } 8 | get name() { 9 | return this._name; 10 | } 11 | get isPrivileged() { return true } 12 | 13 | protected assignCar() {} 14 | 15 | } 16 | 17 | class Manager extends Employee { 18 | constructor(name, grade) { 19 | super(name); 20 | this._grade = grade; 21 | /** 서브클래스에서 수행한다. */ 22 | if (this.isPrivileged) this.assignCar(); 23 | } 24 | 25 | get grade(): any { 26 | return this._grade; 27 | } 28 | 29 | set grade(value: any) { 30 | this._grade = value; 31 | } 32 | private _grade: any; 33 | 34 | } 35 | 36 | const employee = new Employee('hello'); 37 | 38 | // employee.assignCar('a') 호출이 안된다 하위클래스에서만 생성되게 할수 있다 39 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/6.타입코드를서브클래스로바꾸기/advance-before.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 간접 상속 3 | * 아르바이트와 정직원 > 직접 상속 방식 타입 코드 문제 대처할수 없음. 직원 유형 변경 기능 유지하고싶음. 4 | * */ 5 | class Employee { 6 | _type: 'engineer' | 'manager' | 'salesperson'; 7 | private _name: string; 8 | 9 | constructor(name, type) { 10 | this.validateType(type); 11 | this._name = name; 12 | this._type = type; 13 | } 14 | 15 | validateType(type) { 16 | if (['engineer', 'manager', 'salesperson'].indexOf(type) === -1) { 17 | return new Error(`${this._type}라는 직원 유형은 없습니다.`) 18 | } 19 | } 20 | get type() { return this._type } 21 | set type(arg){ this._type = arg } 22 | 23 | get capitalizedType(){ 24 | return this._type.charAt(0).toUpperCase() + this._type.substr(1).toUpperCase() 25 | } 26 | 27 | toString() { 28 | return `${this._name} (${this.capitalizedType})` 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/6.타입코드를서브클래스로바꾸기/after.ts: -------------------------------------------------------------------------------- 1 | type Type = "engineer" | "manager" | "salesperson"; 2 | 3 | class Employee { 4 | private readonly _name: string; 5 | private readonly _type: Type; 6 | 7 | constructor(name, type) { 8 | this.validateType(type); 9 | this._name = name; 10 | this._type = type; 11 | } 12 | validateType(arg){ 13 | if (['engineer','manager','salesperson'].indexOf(arg) === -1){ 14 | throw new Error(`${arg} 라는 직원 유형은 업슷빈다.`) 15 | } 16 | } 17 | getType(){ 18 | return this._type; 19 | } 20 | toString() { 21 | return `${this._name} (${this._type})` 22 | } 23 | 24 | } 25 | 26 | class Engineer extends Employee { 27 | getType(): Type { 28 | return 'engineer'; 29 | } 30 | } 31 | 32 | class Manager extends Employee{ 33 | getType(): "engineer" | "manager" | "salesperson" { 34 | return 'manager'; 35 | } 36 | } 37 | 38 | 39 | class SalesPerson extends Employee { 40 | getType(): "engineer" | "manager" | "salesperson" { 41 | return 'salesperson'; 42 | } 43 | } 44 | 45 | /** 생성자는 객체를 반환할수 있지만 선택 로직을 생성자에 넣으려 하면 필드 초기화와 로직이 꼬여서 엉망이 될수있다. 그러니 생성ㅅ자를 팩터리 함수로 46 | * 바꿔서 선택 로직을 담을 별도 장소를 마련한다. 47 | * */ 48 | // const createEmployee = (name, type) => new Employee(name,type); 49 | 50 | 51 | const createEmployee = (name, type: Type) => { 52 | switch (type){ 53 | case 'engineer': return new Engineer(name, type) 54 | case 'manager': return new Manager(name, type) 55 | case 'salesperson': return new SalesPerson(name, type) 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/6.타입코드를서브클래스로바꾸기/before.ts: -------------------------------------------------------------------------------- 1 | class Employee { 2 | private readonly _name: string; 3 | private readonly _type: 'engineer'|'manager'|'salesperson'; 4 | 5 | constructor(name, type) { 6 | this.validateType(type); 7 | this._name = name; 8 | this._type = type; 9 | } 10 | validateType(arg){ 11 | if (['engineer','manager','salesperson'].indexOf(arg) === -1){ 12 | throw new Error(`${arg} 라는 직원 유형은 업슷빈다.`) 13 | } 14 | } 15 | toString() { 16 | return `${this._name} (${this._type})` 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/7.서브클래스제거하기/after.ts: -------------------------------------------------------------------------------- 1 | class Person { 2 | private readonly _name: any; 3 | private _genderCode: any; 4 | /** 햇갈리니 기본 값 세팅을 해주는 방향으로 하기로 했다 */ 5 | 6 | constructor(name, genderCode: 'M' | 'F') { 7 | this._name = name; 8 | this._genderCode = genderCode; 9 | } 10 | 11 | get name(): any { 12 | return this._name; 13 | } 14 | 15 | get genderCode() { 16 | return 'X' 17 | } 18 | 19 | get isMale() { 20 | return 'M' === this.genderCode 21 | } 22 | } 23 | 24 | /** 서브 클래스가 너무 단순한일을 하니 존재할 필요학 없다 */ 25 | /* class Male extends Person {get genderCode() {return 'M';}*/ 26 | 27 | // class Female extends Person {get genderCode() {return 'F'}} 28 | 29 | const createPerson = (record) => { 30 | let person; 31 | switch (record.gender) { 32 | case 'M': 33 | person = new Person(record, 'M'); 34 | break; 35 | case 'F': 36 | person = new Person(record, 'F'); 37 | break; 38 | default: 39 | person = new Person(record, 'M'); 40 | } 41 | return person; 42 | } 43 | 44 | const loadFromInput = (data) => { 45 | const result = []; 46 | data.forEach(record => { 47 | const person = createPerson(record); 48 | result.push(person); 49 | }) 50 | return result; 51 | } 52 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/7.서브클래스제거하기/before.ts: -------------------------------------------------------------------------------- 1 | class Person { 2 | private _name: any; 3 | 4 | get name(): any { 5 | return this._name; 6 | } 7 | 8 | get genderCode() { 9 | return 'X' 10 | } 11 | 12 | constructor(name) { 13 | this._name = name; 14 | } 15 | } 16 | 17 | class Male extends Person { 18 | get genderCode() { 19 | return 'M'; 20 | } 21 | } 22 | 23 | class Female extends Person { 24 | get genderCode() { 25 | return 'F' 26 | } 27 | } 28 | 29 | 30 | 31 | /** 1. 무언가 영향을 주는것을바꾸려할때 먼저 현재의 표현을 캡슐화 하여 이 변화가 클라이언ㅇ트 코드에 주는 영향을 최소하하한다. 서브크래스 만들기를 캡슐화하는 방법은 32 | * 바로 생성자를 팩터리 함수로 바꾸기다. 지금의 예라면 팩터리를 만드는 방법은 여러가지 존재한다. 33 | * 34 | * */ 35 | 36 | /** 37 | * 가장 직관적이 방법은 팩터리 메서드를 생성자 하나당 하나씩 만드는 것이다. 38 | * 39 | * */ 40 | const createPerson = (name) => new Person(name); 41 | const createMale = (name) => new Male(name); 42 | const createFemale = (name) => new Female(name); 43 | 44 | /** 45 | * 실제로는 이렇게 생성될 가능성이 있다 46 | * */ 47 | 48 | const loadFromInput = (data) => { 49 | const result = []; 50 | data.forEach(record => { 51 | let person; 52 | switch (record.gender){ 53 | case 'M': person = new Male(record); break; 54 | case 'F': person = new Female(record); break; 55 | default: person = new Person(record); 56 | } 57 | result.push(person); 58 | }) 59 | return result; 60 | } -------------------------------------------------------------------------------- /12_상속-다루기/12_상속-다루기_lucid/8.슈퍼클래스호출하기/before.ts: -------------------------------------------------------------------------------- 1 | class Employee { 2 | private _id: any; 3 | private _name: any; 4 | private _monthlyCost: any; 5 | 6 | constructor(name, id, monthlyCost) { 7 | this._id = id; 8 | this._name = name; 9 | this._monthlyCost = monthlyCost; 10 | } 11 | 12 | get annualCost() { 13 | return this._monthlyCost 14 | } 15 | 16 | get id(): any { 17 | return this._id; 18 | } 19 | 20 | get name(): any { 21 | return this._name; 22 | } 23 | 24 | get monthlyCost(): any { 25 | return this._monthlyCost; 26 | } 27 | 28 | } 29 | 30 | class Department { 31 | private _name: any; 32 | private _staff: []; 33 | 34 | constructor(name, staff) { 35 | this._name = name; 36 | this._staff = staff 37 | } 38 | get staff(): any { 39 | return this._staff; 40 | } 41 | 42 | get name(): any { 43 | return this._name; 44 | } 45 | 46 | get totalMonthlyCost(){ 47 | return this.staff.map(e => e.monthlyCost).reduce((sum,cost) => sum + cost) 48 | } 49 | get headCount(){ 50 | return this.staff.length; 51 | } 52 | get totalAnnualCost(){ 53 | return this.totalMonthlyCost * 112; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /12_상속-다루기/12_상속_다루기_Minwoo.md: -------------------------------------------------------------------------------- 1 | # Chapter 12. 상속 다루기 2 | 3 | - 본 챕터는 책을 보면서 동시에 아래 문서에 첨부되어 있는 repository의 commit history를 따라가면서 보는 것이 효과적입니다. 4 | - 문서 작성의 편의성 및 가독성을 고려하여 아래 페이지에 별도로 작성합니다. 5 | - [Chapter 12 정리 문서](https://mwjjeongdev.notion.site/Chapter-12-e659c9a952f84cd7ada8e3464d32108d) 6 | - [Chapter 12 실습 Repository](https://github.com/mwjjeong/refactoring-python/tree/main/Chapter12) 7 | -------------------------------------------------------------------------------- /12_상속-다루기/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metacognition-Polymath/refactoring-2nd-edition/a5563e96eb51c1546253a1f4cd6a4aec732cea3e/12_상속-다루기/a -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/README.md: -------------------------------------------------------------------------------- 1 | # Chapter12. 상속 다루기 2 | 3 | 4 | ### [학습 내용 정리 Notion Page](https://hminn.notion.site/12-7aca1e95077648e7945d08f63a769514) 5 | -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/01.js: -------------------------------------------------------------------------------- 1 | /* 2 | - 두 서브클래스에서 같은 일을 수행하는 메서드가 존재하는 상황 3 | 4 | 1. 똑같은 동작을 하는 메서드인지 체크 5 | 2. 참조 필드 확인 및 슈퍼클래스에서도 호출, 참조가 가능한지 체크 6 | 3. 두 메서드의 시그니처를 통일해준다. (함수 선언 바꾸기) 7 | 4. 서브클래스 중 하나의 메서드를 복사해 슈퍼클래스에 붙여넣는다. 8 | 5. 서브클래스의 해당 메서드를 삭제하고 테스트한다. 9 | */ 10 | 11 | class SubclassResponsibilityError extends Error {} 12 | class Party { 13 | get monthlyCost() { 14 | throw new SubclassResponsibilityError(); 15 | } 16 | 17 | get annualCost() { 18 | return this.monthlyCost * 12 19 | } 20 | } 21 | 22 | class Employee extends Party { 23 | get monthlyCost() { 24 | return 10 25 | } 26 | } 27 | class Department extends Party { 28 | get monthlyCost() { 29 | return 20 30 | } 31 | } 32 | 33 | const e = new Employee() 34 | const d = new Department() 35 | console.log(e.annualCost, d.annualCost) -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/02.js: -------------------------------------------------------------------------------- 1 | class Employee { 2 | name 3 | } 4 | class SalesPerson extends Employee { 5 | } 6 | class Engineer extends Employee { 7 | } -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/03-1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. 공통 코드 찾기 (this.#name = name) 3 | * 2. 생성자 메서드에서의 대입문을 super() 호출 바래 아래로 옮긴다. 4 | * 3. 공통 코드를 슈퍼클래스로 옮기고, 슈퍼클래스 생성자에 매개변수로 건넨다. 5 | * 4. 테스트한다. 6 | */ 7 | 8 | class Party { 9 | #name 10 | constructor(name) { 11 | this.#name = name; 12 | } 13 | 14 | get name() { 15 | return this.#name 16 | } 17 | } 18 | 19 | export class Employee extends Party { 20 | #id 21 | #monthlyCost 22 | constructor(name, id, monthlyCost) { 23 | super(name); 24 | this.#id = id 25 | this.#monthlyCost = monthlyCost 26 | } 27 | get monthlyCost() { 28 | return this.#monthlyCost 29 | } 30 | } 31 | 32 | export class Department extends Party { 33 | #staff 34 | constructor(name, staff) { 35 | super(name) 36 | this.#staff = staff 37 | } 38 | get staff() { 39 | return this.#staff 40 | } 41 | } 42 | 43 | const d = new Department('dennie', 'good'); 44 | console.log(d.name, d.staff); -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/03-2.js: -------------------------------------------------------------------------------- 1 | class SubclassResponsibilityError extends Error {} 2 | 3 | class Employee { 4 | #name; 5 | #grade; 6 | constructor(name, grade) { 7 | this.#name = name; 8 | this.#grade = grade; 9 | } 10 | get isPrivileged() { 11 | throw new SubclassResponsibilityError(); 12 | } 13 | 14 | get grade() { 15 | return this.#grade; 16 | } 17 | 18 | assignCar() { 19 | console.log(this.#name, "car assigned"); 20 | } 21 | 22 | finishConstruction() { 23 | if (this.isPrivileged) this.assignCar(); 24 | } 25 | } 26 | 27 | class Manager extends Employee { 28 | constructor(name, grade) { 29 | super(name, grade); 30 | this.finishConstruction(); 31 | } 32 | get isPrivileged() { 33 | return this.grade > 4; 34 | } 35 | } 36 | 37 | class Producer extends Employee { 38 | constructor(name, grade) { 39 | super(name, grade); 40 | this.finishConstruction(); 41 | } 42 | get isPrivileged() { 43 | return this.grade > 5; 44 | } 45 | } 46 | 47 | const roy = new Employee("로이"); 48 | const jay = new Manager("제이", 4.1); 49 | const kay = new Producer("케이", 6); 50 | -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/07.js: -------------------------------------------------------------------------------- 1 | /** 2 | * - 서브클래스의 역할을 분석해보고 굳이 존재해야할 이유가 없다면 리팩터링을 적용하자. 3 | * - 바로 제거하지 말고 혹시라도 이 클래스들을 사용하는 클라이언트가 있는지 살펴봐야 한다. 4 | */ 5 | 6 | class Person { 7 | #name; 8 | #genderCode; 9 | constructor(name, genderCode) { 10 | this.#name = name; 11 | this.#genderCode = genderCode; 12 | } 13 | get name() { 14 | return this.#name; 15 | } 16 | get genderCode() { 17 | return this.#genderCode; 18 | } 19 | isMale() { 20 | return this.#genderCode === "M"; 21 | } 22 | } 23 | 24 | const createPerson = (record) => { 25 | switch (record.gender) { 26 | case "M": 27 | return new Person(record.name, "M"); 28 | case "F": 29 | return new Person(record.name, "F"); 30 | default: 31 | return new Person(record.name, "X"); 32 | } 33 | }; 34 | const loadFromInput = (data) => data.map((record) => createPerson(record)); 35 | const data = [ 36 | { name: "재남", gender: "M" }, 37 | { name: "지금", gender: "F" }, 38 | { name: "로이", gender: "M" }, 39 | ]; 40 | const males = loadFromInput(data) 41 | .filter((p) => !p.isMale()) 42 | .map((m) => m.name); 43 | console.log(males); 44 | -------------------------------------------------------------------------------- /12_상속-다루기/hyeonmin/src/08.js: -------------------------------------------------------------------------------- 1 | /** 2 | * - 공통된 기능을 사용하는 두 클래스에 대한 예제. 3 | * - 연간 비용과 월간 비용이라는 개념, 그리고 이름이 공통됨을 확인. 4 | * - 두 클래스로부터 슈퍼클래스를 추출하면 이 공통된 동작들을 더 명확하게 드러낼 수 있다. 5 | * 6 | * 1. 빈 슈퍼클래스를 만들고, 두 클래스가 이를 확장하도록 한다. 7 | * 2. 생성자 본문 올리기, 메서드 올리기, 필드 올리기를 차례로 적용 8 | * 3. 공통된 모든 동작들에 대해 일괄적으로 적용 9 | */ 10 | 11 | class Party { 12 | constructor(name) { 13 | this._name = name; 14 | } 15 | get name() { 16 | return this._name; 17 | } 18 | 19 | get annualCost() { 20 | return this.monthlyCost * 12; 21 | } 22 | } 23 | class Employee extends Party { 24 | #id; 25 | #monthlyCost; 26 | constructor(name, id, monthlyCost) { 27 | super(name); 28 | this.#id = id; 29 | this.#monthlyCost = monthlyCost; 30 | } 31 | get monthlyCost() { 32 | return this.#monthlyCost; 33 | } 34 | get id() { 35 | return this.#id; 36 | } 37 | } 38 | 39 | class Department extends Party { 40 | #staff; 41 | constructor(name, staff) { 42 | super(name); 43 | this.#staff = staff; 44 | } 45 | get staff() { 46 | return this.#staff; 47 | } 48 | get monthlyCost() { 49 | return this.#staff 50 | .map((e) => e.monthlyCost) 51 | .reduce((sum, cost) => sum + cost, 0); 52 | } 53 | get headCount() { 54 | return this.staff.length; 55 | } 56 | } 57 | 58 | const roy = new Employee("Roy", "123", 100); 59 | const jay = new Employee("Jay", "456", 200); 60 | const sales = new Department("Sales", [roy, jay]); 61 | 62 | console.log(roy.annualCost); 63 | console.log(jay.annualCost); 64 | console.log(sales.annualCost); 65 | --------------------------------------------------------------------------------