From 457f1bb1040a6d7f1e053bd41a7b38d02d507df3 Mon Sep 17 00:00:00 2001 From: Seongil-Shin Date: Fri, 2 Aug 2024 22:08:35 +0900 Subject: [PATCH] vault backup: 2024-08-02 22:08:35 --- ...0 \355\216\270\354\247\221\354\246\235.md" | 2 +- ...54\354\247\200\352\261\260\353\202\230.md" | 324 ++++++++++++++++++ 2 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 "_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-08-02-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 5\354\236\245 \352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230 \353\266\200\353\237\254\354\247\200\352\261\260\353\202\230.md" diff --git "a/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-07-28-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 4\354\236\245 \354\213\244\354\232\251\354\243\274\354\235\230 \355\216\270\354\247\221\354\246\235.md" "b/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-07-28-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 4\354\236\245 \354\213\244\354\232\251\354\243\274\354\235\230 \355\216\270\354\247\221\354\246\235.md" index 4563a87c0..a50b16b45 100644 --- "a/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-07-28-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 4\354\236\245 \354\213\244\354\232\251\354\243\274\354\235\230 \355\216\270\354\247\221\354\246\235.md" +++ "b/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-07-28-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 4\354\236\245 \354\213\244\354\232\251\354\243\274\354\235\230 \355\216\270\354\247\221\354\246\235.md" @@ -2,7 +2,7 @@ title: 실용주의프로그래머 4장 실용주의 편집증 author: 신성일 date: 2024-07-27 21:04:15 +0900 -categories: study +categories: study, 실용주의프로그래머 tags: - "#study" --- diff --git "a/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-08-02-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 5\354\236\245 \352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230 \353\266\200\353\237\254\354\247\200\352\261\260\353\202\230.md" "b/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-08-02-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 5\354\236\245 \352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230 \353\266\200\353\237\254\354\247\200\352\261\260\353\202\230.md" new file mode 100644 index 000000000..06302c005 --- /dev/null +++ "b/_posts/blog/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/2024-08-02-\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270 5\354\236\245 \352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230 \353\266\200\353\237\254\354\247\200\352\261\260\353\202\230.md" @@ -0,0 +1,324 @@ +--- +title: 실용주의프로그래머 5장 구부러지거나 부러지거나 +author: 신성일 +date: 2024-07-28 20:07:50 +0900 +categories: study, 실용주의프로그래머 +tags: + - "#study" +--- + +이번 장에서는 되돌릴 수 있는 의사결정을 내리는 구체적인 방법을 설명한다. + +- topic 28 : 결합도 줄이기 +- topic 29 : 실세계를 갖고 저글링하기. 이벤트에 반응하는 네 가지 서로 다른 전략 +- topic 30 : 변환 프로그래밍. 함수 파이프라인 +- topic 31 : 상속세. 유연하고 바꾸기 쉬운 코드를 만들 수 있는 대안 +- topic 32 : 설정. 세부사항을 코드 밖으로 옮기는 방법 + +## Topic 28 : 결합도 줄이기 + +코드에서 나타나는 결합의 증상 +- 관계없는 모듈이나 라이브러리 간의 희한한 의존 관계 +- 한 모듈의 간단한 수정이 이와 관계없는 모듈을 통해 시스템 전역으로 퍼져나가거나 시스템의 다른 곳에서 무언가를 꺠뜨리는 경우 +- 개발자가 수정하는 부분이 시스템에 어떤 영향을 미칠지 몰라 코드의 수정을 두려워하는 경우 +- 변경 사항에 누가 영향을 받는지 파악하고 있는 사람이 없어서 결국 모든 사람이 참석해야하는 회의 + +### 열차 사고 + +```c++ +public void applyDiscount(customer, order_id, discount) { + totals = customer.orders.find(order_id).getTotals(); + totals.grandTotal = totals.grandTotal - discount; + totals.discount = discount +} +``` + +이 코드를 지원하기 위해 앞으로 바꾸면 안되는 것들이 너무 많다. 기차의 객차가 연결되어있듯이 메서드나 속성들이 모두 연결되어있다. 이런 코드를 열차 코드라고 부른다. + +만약 할인율을 변경해야할때는 어떻게 해야할까? `totals` 객체의 필드는 어디서든 변경할 수 있다. 따라서 다른 곳에서 필드 값을 바꾸는 부분이 있다면 모두 변경해야한다. + +이것은 책임의 문제이다. `totals` 객체가 합계를 관리하는 책임을 져야하는데 그렇게 하고 있지 않다. 누구나 질의하고 갱신할 수 있는 다수의 필드를 가진 컨테이너일 뿐이기에 그렇다. + +이를 고치려면 다음 원칙을 적용해야한다. + +> 묻지말고 말하라 + +다른 객체의 내부 상태에 따라 판단을 내리고 그 객체를 갱신해서는 안된다. 즉, 할인 처리를 `totals` 객체에 위임해야한다. + +```c++ +public void applyDiscount(customer, order_id, discount) { + customer.orders.find(order_id).getTotals().applyDiscount(discount); +} +``` + +더 개선하면 `customer` 객체에서 주문 컬렉션을 가져오지 말아야한다. + +```c++ +public void applyDiscount(customer, order_id, discount) { + customer.findOrder(order_id).getTotals().applyDiscount(discount); +} +``` + +추가로 `order` 객체가 `totals` 객체를 가졌다는 걸 굳이 드러낼 필요도 없다. + +```c++ +public void applyDiscount(customer, order_id, discount) { + customer.findOrder(order_id).applyDiscount(discount); +} +``` + +여기서 더 숨길 수도 있지만, 이 앱에서는 `customer`와 `order`가 최상위 개념이다. 이정도는 드러내도 괜찮을 것이다. + +**데메테르 법칙** + +LoD(데메테르 법칙) : 어떤 클래스 C에 정의된 메서드는 다음 목록에 속하는 것만 사용할 수 있다. +- C의 다른 인스턴스 메서드 +- 메서드의 매개변수 +- 스택이나 힙에 자신에 생성하는 객체의 메서드 +- 전역 변수 + +다음 말이 좀 더 쉬울 수 있다. +> 메서드 호출을 엮지 말라 + +무언가를 접근할 때 `.`을 딱하나만 사용하도록 노력하라. 다만 엮는 것들이 절대로 바뀌지 않을 것 같다면 괜찮다. (언어에서 기본적으로 포함된 기능들. 외부 라이브러리는 아님) + +**연쇄와 파이프라인** + +함수에서 함수로 데이터를 넘겨가며 데이터를 변환하는 것은 열차사고와는 다르다. 숨겨진 구현 세부 사항에 의존하지 않기 때문이다. 하지만 결합이 생기지 않는 것은 아닌데, 한 함수의 결과물이 다음 함수의 입력 형식이어야하기 때문이다. 하지만 이러한 결합은 열차사고보단 문제가 되는 경우가 적다. + +### 글로벌화의 해악 + +전역 데이터는 애플리케이션 컴포넌트 간의 결합을 만들어낸다. 전역 데이터는 시스템 전체에 영향을 줄 수 있다. 변경될 때 바꿔야할 곳을 전부 바꿨는지 확인하기 어려울 수 있다. + +> 전역 데이터를 피하라. + +**싱글턴도 전역 데이터이다.** + +외부로 노출된 인스턴스 변수가 많은 싱글턴은 여전히 전역 데이터이다. 메서드 안으로 데이터를 숨기면 한층 낫긴하지만 설정데이터를 단 한 벌만 가지고 있는 것에는 변함이 없다. + +**외부 리소스도 전역 데이터다** + +수정 가능한 외부 리소스(DB, API 등)는 모두 전역 데이터다. 이러한 리소스들을 코드로 모두 감싸는 것이 좋다. + +> 전역적이어야 할 만큼 중요하다면 API로 감싸라. + +**필자 여담** + +전역 변수를 메서드로 감싸는 정도가 적절한 것 같음. `하지만 설정데이터를 단 한 벌만 가지고 있는 것에는 변함이 없다.`라는 대목이 있지만 잘 공감이 가지 않는 대목이다. + +### 상속은 결합을 늘린다. + +상속을 잘못 사용하면 결합만 늘어날 수 있다. + +### 결국은 모두 ETC + +직접적으로 아는 것만 다루는 부끄럼쟁이 코드를 계속 유지하라. 그러면 결합도를 크게 낮출 수 있다. + + +--- + +## Topic 29 : 실세계를 갖고 저글링하기 + +이번 장은 반응적인 애플리케이션을 작성하는 법을 다룬다. + +### 이벤트 + +이벤트는 무언가 정보가 있다는 것을 의미한다. 정보는 외부에서 올 수도 있고, 내부에서 생길 수도 있다. 어디에서 온 것이든 애플리케이션이 이런 이벤트에 반응하고 하는 일을 조절하도록 만들면, 실세계에서 더 잘 작동하는 애플리케이션을 만들 수 있다. + +이러한 애플리케이션을 만드는 다음 네가지 전략이 있다. +- 유한 상태 기계 +- 감시자 패턴 +- 게시 - 구독 +- 반응형 프로그래밍과 스트림 + +### 유한 상태기계 + +FSM은 작성해야하는 부분은 적지만, 한번 작성하면 이벤트를 어떻게 처리할지 한눈에 알기 쉽다. + + +### 감시자 패턴 + +감시자 패턴은 이벤트를 발생시키는 쪽인 감시대상과, 이벤트에 관심이 있는 감시자로 이루어진다. + +감시자는 자신이 관심 있는 이벤트를 감시대상으로 등록한다. 보통은 호출될 함수의 참조도 같이 넘긴다. 추후 해당 이벤트가 발생 시, 등록된 감시자 목록을 보며 함수들을 일일이 호출한다. + +감시자 패턴은 수십년간 잘 작동해왔지만 한가지 문제점이 있다. 바로 모든 감시자가 감시 대상에 등록해야하는 결합이 생긴다는 것이다. 또 감시 대상이 콜백을 직접 호출하기에 성능 병목이 생길 수 있다. 이러한 문제점은 `게시-구독`으로 해결한다. + +### 게시 - 구독 + +감시자 패턴을 일반화한 것으로 감시자 모델의 결합도를 높이는 문제와 성능 문제를 해결한다. + +게시-구독 모델은 `게시자`와 `구독자`가 있고, 채널로 연결된다. 채널은 코드 밖에 있으며, 게시자가 채널로 이벤트를 보내고, 구독자는 채널을 통해 이벤트를 받는다. 이는 비동기적으로 이루어진다. + +이러한 게시-구독 모델은 대부분의 클라우드 서비스가 제공하기에 직접 구현할 필요는 없다. 또한 추가적인 결합이 없어 기존 코드를 수정하지 않고 이벤트 처리코드를 추가하거나 교체할 수 있다. + +단점은 너무 많이 사용하는 시스템에서는 현재 어떤 일이 벌어지는지 파악하기가 어렵다는 것이다. 게시자가 이벤트를 보내고 어떤 구독자가 처리하는지 확인하기 어렵다. + +### 반응형 프로그래밍과 스트림 그리고 이벤트 + +반응형 프로그래밍 : 하나의 값이 바뀌면, 그 값을 사용하는 다른 값이 반응하는 것 +- ex) 엑셀, 리액트, 뷰 + +스트림 : 이벤트를 일반적인 자료구조처럼 다룰 수 있게 해줌. 이벤트의 리스트. 이벤트를 처리하고, 조합하고, 골라내는 등 일반적인 자료구조처럼 다룰 수 있음 +- 비동기적으로 작동할 수도 있다. + +반응형 이벤트의 표준은 https://reactivex.io/ 에서 정의했다. 이 사이트에서는 언어에 무관한 원칙들을 정의하고 몇가지 공통 구현사항을 문서화 했다. 이를 구현한 것으로는 자바스크립트 RxJS 등이 있다. + +**후기)** + +스트림 부분은 잘 이해가 안갔음. 사용해본적이 없어서 정확히 어떤 것에서 큰 장점이 있는지 모르겠음. 한번 예제라도 건드려 보는게 좋을거 같다 + +### 어디에나 이벤트가 있다 + +이벤트는 모든 곳에 있다. 하지만 이벤트가 어디서 발생하든 이벤트를 중심으로 공들여 만든 코드는 일직선으로 수행되는 코드보다 더 잘 반응하고 결합도가 낮다. + + +--- + +## Topic 30 : 변환 프로그래밍 + +모든 프로그램은 데이터를 변환한다. 코드에만 집중하여 핵심을 놓치지말고, 프로그램이란 입력을 출력으로 바꾸는 것이라고 생각하라. 이렇게하면 프로그램 구조는 명확해지고, 결합도 대폭 줄어들 것이다. + +> 프로그래밍은 코드에 관한 것이지만, 프로그램은 데이터에 관한 것이다. + +### 변환 찾기 + +요구사항에서 입력과 출력이 무엇인지 찾으면 전체 프로그램을 나타내는 함수가 정해짐. 이후에는 입력을 출력으로 바꾸는 단계를 차례대로 찾으면 됨. (top-down 방식) + +### 변환 모델의 좋은점 + +객체지향프로그래밍을 하다보면 데이터를 숨기고, 객체 안에 캡슐화해야한다고 생각한다. 이런 객체들이 서로 대화하며 서로의 상태를 변경하는데, 이런 방식은 결합을 많이 만들어내고, 이는 이후 유지보수가 어려운 코드를 만들게한다. + +> 상태를 쌓아놓지말고 전달하라. + +변환 모델은 데이터를 전체 시스템에 흩어놓는 대신, 데이터를 거대한 강으로 생각한다. 데이터는 기능과 동등해져서 파이프라인은 코드 -> 데이터 -> 코드 -> 데이터의 연속이다. 데이터는 특정한 함수들과 묶이지 않는다. 대신 우리 애플리케이션이 입력을 출력으로 바꾸어 나가는 진행 상황을 데이터로 자유롭게 표현하여 결합을 크게 줄일 수 있다. 어떤 함수든 매개변수가 다른 함수의 출력결과와 맞기만 하면 어디서나 재사용할 수 있다. + +ex) 보통 객체지향에서는 두번째처럼하는데, 두번째는 앞단계에서 반환하는 객체에 다음 함수가 구현되어있어야한다. 만약 새로운 요구사항이 생겼을때, 두번째는 해당 객체에서 함수를 변경해야하는데 다른 코드에 영향이 갈까 두려움이 생길 것이다. 첫번째 방식은 그냥 한단계를 추가하면 된다. + +- 데이터 변환 +```js +const content = File.read(fileName); +const lines = findMatchingLines(content, pattern); +const result = truncateLines(lines); +``` + +- 메서드 연결 +```js +const result = contentOf(fileName) + .findMatchingLines(pattern) + .truncateLines(); +``` + +### 오류 처리는 어떻게 하나? + +공통적으로 연쇄 변환 도중 변환 사이에 값을 날것으로 넘기지 않는 관례가 있다. Wrapper 역할을 하는 자료구조로 값을 싸서 넘긴다. + +이런 개념을 이용하여 오류 검사를 변환 안에서 하거나, 변환 바깥에서 할 수 있다. + +--- + +## Topic 31 : 상속세 + +### 코드를 공유하기 위해 상속을 쓸때의 문제 + +상속도 일종의 결합이다. 만약 최상위 클래스만 바꾸고 싶을때도 하위 클래스에서 어떻게 사용하고 있는지에따라 코드가 망가질 수 있다. 최상위 클래스의 담당자가 자기는 한 클래스의 변수명을 변경하였는데, 코드 전체가 망가지면 당황할 것이다. + +### 타입을 정의하기 위해 상속을 쓸때의 문제 + +상속을 타입을 정의하기 위해 사용하는 경우도 있다. 이때 클래스 계층도 같은 것을 사용할 수 있다. 하지만 클래스 사이의 미묘한 차이를 위해 계층을 계속 더하다보면 클래스 계층도는 매우 복잡해진다. + +다중 상속을 지원하지 않는 것도 문제가 될 수 있는데, 어떤 클래스를 제대로 모델링하려면 다중 상속이 필요해질 수 있기 때문이다. + +## 더 나은 대안 + +**인터페이스** + +자바의 인터페이스 같은 것을 사용할 수 있다. 인터페이스는 단순한 선언이고, 아무런 코드도 만들지 않으며 단순히 선언된 메서드를 구현해야한다고 지시할 뿐이다. 또한 이들을 타입으로 사용할 수 있고, 해당 인터페이스를 구현한 클래스라면 무엇이든 그 타입과 호환된다. + +> 다형성은 인터페이스로 표현하는 것이 좋다. + +```java +public interface Locatable { + Coordinate getLocation(); + boolean locationIsValid(); +} +``` + +**위임** + +상속을 사용하게 되면 딱 2개의 메서드만 필요해도 부모의 20개의 메서드가 한번에 딸려온다. 클래스가 자신의 인터페이스를 제어할 수 없게 되는 것이다. + +대신에 위임을 사용하면 클래스는 필요한 것만 노출할 수 있다. + +> 서비스에 위임하라. Has-A 가 Is-A보다 낫다. + + +**믹스인, 트레이트, 카테고리, 프로토콜 확장 등** + +언어마다 이름은 다르지만, 클래스나 객체에 상속을 사용하지 않고 새로운 기능을 추가하여 확장하고 싶을 떄 사용하는 기능이다. + +```ruby +mixin CommonFinders { + def find(id) {...} + def findAll() {...} +} + +class AccountRecord extends BasicRecord with CommonFinders +class OrderRecord extends BasicRecord with CommonFinders +``` + +> 믹스인으로 기능을 공유하라 + +예를들어 검증을 적용할 수 있는 여러 계층이 있을때, 각 상황에 맞는 전문화된 클래스를 믹스인을 사용하여 만들 수 있다. +```ruby +class AccountForCustomer extends Account with AccountValidations, AccountCustomerValidations + +class AccountForAdmin extends Account with AccountValidations, AccountAdminValidations +``` + +### 상속이 답인 경우는 드물다 + +- 인터페이스 +- 위임 +- 믹스인과 트레이트 + +상황에 따라 상속보다 더 나은 방법이 있을 것이다. 타입 정보를 교환하고 싶은건지, 기능을 더하고 싶은건지, 메서드를 공유하고 싶은건지에 따라 다르다. 우리의 목표는 의도를 가장 잘 드러내는 기법을 사용하는 것이다. 그리고 정글 전체를 끌어들이지 않도록 조심하다 + +--- + +## Topic 32 : 설정 + +프로그램 출시 이후 변경될 것이라 생각되는 값들은 프로그램 외부에서 관리하라. 이렇게하면 프로그램을 조정할 수 있게 된다. + +> 외부 설정으로 애플리케이션을 조정할 수 있게하라. + +소스 코드 본체 바깥에 표현할 수 있는 것을 찾아라. + +### 정적 설정 + +대부분의 프레임워크에서는 설정을 일반 파일이나 데이터베이스 테이블로 관리한다. 어떤 형태로 사용하든 설정을 자료구조 형태로 불러온다. 보통 처음 애플리케이션이 실행 될 때 읽어올 것이다. + +보통 이 설정을 전역에서 접근할 수 있도록 하는데, 그것보다는 API 뒤로 숨기는 것이 좋다. 그러면 설정을 표현하는 세부 사항으로부터 코드를 떼어놓을 수 있다 + +### 서비스형 설정 + +설정을 외부에서 관리하긴하지만, 일반 파일이나 데이터베이스가 아니라 서비스 API 뒤에서 관리하는 방식이다. 여기에는 몇가지 장점이 있다. +- 여러 애플리케이션이 설정 정보를 공유할 수 있다. +- 여러 인스턴스에 걸쳐서 전체 설정을 한번에 바꿀 수 있다. +- 설정 데이터를 전용 UI로 관리할 수 있다. +- 설정 데이터를 동적으로 계속 바꿀 수 있다. -> 새로이 빌드-배포가 필요없다. + +설정 정보를 바꾸기 위해 코드 빌드가 필요없는 방식이다. + +### 지나치게 하지는 말라 + +지나치게 모든 변수를 설정할 수 있게하지는 말라. + + +**여담** + +실무를 경험해본 결과 서비스형 설정이 확실히 좋았다. 이러한 값들은 빌드가 필요없이 바로 참조가 되어 이슈가 발생했을 경우 곧바로 대응을 할 수가 있기 때문이다. (가이드를 제대로 남긴다면 개발자한테까지 연락이 안오고 기획자가 알아서 수정도 할 수 있다. 개발자는 그동안 꿀잠을 잘 수 있다.) + +물론 스프링에서 properties 파일과 같은 정적 설정도 장점이 있다. 설정 정보의 성격에 따라 적절히 분리해서 관리하면 될 거 같다. +- 정적 설정 : 자주 변경되지 않지만 애플리케이션에 거쳐 많이 사용되거나 외부로 뺄 필요가 있어보이는 값들. 급하게 변경하지 않아도 이슈가 없는 값들 +- 서비스형 설정 : 자주 변경될거라 예상되는 값들. 급하게 변경이 발생할 수 있는 값들. \ No newline at end of file