- 소프트웨어 설계에 대한 실용적인 정의를 소개
- 계층형 설계를 이해하고 어떤 도움이 되는지 알아보자.
- 깨끗한 코드를 만들기 위해 함수를 추출하는 방법
- 계층을 나눠서 소프트웨어를 설계하면 왜 더 나은 생각을 할 수 있는지 알아보자.
코드를 만들고, 테스트하고, 유지보수하기 쉬운 프로그래밍 방법을 선택하기 위해 미적 감각을 사용하는 것. 결국 설계는 코드를 쉽게 만들고 테스트하고 유지보수하기 위해 하는 것이다.
계층형 설계는 소프트웨어를 계층으로 구성하는 기술이다. 추상화 정도에 따라 계층을 나누는데, 각 계층에 있는 함수는 바로 아래 계층에 있는 함수를 이용해 정의한다.
계층이라고 하니 네트워크의 OSI 7 계층이 생각난다. OSI 7 계층도 유사하에 위 아래로 근접한 계층 끼리만 소통을 한다.
: 전문가는 본인의 전문 분야를 잘 알지만, 설명은 잘 못한다. 그만큼 복잡하고 다양하기 때문에 그렇다.
- 함수 본문
- 길이
- 복잡성
- 구체화 단계
- 함수 호출
- 프로그래밍 언어의 기능 사용
- 계층 구조
- 화살표 길이
- 응집도
- 구체화 단계
- 함수 시그니처
- 함수명
- 인자 이름
- 인잣값
- 리턴값
- 조직화
- 새로운 함수를 어디에 놓을지 결정
- 함수를 다른 곳으로 이동
- 구현
- 구현 바꾸기
- 함수 추출하기
- 데이터 구조 바꾸기
- 변경
- 새 코드를 작성할 곳 선택하기
- 적절한 수준의 구체화 단계 결정하기
- 직접 구현
- 추상화 벽
- 작은 인터페이스
- 편리한 계층
이 중, 이번 챕터에서는 직접 구현을 다룬다.
넥타이 하나를 사면 무료로 넥타이 클립을 하나 주는 코드가 있다.
function freeTieClip(cart) {
let hasTie = false;
let hasTieClip = false;
for(let i = 0; i < cart.length; i++) {
let item = cart[i];
if(item.name === 'tie')
hasTie = true;
if(item.name === 'tie clip')
hasTieClip = true;
}
if(hasTie && !hasTieClip) {
let tieClip = make_item("tie clip", 0);
return add_item(cart, tieClip);
}
return cart;
}
이 함수에서 장바구니(cart)가 배열이라는 사실을 알아야 할까?
No, freeTieClip() 함수가 알 필요가 없는 구체적인 내용(함수)이다.
어떤 구현을 할 때, 기존의 함수를 활용하는게 베스트.
새로운 함수를 구현하기 전에, 기존의 함수를 나열해서 재사용할 수 있는 함수를 살펴보자.
책에서는 장바구니 관련 함수를 나열해보았지만, 장바구니에 제품이 있는지 확인하는 함수는 구현되어 있지 않았다.
새롭게 구현해보자.
function freeTieClip(cart) {
let hasTie = isInCart(cart, 'tie');
let hasTieClip = isInCart(cart, 'tie clip');
if(hasTie && !hasTieClip) {
let tieClip = make_item("tie clip", 0);
return add_item(cart, tieClip);
}
return cart;
}
function isInCart(cart, name) {
for(let i = 0; i < cart.length; i++) {
if(cart[i].name === name) return true
}
return false;
}
장바구니에 제품이 있는지 확인하는 함수를 생성하고, 바꿔보았다.
이제 freeTieClip()은 cart가 배열인지 몰라도 된다.
또한 함수 본문의 길이도 짧아져서 명확해지고, 모두 비슷한 구체화 수준에서 작동하고 있어서 읽기도 쉽다.
개인적으로 한 가지 더 고려해보고 싶은 점은 isInCart()를 생성하면서 cart를 기존보다 더 순회해야 한다는 점이다.
확인하는 아이템의 수가 n개 늘어난다면 그만큼 순회 횟수가 증가하게 된다.
어찌보면 처리량이 상대적으로 많아지기 때문에 꺼려할 수도 있을 것 같다.
다른 사람들은 어떻게 생각 하는지?
개인적으로는 장바구니의 아이템 수가 n, 확인하는 제품의 수가 m이면, O(nm)이라서 m이 그렇게 크지 않다면 속도면에서 큰 차이는 없을 것 같고 가독성에 투자를 하는게 좋아보인다. 하지만, m이 n만큼 커진다면 다른 기법이 필요해 보임.
기존의 함수나 필요한 함수를 리스트로 나열하는 방법도 있지만, 호출 그래프를 그려볼 수도 있다.
호출 그래프를 만들어 함수 호출을 시각화하면, 호출되는 함수들의 계층을 나누기 쉬워진다(코드를 보고 나열하는 것보다).
호출 그래프는 화살표로 함수 호출을 나타내고 계층에 따라 높이를 다르게 한다.
같은 계층에 있는 함수는 같은 목적을 가져야 한다.
호출되는 함수들의 계층이 다르다고 생각이 든다면, 추상화 수준이 다르다고 볼 수 있다.
freeTieClip()에서 배열 관련 함수를 추상화 하니 더이상 해당 함수에서는 cart가 배열인지 몰라도 된다.
반복문을 빼서 새로운 함수를 만들면 가독성이 좋아지고, 다이억램에서도 보기 쉬워진다. 새로운 함수를 만들는 것이기 때문에 추상화가 높아진다.
새 함수를 만들어 배열 인덱스를 직접 참조하는 부분을 없애보는 기억하세요 가 있었다.
function arrayGet(array, idx) {
return array[idx];
}
arrayGet()을 만든거에 대해 개인적으로 느낀 점? 배열 인덱스를 직접 참조한거랑 직접 참조하지 않고 함수를 만든거라 느낀점을 적으라 했는데, 굳이 나누지 않고 뭉뚱그려 얘기해보려함..
개인적으로는 굳이 함수로 만들어야 하나라는 생각이 들었다.
재사용성이 좋아졌나? 코드 라인만 늘어난 느낌
arrayGet()에 대한 테스트를 만들어줘야하나? 함수 내부에서 idx의 범위를 확인하거나 array인지 확인하는 것도 아니라서 굳이?
계층에 차이가 있나? 함수로 만들었다고 해서 계층이 높아졌다고 볼 수 있는가?
책에서 이야기했던 array인지 아닌지 모르고 사용할 수 있어서 추상화가 좋아졌다고 얘기 하는 부분이 있었는데 array인지 숨기기 위해 함수를 만들어야 하나?
추상화의 정도가 비슷하다면 가독성이 높아지지만, 배열에 인덱스로 접근하는 문법을 함수로 감싼다고 가독성이 높아지는지도 의문.
- 직접 구현한 코드는 한 단계의 구체화 수준에 관한 문제만 해결한다. : 코드가 서로 다른 구체화 단계에 있다면 읽기 어렵다. 직접 구현하면 코드를 읽기 위해 알아야 하는 구체화 단계의 범위를 줄일 수 있어 이해하기 쉬워진다.
- 계층형 설계는 특정 구체화 단계에 집중할 수 있게 도와준다.
- 호출 그래프는 구체화 단계에 대한 풍부한 단서를 보여준다. : 코드보다는 다이어그램으로 그리는 것이 설계를 개선하기 위한 단서를 찾기 쉽다.
- 함수를 추출하면 더 일반적인 함수로 만들 수 있다. : 함수가 더 구체적인 내용을 다루지 않도록 일반적인 함수로 빼내는 것이다.
- 일반적인 함수가 많을수록 재사용또한 좋다. '중복 코드'를 찾아 함수를 빼내는 것과는 다르다. 일반적인 함수가 구체적인 함수보다 더 많은 곳에서 쓰일 수 있기 때문에 사용할 곳을 따로 찾지 않아도 재사용할 수 있는 곳을 발견할 수 있을 것이다.
- 복잡성을 감추지 않는다. 명확하지 않은 코드를 감추기 위해 헬퍼 함수(아마 arrayGet()을 의미하는듯)를 만들게 된다. 하지만 이렇게 하는 것은 계층형 설계가 아니다. 계층형 설계에서 모든 계층은 바로 아래 계층에 의존해야 하는데, 같은 계층에 의존하기 때문이다.
- 계층형 설계는 코드를 추상화 계층으로 구성한다. 각 계층을 볼 때 다른 계층에 구체적인 내용을 몰라도 된다.
- 문제 해결을 위한 함수를 구현할 때 어떤 구체화 단계로 쓸지 결정하는 것이 중요하다. 그래야 함수가 어떤 계층에 속할지 알 수 있다.
- 함수가 어떤 계층에 속할지 알려주는 요소는 많이 있다. 함수 이름과 본문, 호출 그래프 등이 그런 요소
- 함수 이름은 의도를 알려 준다. 비슷한 목적의 이름을 가진 함수를 함께 묶을 수 있다.
- 함수 본문은 중요한 세부 사항을 알려준다. 함수 본문은 함수가 어떤 계층 구조에 있어야 하는지 알려준다.
- 호출 그래프로 구현이 직접적이지 않다는 것을 알 수 있다. 함수를 호출하는 화살표가 다양한 길이를 가지고 있다(함수 내 함수의 추상화가 다양하다)면 직접 구현되어 있지 않다는 신호이다.
- 직접 구현 패턴은 함수를 명확하고 아름답게 구현해 계층을 구성할 수 있도록 알려준다.