SmalltalkObjectsandDesign:Chapter 08
- 제 8 장 연습 - 디자인 구현하기
연습 - 디자인 구현하기
이제 당신에겐 스몰토크 애플리케이션의 개발을 시작하기 위한 기본 기술이 있다. 당신의 첫 애플리케이션은 앞장에서 우리가 디자인한 애플리케이션의 프로토타입이 될 것이다. 구현부의 모든 세부내용을 상세히 설명하기보다는 주요 단계를 안내하고 세부내용을 충분히 생각하도록 장려할 것이다. 지금쯤이면 눈치를 챘겠지만 스몰토크 프로그래머들은 애플리케이션을 개발하면서 많이 탐구하므로 이 글을 읽는 독자 또한 진행하는 동안 탐구하고 실험할 것을 예상해야 한다. 이번 장에서 소개하는 연습에는 3시간 정도 투자하도록 한다.
클래스 생성하기
❏ 시작하고자 하는 작업을 위해 새 애플리케이션을 먼저 생성하라.
❏ 이후에 Account 와 Transaction 클래스를 생성하라.
❏ 두 개의 클래스에 대해 인스턴스 변수를 정의하라. 적절한 인스턴스 변수를 살펴보려면 86 페이지에 그림을 살펴보고, 클래스와 인스턴스 변수를 생성하는 방법을 참고하려면 제 4장에서 진행한 작업을 검토하라.
테스트 사례 (test case)
성실하게 계속하기 위해서는 테스트 사례로 사용할 수 있는 메서드를 작성하는 것이 바람직하다.
❏ Account 클래스에 대해 example 이란 이름으로 된 클래스 메서드를 생성하라. 그것이 호출하는 메서드는 아직 존재하지 않기 때문에 실행하더라도 의미가 없다.
example
"Test by executing:
Account example inspect
"
|account transaction|
account := Account newBalance: 2500.
transaction := Transaction newAmount: -300 date: Date today.
account handleTransaction: transaction.
^account
이 테스트는 클래스 (factory) 메서드를 사용하여 account 와 transaction 을 생성하고, transaction 을 처리하며, account 를 리턴한다. 주석에 빈 공간이 많은 이유는 Account example inspect 를 마우스로 강조하여 실행하기 좋은 대상으로 만들기 위함이다. 테스트를 실행하고 싶을 때마다 메서드를 Transcript 로 이동할 필요없이 메서드를 브라우징하는 동시에 실행할 수 있다. 꼭 그래야 하는 것은 아니지만 편리한 트릭이다. 여태까지 목격한 대부분의 다른 new 메서드와는 달리 위의 Account 와 Transaction 의 새 인스턴스를 생성하기 위한 메서드는 arguments를 기대한다는 사실도 주목한다.
"new" 메서드 작성하기
❏ 애플리케이션에 두 클래스의 새 인스턴스를 생성할 것이기 때문에 Account 와 Transaction 에 대해 "new" 메서드를 작성하라. 이는 클래스 메서드이지 인스턴스 메서드가 아님을 기억하라. Account 클래스에 대한 메서드의 예를 들어보겠다:
newBalance: anInteger
"Answer a new instance of the receiver with balance anInteger"
^self new initialBalance: anInteger
initialBalance: 이름의 인스턴스 메서드가 필요하게 될 것임을 주목한다.
그건 그렇고, 클래스의 새 인스턴스를 생성하는 클래스 메서드는 newBalance: 와 같이 "new" 를 붙이는 것이 보편적이지만 꼭 사용해야 할 이유는 없다.
인스턴스 메서드 작성하기
❏ 우리 디자인에 따라 Account 와 Transaction 클래스에 대한 인스턴스 메서드를 준비하라. CRC 카드는 85 페이지를 참고하라.
<= 메서드는 종종 초보자들을 당혹스럽게 만든다. 디자인은 transaction 이 다른 transaction 과 스스로를 비교할 만큼 영리하므로 Transaction 클래스에 이항 메서드가 필요하다:
<= anotherTransaction
"Answer true if my date is before anotherTransaction's date,
false otherwise"
...
그렇지만 어떻게 해야 할까? 코드는 나의 (수신하는 transaction의) 일자를 다른 transaction의 일자와 명시적으로 비교해야만 한다. 나는 어떻게 다른객체의 내부로 접근할 수 있을까? 이는 캡슐화에서 기본적으로 금지되는 방법이다: 접근할 수 없다는 말이다. 내가 그 정보를 얻는 것은 다른 객체가 그것을 의무화하는 메서드를 가질 때에만 가능하다. 따라서 transactions는 아래 형태의 메서드도 지원해야 한다:
getDate
^date
해답 테스트하기
이제 Account example inspect 를 실행하여 (또는 그에 상응하게 Account example 을 검사하여) 테스트 사례를 실행해보자. 인스펙터 창은 account을 열지만, walkback이 열리는 것처럼 무언가 잘못되면 자신의 디버깅 기술을 실습할 기회로 활용하면 된다. (필요 시 57 페이지의 연습을 참고.) Account에 인스펙터 창이 생기면 그것이 당신의 transaction을 보유하는지 검사하라. 인스펙터에서 엔트리를 더블 클릭하면 그 곳에 바로 다른 인스펙터가 열린다. 더블 클릭을 반복하면 드릴 다운하여 account의 로그, 로그 내 원하는 transaction, transactions 내의 일자와 금액을 모두 살펴볼 수 있다.
위의 첫 번째 테스트는 너무나 간단해서 자신의 코드를 완전하게 테스트할 수는 없다ㅡ하나 의 transaction는 흥미롭지 못하다. 첫 번째 것과 비슷한 두 번째 테스트 사례, example2 를 작성하되, 일자가 서로 다른 transaction 을 최소 두 개 이상 처리하도록 하라. 서로 다른 일자를 생성하기 위한 방식에는 여러 가지가 있다: Date의 클래스 메서드를 살펴보거나, 56 페이지의 일자로 작업을 검토하라. example2 를 실행하여 인스펙터 수준을 더블 클릭하면 로그가 자신의 모든 transaction 을 포함하는지 확인하고, 시간 순으로 발생함을 검사할 수 있다.
공학 원칙
스몰토크 환경은 동적인 것만큼이나 프로그래머들이 재빠르게 변화를 시도하도록 장려하고, 때로는 탄탄한 공학 실습을 훼손하기도 한다. 예를 들어, 별도로 조심하지 않는 한 많은 학생들은 추가 example2 를 작성하고 기존의 example 를 회귀 검사용으로 보관하는 대신 example 를 직접 수정하는 선택을 한다. 테스트 사례를 파기하기 전에는 두 번씩 생각하는 연습을 하라. 오래된 테스트 사례들은 자신의 코드에 원하지 않는 변경내용을 도입하지 않았는지 확인할 수 있는 최선의 기회를 제공한다. 재타이핑 혹은 잘라서 붙여 넣는 테스트 방법을 작성해야 한다는 염려를 하지 않도록, 메서드의 첫 행만 덮어쓰고 저장(컴파일)만 하더라도 새로운 메서드ㅡ다른 이름, 동일한 코드ㅡ를 생성할 수 있다는 사실에 주목하라. 이러한 기법을 이용하면 별 수고를 들이지 않고 원본의 복제본을 생성하여 복제본을 원하는 대로 수정할 수 있다. 따라서 의식적으로 제거하길 원할 때까지 원본은 유지할 수 있는 것이다.
작은 변형(minor variation)
어떤 사람들은 account 객체 자체가 새로운 transactions를 빌드하고 그것들을 처리하여, 아래와 같은 표현식을 포함하는 테스트 메서드를 야기하는 해결책을 선호한다:
account transactionAmount: -300 date: Date today.
❏ 이런 마음가짐으로 테스트 메서드를 준비한 후, Account 클래스에 대해 인스턴스 메서드 transactionAmount:date: 를 작성하여 변형(variation)을 구현하라. 이 메서드는 하나의 스몰토크 표현식만 포함할 것이며, 당신은 다른 메서드들을 작성하거나 수정할 필요가 없다.
"Private" 메서드
스몰토크 초창기에 메서드가 동일한 클래스에서 (또는 서브클래스에서) 다른 메서드에 의해서만 호출될 수 있을 경우 메서드를 private로 간주했다; 메서드가 다른 클래스로부터의 메서드로부터 호출될 수 있다면 public한 것으로 간주했다. Private 메서드들은 해당하는 클래스를 개발 중이던 프로그래머에 의해서만 사용되었다. 따라서 다른 클래스를 작업하던 프로그래머들은 private 메서드들을 호출할 수 없었다. 이러한 이해관계를 바탕으로 클래스의 소유자는 그것을 재작성하며, public 메서드가 그 이름과 함수를 유지하는 한 임의로 클래스의 private 메서드를 개조하였다.
최근 몇 년간 많은 스몰토크 프로그래머들은 private 메서드에 대한 해석을 완화하였다. 이제 동일한 클래스뿐만 아니라 밀접하게 협력하는 클래스로부터 private 메서드를 호출하는 경우도 허용하고 있다. Privacy를 각 클래스 내부로 제한하지 않고 클래스의 프레임워크 또는 서브시스템 내부로 확장해왔다.
일부 스몰토크에서는 메서드의 주석에 "private"이라는 단어를 단순히 추가하여 메서드가 private하다고 나타낼 수 있다. 그 외 스몰토크에서는 메서드를 특수 범주에 위치시키거나 모든 public 선택자 리스트와 모든 private 선택자 리스트 간 토글이 가능한 버튼을 이용하여 private로 선택을 가능하게 해주는 향상된 브라우저를 제공한다.
어쨌든 모든 사례에서 메서드를 private하게 지정하는 것은 정보 제공을 목적으로 한다. C++ 컴파일러와 달리 스몰토크 컴파일러는 당신의 코드가 호출하는 메서드가 private한지 여부는커녕 심지어 메서드가 존재하는지조차 결정내릴 수 있는 방법이 없다. 따라서 메서드 작성자(author)가 의도한 바와 다를 수도 있지만 당신의 코드가 private인지 여부와 상관없이 원하는 메서드는 자유롭게 호출할 수 있다. 짧게 말해, 스몰토크 privacy는 권장용이지 강제성은 없다.
❏ 일부 스몰토크 클래스를 여기저기 훑어보고 몇 가지 private 메서드를 찾아라.
❏ Checking account 에서 어떤 메서드를 private 하게 표시해야 하는가? 끌어당기거나 Methods 메뉴를 팝업시켜 Change public/private 를 선택하여 private 으로 만들어라.
논평: getters와 setters
인스턴스 변수를 리턴하는 데에 그치는 getDate 와 같은 간단한 메서드들을 "getter" 메서드라고 부른다. 스몰토크 스타일리스트는 보통 getters를 아래와 같이 간결하게 작성한다:
date
^date
이러한 스타일은 생긴 것만큼 모호하지는 않다: 첫 번째 date 는 메서드 선택자이고 두 번째는 transaction 객체 내 인스턴스 변수이다.
getter 의 반대가 바로 "setter" 인데, setDate: 또는 간단히 date: 와 같은 선택자가 붙는다. 메서드는 아래와 같이 작성할 수 있겠다:
date: aDate
date := aDate
getters와 setters는 (둘을 함께 accessor 메서드라고 부른다) 객체 지향 프로그래밍에서 일반적인 pedestrian(보행자) 메서드지만 남용하지 않도록 한다. 모든 인스턴스 변수마다 public getter와 setter를 작성하는 것은 캡슐화 정신에 위배되는데, 그 이유는 어떤 객체든 인스턴스 변수로 접근할 수 있다고 알리기 때문이다.
반면, 멋진 학파들은 전부 모든 인스턴스 변수마다 private getter와 setter를 작성할 것을 권한다. 객체 자체는 getter와 setter 메서드의 호출을 통해서만 자신의 인스턴스 변수로 접근해야 하며 절대 직접적으로 접근해선 안 된다. 이러한 규칙으로 인해 디자인의 불안전성은 줄어든다. 예를 들어, 지금은 인스턴스 변수인 정보를 추후에 전혀 다른 객체로 이동하기로 결정했다고 치자. Getter와 setter가 없다면 인스턴스 변수를 건드는 모든 메서드는 부서진다. Getter와 setter가 있다면 getter 와 setter 메서드만 재작성해야 한다.
이를 비롯해 다른 스몰토크 코딩 규칙과 그 원리에 대한 논의는 [Skublics et al. 1996]을 참고한다.
요약
이번 장에 실린 연습에서는 작고 인위적이지만 완전히 작동하는 애플리케이션을 소개하였다. 그 말인즉슨, 모든 애플리케이션 로직이 존재하고, 올바르게 실행된다는 말이다. 하지만 스몰토크 애플리케이션에서 기대했던 창, 버튼, 스크롤바는 존재하지 않는다는 사실을 눈치 챘을 것이다. 당신의 애플리케이션엔 이렇다 할 만한 사용자 인터페이스가 아직 없다.
이러한 부재가 우연은 아니다. 제 11-13장에 걸쳐 살펴보겠지만, 진지한 객체 지향 개발자들은 자신의 인터페이스 코드를 애플리케이션 로직으로부터 분리시키기 위해 최선을 다한다. 제 7장의 당좌 예금 계좌(checking account) 애플리케이션을 디자인하는 동안 우리는 사용자 인터페이스가 아니라 애플리케이션 객체에만 중점을 두었다. 따라서 창(Windows)은 나중에 다루겠다.