해당 포스트는
쏙쏙 들어오는 함수형 코딩
을 학습하며 필요한 내용을 정리한 포스트입니다.
🌈 9. 계층형 설계 2
이번 장에서 살펴볼 내용
코드를 모듈화하기 위해 추상화 벽을 만드는 법
좋은 인터페이스가 어떤 것이고, 어떻게 찾는지
설계가 이만하면 되었다고 할 수 있는 시점
왜 계층형 설계가 유지보수와 테스트, 재사용에 도움이 되는지 이해하기
💻 계층형 설계 패턴
🍳 패턴 1. 직접 구현
직접 구현된 함수를 읽을 때, 함수 시그니처가 나타내고 있는 문제를 함수 본문에서 적절한 구체화 수준에서 해결해야 한다.
🍳 패턴 2. 추상화 벽
계층형 설계를 할 때 어떤 계층에서는 세부 구현을 감추고 인터페이스를 제공한다.
🍳 패턴 3. 작은 인터페이스
비즈니스 개념을 나타내는 중요한 인터페이스는 작고 강력한 동작으로 구성하는 것이 좋다.
🍳 패턴 4. 편리한 계층
계층을 잘 나누고 각 계층이 하는 일을 잘 추상화 해야 한다.
💻 패턴 2: 추상화 벽
추상화 벽(abstraction barrier)이라고 하는 두 번째 패턴에 대해 알아보자.
추상화 벽은 여러 가지 문제를 해결한다. 그중 하나는 팀 간 책임을 명확하게 나누는 것이다.
💻 추상화 벽으로 구현을 감춘다
추상화 벽(abstraction barrier)은 세부 구현을 감춘 함수로 이루어진 계층이다.
추상화 벽에 있는 함수를 사용할 때는 구현을 전혀 몰라도 함수를 쓸 수 있다.
💻 세부적인 것을 감추는 것은 대칭적이다
대칭적이라는 것은 서로 신경쓰지 않아도 된다는 뜻이다.
즉, 독립적으로 일할 수 있다는 뜻이다.
추상화 벽은 흔하게 사용하는 라이브러리나 API와 비슷하다.
예를 들어,
날씨 어플을 만들기 위해 A회사에서 제공하는 기상 데이터 API를 사용한다고 생각해 보자.
A의 개발팀은 기상 데이터 서비스를 구현하는 역할을 한다.
A 개발팀은 날씨 어플에 신경 쓰지 않는다.
기상 데이터 API는 책임을 명확하게 나눠주는 추상화 벽과 같다고 할 수 있다.
개발팀은 추상화 벽의 한계를 시험하기 위해 장바구니 데이터 구조를 변경하려고 한다.
추상화 벽이 잘 동작한다면 마케팅팀 코드를 바꾸지 않아도 되기 때문에 알려줄 필요가 없다.
💻 장바구니 데이터 구조 바꾸기
배열을 순서대로 검색하는 것은 비효율적이다.
해시 맵을 사용하는 것이 확실한 방법이고, 자바스크립트에서는 객체를 사용하면 됩니다
🔻 배열 기반 장바구니 코드
1
2
3
4
5
6
7
8
9
10
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
36
37
38
39
40
41
function add_item(cart, item) {
return add_element_last(cart, item);
}
function calc_total(cart) {
let total = 0;
for(let i = 0; i < cart.length; i++) {
let item = cart[i];
total += item.price;
}
return total;
}
function setPriceByName(cart, name, price) {
let cartCopy = cart.slice();
for(let i = 0; i < cartCopy.length; i++) {
if(cartCopy[i].name === name)
cartCopy[i] = setPrice(cartCopy[i], price);
}
return cartCopy;
}
function remove_item_by_name(cart, name) {
let idx = indexOfItem(cart, name);
if(idx !== null)
return splice(cart, idx, 1);
return cart;
}
function indexOfItem(cart, name) {
for(let i = 0; i < cart.length; i++) {
// 배열을 순서대로 검색하는 것보다 해시 맵에서 찾는 것이 빠르다.
if(cart[i].name === name)
return i;
}
return null;
}
function isInCart(cart, name) {
return indexOfItem(cart, name) !== null;
}
💻 장바구니를 객체로 다시 만들기
장바구니를 자바스크립트 객체로 만들면 더 효율적이고 첫 번째 패턴인 직접 구현 패턴에 더 가깝다.
배열보다 객체가 어떤 위치에 추가하거나 삭제하기 좋다.
🔻 객체 기반 장바구니 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function add_item(cart, item) {
return objectSet(cart, item.name, item);
}
function calc_total(cart) {
let total = 0;
let names = Object.keys(cart);
for(let i = 0; i < names.length; i++) {
let item = cart[names[i]];
total += item.price;
}
return total;
}
function setPriceByName(cart, name, price) {
if(isInCart(cart, name)) {
let item = cart[name];
let copy = setPrice(item, price);
return objectSet(cart, name, copy);
} else {
let item = make_item(name, price);
return objectSet(cart, name, item);
}
}
function remove_item_by_name(cart, name) {
return objectDelete(cart, name);
}
function isInCart(cart, name) {
return cart.hasOwnProperty(name);
}
💻 추상화 벽이 있으면 구체적인 것을 신경 쓰지 않아도 된다
🍳 장바구니 동작을 쓰는 함수를 전부 바꾸지 않고 어떻게 데이터 구조를 바꿀 수 있었나요?
처음에는 장바구니에 제품을 담기 위해 배열을 사용했는데 비효율적인 것을 깨닫고, 장바구니를 조작하는 함수를 고쳐 사용하고 있는 데이터 구조를 완전히 바꾸었다.
그런데 마케팅팀은 코드를 고치지 않았다. 심지어 마케팅팀은 구조가 바뀌었는지도 모른다. 어떻게 한 것일까?
데이터 구조를 변경하기 위해 함수 다섯 개만 바꿀 수 있던 이유는 바꾼 함수가 추상화 벽에 있는 함수이기 때문이다.
추상화 벽은 “어떤 것을 신경 쓰지 않아도 되지?” 라는 말을 거창하게 표현한 개념
추상화 벽은 필요하지 않은 것은 무시할 수 있도록 간접적인 단계를 만든다.
💻 추상화 벽은 언제 사용하면 좋을까?
🍳 1. 쉽게 구현을 바꾸기 위해
구현에 대한 확신이 없는 경우 추상화 벽을 사용하면 구현을 간접적으로 사용할 수 있기 때문에 나중에 구현을 바꾸기 쉽다.
프로토타이핑과 같이 최선의 구현을 확신할 수 없는 작업에 유용하다.
🍳 2. 코드를 읽고 쓰기 쉽게 만들기 위해
추상화 벽을 사용하면 세부적인 것을 신경쓰지 않아도 된다.
때로는 구체적인 것이 버그를 만든다.
세부적인 것은 신경 쓰지 않고 쉽게 코드를 만들 수 있다.
🍳 3. 팀 간에 조율해야 할 것을 줄이기 위해
추상화 벽을 사용하면 코드를 수정해도 문제가 없기 때문에 각 팀에 관한 구체적인 내용을 서로 신경 쓰지 않아도 일할 수 있어 더 효율적이고 빠른 작업을 할 수 있다.
🍳 4. 주어진 문제에 집중하기 위해
해결하려는 문제의 구체적인 부분을 무시할 수 있어 복잡한 생각이나 고민해야할 구체적 문제를 줄일 수 있다. 그렇기에 코드의 실수를 줄일 수 있다.
💻 패턴 2 리뷰: 추상화 벽
추상화 벽은 강력한 패턴이다.
추상화 벽으로 벽 아래에 있는 코드와 위에 있는 코드의 의존성을 없앨 수 있다.
서로 신경 쓰지 않아도 되는 구체적인 것을 벽을 기준으로 나눠서 서로 의존하지 않게 한다.
모든 추상화는 서로 의존하지 않게 정의한다.
- 추상화 단계의 상위에 있는 코드와 하위에 있는 코드는 서로 의존하지 않게 정의한다.
- 추상화 단계의 모든 함수는 비슷한 세부 사항을 무시할 수 있도록 정의한다.
신경 쓰지 않아도 되는 것을 다루는 것이 추상화 벽의 핵심
- 어느 부분을 신경 쓰지 않도록 만들면 좋을까?
- 사람들이 몰라도 되면 좋은 것은 무엇일까?
- 어떤 함수들이 비슷한 세부 사항을 신경 쓰지 않아도 되는 함수일까?
💻 패턴 3: 작은 인터페이스
설계 감각을 키우기 위한 세 번째 패턴은 작은 인터페이스(minimal interface)이다.
작은 인터페이스 패턴은 새로운 코드를 추가할 위치에 관한 것이다.
인터페이스를 최소화하면 하위 계층에 불필요한 기능이 쓸데없이 커지는 것을 막을 수 있다.
🍳 마케팅팀에서 시계를 할인하려고 한다.
장바구니 총합이 $100 을 넘으면 10% 할인을 하려고 한다
1
2
3
4
5
if 장바구니 총합 > $100
and
장바구니에 시계가 있으면
then
시계를 10% 할인해준다.
🍳 시계 할인 마케팅을 구현하기 위한 두 가지 방법
방법 1: 추상화 벽에 만들기
추상화 벽 계층에 있으면 해시 맵 데이터 구조로 되어 있는 장바구니에 접근 가능하다.
하지만 같은 계층에 있는 함수는 사용 불가하다.
1
2
3
4
5
6
7
8
9
function getWatchDiscount(cart) {
let total = 0;
let names = Object.keys(cart);
for(let i = 0; i < cart.lengthl i++) {
let item = cart[names[i]];
total += item.price;
}
return total = > 100 && cart.hasOwnProperty("watch");
}
방법 2: 추상화 벽 위에 만들기
추상화 벽 위에 만들면 해시 데이터 구조를 직접 접근할 수 없다.
추상화 벽에 있는 함수를 사용하여 장바구니에 접근해야 한다.
1
2
3
4
5
6
function getWatchDiscount(cart) {
let total = calcTotal(cart);
let hasWatch = isInCart("watch");
return total > 100 && hasWatch;
}
🍳 추상화 벽 위에 있는 계층에 구현하는 것이 더 좋다.
시계 할인 마케팅 관련 코드는 두 번째 방법인 추상화 벽 위에 있는 계층에 만드는 것이 더 좋다.
직접 구현에 더 가까워 더 좋다.
첫 번째 방법은 시스템 하위 계층 코드가 늘어나므로 좋지 않다.
새로운 기능을 만들 때 하위 계층에 기능을 추가하거나 고치는 것보다 상위 계층에 만드는 것이 작은 인터페이스라고 할 수 있다.
작은 인터페이스 패턴을 사용하면 하위 계층을 고치지 않고 상위 계층에서 문제를 해결할 수 있다.
작은 인터페이스 패턴은 추상화 벽뿐만 아니라 모든 계층에 적용할 수 있는 패턴
작은 인터페이스 패턴을 사용하면 깨끗하고 단순하고 믿을 수 있는 인터페이스에 집중할 수 있다.
감춰진 코드의 나머지 부분을 대신하는 코드로 사용할 수 있다. 또 인터페이스가 많아져서 생기는 불필요한 변경이나 확장을 막아준다.
💻 패턴 3 리뷰: 작은 인터페이스
🍳 추상화 벽을 작게 만들어야 하는 이유?
1) 추상화 벽에 코드가 많을수록 구현이 변경되었을 때 고쳐야 할 것이 많아진다.
2) 추상화 벽에 있는 코드는 낮은 수준의 코드이기 때문에 더 많은 버그가 있을 수 있다.
3) 낮은 수준의 코드는 이해하기 더 어렵다
4) 추상화 벽에 코드가 많을수록 팀 간 조율해야 할 것도 많아진다.
5) 추상화 벽에 인터페이스가 많으면 알아야 할 것이 많아 사용하기 어렵다.
상위 계층에 어떤 함수를 만들 때 가능한 현재 계층에 있는 함수로 구현하는 것이 작은 인터페이스를 실천하는 방법이다.
앞에서 추상화 벽을 개선하는 데 작은 인터페이스를 사용했지만 사실 모든 계층에서 쓸 수 있다.
이상적인 계층은 더도 덜도 아닌 필요한 함수만 가지고 있어야 한다.
함수는 바뀌어도 안 되고 나중에 더 늘어나도 안된다.
계층이 가진 함수는 완전하고, 적고, 시간이 지나도 바뀌지 않아야 한다.
이것이 작은 인터페이스가 전체 계층에 사용되는 이상적인 모습니다.
이상적 모습을 목표로 하는 것보다 현실적으로 이 목표에 가려고 하는 노력이 더 중요하다.
💻 패턴 4: 편리한 계층
다른 패턴과 다르게 조금 더 현실적이고 실용적인 측면을 다루고 있다.
현실적으로 추상화 계층을 높고 강력하게 만드는 것은 어려우므로 시간적 여유 또한 없다.
편리한 계층은 언제 패턴을 적용하고 또 언제 멈춰야 하는지 실용적인 방법을 알려준다.
작업하는 코드가 편리하다고 느낀다면 설계를 멈춰도 된다.
구체적인 것을 너무 많이 알아야 하거나 코드가 지저분하다고 느껴지면 다시 패턴을 적용하자.
💻 그래프로 알 수 있는 코드에 대한 정보는 무엇이 있을까?
기능적 요구사항 - 소프트웨어가 정확히 해야 하는 일
비기능적 요구사항 - 테스트를 어떻게 해야 할 것인지, 재사용을 잘 할 수 있느지, 유지보수하기 어렵지 않은지와 같은 요구사항
🍳 호출 그래프의 구조는 세 가지 중요한 비기능적 요구사항을 꾸밈없이 보여준다.
- 유지보수성(maintainability) : 요구 사항이 바뀌었을 때 가장 쉽게 고칠 수 있는 코드는 어떤 코드인가.
- 가장 위에 있는 코드는 어디서도 호출하지 않으니 고치기 쉽다. 그러나 바뀌는 것이 많은 가장 높은 곳은 적게 유지하는 것이 좋다.
- 테스트성(testability) : 어떤 것을 테스트하는 것이 가장 중요할까.
- 위에 있는 코드를 테스트시 많은 코드를 확인할 수 있다. 다만 자주 바뀌기에 테스트가 오래 유효하지 않다.
- 가장 아래의 코드를 테스트시 많은 코드가 해당 코드에 의존하기에 유용하다.
- 하위 계층 코드를 테스트할수록 얻는 것이 더 오래간다.
- 재사용성(reusability) : 어떤 함수가 재사용하기 좋을까.
- 아래에 있는 코드는 의존하는 코드도 많고 쉽게 변하지 않으니 재사용하기 용이하다.
- 아래쪽으로 가리키는 화살표가 많은 함수는 재사용하기 어렵다.
호출 그래프에 함수 이름을 빼고 보면, 코드 위치를 통해 세 가지 중요한 비기능적 요구사항에 답할 수 있다.
💻 그래프의 가장 위에 있는 코드가 고치기 가장 쉽다
가장 아래에 있는 코드는 고치기 어렵다. 이 코드 위에 너무 많은 코드를 만들었기 때문이다.
코드를 적절한 위치에 두면 유지보수 비용을 많이 줄일 수 있다.
자주 바뀌는 코드는 그래프 위에 있을수록 쉽게 일할 수 있으나, 바뀌는 것이 많은 가장 높은 곳은 적게 유지하는 것이 좋다.
💻 아래에 있는 코드는 테스트가 중요하다
모든 것을 테스트할 수 없다면 장기적으로 좋은 결과를 얻기 위해 아래 있는 것을 테스트하는 것이 좋다.
가장 위에 있는 코드는 자주 바뀌므로 테스트가 오래가지 않는다. 아래 있는 것은 자주 바뀌지 않으므로 테스트도 자주 바뀌지 않는다.
테스트는 만들려면 시간이 걸리는 일이다. 또한 일을 가능한 한 효율적으로 해야 한다.
하위 계층 코드를 테스트할수록 얻은 것이 더 오래간다.
💻 아래에 있는 코드가 재사용하기 더 좋다
아래쪽으로 그래프를 확장할수록 표준 라이브러리처럼 재사용할 수 있는 코드가 많아진다.
계층형 설계 패턴을 적용하면 재사용 가능한 계층으로 코드를 만들 수 있다.
💻 요약: 그래프가 코드에 대해 알려주는 것
분류 | 유지 보수성 | 테스트 가능성 | 재사용성 |
---|---|---|---|
규칙 | 위로 연결된 것이 적은 함수가 바꾸기 쉽다. | 위쪽으로 많이 연결된 함수를 테스트하는 것이 더 가치있다. | 아래쪽에 함수가 적을수록 더 재사용하기 좋다. |
핵심 | 자주 바뀌는 코드는 가능한 위쪽에 있어야 한다. | 아래쪽에 있는 함수를 테스트하는 것이 위쪽에 있는 함수를 테스트하는 것보다 가치 있다. | 낮은 수준의 단계로 함수를 빼내면 재사용성이 더 높아진다. |
💻 결론 및 요약
🍳 결론
계층형 설계는 바로 아래 계층에 있는 함수로 현재 계층의 함수를 구현해 코드를 구성하는 기술이다.
비즈니스 요구를 해결하기 충분히 편리한 코드인지 아닌지는 직관을 따라야 한다.
🍳 요약
추상화 벽 패턴을 사용하면 세부적인 것을 완벽히 감출 수 있기 때문에 더 높은 차원에서 생각할 수 있다.
작은 인터페이스 패턴을 사용하면 완성된 인터페이스에 가깝게 계층을 만들 수 있다. 중요한 비즈니스 개념을 표현하는 인터페이스는 한번 잘 만들어 놓고 더 바뀌거나 늘어나지 않아야 한다.
편리한 계층 패턴을 이용하면 다른 패턴을 요구 사항에 맞게 사용할 수 있다. 패턴을 사용하다 보면 너무 과한 추상화를 할 수 있다. 패턴들은 요구 사항에 맞게 적용해야 한다.
호출 그래프 구조에서 규칙을 얻을 수 있다. 이 규칙으로 어떤 코드를 테스트하는 것이 가장 좋은지, 유지보수나 재사용하기 좋은 코드는 어디에 있는 코드인지 알 수 있다.
📚 레퍼런스
Normand, 김은민, and Normand, Eric. (쏙쏙 들어오는) 함수형 코딩 : 심플한 코드로 복잡한 소프트웨어 길들이기 / 에릭 노먼드 지음 ; 김은민 옮김 (2022). Print.