1. +
소프트웨어 설계 악취: 기술 부채 관리 방법
엑셈 CTO I 박재호(jaypark@ex-em.com)
2. +
목차
n 기술 부채
n 설계 악취
n 설계 원칙
n 악취에 대한 사례 연구: 추상화, 캡슐화, 모듈화, 계층화
n 깨끗한 코드
3. +
프로그램이 진화함에 따라 유지보수 작업을 행하거나 복잡도를 줄이지 않는
이상 프로그램의 복잡도는 증가한다.
- 늘어나는 복잡도에 대한 레만의 법칙
4. +
부채
n 부채
n 미래의 부를 현재로 할인해서 당겨오는 행위로 인해 발생
n 부의 확장, 편의성 추구, 생존을 위한 지렛대로 사용할 수 있지만 기본적
으로는 최대한 작게 유지(risk!)
n 이자 발생
n 상환 기간
n 복리
n 변동 금리
n 인플레이션
5. +
설계 악취와 기술 부채
n 설계 악취의 정의
n 설계 품질에 부정적인 영향을 미치고 기본 설계 원칙을 위반하는 설계상
의 특정 구조
n 설계 구조에서 잠재적인 문제
n 기술 부채의 정의
n 잘못되거나 최적화되지 않은 설계 결정을 의식적이거나 무의식적으로 내
릴 경우 축적되는 부채다
n 워드 커닝햄(1992)이 처음으로 사용
n 누적된 이자를 갚지 못하면 파산 à 기술 부채를 해소하지 못하면 제품
파산
n 코드 부채, 설계 부채, 테스트 부채, 문서 부채
6. +
기술 부채가 등장하는 이유?
n 빨라진 제품 주기
n 경쟁사에 비해 더 빨리 더 저렴하게 시장에 신제품 출시 요구
n 설계 결정의 영향력을 적절히 평가할 기회나 시간적 여유 부족
n 지역적인 설계 결정의 집합이 구조적인 품질을 떨어뜨림 à 설계 부채 축
적
n 문제는 유지보수!
n 기술 부채가 눈에 보이지 않는 이유?
n 결함은 최종 사용자에게 직접 보임
n 기술 부채는 소프트웨어 시스템의 내부 품질에 영향을 미침 à 보이지 않
으므로 무시됨
7. +
기술 부채가 미치는 영향
n 기술 부채의 구성 요소
n 원금(꼼수나 지름길)과 이자(상환하지 않을 경우 치뤄야 할 대가)
n 기술 부채에서 복리로 붙는 이자 문제
n 소프트웨어에 가해지는 새로운 변경이 부채에 허덕이는 설계 구조와 결
합해 부채를 더 늘이기 때문 à 더 많은 꼼수 à 더 많은 부채
n 변경 비용이 기술 부채에 따라 기하급수적으로 증가함 à 기술 파산
n 개발팀의 사기 저하
n 부채를 짊어진 3류 제품을 개발하고픈 개발자는 아무도 없다!
n 단기간에 부채를 갚기란 쉽지 않기 때문
n 소프트웨어 변경을 시도할 경우 발생하는 불확실성과 위험에 직접 노출
9. +
기술 부채를 초래하는 원인
n 관리자, 아키텍트, 개발자들이 내린 엉뚱한 결정의 원인
n 일정 압력: 복사해붙이기
n 좋은/숙달된 설계자 부족: 악화(나쁜 관례)가 양화를 구축
n 설계 원칙의 응용 부족: 경험/인식 부족
n 설계 악취와 리펙터링에 대한 인식 부족
Programming == Googling stackoverflow?
10. +
기술 부채를 관리하는 방법
n 기술 부채에 대한 점진적인 자각
n 기술 부채의 감지와 상환
n 기술 부채의 누적 방지
11. +
설계 악취란?
n 설계가 잘못되었음을 알려주는 징표
n 악취에 신경을 써야 하는 이유?
n 소프트웨어 이해 가능성에 치명적인 문제를 일으킴
n 결함 수정과 개선을 위한 변경이 끊이지 않고 유지 보수 시간이 늘어남
n 논리적인 재사용이 불가함
n 안정성과 테스트 가능성이 점점 나빠짐
n 소프트웨어 설계가 소프트웨어 품질에 영향을 미침
n 설계가 잘못되면 악취가 발생!
12. +
악취가 영향을 미치는 품질 속성
n 소프트웨어 비기능 측면
n 이해 가능성
n 변경 가능성
n 확장 가능성
n 재사용 가능성
n 테스트 가능성
n 안정성
n 주의: 비기능은 소프트웨어의 내재된 속성이므로 개발자가 보증해
야 한다!
13. +
설계 악취를 일으키는 요인
n 설계 원칙 위반
n 부적절한 패턴 사용
n 언어 제약
n 객체지향에서 절차적인 사고 방식
n 점성
n 우수 관계와 우수 프로세스의 미준수
14. +
설계 악취를 일으키는 요인
n java.util 패키지 일부인 Calendar 클래스
n 실세계의 달력 기능을 추상화
n 날짜와 관련된 기능을 제공
n 하지만 시간 관련 기능도 제공
n 여기서 위반된 설계 원칙
n 추상화는 유일한 책임만 맡아야 함 à 추상화 원칙 위반
n 다면적인 추상화 악취 발생
사례(설계 원칙 위반)
15. +
설계 악취를 일으키는 요인
n JDK의 AbstractQueuedSynchronizer와
AbstractQueuedLongSynchronizer 클래스
n 두 클래스는 AbstractOwnableSynchronizer 에서 파생
n 원시 타입의 차이뿐!
n 여기서 위반된 설계 원칙
n 펙터링이 안 된 설계 계층 악취
n 문제) 자바에서는 제네릭에서 원시 타입을 지원하지 않음! à 필연적인
코드 중첩 발생
사례(언어 제약)
16. +
설계 악취를 일으키는 요인
n 소프트웨어 점성이란?
n 올바른 해법을 문제에 적용하는 과정에서 반드시 직면하는 저항(늘어나
는 노력과 시간)
n 꼼수는 시간과 노력을 적게 요구(낮은 저항)
n 환경 점성이란?
n 우수 관례를 따르기 위해 반드시 극복해야 할 소프트웨어 개발 환경으로
인한 ‘저항’
n 우수 관계를 따를 때 시간이 더 많이 걸리면 나쁜 관례를 선택!
n 개발 프로세스, 재사용 프로세스, 조직 요구 사항, 법적인 제약
사례(점성)
17. +
악취 분류에 기반한 설계 원칙
n 악취 맥락에서 위반이 일어난 설계 원칙을 인식할 필요가 있음
n 추상화
n 단순화와 일반화 기법을 사용해 엔티티의 단순화를 옹호함
n 단순화는 불필요한 세부 사항을 제거하며, 일반화는 공통적이며 중요한 특성을 파악
하고 명세함
n 캡슐화
n 추상화의 세부 구현과 변형을 숨기는 기법을 사용해 관심사 분리와 정보 은닉을 옹호
n 모듈화
n 지역화와 분해 기법을 사용해 응집력은 높고 결합력은 낮은 추상화 생성을 옹호
n 계층 구조
n 분류, 일반화, 대체, 배치와 같은 기법을 사용해 계층적인 추상화 구조 생성을 옹호
18. +
몇 가지 추가적인 설계 원칙(1)
n OCP(Open/Close Principle)
n 모듈은 확장에 대해 열려야 하지만 변경에 대해 닫혀야 마땅하다
n 모듈은 코드 변경 없이 새로운 요구 사항을 지원할 수 있어야 마땅하다
n 단일 책임 원칙(SRP, Single Responsibility Principle)
n 클래스가 변경되어야 하는 이유가 결코 둘 이상 있어서는 안 된다
n 각 책임은 변경 축이되므로, 각 변경은 단일 책임에만 영향을 미쳐야 마
땅하다
n LSP(LISKOV’S SUBSTITUTION PRINCIPLE)
n 상속 받은 클래스는 사용자가 차이점을 알 필요 없이 기초 클래스 인터페
이스를 통해 사용 가능해야만 한다
19. +
몇 가지 추가적인 설계 원칙(2)
n KISS(KEEP IT SIMPLE SILLY) 원칙
n 소프트웨어 설계 구성에는 두 가지 방법이 있다. 하나는 아주 단순하게 만들
어 명백하게 의존성이 하나도 없게 만드는 방법이며, 다른 하나는 아주 복잡
하게 만들어 명백한 의존성이 하나도 없게 만드는 방법이다
n 정보 은닉(Information Hiding) 원칙
n 어렵거나 변경되기 쉬운 설계 결정을 파악하고 적절한 모듈이나 타입을 생성
해 다른 모듈이나 타입으로부터 이런 결정을 감추는 방식
n DRY(Don’t Repeat Yourself) 원칙
n 모든 지식은 시스템 내부에서 단일하고 모호하지 않고 권위 있는 표현이 되어
야만 한다
n 비순환 의존성 원칙(ADP, Acyclic Dependencies Principle)
n 패키지 사이의 의존성은 순환을 형성해서는 안 된다
21. +
사례 연구
n 중복된 추상화 악취란?
n 이름이 같거나 구현이 동일하거나 이름도 같고 구현도 동일할 때 발생하
는 악취
n 동일한 이름: 추상화 이름이 동일(우연히 이름이 동일할 가능성에 주
의!)
n 동일한 구현: 의미상으로 동일한 멤버 정의가 있는 경우
n 문제의 원인: DRY, 중복 방지 원칙을 어김(예: 복붙)
n 여파: 코드 중복으로 인한 유지보수가 어려워짐
추상화 악취 중 중복된 추상화
22. +
사례 연구
n 예제: JDK에 존재하는 이름이 동일한 클래스
n 자바 7에서 4005개 타입 중 135개 타입이 중복(3.3%)!
n 예: java.util.Date와 여기서 파생된 java.sql.Date 클래스
n 컴파일러가 문제 삼지 않는 이유
n 패키지가 다르다
n 사용자 입장에서 문제가 되는 이유
n 두 클래스를 동시에 import할 경우 명시적으로 한정해야 한다 à 모호성
문제를 수동으로 해소
추상화 악취 중 중복된 추상화
23. +
사례 연구
n 리펙터링 제안
n java.sql.Date JavaDoc 문서:
“SQL DATE 정의에 순응하기 위해, java.sql.Date 인스턴스가 감싼 밀리
초 값은 관련된 인스턴스가 위치한 특정 시간대에 맞춰 시, 분, 초, 밀리
초를 0으로 설정하는 방식으로 ‘정규화되어야’만 한다.”
n 더 나은 설계안
n java.sql.Date에서 java.util.Date 인스턴스를 감싸기
n 상속을 위임으로 변환
n 또한 java.sql.SQLDate로 이름 변경
추상화 악취 중 중복된 추상화
24. +
사례 연구
n 누락된 캡슐화 악취란?
n 구현 변형을 계층 내부나 추상화 내부에서 캡슐화하지 않을 경우 발생하
는 악취
n 문제의 원인: OCP 위반(타입의 행동 양식은 변경이 아니라 확장에 의해
바꿔야 함)
n 여파: 계층에 새로운 변형을 지원하려고 시도할 때마다 불필요한 클래스
‘폭발’ 발생
캡슐화 악취 중 누락된 캡슐화
25. +
사례 연구
n 예제(1)
n 데이터 암호화를 위한 Encryption 클래스: DES (Data Encryption
Standard), AES (Advanced Encryption Standard), TDES (Triple
Data Encryption Standard)를 포함해 알고리즘에 대한 다양한 선택이
가능
n 초보 개발자는 Encryption 클래스 내부에 EncryptUsingDES(),
EncryptiUsingAES(), …와 같은 수많은 메소드를 추가
n 문제점
n Encryption 클래스가 알고리즘 추가에 따라 점점 커짐(한번에 알고리즘
을 하나만 사용하더라도 덩치 큰 클래스를 가져와야 함)
n 새로운 알고리즘 추가가 어려움
n 특정 알고리즘 하나만 재사용하기가 불가능
캡슐화 악취 중 누락된 캡슐화
26. +
사례 연구
n 예제(2)
n 데이터 암호화를 위한 알고리즘 뿐만 아니라 다양한 유형의 데이터를 암
호화
n 내용 유형과 암호화 알고리즘 유형의 두 가지 변형이 존재
n DESImageEncryption, AESTextEncryption, …
n 문제점
n 구현에서 변형이 서로 뒤섞이며, 독자적으로 캡슐화되지 못함
n 클래스 폭발로 마무리
캡슐화 악취 중 누락된 캡슐화
27. +
사례 연구
n 리펙터링 제안
n 예제(1)
n EncryptionAlgorithm 인터페이스를 생성
n DESEncryptionAlgorithm와 AESEncryptionAlgorithm는
EncryptionAlgorithm 인터페이스를 구현하고 각각 DES와 AES 알고
리즘을 정의
n Encryption 클래스는 EncryptionAlgorithm 인터페이스에 대한 참조
를 유지
n 장점
n Encryption 객체는 실행 시간에 특정 암호화 알고리즘으로 구성 가능
n EncryptionAlgorithm 계층에 정의된 알고리즘은 다른 맥락에서 재사
용 가능
n 새로운 알고리즘 추가 용이
캡슐화 악취 중 누락된 캡슐화
29. +
사례 연구
n 리펙터링 제안
n 예제(2)
n 두 가지 직교적인 관심사에 대해 독자적으로 변형을 캡슐화
캡슐화 악취 중 누락된 캡슐화
30. +
사례 연구
n 불충분한 모듈화 악취란?
n 완벽하게 분해되지 않은 추상화 à 추가로 분해할 경우 크기/구현 복잡도
해소 가능
n 부풀어로은 인터페이스: 추상화의 공개 인터페이스에 엄청나게 많은
멤버 존재
n 부풀어오른 구현: 구현에 엄청나게 많은 메소드 존재/구현 복잡도가 과
도하게 높은 메소드가 하나 이상 존재
n 문제의 원인: 추상화를 관리 가능한 크기로 유지하지 못함
n ‘연산과 클래스는 조화로운 크기가 되어야 마땅하다. 다시 말해, 크기
는 양극단을 피해야 마땅하다.”
n 주의: SRP와 혼동 à 책임이 하나인 추상화면서도 여전히 크고 복잡할 수
있다. 유사하게, 작은 추상화면서도 여전히 책임이 여럿일 수 있다
모듈화 악취 중 불충분한 모듈화
31. +
사례 연구
n 불충분한 모듈화 악취란?
n 완벽하게 분해되지 않은 추상화 à 추가로 분해할 경우 크기/구현 복잡도
해소 가능
n 부풀어로은 인터페이스: 추상화의 공개 인터페이스에 엄청나게 많은
멤버 존재
n 부풀어오른 구현: 구현에 엄청나게 많은 메소드 존재/구현 복잡도가 과
도하게 높은 메소드가 하나 이상 존재
n 문제의 원인: 추상화를 관리 가능한 크기로 유지하지 못함
n ‘연산과 클래스는 조화로운 크기가 되어야 마땅하다. 다시 말해, 크기
는 양극단을 피해야 마땅하다.”
n 주의: SRP와 혼동 à 책임이 하나인 추상화면서도 여전히 크고 복잡할 수
있다. 유사하게, 작은 추상화면서도 여전히 책임이 여럿일 수 있다.
모듈화 악취 중 불충분한 모듈화
32. +
사례 연구
n 예제: JDK에 존재하는 java.awt.Component 클래스
n 메소드가 332개(그 중 259개가 public으로 선언!)
n 중첩된 내부 클래스가 11개, 필드가 107개(상수 포함)
n 원시 코드: 10,102행
n 문제점
n 분석, 유지보수, 기능 추가 과정에서 이 클래스를 이해하기가 너무 복잡
하다!
추상화 악취 중 중복된 추상화
33. +
사례 연구
n 리펙터링 방안
n 부풀어오른 인터페이스
n 공개 인터페이스의 부분 집합이 밀접히 연관된 (응집력이 높은) 멤버를 포
함하면, 부분 집합을 독자적인 추상화로 추출
n 클래스가 멤버로 구성된 부분 집합을 둘 이상 포함하며 각 부분 집합의 응
집력이 높으면, 이런 부분 집합을 독자적인 클래스로 나눔
n 클래스 인터페이스가 클라이언트에 밀접한 메소드를 사용해 여러 클라이
언트를 서비스하면, 원본 인터페이스를 클라이언트에 밀접한 여러 인터페
이스로 분리하기 위해 ISP(Interface Segregation Principle)를 적용
n 부풀어오른 구현
n 메소드 논리가 복잡하면, 해당 메소드에 있는 코드를 단순하게 만들기 위
해 사적인 도우미 메소드를 도입
n 불충분한 모듈화 악취와 함께 다면적인 추상화 악취까지 포함하는 추상화
라면, 각 책임을 분리된 (새로 만들거나 기존에 존재하는) 추상화에 캡슐화
추상화 악취 중 중복된 추상화
34. +
사례 연구
n 넓은 계층 악취란?
n 중간 타입이 빠졌을지도 모른다는 사실을 암시하는 ‘너무’ 넓은 상속 관계
가 존재하는 악취
n 문제의 원인: ‘의미있는 일반화 적용’이라는 요소 기술을 위반
n 빠진 중간 타입은 계층에 있는 클라이언트가 하위 타입을 직접 참조하
게 강제할지도 모른다. 이런 의존성은 계층의 변경 가능성과 확장 가능
성에 영향을 미친다.
n 타입 내부에 불필요한 중복이 있을지도 모른다(중간 타입의 부족으로
인해 공통성을 적절히 추상화할 수 없다)
n 영향: 동일 층에 하위 타입 수가 너무 많으면(9개를 넘길 경우), 계층을
이해하거나 사용하기가 더 어려워짐
계층 악취 중 넓은 계층
35. +
사례 연구
n 예제
n JDK 7에서 바로 아래 하위 클래스 36개를 거느리는 java.util.EventObject 클래스
n 문제점
n 형제자매 클래스가 엄청나게 많아서 이해가 어려움
계층 악취 중 넓은 계층
36. +
사례 연구
n 리펙터링 제안
n 중간 타입을 도입
n 예: TreeEvent
계층 악취 중 넓은 계층
37. +
설계에서 코드로
n 설계 리펙터링은 코드 리펙터링과 밀접한 관련이 있다!
n 깨끗한 설계를 유지하는 동시에 깨끗한 코드를 유지해야 한다