generated from muhandojeon/study-template
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,363 @@ | ||
## 들어가며 | ||
|
||
> 이번 장에서 기대할 수 있는 내용들 | ||
- 되돌릴 수 있는 의사 결정을 내리는 구체적인 방법 | ||
- 관계 없는 개념들을 분리하여 결합도를 낮추는 방법 | ||
- 이벤트를 관리하고, 이벤트에 반응하는 전략 | ||
- 결합도가 낮은 함수 파이프라인 기법 | ||
- 유연하고 바꾸기 쉬운 코드를 만들 수 있는 더 나은 대안 | ||
- 세부 사항을 완전히 코드 밖으로 옮기는 방법 | ||
|
||
<br /> | ||
|
||
## Topic 28 : 결합도 줄이기 | ||
|
||
소프트웨어의 구조는 유연해야 한다. 그리고 유연하려면 각각의 부품이 다른 부품에 가능한 한 조금만 연결되어야 한다. | ||
|
||
<br /> | ||
|
||
### 결합도가 낮은 코드가 바꾸기 쉽다 | ||
|
||
- 결합은 두 개의 코드 조각이 무언가를 공유하면 언제나 일어날 수 있다. | ||
- 다음과 같은 결합의 증상을 놓치지 않도록 주의한다. | ||
+ 관계 없는 모듈이나 라이브러리 간의 희한한 의존관계 | ||
+ 한 모듈의 '간단한' 수정이 이와 관계없는 모듈을 통해 시스템 전역으로 퍼져 나가거나 시스템의 다른 곳에서 무언가를 깨뜨리는 경우 | ||
+ 개발자가 수정하는 부분이 시스템에 어떤 영향을 미칠지 몰라 코드의 수정을 두려워하는 경우 | ||
+ 변경 사항에 누가 영향을 받는지 파악하고 있는 사람이 없어서 결국 모든 사람이 참석해야 하는 회의 | ||
|
||
<br /> | ||
|
||
### 묻지 말고 답하라(TDA; Tell, Don't Ask) | ||
|
||
- 다른 객체의 내부 상태에 따라 판단을 내리고 그 객체를 갱신해서는 안 된다. | ||
- 객체의 내부 상태를 묻는 것 | ||
+ 캡슐화의 장점이 사라진다. | ||
+ 구현에 대한 지식이 코드 여기저기로 퍼져 버린다. | ||
|
||
<br /> | ||
|
||
### 메서드 호출을 엮지 말라 | ||
|
||
- 애플리케이션에 있는 것은 모두 바뀌리라 생각해라. | ||
- 외부의 라이브러리 역시 불안정하다고 여겨라. | ||
|
||
<br /> | ||
|
||
### 파이프라인 | ||
|
||
- 파이프라인은 구현 세부사항을 숨기는 객체지향과 달리 구현과정을 공개한다. | ||
- 파이프라인이 결합을 하나도 만들지 않는 것은 아니다. | ||
- 파이프라인의 함수에서 반환하는 데이터는 반드시 다음 함수가 처리할 수 있는 형식이어야 한다. | ||
|
||
<br /> | ||
|
||
> 파이프라인에 대한 나의 생각 | ||
파이프라인의 예시로는 Promise가 생각났다. `.then()`, `.catch()`, `.finally()` Promise 객체의 정적 메서드는 또다른 Promise 객체를 반환하여 같은 동작을 이어나갈 수 있게 한다. 또는 Builder Pattern을 사용한 pipeline 방식도 최근에 접했는데 코드 구현은 다음과 같다. | ||
|
||
<br /> | ||
|
||
```java | ||
// 출처 : https://inpa.tistory.com/entry/GOF-💠-빌더Builder-패턴-끝판왕-정리 | ||
|
||
Student student = new StudentBuilder(2016120091) | ||
.setName("홍길동") | ||
.setGrade("freshman") | ||
.setPhoneNumber("010-5555-5555") | ||
.build(); | ||
|
||
|
||
class StudentBuilder { | ||
// 초기화 필수 멤버 | ||
private int id; | ||
|
||
// 초기화 선택적 멤버 | ||
private String name; | ||
private String grade; | ||
private String phoneNumber; | ||
|
||
// 필수 멤버는 빌더의 생성자를 통해 설정 | ||
public StudentBuilder(int id) { | ||
this.id = id; | ||
} | ||
|
||
// 나머지 선택 멤버는 메서드로 설정 | ||
public StudentBuilder name(String name) { | ||
this.name = name; | ||
return this; | ||
} | ||
|
||
... | ||
|
||
public Student build() { | ||
return new Student(id, name, grade, phoneNumber); | ||
} | ||
} | ||
``` | ||
|
||
이 빌더 패턴 역시 class 인스턴스의 메서드들이 this를 반환하며 계속 인스턴스 스스로를 이어갈 수 있도록 하는 방식인데, 다음 함수가 처리할 수 있는 형식이라는 점에서 내용의 맥락과 비슷하다고 느꼈다. | ||
|
||
<br /> | ||
|
||
### 글로벌화의 해악 | ||
|
||
- 어디서나 접근할 수 있는 데이터는 교묘하게 애플리케이션 컴포넌트 간의 결합을 만들어 낸다. | ||
- 전역 데이터의 구현을 변경할 때 시스템 코드 전체에 영향을 줄 수 있음음 분명하다. | ||
- 문제는 바꿔야 하는 곳을 모두 바꿨는지 확인하기 힘들다는 데 있다. | ||
|
||
<br /> | ||
|
||
### 코드 재사용과 전역 데이터 | ||
|
||
- 코드를 처음 작성하는 시점의 제 1 관심사가 코드 재사용이어서는 안 될 것 | ||
- 코드를 재사용할 수 있게 하려면 깨끗한 인터페이스를 만들고 나머지 코드와의 결합을 없애야 한다. | ||
- 코드가 전역 데이터를 사용한다면 나머지로부터 떼어 내기 힘들어질 것이다. | ||
|
||
<br /> | ||
|
||
> 전역 데이터에 대한 나의 생각 | ||
프론트엔드 개발자의 입장에서 생각했을 때 전역 데이터를 쓰지 않는 것이 좋은 이유는 리렌더링과 관련된 성능 저하 위주로만 생각했다. 그리고 컴포넌트 트리를 따라 내려가면서 props drilling이 너무 심해질 때 전역 데이터는 이것을 해결할 수 있는 좋은 대안이라고만 생각했다. | ||
|
||
<br /> | ||
|
||
하지만 생각해보면 전역 데이터를 피하는 것이 좋다는 이야기를 많이 들었는데 그 근거를 제대로 알지 못했다가, 어디서든 접근하고 변경할 수 있다는 것은 양날의 검이라는 사실, 특히 재사용과 유지보수에서 어려움이 있을 것이라는 것을 다시 느끼게 되었다. 앞으로는 보수적, 지역적으로 사용할 필요를 느꼈다. | ||
|
||
<br /> | ||
|
||
### 외부 리소스도 전역 데이터다 | ||
|
||
- 수정 가능한 외부 리소스는 모두 전역 데이터다. | ||
- 해법은 반드시 이 리소스들을 여러분이 작성하는 코드로 모두 감싸는 것이다. | ||
- **전역적이어야 할 만큼 중요하다면 API로 감싸라.** | ||
|
||
<br /> | ||
|
||
### 결국은 모두 ETC | ||
|
||
- 결합된 코드는 바꾸기 힘들다. | ||
- 코드의 한 곳을 바꾸면 다른 곳에 여파가 미칠 수 있다. | ||
- 직접적으로 아는 것만 다루는 부끄럼쟁이 코드를 계속 유지하라. | ||
|
||
<br /> | ||
|
||
## Topic 29 : 실세계를 갖고 저글링하기 | ||
|
||
- 우리가 작성하는 애플리케이션은 맡은 일을 어떻게든 수행해야 한다. | ||
- 비록 우리의 세계는 엉망이고 사람들은 생각을 자주 바꾸지만 말이다. | ||
|
||
<br /> | ||
|
||
### 이벤트 | ||
|
||
- 이벤트는 무언가 정보가 있다는 것을 의미한다. | ||
- 어떻게 이벤트에 잘 반응하는 애플리케이션을 만들 수 있을까? | ||
|
||
<br /> | ||
|
||
### 감시자 패턴 | ||
|
||
- 이벤트를 발생시키는 '감시대상(observable)'과 이벤트에 관심이 있는 클라이언트인 '감시자(observer)'로 구성된다. | ||
- 동작 방식 | ||
+ 감시자는 자신이 관심 있는 이벤트를 감시 대상에 등록한다. 호출될 함수의 참조도 등록할 때 함께 넘긴다. | ||
+ 해당 이벤트가 발생하면 감시 대상은 등록된 감시자 목록을 보며 함수들을 일일이 호출한다. 이때, 발생한 이벤트를 감시자 함수의 인자로 넘긴다. | ||
|
||
<br /> | ||
|
||
### 감시자 패턴의 문제 | ||
|
||
- 모든 감시자가 감시 대상에 등록을 해야 하기 때문에 결합이 생긴다. | ||
- 일반적으로 감시 대상이 콜백을 직접 호출하도록 구현하기 때문에 성능 병목이 될 수 있다. (동기 처리 특성상 콜백 실행을 마칠 때까지 감시 대상이 계속 기다려야 함) | ||
|
||
<br /> | ||
|
||
### 게시-구독 모델 | ||
|
||
- 게시-구독(Publish-Subscribe) 혹은 발행-구독 모델, 줄여서 pubsub이라고 부른다. | ||
- 감시자 패턴을 일반화한 것 | ||
- 감시자 모델의 결합도를 높이는 문제와 성능 문제도 해결 | ||
- 동작 방식 | ||
+ 구독자는 관심사를 하나 이상의 채널에 등록하고 게시자는 채널에 이벤트를 보낸다. | ||
+ 감시자 패턴과는 다르게 게시자와 구독자 사이의 통신은 코드 밖에서 일어난다. | ||
+ (아마 비동기적으로) | ||
|
||
<br /> | ||
|
||
### 게시-구독 모델의 장점 | ||
|
||
- 추가적인 결합 없이 비동기 이벤트 처리를 구현하기에 좋다. | ||
- 다른 기존 코드를 수정하지 않고 이벤트 처리 코드를 추가하거나 교체할 수 있다. | ||
- 애플리케이션이 작동하고 있는 도중에 작업이 가능할 수도 있다. | ||
|
||
<br /> | ||
|
||
### 게시-구독 모델의 단점 | ||
|
||
- 게시-구독 모델을 아주 많이 사용하는 시스템에서는 현재 어떤 일이 벌어지고 있는 지 파악하기가 힘들다. | ||
- 게시자가 메시지를 보내는 것을 확인했더라도 어떤 구독자가 메시지를 처리하는지 바로 이어서 볼 수 없다. | ||
|
||
<br /> | ||
|
||
### 스트림 모델 | ||
|
||
- 이벤트를 이리저리 연결하는 것은 쉽지 않다. | ||
- 스트림은 이벤트를 일반적인 자료 구조처럼 다룰 수 있게 해준다. | ||
- 익숙한 방식으로 스트림을 다룰 수 있어서 좋다. 우리가 아는 온갖 작업을 일반적인 자료 구조와 마찬가지 방법으로 할 수 있다. | ||
- 이벤트 스트림과 일반 자료 구조를 조합할 수 있다. | ||
- Rxjs가 그 예시 | ||
- 이벤트 스트림은 동기적 처리와 비동기적 처리를 하나의 편리한 공통 API로 감싸서 통합한다. | ||
|
||
<br /> | ||
|
||
### 이벤트를 다룰 때 | ||
|
||
- 이벤트는 모든 곳에 있다. | ||
- 이벤트가 어디서 발생하든 이벤트를 중심으로 공들여 만든 코드는 일직선으로 수행되는 코드보다 더 잘 반응하고 결합도가 더 낮다. | ||
|
||
<br /> | ||
|
||
## Topic 30 : 변환 프로그래밍 | ||
|
||
- 프로그램이란 입력을 출력으로 바꾸는 것이라는 사고방식으로 돌아갈 필요가 있다. | ||
- 구조는 명확해지고 더 일관적으로 오류를 처리하게 되어 결합도 대폭 줄어들 것이다. | ||
|
||
<br /> | ||
|
||
### 변환 찾기 | ||
|
||
- 요구사항에서 시작하는 게 변환을 찾는 가장 쉬운 방법 | ||
- 요구사항의 입력과 출력이 무엇인지 찾아라. | ||
- 그러면 전체 프로그램을 나타내는 함수가 정해진다. | ||
- 이제 더 작은 단계로 나누어 각각을 찾아간다. | ||
- (일종의 top-down 접근 방식) | ||
- (위의 파이프라인과 연결) | ||
|
||
<br /> | ||
|
||
### 객체 지향 프로그래밍과의 비교 | ||
|
||
- 객체 지향 프로그래밍 | ||
+ 객체 지향 프로그래밍 경험이 많다면 반사적으로 데이터를 숨기고, 객체 안에 캡슐화해야 한다고 느낄 것이다. | ||
+ 이런 객체들은 서로 이리저리 이야기하며 서로의 상태를 변경 | ||
+ 결합을 많이 만들어 내고, 결국 객체 지향 시스템이 바꾸기 어려워지는 큰 요인 | ||
|
||
- 변환 모델 | ||
+ 요구 사항을 달성하기 위해 필요한 것은 하나로 연결된 변환들 뿐 | ||
+ 데이터를 전체 시스템 여기저기의 작은 웅덩이에 흩어 놓는 대신, 데이터를 거대한 강으로, 흐름으로 생각 | ||
+ 데이터는 기능과 동등해진다. | ||
+ 데이터는 더 이상 특정한 함수들과 묶이지 않는다.(클래스와 달리) | ||
+ 어떤 함수든 매개 변수가 다른 함수의 출력 결과와 맞기만 하면 어디서나 사용하고 또 재사용할 수 있다. | ||
|
||
<br /> | ||
|
||
### 오류 처리는 어떻게 하나? | ||
|
||
- 연쇄 변환이 일직선으로만 이어진다면 어떻게 오류 검사에 필요한 조건부 논리를 추가할 수 있을까? | ||
- 공통적으로 사용하는 기본적인 관례가 있다. 바로 변환 사이에 값을 절대 날것으로 넘기지 않는 것 | ||
+ 래퍼(wrapper) 역할을 하는 자료 구조나 타입으로 값을 싸서 넘긴다. | ||
+ 이런 자료 구조나 타입은 안에 들어 있는 값이 유효한지를 추가로 알려 준다. | ||
+ 파이프라인의 각 함수들은 내부에서 특정 오류에 대해 확인하는 코드들을 내재하고, 파이프 진행 중 오류를 맞이하면 파이프라인 전체를 중단하고 오류를 반환한다. | ||
|
||
<br /> | ||
|
||
### 변환은 프로그래밍을 변환한다 | ||
|
||
- 코드를 일련의 (중첩된) 변환으로 생각하는 접근 방식은 프로그래밍을 해방시킨다. | ||
- 습관을 들이면 코드가 더 명확해지고, 함수는 짧아지며, 설계는 단순해진다. | ||
|
||
<br /> | ||
|
||
## Topic 31 : 상속세 | ||
|
||
> 상속을 사용하고 있다면 아마 여러분에게 필요한 것은 상속이 아닐 것이다. | ||
<br /> | ||
|
||
### 상속의 문제 | ||
|
||
- 상위 클래스를 수정했을 때 이를 상속하는 하위 클래스는 함께 망가질 수 있다.(결합이 많다.) | ||
- 계층 위에 계층을 덧붙이다 보면, 클래스 계층도는 엄청나게 복잡해진다. 이는 애플리케이션을 취약하게 만든다. | ||
- 더 나쁜 것은 다중 상속이다. 특정 클래스가 여러 다른 도메인의 상위 클래스가 될 수 있다면 추후 변경을 할 수 없는 지경이 된다. | ||
- (다행히도 이제는 많은 객체 지향 언어에서 다중 상속을 지원하지 않는다.) | ||
|
||
<br /> | ||
|
||
### 상속을 쓸 필요가 없게 해주는 기법 | ||
|
||
- 인터페이스와 프로토콜 | ||
+ 선언들은 아무런 코드를 만들지 않는다. | ||
+ 타입으로 사용할 수 있다. | ||
+ 해당 인터페이스를 구현하는 클래스라면 무엇이든 그 타입과 호환된다. | ||
+ 다형성은 인터페이스로 표현하는 것이 좋다. | ||
- 위임 | ||
- 믹스인과 트레이트 | ||
|
||
<br /> | ||
|
||
> 상속에 대한 나의 생각 | ||
프론트엔드 진영에서 상속이라 할만한 것은 interface가 interface를 상속하는 정도일 것 같다. 그런데 상속의 부모 인터페이스의 변경으로 인해 자식 인터페이스가 함께 영향을 받는 일도 비일비재했다. 하지만 이를 더 쪼개서 더 작은 인터페이스로 만들고 이를 상속하는 것은 조금 애매한 것 같다. | ||
|
||
<br /> | ||
|
||
그리고 위임과 믹스인, 트레이트는 도대체 뭘 말하는건지 모르겠다. 자바같은 객체 지향 언어는 조금 더 와닿을 수도 있을 것 같은데 아직까지는 상속을 그리 사용해보지는 않았다. 다만 다음에 클래스형으로 개발할 일이 있을 때 참고해 봐야겠다. 물론 그럴 일이 있을까 싶지만... | ||
|
||
<br /> | ||
|
||
## Topic 32 : 설정 | ||
|
||
나중에 바뀌리라 알고 있는 것, 소스 코드 본체 바깥에 표현할 수 있는 것을 찾아라. 그리고 설정 더미에 던져 넣어라. | ||
|
||
<br /> | ||
|
||
### 애플리케이션 외부에서 설정/관리해야 하는 값들 | ||
|
||
- 출시된 이후 바뀔 수도 있는 값 | ||
- 여러 환경에서 실행될 때 특정 환경에 한정된 값 | ||
- 여러 고객을 위해 실행될 때 특정 고객에 한정된 값 | ||
|
||
<br /> | ||
|
||
### 정적 설정 | ||
|
||
- 정보를 일반 파일로 관리할 때는 일반 텍스트 형식을 사용하는 추세(2021년 기준 YAML과 JSON이 가장 많이 사용) | ||
- 어떤 형태를 사용하든 애플리케이션에서는 설정을 자료 구조 형태로 불러온다. | ||
- 보통 처음 애플리케이션을 시작할 때 읽어올 것이다. | ||
- 흔히 이 자료 구조를 전역에서 접근할 수 있도록 하는데, 코드의 어느 부분에서든 설정 정보에 쉽게 접근할 수 있도록 하기 위해서일 것이다. | ||
- 이것은 추천하지 않으며, 대신 **설정 정보를 (얇은) API 뒤로 숨기는 걸 권한다.** | ||
- 그러면 설정을 표현하는 세부 사항으로부터 코드를 떼어 놓을 수 있다. | ||
|
||
<br /> | ||
|
||
### 서비스형 설정 | ||
|
||
- 우리는 일반 파일이나 데이터베이스가 아니라, 서비스 API 뒤에서 관리하는 것을 선호한다. | ||
- 장점 | ||
+ 여러 애플리케이션이 설정 정보를 공유할 수 있다. | ||
+ 인증과 접근 제어를 붙여서 애플리케이션마다 보이는 정보가 다르게 만들 수 있다. | ||
+ 여러 인스턴스에 걸쳐서 전체 설정을 한번에 바꿀 수 있다. | ||
+ 설정 데이터를 전용 UI로 관리할 수 있다. | ||
+ 설정 데이터를 동적으로 계속 바꿀 수 있다. | ||
- 설정 데이터를 동적으로 바꿀 수 있다는 점은 고가용성 애플리케이션을 만들 때 매우 중요하다. | ||
|
||
<br /> | ||
|
||
### 설정에 대한 당부 | ||
|
||
- 너무 지나치게 하지는 말라. | ||
- 게으름 때문에 결정을 내리지 않고 설정을 추가하여 사용자에게 미루지 말라. | ||
|
||
<br /> | ||
|
||
## 총평 | ||
|
||
전체적으로 이번 장에는 루비나 엘릭서 코드가 많아서 코드가 한눈에 들어오지는 않았다... 차라리 잘 써보지도 않은 자바 코드가 더 나을 것 같다고 느끼기도 했다. 한편 루비 코드는 github Page로 블로그를 만들 때 사용했던 jekyll 엔진 기반의 liquid 언어의 템플릿 코드와 비슷한 것 같다고 느끼긴 했다. (그래도 싫은 건 싫은 거다.) | ||
|
||
<br /> | ||
|
||
현재는 함수형으로 굳어진 프론트엔드 패러다임에 객체지향과 상속, 파이프라인과 관련된 내용들이 많아 조금은 백엔드나 더 low-level 개발에 적합한 조언들이 아니었나 싶기도 하다. | ||
|
||
<br /> | ||
|
||
그렇지만 특정한 설정을 외부로 옮겨서 설정을 용이하게 한다는 등 몇몇 팁들은 개인 개발에서라도 써먹어볼 수 있을 것 같다고 느꼈다. 예를 들면 현재 기획/개발이 진행중인 블로그 개발 프로젝트에서 특정 카테고리 포스트들을 모두 숨기고 LNB에 표시하지 않게 하려는 등의 기획을 하고 있는데 이걸 사용할 때 설정값들을 별도의 정적파일이나 데이터베이스에 따로 담아서 API로 호출할 수 있도록 하려고 생각했는데, 이 생각의 연장으로 사용해볼 수 있을 것 같다. |