From ef872716460805c84d00bef47f725410c9796007 Mon Sep 17 00:00:00 2001 From: Hanwe Lee Date: Sat, 17 Jun 2023 22:13:35 +0900 Subject: [PATCH] 11, 12 --- .../README.md" | 241 ++++++++++++++++++ .../README.md" | 72 ++++++ .../README.md" | 34 +++ .../README.md" | 59 +++++ 4 files changed, 406 insertions(+) create mode 100644 "Topic 09. \354\244\221\353\263\265\354\235\230 \355\225\264\354\225\205/README.md" create mode 100644 "Topic 10. \354\247\201\352\265\220\354\204\261/README.md" create mode 100644 "Topic 11. \352\260\200\354\227\255\354\204\261/README.md" create mode 100644 "Topic 12. \354\230\210\352\264\221\355\203\204/README.md" diff --git "a/Topic 09. \354\244\221\353\263\265\354\235\230 \355\225\264\354\225\205/README.md" "b/Topic 09. \354\244\221\353\263\265\354\235\230 \355\225\264\354\225\205/README.md" new file mode 100644 index 0000000..406ec9c --- /dev/null +++ "b/Topic 09. \354\244\221\353\263\265\354\235\230 \355\225\264\354\225\205/README.md" @@ -0,0 +1,241 @@ +# Topic 09. DRY: 중복의 해악 +지식은 고정적이지 않음. 지식은 변화한다. +이런 불안정성은 유지 보수 모드에서 우리가 대부분의 시간을 쓸 거라는 뜻 +이유가 무엇이건, 유지 보수는 별개의 활동이 아니며 전체 개발 과정의 일상적인 부분이다. +문제는 유지보수 과정에서 명세와 프로세스, 개발하는 프로그램에 지식을 중복해서 넣기 쉽다는 것이다. 그렇게 된다면 유지보수의 악몽이 시작될 것이다. +이렇게 안하려면 DRY(Don't Reapeat Yourself)원칙을 따라보자. + +## DRY는 코드 밖에서도 +DRY원칙에 적용되는 것들은 소스 복붙뿐 아니라 지식의 중복, 의도의 중복에 대한것도 포함이다. 똑같은 개념을 다른 곳 두군데에서 표현하면 안 된다는 뜻이다. + +## 코드의 중복 + +```Ruby +def prit_balance(account) + printf "Debites: %10.2f\n", account.debits + printf "Credits: %10.2f\n", account.credits + if account.fees < 0 + printf "Fees: %10.2f-\n", account.fees + else + printf "Fees: %10.2f\n", account.fees + end + printf " ---------\n" + if account.balance < 0 + printf "Balance: %10.2f-\n", account.balance + else + printf "Balance: %10.2f\n", account.balance + end +end + +``` +> 중복이 많이 보인다. 개선해보자. +```Ruby + +def format_amount(value) + result = sprintf("%10.2f", value.abs) + if value < 0 + result + "-" + else + result + " " + end +end + +def prit_balance(account) + printf "Debites: %10.2f\n", account.debits + printf "Credits: %10.2f\n", account.credits + # if account.fees < 0 + # printf "Fees: %10.2f-\n", account.fees + # else + # printf "Fees: %10.2f\n", account.fees + # end + + printf "Fees: %s\n", format_amount(account.fees) + + + printf " ---------\n" + # if account.balance < 0 + # printf "Balance: %10.2f-\n", account.balance + # else + # printf "Balance: %10.2f\n", account.balance + # end + + printf "Balance: %s\n", format_amount(account.balance) +end + +``` + +-> + +```Ruby + +def format_amount(value) + result = sprintf("10.2f", value.abs) + if value < 0 + result + "-" + else + result + " " + end +end + +def prit_balance(account) + # printf "Debites: %10.2f\n", account.debits + printf "Debites: %s\n", format_amount(account.debits) + # printf "Credits: %10.2f\n", account.credits + printf "Credits: %s\n", format_amount(account.credits) + + printf "Fees: %s\n", format_amount(account.fees) + printf " ---------\n" + printf "Balance: %s\n", format_amount(account.balance) +end + +``` + +-> + +```Ruby + +def format_amount(value) + result = sprintf("10.2f", value.abs) + if value < 0 + result + "-" + else + result + " " + end +end + +def print_line(label, value) # 추가 + printf "%-9s%s\n", label, value +end + +def report_line(label, amount) # 추가 + print_line(label + ":", format_amount(amount)) +end + +def prit_balance(account) + # printf "Debites: %s\n", format_amount(account.debits) + report_line("Debites", account.debits) + # printf "Credits: %s\n", format_amount(account.credits) + report_line("Credits", account.credits) + # printf "Fees: %s\n", format_amount(account.fees) + report_line("Fees", account.fees) + # printf " ---------\n" + # printf "Balance: %s\n", format_amount(account.balance) + report_line("Balance", account.balance) +end + +``` + +## 모든 코드 중복이 지식의 중복은 아니다. + +```Ruby +def validate_age(value): + validate_type(value, :integer) + validate_min_integer(value, 1) + +def validate_quantity(value): + validate_type(value, :integer) + validate_min_integer(value, 1) +``` +> 두 함수가 동일하기 때문에 DRY 위반? ㅡㄹ렸다. 코드는 동일하지만 두 함수가 표현하는 지식은 다르다. 두 함수는 각각 서로 다른 것을 검증하고 있지만, 우연히 규칙이 같은 것뿐이다. 이것은 우연이지 중복이 아니다. + + +## 문서화 중복 +왜인지 모르겠지만 모든 함수에 주석을 달아야 한다는 미신이 생겨났다. +그런 사람들은 이런 코드를 작성한다. + +```Ruby +# 계좌의 수수료를 계산한다. +# +# * 반려된 수표 하나당 $20 +# * 계좌가 3일 넘게 마이너스이면 하루에 $10 부과 +# * 평균 잔고가 $2,000보다 높으면 수수료 50% 감면 + +def fees(a) + f = 0 + if a.returned_check_count > 0 + f += 20 * a.returned_check_count + end + if a.overdraft_days > 3 + f += 10 * a.overdraft_days + end + if a.average_balance > 2_000 + f /= 2 + end + f +end +``` +이 함수는 중복되어있다. 한 번은 코드로, 또 한 번은 주석으로. +클라이언트가 수수료를 바꾸면 우리는 두 군데를 함께 고쳐야 한다. 시간이 지남에 따라 주석과 코드의 내용이 서로 어긋나게 될 거라고 거의 확실히 장담할 수 있다. +함수 이름과 코드로 의도를 파악하도록 만들자 + +## 데이터의 DRY 위반 +```Java + +class Line { + Point start; + Point end; + double length; +} + +``` +시작과 끝이 있으면 길이도 있다. 길이는 계산되는 필드로 만드는 편이 낫다. + +```Java + +class Line { + Point start; + Point end; + double length() { return start.distanceTo(end); } +} + +``` +요렇게! + +```Java + +class Line { + private double length; + private Point start; + private Point end; + + public Line(Point start, Point end) { + this.start = start; + throw .end = end; + calculateLength(); + } + + // public + void setStart(Point p) { this.start = p ; calculateLength() } + void setEnd(Point p) { this.end = p ; calculateLength() } + + Point getStart() { return this.start; } + Point getEnd() { return this.end; } + + double getLength() { return this.length; } + + private void calculateLength() { + this.length = start.distanceTo(end); + } + +} + +``` + +## 표현상의 중복 +서드파티를 연동하면 한쪽에서 수정하면 다른 한쪽이 망가질 것이다. +이런 중복을 아예 피할 수는 없지만 다소 완화할 수는 있다. + +### 내부 API에서 생기는 중복 +언어나 기술에 중립적인 형식으로 내부 API를 정의할 수 있는 도구를 찾아보아라. 이런 도구는 일반적으로 문서와 Mock API, 기능 테스트를 생성해주고, API 클라이언트도 여러 가지 언어로 생성해준다. + +### 외부 API에서 생기는 중복 +공개 API를 OpenAPI 같은 형식으로 엄밀하게 문서화하는 경우가 많음. API도구를 사용해보자(포스트맨 같은거 말하는듯?) + +### 데이터 저장소와의 중복 +많은 데이터 저장소가 데이터 스키마 분석 기능을 제공한다. 이런 기능을 이용하면 데이터 저장소와 코드 간의 중복을 많이 제거할 수 있다. + + +## 개발자 간의 중복 +똑같은 일을 하는 코드가 우연히 중복으로 추가 될 수 있음. 알아차릭기 힘들다. +일일 스크럼을 하든 프로젝트의 사서를 지정하든 해라.. 코드리뷰도 좋음. + diff --git "a/Topic 10. \354\247\201\352\265\220\354\204\261/README.md" "b/Topic 10. \354\247\201\352\265\220\354\204\261/README.md" new file mode 100644 index 0000000..ddc75dd --- /dev/null +++ "b/Topic 10. \354\247\201\352\265\220\354\204\261/README.md" @@ -0,0 +1,72 @@ +# Topic 10. 직교성 + + +## 직교성이란 +독립성이나, 결합도 줄이기를 의미함. + +## 직교성의 장점 +컴포넌트들이 각기 격리 되어 있으면 어느 하나를 바꿀 떄 나머지 것들을 걱정하지 않아도 된다. +직교적인 시스템을 작성하면 두 가지 큰 장점이 있다. 바로 생산성 향상과 리스크 감소다. + +### 생산성 향상 + - 변화를 국소화해서 개발 시간과 테스트 시간이 줄어든다. + - 재사용을 촉진한다 + - 생산성 향상 + +### 리스크 감소 + - 감염된 코드가 격리되어 있다. + - 시스템이 잘 깨지지 않는다. + - 테스트를 설계하고 실행하기 쉽다. + - 업체나, 제품, 플랫폼에 덜 종속된다. + +## 설계 +시스템은 서로 협력하는 모듈의 집합으로 구성되어야 하고, 각 모듈은 다른 부분과 독립적인 기능을 구현해야 한다. +계층 구조는 직교적 시스템을 설계하는 강력한 방법이다. 각 계층은 자기 바로 밑에 있는 계층이 제공하는 추상화만을 사용하기 때문에, 다른 코드에 영향을 끼치지 않으면서 기반 구현들을 변경할 수 있게 되어 유연성이 높아짐 +![KakaoTalk_Photo_2023-06-15-16-17-12](https://github.com/WBBookStudy/ProgramProgrammingProgrammer/assets/60125719/0a64db9a-07a0-4c6b-bfe8-7591c9fe7756) +설계가 직교적인지 확인하는 손쉬운 방법은 컴포넌트들을 나누었을 때 스스로에게 물어보아라. '특정 기능에 대한 요구 사항을 대폭 변경해야 하는 경우 몇 개의 모듈이 영향을 받는가?' 정답은 하나여야 함. + +## 툴킷과 라이브러리 +외부에서 만든 툴킷이나 라이브러리를 도입할 때 시스템의 직교성을 해치지 않는지 주의 깊게 살펴보아라. + +## 코딩 +코드를 작성할 때마다 애플리케이션의 직교성을 떨어트릴 위험을 감수하는 셈이다. +직교성을 유지하기 위해 사용할 수 있는 몇 가지 기법이 있다. + + - 코드의 결합도를 줄여라 + - 전역 데이터를 피하라 + - 유사한 함수를 피하라 + +자신이 작성하는 코드를 항상 비판적으로 바라보는 습관을 길러라. 기회가 있을 때마다 코드의 구조와 직교성을 개선하기 위해 노력하라. + +## 테스트 +직교적으로 설계하고 구현한 시스템은 테스트하기 더 쉽다. 시스템 컴포넌트 간의 상호 작용이 형식을 잘 갖추고 있고 제한적이기 때문에 시스템 테스트 중 많은 부분을 개별 모듈 수준에서 수행할 수 있다. +단위 테스트가 나머지 시스템의 많은 부분들을 불러와야해서 힘들다면 결합도를 충분히 줄이지 못했다는 뜻이다. +버그 수정을 통해 어떤 한 부분만 수정하면 되었는가? 또 다른 버그가 생기지는 않았는가? 이 질문들에 제대로 답하려면 자동화도 적용을 해야한다. + +## 문서화 +놀랍게도 직교성은 문서에도 적용할 수 있다. +정말 직교적인 문서라면 내용 변화 없이 모양새를 완전히 바꿀 수 있다. + +## 직교적으로 살아가기 +직교성은 DRY 원칙과도 밀접한 관계가 있다. 당연한 말이겠지만 DRY원칙으로 무장하고 직교성 원칙을 충실히 적용한다면 개발하고 있는 시스템이 더 유연하고 이해하기 쉬워질 것이다. 디버깅, 테스트, 유지보수도 쉬워질 것이다. + + + + + + + + + + + + + + + + + + + + + diff --git "a/Topic 11. \352\260\200\354\227\255\354\204\261/README.md" "b/Topic 11. \352\260\200\354\227\255\354\204\261/README.md" new file mode 100644 index 0000000..8da6cbb --- /dev/null +++ "b/Topic 11. \352\260\200\354\227\255\354\204\261/README.md" @@ -0,0 +1,34 @@ +# Topic 11. 가역성 +엔지니어는 문제를 풀 때 단순한 하나의 해결책을 좋아하지만 세상엔 영원한 것은 없다. +중요한 결정이 많이 내려졌을 즈음엔 목표가 너무 작아져서 목표가 움직이거나, 바람의 방향이 바뀌거나, 도쿄의 나비가 날갯짓을 한다면 여러분의 겨냥은 빗나갈 것이다. + + +## 가역성 +이 책의 많은 주제는 유연하고 적용 가능한 소프트웨어를 만드는것. DRY원칙, 결합도 줄이기, 외부 설정 사용하기 등이 그런 예시 +되돌릴 수 없는 결정을 줄이자. 왜냐하면 우리가 프로젝트 초기에 늘 최선의 결정을 내리지 못하기 때문. + +## 유연한 아키텍처 +아키텍처, 배포, 외부 제품과의 통합 영역을 유연하기 유지하는 데에 관심을 기울이자. +우리가 할 수 있는건 바꾸기 쉽게 만드는 것이다. 외부의 API를 우리가 만든 추상화 계층으로 숨겨라. 우리의 코드를 여러 컴포넌트로 쪼개라. 결국에는 하나의 거대한 서버에 배포하게 되더라도, 이 방식이 거대한 단일 모듈 어플리케이션을 가져다 쪼개는 것보다 훨씬 쉽다. 그리고 **유행을 좇지 말라.** 누구도 어떤 미래가 펼쳐질지 알 수 없으며, 우리는 특히 더 그렇다. + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Topic 12. \354\230\210\352\264\221\355\203\204/README.md" "b/Topic 12. \354\230\210\352\264\221\355\203\204/README.md" new file mode 100644 index 0000000..b5527be --- /dev/null +++ "b/Topic 12. \354\230\210\352\264\221\355\203\204/README.md" @@ -0,0 +1,59 @@ +# Topic 12. 예광탄 + +움직이는 목표물을 맞히려면 실제 조건하에서 즉각적인 피드백을 받아야 한다. 우리는 이것을 시각적으로 묘사하기 위해 '예광탄 개발'이라는 말을 쓴다. + +## 어둠 속에서 빛을 내는 코드 +예광탄이 효과적인 까닭은 일반 탄환과 동일한 환경 및 제약 조건에서 발사되기 때문이다. +코딩에서 동일한 효과를 얻으려면 우리를 요구 사항으로부터 최종 시스템의 일부 측면까지 빨리, 눈에 보이게, 반복적으로 도달하게 해 줄 무언가를 찾아야 한다. +시스템을 정의하는 중요한 요구 사항을 찾아라. 의문이 드는 부분이나 가장 위험이 커 보이는 곳을 찾아라. 이런 부분의 코드를 가장 먼저 작성하도록 개발 우선순위를 정하라. +![KakaoTalk_Photo_2023-06-17-22-04-07](https://github.com/WBBookStudy/ProgramProgrammingProgrammer/assets/60125719/58e9b9f8-4b58-4579-af69-5c2face8d90d) +> 그림의 화살표는 해당 기능을 위해 구현해야 하는 코드의 경로를 뜻한다. 작동하게 만들려면 각 계층의 단색으로 칠해진 부분을 구현해야만 한다. 구불구불한 선 영역은 나중에 할 것이다. + +우리는 이들을 어떻게 통합할 수 있을지 걱정이 된다. 그러니 간단한 기능을 골라 통합을 한번 해 보는 것이다. +예광탄 개발 방법은 '프로젝트는 결코 끝나지 않는다.'는 견해와도 일맥상통한다. 예광탄 개발 방법은 점진적인 접근 방법이다. + +예광탄 코드 접근 방법에는 여러 장점이 있다. + - 사용자가 뭔가 작동하는 것을 일찍부터 보게 된다. + - 개발자가 들어가서 일할 수 있는 구조를 얻는다. 아무것도 쓰여 있지 않은 백지가 가장 채우기 힘든 법이다. + - 통합 작업을 수행할 기반이 생긴다. 한꺼번에 모든 것을 통합하려고 노력하는 대신 매일 통합할 수 있다. 더 빠르고 정확하게 디버깅하고 테스트할 수 있다. + - 보여줄 것이 생긴다. + - 진행 상황에 대해 더 정확하게 감을 잡을 수 있다. + +## 예광탄이 언제나 목표물을 맞히는 것은 아니다 +목표를 못맞출 수 있다. 그럴 경우 목표물에 맞을 때까지 조준을 옮겨야 한다. 이것이 핵심이다. + +## 예광탄 코드 대 프로토타이핑 +프로토타이핑과 다른점이 뭐가 있을까? +프로토 타입은 최종 시스템의 어떤 특정한 측면을 탐사해 보는 것이 목표다. 진짜 프로토타입 방식을 따른다면 프로토타입은 어떤 개념을 실험해 보느라 대충 끼워 맞추어 구현한 것이므로 모두 버려야한다. 그리고 실험 과정에서 얻은 교훈을 바탕으로 코드를 새로 작성한다. +예광탄 코드 접근 방법은 다른 종류의 문제를 푼다. 애플리케이션이 전체적으로 어떻게 연결되는지를 알고 싶다. 사용자에게 실제로 애플리케이션의 요소들이 어떻게 상호 작용하는지 보여주고 싶고, 개발자에게는 코드를 붙일 아키텍처 골격을 제시하고 싶다. 이떄 예광탄을 만들게 되면 사용자와 개발자에게 보여줄 프레임워크가 생긴다. +우리는 이 프레임워크에 자리만 만들어 두었던 루틴을 채워 나가면서 새로운 기능을 차근차근 추가한다. 하지만 프레임워크는 손대지 않고 그대로 남는다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +