바꾸기 쉬움
고친 사람 github-actions[bot]
바꾸기 쉬움은 시스템을 고칠 때 얼마나 적게 손대고 끝낼 수 있는지를 따집니다. 결제 수단 하나를 붙이는 데 파일 하나만 고치는 시스템은 바꾸기 쉽습니다. 같은 일에 열 군데를 고쳐야 하는 시스템은 바꾸기 어렵습니다. 설계를 고를 때 따지는 품질 속성 가운데 하나입니다.
쉽고 빠른 이해
바꾸기 쉬움은 변경 하나에 드는 수고가 얼마나 적은지를 봅니다. 할인 규칙 하나를 고치는데 할인 코드만 열면 되는 시스템이 바꾸기 쉬운 시스템입니다.
소프트웨어는 다 만든 뒤에도 계속 고칩니다. 고칠 때마다 여러 곳을 같이 손봐야 하면 기능 하나 붙이는 데 오래 걸립니다. 빠뜨린 곳에서 버그도 납니다.
어떻게 높이나:
- 한 가지 일은 한 곳에 모읍니다
- 서로를 속속들이 알지 못하게 떼어 놓습니다
- 자주 바뀔 곳을 미리 짚어 두고 거기에만 인터페이스나 설정 파일 같은 장치를 둡니다
대가도 있습니다. 떼어 놓으려고 중간 계층을 하나 더 두면 코드가 늡니다. 따라 읽기도 번거로워집니다. 올 줄 알았던 변경이 안 오면 그 수고는 헛일이 됩니다.
상세
선반을 나사로 조인 책장과 본드로 붙인 책장이 있습니다. 선반 한 칸의 높이를 바꾸려 할 때, 나사로 조인 책장은 나사 두 개만 풀면 됩니다. 본드로 붙인 책장은 거의 새로 짜야 합니다.
소프트웨어에서 이 차이를 가리키는 품질 속성이 바꾸기 쉬움입니다. 품질 속성은 시스템이 무엇을 하느냐가 아니라 얼마나 잘 해내느냐를 따지는 잣대입니다. 바꾸기 쉬움은 그중에서 변경 하나에 드는 비용을 봅니다. 비용은 손대는 코드의 양, 걸리는 시간, 고치다 새로 생기는 버그로 잽니다.
영어로는 modifiability 라고 부릅니다. 한국어 문서에서는 변경 용이성이나 수정 용이성이라고도 씁니다. 이 항목은 바꾸기 쉬움으로 통일합니다.
이 성질을 따지는 까닭은 소프트웨어가 오래 고쳐 쓰는 물건이기 때문입니다. 처음 만드는 기간보다 기능을 덧붙이고 규칙을 바꾸며 쓰는 기간이 훨씬 긴 경우가 흔합니다. 그래서 소프트웨어 아키텍처를 정할 때 바꾸기 쉬움을 성능이나 가용성과 나란히 놓고 따집니다.
이 절은 주문 시스템 하나를 예로 들어 봅니다. 먼저 어떤 변경이 오고 그 변경이 왜 여러 곳으로 번지는지 봅니다. 이어서 번지는 범위를 줄이는 설계 수단과 바꾸기 쉬움을 재는 법을 봅니다. 마지막으로 그 대가와 시간이 지나며 떨어지는 까닭을 보고, 헷갈리는 이웃 이름을 가릅니다.
시스템에 오는 변경
바꾸기 쉬움은 늘 「어떤 변경에 대해」 따집니다. 모든 변경에 똑같이 쉬운 시스템은 없습니다. 그래서 먼저 어떤 변경이 올지 짚어야 합니다.
주문 시스템에 흔히 오는 변경은 아래 넷입니다.
| 변경의 종류 | 주문 시스템의 예 |
|---|---|
| 기능을 덧붙인다 | 새 결제 수단을 받는다 |
| [[비즈니스 로직 | 업무 규칙]]을 바꾼다 |
| 의존하는 외부 요소를 갈아 끼운다 | 저장소를 다른 데이터베이스로 옮긴다 |
| 돌리는 환경을 옮긴다 | 사내 서버에서 클라우드로 옮긴다 |
표의 네 변경은 번지는 곳이 서로 다릅니다. 결제 수단 추가에 쉬운 설계가 데이터베이스 교체에는 어려울 수 있습니다. 설계하는 사람은 이 시스템에 자주 올 변경을 골라 그 변경을 쉽게 만듭니다.
변경이 번지는 까닭
한 곳을 고쳤는데 다른 곳까지 따라 고쳐야 하는 일이 있습니다. 고친 코드에 의존하던 코드가 있기 때문입니다. 이렇게 변경 하나가 다른 변경을 부르는 현상이 파급 효과입니다. 따라 고칠 곳을 하나라도 빠뜨리면 그 코드에서 버그가 납니다.
시스템의 코드는 보통 맡은 일에 따라 여러 덩어리로 나눕니다. 이 한 덩어리가 모듈입니다. 주문을 받는 코드는 주문 모듈에, 돈을 맞추는 코드는 정산 모듈에 두는 식입니다.
파급 효과가 얼마나 번질지는 모듈끼리 서로를 얼마나 아느냐에 달려 있습니다. 한 모듈이 다른 모듈의 내부 사정에 의존하는 정도를 결합도라고 합니다. 결합도가 높을수록 한쪽을 고칠 때 다른 쪽도 흔들립니다.
아래 그림은 결합도가 높을 때와 낮을 때를 견줍니다. 주문 테이블의 열 이름 하나를 바꾸는 경우입니다.
flowchart TD
subgraph strong["결합도 높음 · 세 모듈이 테이블을 직접 읽을 때"]
T1["주문 테이블 열 이름 변경"] --> A1["주문 모듈 수정"]
T1 --> B1["정산 모듈 수정"]
T1 --> C1["통계 모듈 수정"]
end
subgraph weak["결합도 낮음 · 저장소 모듈만 테이블을 알 때"]
T2["주문 테이블 열 이름 변경"] --> R2["저장소 모듈 수정"]
R2 -.- K2["주문 · 정산 · 통계는 손대지 않음"]
end
B1 ~~~ T2
결합도가 높은 위쪽은 세 모듈을 다 고칩니다. 아래쪽은 주문·정산·통계 모듈이 저장소 모듈에게 값을 달라고만 하므로 저장소 모듈 하나만 고치고 끝납니다.
모듈 안쪽도 봐야 합니다. 한 모듈 안의 코드가 한 가지 일로 모여 있는 정도를 응집도라고 합니다. 할인 규칙이 주문·정산·통계 모듈에 흩어져 있으면 응집도가 낮은 것입니다. 할인율을 바꿀 때 세 모듈을 다 열어야 합니다.
번지는 범위를 줄이는 설계 수단
바꾸기 쉬움을 높이는 설계는 파급 효과를 좁히는 일입니다. 흔히 쓰는 수단은 아래 넷입니다.
| 수단 | 하는 일 | 주문 시스템의 예 |
|---|---|---|
| 한 가지 일은 한 모듈에 모은다 | 변경이 한 모듈 안에서 끝난다 | 할인 규칙을 할인 모듈 하나에 둔다 |
| 속을 감추고 바깥에 내보일 메서드만 정한다 | 속을 바꿔도 부르는 쪽은 모른다 | 결제 방식을 결제 인터페이스 뒤에 감춘다 |
| 사이에 계층을 하나 둔다 | 양쪽이 서로를 직접 모른다 | 저장소 모듈이 테이블을 대신 읽는다 |
| 바뀔 값을 코드 밖으로 뺀다 | 코드를 안 고치고 값만 바꾼다 | 무료 배송 기준 금액을 설정 파일에 둔다 |
첫째 줄은 응집도를 높이는 수단입니다. 한 가지 일을 한 곳에 모으면 그 일이 바뀔 때 열어 볼 곳도 하나입니다.
둘째 줄은 정보 은닉입니다. 모듈이 바깥에 내보이는 약속과 안쪽의 처리 방식을 떼어 놓는 원칙입니다. 그래서 속을 바꿔도 부르는 쪽이 흔들리지 않습니다.
셋째 줄은 앞 그림의 저장소 모듈이 한 일입니다. 주문 모듈과 테이블 사이에 저장소 모듈을 끼우면 주문 모듈은 테이블을 몰라도 됩니다. 이렇게 사이에 끼워 역할을 나눈 코드 묶음 하나하나를 계층이라고 부릅니다.
넷째 줄은 자주 바뀌는 값을 코드 밖에 두는 수단입니다. 기준 금액이 코드 안에 박혀 있으면 값 하나 바꾸려고 다시 빌드하고 배포해야 합니다. 설정 파일에 두면 값만 바꿔 다시 띄우면 됩니다.
인터페이스로 감춘 결제
둘째 수단을 코드로 봅니다. 모듈이 바깥에 내보이는 약속, 곧 어떤 메서드를 어떤 모양으로 부를 수 있는지를 코드로 적은 것이 인터페이스입니다. 부르는 쪽은 이 약속만 보고 속은 보지 않습니다.
먼저 결제 방식마다 분기가 주문 코드 안에 들어 있는 모습입니다.
// 주문 코드가 결제 방식을 다 안다
if (type.equals("CARD")) {
payByCard(order);
} else if (type.equals("BANK")) {
payByBank(order);
}
이 코드에서는 결제 수단을 하나 붙일 때마다 주문 코드에 분기를 하나 더 넣어야 합니다. 결제와 상관없는 주문 코드가 결제 수단이 늘 때마다 바뀝니다.
다음은 결제 방식을 인터페이스 뒤에 감춘 모습입니다.
interface Payment {
void pay(Order order);
}
class CardPayment implements Payment {
public void pay(Order order) { ... }
}
// 주문 코드는 Payment 만 안다
payment.pay(order);
주문 코드는 이제 Payment 라는 약속만 압니다. 새 결제 수단은 Payment 를 따르는 클래스 하나를 더 만들어 붙입니다. 주문 코드는 한 줄도 안 바뀝니다. 기존 코드를 고치지 않고 새 코드를 덧붙여 넓히라는 원칙을 개방-폐쇄 원칙이라고 부릅니다.
바꾸기 쉬움을 재는 법
「바꾸기 쉽게 만들자」는 목표가 되지 못합니다. 어떤 변경이 얼마나 쉬워야 하는지가 빠져 있어서 다 만든 뒤에 맞췄는지 가릴 수 없습니다.
그래서 변경 하나를 골라 장면으로 적습니다. 「새 결제 수단을 붙일 때 결제 모듈 밖의 코드는 고치지 않는다. 개발자 한 명이 하루 안에 끝낸다」처럼 적습니다. 이렇게 무슨 일이 일어나고 시스템이 어떻게 반응해야 하는지 적은 문장을 품질 속성 시나리오라고 합니다.
장면을 적었으면 재는 값이 따라 나옵니다. 그 변경에 손댄 모듈 수, 손댄 파일 수, 걸린 시간, 고친 뒤 새로 나온 버그 수가 흔히 쓰는 값입니다. 결제 수단을 하나 붙여 보고 손댄 파일을 세면 맞췄는지 알 수 있습니다.
바꾸기 쉬움의 대가
바꾸기 쉬움을 들이는 수단에는 값이 붙습니다. 첫째 값은 성능입니다. 사이에 계층을 하나 둘 때마다 요청이 지나는 길이 한 단계 길어집니다.
둘째 값은 읽기 어려움입니다. 인터페이스 뒤로 감추면 코드를 따라 읽는 사람은 실제로 어느 클래스가 불리는지 한 번 더 찾아야 합니다. 계층이 늘수록 코드 줄 수도 늡니다.
셋째 값은 짐작이 빗나갈 때 치릅니다. 올 줄 알았던 변경이 끝내 안 오면 그 변경을 위해 들인 계층과 인터페이스는 짐만 됩니다. 필요해질 때까지 미리 만들지 말라는 원칙을 YAGNI(You Aren't Gonna Need It, 그건 필요 없을 것이다)라고 부릅니다. 이 셋째 값을 경계하는 원칙입니다.
그래서 바꾸기 쉬움은 시스템 전체에 고르게 들이지 않습니다. 자주 바뀌어 온 곳, 바뀔 것이 뻔한 곳에 골라서 들입니다. 하나를 얻으려고 다른 하나를 내주는 이런 관계가 트레이드오프입니다.
시간이 지나며 떨어지는 바꾸기 쉬움
바꾸기 쉬움은 한 번 갖추면 계속 가는 성질이 아닙니다. 급하게 고친 코드가 쌓이면 모듈 사이에 없던 의존이 생깁니다. 그만큼 결합도가 올라갑니다. 사용자는 못 느끼는 사이에 기능 하나를 붙이는 시간이 조금씩 길어집니다.
이렇게 나중에 고칠 거리로 미뤄 둔 설계의 흠을 기술 부채라고 부릅니다. 빚처럼 오래 둘수록 갚을 몫이 커집니다.
떨어진 바꾸기 쉬움을 되찾는 작업이 리팩터링입니다. 겉으로 하는 일은 바꾸지 않고 코드의 구조만 고칩니다. 흩어진 규칙을 한 모듈로 모으거나 직접 읽던 테이블을 저장소 모듈 뒤로 감추는 일이 여기에 듭니다.
헷갈리는 이웃 이름
바꾸기 쉬움 둘레에는 비슷하게 들리는 이름이 여럿 있습니다. 묻는 것이 서로 달라서 한 표로 가릅니다.
| 이름 | 묻는 것 |
|---|---|
| 바꾸기 쉬움 | 변경 하나에 드는 수고가 적은가 |
| 유지보수성 | 고쳐 가며 오래 쓰기 쉬운가. 읽고 이해하기 쉬움과 고친 뒤 확인하기 쉬움까지 묶는다 |
| 기능 확장성 | 새 기능을 덧붙이기 쉬운가 |
| 확장성 | 일이 늘 때 자원을 보태 버티는가 |
유지보수성은 바꾸기 쉬움보다 넓은 말입니다. 흔히 바꾸기 쉬움을 유지보수성의 한 갈래로 봅니다. 기능 확장성은 반대로 더 좁습니다. 여러 변경 가운데 덧붙이기 하나만 봅니다.
확장성은 이름만 비슷합니다. 보통은 서버를 늘려 더 많은 요청을 받는 성질을 가리킵니다. 한국어에서는 새 기능을 붙이기 쉬운 정도도 확장성이라고 부르는 일이 있어 헷갈립니다. 그 뜻은 기능 확장성으로 가려 부르면 섞이지 않습니다.
관련 항목
바꾸기 쉬움이 속하는 상위 분류
품질 속성 · 비기능 요구사항 · 소프트웨어 아키텍처 · 소프트웨어 공학
바꾸기 쉬움과 헷갈리는 이웃 성질
유지보수성 · 기능 확장성 · 확장성 · 테스트 용이성 · 이식성 · 재사용성
변경 비용을 가르는 설계 원칙
결합도 · 응집도 · 관심사 분리 · 정보 은닉 · 캡슐화 · 단일 책임 원칙 · 개방-폐쇄 원칙 · 의존성 역전 원칙 · SOLID
바꾸기 쉬움을 얻으려고 쓰는 설계 수단
모듈 · 인터페이스 · 계층 · 레이어드 · 의존성 주입 · 플러그인 아키텍처 · 설정 파일 · 기능 플래그 · 아키텍처 전술
바꾸기 쉬움이 떨어질 때 생기는 문제
파급 효과 · 산탄총 수술 · 기술 부채 · 스파게티 코드 · 빅 볼 오브 머드 · 분산 모놀리스
바꾸기 쉬움을 들일 때 치르는 값
성능 · 트레이드오프 · YAGNI · 과잉 설계
바꾸기 쉬움을 재고 되찾는 방법
품질 속성 시나리오 · 리팩터링 · 아키텍처 결정 기록 · ATAM
다른 이름: modifiability · 변경 용이성 · 수정 용이성