성공과 실패를 결정하는 1%의 객체 지향 원리

지은이 / Akiara Hirasawa | 옮긴이 / 이길섭, 신동완

by Joong

성공과 실패를 결정하는 1%의 객체 지향 원리
2005년 6월 10일 1판 1쇄 발행
글쓴이 : Akira Hirasawa
번역 : 이길섭, 신동완
펴낸이 : 이종춘
펴낸곳 : 성안당(경기도 고양시 일산구 장항동 596-15)


그러므로 「객체 지향은 현실 세계를 그대로 소프트웨어로 표현하는 기술」이라고 하는 설명은 틀린 것입니다. 객체 지향 프로그래밍의 구조와 현실 세계는 비슷한 부분도 있지만, 기본적으로 구조가 크게 다른 것으로, 오히려 「닮았지만 다른 것」입니다. [46p]


폴리모피즘(polymorphism)은 별로 익숙하지 않은 단어지만 영어로 「여러 가지 형태로 변한다」라는 의미를 갖는 말로써 우리말로는 「다형성」등으로 번역되고 있습니다.
이 구조를 한마디로 표현하자면 「비슷한 클래스에 대하여 메시지를 보내는 방법을 공통으로 하는 구조」라고 말할 수 있겠습니다. 「상대가 구체적으로 어떤 클래스의 인스턴스인지를 의식하지 않고 메시지를 보내는 구조」라고 해도 좋겠죠. [51p]


객체 지향에서는 전체 집합을 「수퍼 클래스」, 부분 집합을 「서브 클래스」라고 부릅니다. [54p]


즉, 여기서는 클래스와 인스턴스의 관계가 현실 세계와는 정반대로 되어 있습니다. 현실 세계에서는 먼저 「구체적인 사물」이 있고, 그것을 보는 쪽의 입장과 관심의 차이에 의해 여러 가지 기준으로 분류됩니다. 필자 자신을 예로 들어도 회사에 가면 ‘회사원’, 손님과 상담을 할 때는 ‘기술자’ 또는 ‘컨설턴트’ 집에 돌아가면 ‘아버지’, 길을 걷고 있을 때는 ‘지나가는 남자’입니다.
차이는 또 있습니다. 객체 지향의 세계에서는 클래스로부터 인스턴스가 만들어지기 때문에 시간이 지나도 다른 클래스로 되는 것은 불가능합니다. 아기 클래스로부터 만들어진 인스턴스는 시간이 지남에 따라 성장을 하고 노화합니다. 그리고 그에 따라 분류도 바뀝니다. 역시 필자의 예로 설명하면 이전에는 ‘소년’이나 ‘청년’으로 분류되었지만, 최근에는 완전히 ‘아저씨’로 분류되고 있습니다. 이렇게 클래스와 인스턴스라고 하는 가장 기본적인 구조를 예로 들어도 객체 지향과 현실 세계는 상당히 다릅니다. [58p]


객체 지향의 세계에서는 미리 모든 행동을 정의합니다. 그리고 일절 거부하지 않고 따르는 것을 전제로 합니다. 폴리모피즘의 구조도 이를 기초로 하고 있습니다. [60p]


서브루틴의 독립성을 높이는 방법은 호출하는 쪽인 메인루틴과 서브루틴에서 공유하는 정보를 적게 하는 것입니다. 여기서 공유하는 정보란 변수에 저장되는 데이터를 말합니다. 또한 복수의 서브루틴이 공유하는 이러한 변수는 전역변수(globla variable)라고 부릅니다.
프로그램의 논리는 순서대로 따라가면 해석이 가능합니다. 그러나 변수는 프로그램의 어디에서 참조되고 있는지 한눈에 파악하는 것이 곤란하기 때문에 많은 변수가 정의된 프로그램의 보수는 쉽지 않습니다. 특히 전역변수는 프로그램 전체의 어디에서도 접근이 가능하기 때문에 결함을 제거할 때 변수의 내용이 틀렸다는 것을 알게 되면, 모든 소스 코드를 조사해 보지 않으면 안됩니다. 따라서 이러한 전역변수를 얼마나 줄일 수 있을까 하는 것이 프로그램 전체의 보수성을 향상시키기 위한 중요한 사항인 것입니다. [74p]


만일 서브루틴이 3개밖에 없고, 논리도 짧으면 별다른 문제가 되지 않겠지요. 하지만 서브루틴의 수가 수백, 수천, 수만에 이르는 대규모 어플리케이션을 상상해 보세요. 그러한 경우라면 전역변수의 사양을 변경하는 것은 거의 불가능한 일이 아닐까요? 이러한 문제를 피하기 위해서는 2가지 구조를 생각해 볼 수 있습니다. 하나는 지역변수이고 또 하나는 값 복사에 의한 인수값 전달 방법입니다.(call by value). [75p]


그러나 구조적 프로그래밍에서는 아직 해결되지 않은 2가지 문제가 남아 있습니다. 그것은 전역변수 문제와 빈약한 재사용입니다.
첫 번째, 전역변수 문제입니다. 구조적 언어에서는 지역변수와 값 복사에 의한 인수값 전달(call by value) 구조가 도입되었기 때문에 서브루틴 간 인수의 주고받기는 최소한으로 억제하는 것이 가능하게 되었습니다. 그러나 지역변수는 서브루틴 호출이 끝나면 존재 자체가 없어져 버리는 일반적인 변수입니다. 따라서 서브루틴의 실행 기간이 끝나더라도 보존할 필요가 있는 정보는 서브루틴의 바깥쪽, 즉 전역변수로 보존할 수밖에 없습니다. -중략-
또 하나의 문제는 빈약한 재사용입니다. 구조적 언어에서 재사용 가능한 것은 ‘서브루틴’이었습니다. 그 시대에는 코드 변환과 입출력 처리, 수치 계산, 문자열 처리 등의 범용 라이브러리가 제공되어 기본적인 처리에 대해서는 기존 프로그램을 재사용하는 경우도 어느 정도 행해졌었습니다. 그러나 그렇다고 해도 증대하는 어플리케이션의 규모 전체로 보면 미미한 수준이었습니다. [82~83p]


클래스는 ‘모으기, 감추기, 많이 만들기’구조
서브루틴과 변수를 ‘모으기’
클래스의 내부에서만 사용하는 변수와 서브루틴을 ‘감추기’
하나의 클래스로부터 인스턴스를 ‘많이 만들기’[93p]


클래스가 없는 구조적 언어에서는 모든 서브루틴에 다른 이름 붙이지 않으면 안되었습니다. 그러나 클래스에 포함되는 요소의 이름의 이름은 ‘그 안에서만 중복되지 않으면 된다’라는 약속이 되어 있습니다. [96p]


지역변수 전역변수 인스턴스 변수


복수 서브루틴(메소드)으로부터 접근
X

O

O


접근 가능 범위의 한정
O(하나의 서브루틴으로부터밖에 접근 불가능)
X(프로그램의 어디에서도 접근 가능)
O(같은 클래스 내의 메소드로부터만 접근 가능하도록 지정 가능)


존재 기간의 길이
X(서브루틴을 호출할 때 만들어져서 빠져나갈 때 폐기되는 일시적인 것)
O(어플리케이션의 개시부터 종료까지)
O(인스턴스가 만들어져서 필요 없어질 때까지)


변수 영역의 복제
X(하나의 시점에서는 하나밖에 만들 수 없다)
X(하나의 변수에 하나씩밖에 만들 수 없다)
O(실행 때에 얼마든지 만들 수 있다)
[106p]


다형성을 간단히 표현하면 ‘고통 메인루틴’을 만들기 위한 구조입니다. 공통 메소드는 호출당하는 쪽의 논리를 하나로 모으게 되지만, 다형성은 반대로 호출하는 쪽의 논리를 하나로 모으게 됩니다. [108p]


OOP의 3대 요소 마지막은 상속(inheritance)입니다. 02장에서 상속은 ‘닮은 클래스의 공통점과 상이점을 정리하는 구조’로 집합론의 전체 집합과 부분 집합에 상당한다고 설명하였습니다. 그러나 원래 OOP의 클래스는 인스턴스를 분류하는(classify)것이라고 하기보다는 인스턴스의 제조 장치라 할 수 있습니다. [113p]


따라서 이러한 혼란스런 비유의 이야기는 잊어버리고 ‘상속은 클래스 정의의 공통 부분을 다른 클래스에 모으는 것으로 코드의 중복을 배제하는 구조’라고만 기억합시다. [114p]
-
3대 요소
클래스
폴리모피즘(다형성)
상속


설명
서브루틴과 변수를 모아서 소프트웨어 구성품을 만듦
메소드를 호출하는 쪽을 공통화
중복되는 클래스 정의를 공통화


목적
정리 정돈
불필요한 부분을 생략
불필요한 부분을 생략


기억 방법
모아서, 숨기고, 많이 만드는 구조
공통 메인루틴을 만드는 구조
클래스의 공통 부분을 별도의 클래스로 모으는 구조
[116p]


OOP에서는 이 ‘형을 점검하는 구조’가 추진되어 프로그래머가 정의한 클래스를 형으로 지정할 수 있도록 되어 있습니다. 이제 이 구조를 구체적으로 설명하기 위해 잠시 전에 클래스의 설명에서 소개했던 샘플 코드를 다시 한 번 예로 듭니다.
TextFileReader Reader = new TextFileReader();
이것은 TextFileReader 클래스의 인스턴스를 작성하고 있는 부분인데 등식의 좌변을 주목해 보세요. 여기서 Reader라는 이름의 변수를 정의하고, 이 형이 TextFileReader클래스라고 선언하고 있습니다. 이것은 컴파일러에 대해서 변수 Reader에는 TexfFileReader 클래스의 인스턴스밖에 저장되지 않는다고 선언하고 있는 것이 됩니다. 그리고 만약 이 변수에 수치와 다른 클래스의 인스턴스를 저장하는 논리를 썼을 경우에는 컴파일러가 오류를 발견해 줍니다. [119p]


앞서 ‘모으다’ 구조로서 클래스를 소개했습니다. 그런데 이 패키지는 클래스를 다시 한 번 ‘모으는’ 구조입니다.
하지만 패키지는 단지 모으기 위한 용기에 불과하기 때문에 클래스와는 달리 메소드와 인스턴스 변수를 정의할 수 없습니다. 이러한 구조가 도대체 어디에 도움이 될까 하고 생각될지도 모르겠지만, 파일 시스템의 디렉토리를 한번 떠올려 보세요. 디렉토리는 파일을 보관하기만 하는 용기에 불과합니다. 그러나 디렉토리에 이름을 붙여서 그 안에 파일을 보관한다면 파일 관리가 상당히 편하게 됩니다. 반대로 디렉토리가 존재하지 않고 루트 디렉토리 밑에 모든 파일이 보관되어 있는 상태를 상상해 보세요. 만일 파일 시스템이 이러한 구조였다면 분명히 사용하기가 불편할 것입니다. 패키지의 역할도 이와 비슷합니다. 디렉토리와 마찬가지로 패키지도 클래스뿐만 아니라 다른 패키지를 보관하는 계층 구조를 만드는 것이 가능합니다. [123p]


다음은 예외입니다. 이 구조를 한마디로 표현하면 ‘반환값(return value)과는 다른 형식으로 메소드로부터 특별한 오류를 돌려 주는 구조’라고 할 수 있습니다. 그리고 특별한 오류의 예로는 네트워크의 통신 장애와 디스크의 접그 장애, 데이터베이스의 교착 상태 등이 발생한 경우를 들 수 있습니다. 또한 이러한 장애 상태가 아니더라도 파일을 읽을 때 EOF(End Of File) 검출 등과 같이 반환값으로서 적절한 값을 되돌려 주지 않는 경우가 해당이 됩니다. [125p]


스레드는 프로세스보다 작은 단위로, 하나의 프로세스 중에 복수 존재하는 것이 가능합니다. 실제로 많은 어플리케이션이 복수의 스레드에 의해 실현되어 있습니다. 친근한 예로 문서 작정 소프트웨어(워드)의 문서 작성, 인쇄 처리와 웹 브라우저의 요청 처리와 ‘중지’ 버튼 처리 등을 들 수 있습니다. 이들 독립된 처리는 하나의 어플리케이션 내에서 서로 다른 역할을 갖는 복수의 스레드가 동시에 병행해서 움직이는 것으로 실현되어 있습니다.
그런데 여기서 ‘복수의 스레드가 동시에 병행해서 동작한다’라는 설명은 엄밀히 말하면 정확한 표현은 아닙니다. 실제로는 컴퓨터의 심장부인 CPU가 특정 시점에서 실행 가능한 처리는 하나뿐입니다. 그러면 도대체 어떻게 해서 이러한 동시 병렬 처리를 실현하는 것일까요? CPU가 복수의 스레드 처리를 조금씩 순차적으로 처리하기 때문입니다. CPU는 스레드 처리를 실행할 때 어느 하나의 스레드를 처음부터 끝가지 한꺼번에 처리하는 것이 아니고, 1000분의 1초(ms)단위 정도의 상당히 짧은 시간만 실행합니다 [143p]


프로그램의 메모리 영역은 정적(static)영역, 힙(heap)영역, 스택(stack)영역의 3가지로 나뉘어 관리됩니다. [145p]


-
종류
정적 영역
힙 영역
스택 영역


사용법
어플리케이션이 시작할 때 확보된다.
시작할 때 일정 영역이 확보되어 필요한 경우 어플리케이션에 할당
LIFO(후입 선출 방식)


저장되는 정보
전역변수, 실행 코드
임의(어플리케이션에 따름)
호출한 서브루틴의 인수, 지역변수, 복귀 위치


확보 단위
어플리케이션에서 모아서 1개
시스템 또는 어플리케이션에서 1개
스레드마다 1개

[147p]


인스턴스를 만드는 명령(Java에서는 new 명령)이 실행되면 이 클래스의 인스턴스 변수를 저장하기 위해 필요한 만큼의 메모리가 힙 영역에 할당됩니다. 이때 인스턴스를 지정해서 기동시키는 OOP의 메소드 호출을 실현하기 위해 인스턴스로부터 메소드 영역에 있는 클래스 정보의 관계 설정도 행해집니다. [151p]


TextFileReader, Reader, = new TextFileReader,();
이제까지의 설명으로, 작성된 TextFileReader 클래스의 인스턴스가 힙 영역에 확보된다는 것은 알고 계시겠죠?
그러면 변수 Reader에는 어떤 정보가 보관되어 있을가요? 이 변수는 반드시 힙 영역에 있다고 할 수는 없습니다. 메소드의 인수와 로컬변수라면 스택에 있을 것이고, 메소드 영역(정적 영역)에 있는 클래스 정보에 배치하는 것도 가능합니다. 따라서 변수 Reader에는 TextFileReader 클래스의 인스턴스 그 자체를 보관하지 않습니다. 그 대신 힙 영역에 만들어진 인스턴스의 ‘포인터’를 저장합니다.
인스턴스를 보관하는 변수에는 인스턴스 그 자체가 아니라 인스턴스의 ‘포인터’가 보관됩니다.
[153p]


클래스 라이브러리의 ‘클래스’는 물론 OOP의 구조입니다. 라이브러리(library)를 사전에서 찾아보면 ‘도서관’, ‘장서’를 의미한다고 쓰여 있지만, 여기서는 ‘많이 축적된 것’이라고 해석하는 것이 타당할 것 같습니다. 즉, 클래스 라이브러리는 ‘일반적인 기능을 갖는 클래스를 많이 모아 놓은 것’이 됩니다.
이러한 범용의 라이브러리는 예전부터 존재하고 있었고 ‘함수 라이브러리’ 또는 ‘공통 서브루틴 라이브러리’라고도 불리고 있었습니다. 그러다가 OOP에서는 소프트웨어를 기술하는 단위가 ‘클래스’로 되어 있기 때문에 ‘클래스 라이브러리’가 된 것입니다.
-중략- 하지만 OOP에서는 클래스, 폴리모피즘, 상속이라는 구조가 구비되어 있기 때문에 단지 어플리케이션으로부터의 ‘호출’뿐만이 아니라 다음과 같은 기능도 가능하게 되었습니다.
라이브러리 중의 클래스로부터 인스턴스를 작성하고 메소드와 변수 정의를 모아서 이용합니다(클래스의 이용).
2. 라이브러리로부터 호출되는 쪽의 논리를 어플리케이션 고유의 처리로 바꾸어 놓습니다(폴리모피즘의 이용).
3. 라이브러리 중의 클래스에 메소드와 변수를 추가로 정의해서 새로운 클래스를 작성합니다.(상속의 이용).
이렇게 OOP 구조를 전제로 함에 따라 종래와 비교해서 재사용 가능한 기능의 범위가 비약적으로 넓어지게 된 것입니다. [179p]


‘프레임워크’(framework)는 사전적 의미로는 ‘골격’, ‘조직’이라는 뜻으로, 소프트웨어 개발 기술분야에서는 ‘기본적인 골격만을 준비해서 내용은 개별 사정에 따라 바꾸어 넣는 구조’를 가리킵니다. 그런데 소프트웨어 개발 기술 분야에서 프레임워크라는 용어는 크게 2가지로 나뉘어집니다.
하나는 어플리케이션의 기본적인 부분을 소스 코드로 제공하는 틀이고, 다른 하나는 시스템 개발 전체에 대한 진행 방법을 제공하는 틀입니다. 각각을 ‘협의의 프레임워크’, ‘광의의 프레임워크’라고도 합니다.[181p]


클래스 라이브러리와 프레임워크의 차이는 목적과 재사용 구성품의 사용 방법입니다. 클래스 라이브러리의 경우는 일반적으로 OOP의 구조를 사용해서 만들어진 재사용 구성품을 가리키는 것으로 목적과 사용법을 특별하게 한정하지는 않습니다. 그러나 프레임워크라고 부르는 경우는 단지 OOP를 사용해서 만들어진 라이브러리라고 하는 것뿐만 아니라 특정 목적을 달성하기 위한 어플리케이션의 미완성품을 가리킵니다. 따라서 프레임워크의 정의는 그 사용 방법을 강조하면 ‘기본적인 제어의 흐름을 미리 준비하고, 어플리케이션으로 개별 처리를 짜 넣도록 한 소프트웨어 구성 요소’라고 할 수 있습니다. [182p]


재사용 구성 요소의 마지막은 컴포넌트입니다. ‘컴포넌트’(component)는 영어로 ‘성분’, ‘구성품’을 의미하며, 소프트웨어 개발 기술 분야에서는 ‘구성품’을 가리키는 단어로 사용되고 있지만 실제 정의는 다소 애매합니다.
그리고 앞서 클래스 라이브러리와 프레임워크는 동의어로 사용되는 경우도 많다고 했지만, 이 컴포넌트라는 용어는 그것들과는 다소 다른 뉘앙스를 가집니다.
컴포넌트의 정의는 종종 아래와 같이 말해지곤 합니다.
OOP의 클래스보다 밀도가 큽니다.
소스 코드 형식이 아니고, 바이너리 형식으로 제공됩니다.
컴포넌트 정의 정보를 포함해서 제공됩니다.
기능적으로 독립성이 높고, 내부를 몰라도 사용이 가능합니다.
[185p]


설계 패턴을 다른 말로 하면 ‘설계의 정석집’이라 할 수 있습니다. 바둑과 장기를 할 줄 아는 분이라면 쉽게 이해하시리라 생각되지만, 빠른 시간에 강해지는 비결은 정석을 외워 버리는 것입니다. 설계 패턴도 말하자면 설계의 정석집이므로, 먼저 몇 개의 패턴을 외워 버리는 것이 지름길입니다. [192p]


OOP에서는 클래스는 서브루틴과 변수를 모아 둔 것입니다. 그리고 인스턴스는 실행할 때에 확보되는 메모리 영역입니다. 이 ‘하나의 클래스로부터 실행시에 많은 인스턴스를 만드는 구조’가 집합론의 ‘집합’, ‘요소’와 비슷하기 때문에 상위 공정에서 클래스와 인스턴스는 집합론을 응용하게 되었습니다.
이렇게 생각하면 클래스와 인스턴스의 적용 범위는 훨씬 넓어집니다. ‘아군과 김군이 인스턴스이고, 종업원이 클래스’, ‘2월 3일에 주문한 2권이 책이 인스턴스이고, 주문이 클래스’ 등등 얼마든지 응용할 수가 있습니다. 02장에서 이상한 비유로 비판했던 ‘우리 아들(또는 딸)이 인스턴스이고, 아기가 클래스’라고 하더라도 전혀 문제가 없습니다.
또한 OOP에서는 클래스 정의의 공통 부분을 모아서 다른 클래스에 나누어 주는 구조였던 상속도 전체 집합과 부분 집합의 사고방식에 응용되었습니다. 이렇게 함으로써 역시 ‘고객을 개인 고객과 법인 고객으로 분류하는 것’과 ‘인간을 남자와 여자로 분류하는 것’등 여러 가지로 예로 응용하는 것이 가능하게 됩니다.
두 번째 역할 분담의 사고방식입니다. OOP에 있어서 ‘메시지 번역’은 인스턴스를 지정해서 메소드(즉 클래스에 모아진 서브루틴)를 호출하는 것이었지만, 상위 공정에서는 ‘어느 특정의 역할을 가진 것의 모임이 정해진 방법으로 서로 연락하는 모습’을 표현하는 일반적인 역할 분담의 모델로 응용되었습니다. [204~205p]


객체 지향에는 추상적인 ‘범용 정리술’과 땅에 다리를 붙이고 있는 ‘프로그래밍 기술’이라는 전혀 다른 2가지의 측면이 있습니다.
그리고 객체 지향이 ‘어렵고 첫인상이 안 좋은 기술이다’라고 말하는 원인도 여기에 있습니다. [207p]


응용 기술
OOP, 정리술의 어느 쪽에 분류될까?
클래스 라이브러리
프레임워크
컴포넌트
OOP확장
설계패턴
OOP확장
UML
OOP, 정리술의 양쪽에 대응
모델리(업무 분석, 요구 정의)
정리술
객체 지향 설계
OOP확장
개발 프로세스
관계 없음
-
[208p]


UML은 소프트웨어 기능과 구조를 2차원의 그림으로 표현하는 형식을 정한 세계 표준입니다. UML을 한마디로 표현하면 ‘형태가 없는 소프트웨어를 보는 도구’라고 말할 수 있겠지요. 본격적인 시스템에서는 프로그램의 행 수로 수십만 행 이상, 외부 사양을 기술하는 문서도 수백 페이지 이상의 규모가 됩니다. 이때 UML의 다이어그램을 이용하면 이러한 방대한 정보로부터 중요한 부분만을 추출해서, 논리적이고 직감적인 형식으로 표현 가능합니다. [211p]


클래스의 다이어그램은 클래스를 기본적인 단위로 하는 OOP의 프로그램 구조를 표현합니다. OOP 이전의 프로그램 구조는 서브루틴(함수)을 기본 단위로 만들었기 때문에 구조화 다이어그램을 사용하여 표현할 수 있었습니다.
그러나 OOP에서는 클래스에 정의되어 있는 인스턴스 변수가 다른 클래스의 인스턴스를 참조하거나 상속에 의한 다른 클래스의 정의 정보를 그대로 빌려 쓴다든지 하는 예전에는 존재하지 않던 구조를 갖추고 있습니다. 따라서 구조화 차트에서는 OOP 프로그램의 구조를 적절히 표현할 수가 없었습니다. 이러한 OOP 특유의 기능을 그림으로 표현하기 위해 고안된 것이 클래스 다이어그램입니다. -중략- 직사각형은 클래스를 나타내고 직사각형을 연결하는 선은 클래스 간의 관계를 나타냅니다. 그리고 인스턴스의 참조와 상속 등의 차이는 화살표의 모양에 따라 표현합니다. [219p]


UML의 효과는 여기에 있습니다. 형태가 없는 소프트웨어를 2차원의 그림으로 표현함으로써 전체를 이해하고 기억하는 것을 강려갛게 지원합니다. 그런데 기억력이 좋은 분이라면 (그림 8-3)이 06장에서 소개한 설계 패턴의 복합체(Composite) 패턴을 사용하고 있다는 것을 눈치챘을지도 모르겠습니다. ‘패턴 인식’이라는 단어가 있지만 그림을 사용해서 표현하는 것으로, 이러한 설계 패턴도 알아보기 쉽게 합니다. 이와 같이 UML은 아이디어의 재사용을 촉진시키는 역할도 맡고 있습니다. [222p]


클래스 다이어 그램 : 클래스의 정의 정보와 클래스 간의 관계를 표현합니다.
시퀸스 다이어 그램 : 실행될 경우에 인스턴스 간의 메소드 호출을 시계열로 표현합니다.
커뮤니케이션 다이어그램 : 실행될 겨웅에 인스턴스 간의 메소드 호출을 인스턴의 관계를 중심으로 표현합니다. [225p]


설계 목표의 첫 번째는 중복의 배제입니다. 기능의 중복이 있으면 그만큼 규모가 커지게 되므로, 테스트하기도 힘들고 이해하기도 어렵게 됩니다. 특히 심각한 문제는 변경을 할 경우에 수정하는 것을 빠뜨리는 것이죠. 최초에 프로그램을 작성할 때는 아직 그렇지 않으나 어느 정도 시간이 지나고 나서 그 프로그램을 변경할 필요가 생기는 경우에는 중복의 간과로 수정하는 것을 빠뜨리는 경우가 일어나기 쉽습니다.
따라서 설계 단계에서 가능한 한 기능의 중복이 일어나지 않도록 배려할 필요가 있스빈다. 이것은 처음 만들 때뿐만 아니라 나중에 변경을 하는 겨웅에도 마찬가지입니다. 종종 기존 프로그램에 영향을 미치지 않도록 하기 위해서 프로그램의 일부를 그대로 카피해서 수정을 하는 copy and paste 프로그래밍을 행하는 경우가 있지만, 오랫동안 계속해서 사용할 소프트웨어에 대해서는 안일한 수정을 행하는 것은 피하는 것이 좋습니다. [280p]


일반적으로 복잡한 것을 알기 쉽게 하는 비결은 ‘분할하는’ 것입니다. 복잡한 것은 분할해서 작게 하면 이해하기 쉽게 됩니다. 한 마디로 ‘나누면 알 수 있다’라고 할 수 있습니다.
이를 소프트웨어에 적용해서 생각하면, 전체를 한 덩어리로 작성하지 말고 복수의 서브 시스템과 구성품으로 구성되도록 만드는 것이라 할 수 있겠습니다. 그러나 단지 서브 시스템과 구성품으로 분할하는 것만으로는 부족하고, 개개의 서브 시스템과 구성품의 ‘독립성이 높은 것’이 중요하게 됩니다. 이렇게 구성품의 독립성을 높임으로써, 서브 시스템과 구성품의 기능이 명확해져 변경될 때에 수정할 곳의 판단이 가능하게 되며, 특정 구성품과 서브 시스템을 수정한 경우에도 다른 곳의 영향을 최소한으로 억제하는 것이 가능합니다. 또한 독립된 구성품을 빼 내고 다른 어플리케이션으로 대체하는 것도 용이하게 되겠죠. [281p]

keyword
매거진의 이전글코딩을 지탱하는 기술