TheArtandScienceofSmalltalk:Chapter 13
- 제 13 장 스몰토크에서 코딩하기
스몰토크에서 코딩하기
앞장에서는 스몰토크를 위한 디자인의 여러 측면들을 살펴보았다. 그 과정에서 논한 조언과 기법들은 스몰토크에 최적화되었고 스몰토크의 맥락에서 제시되었다. 그렇긴 하지만 구체적인 코딩 기법에 관해서는 그다지 많이 논하지 못했다. 이번 장에서는 스몰토크에서의 코딩을 논하면서 균형을 바로잡고자 한다.
본장에 걸쳐 스몰토크 코딩 스타일의 많은 측면들을 고려할 것이다. 우선 스몰토크 소스 코드를 좀 더 가독성 있게 만드는 데 도움이 되는 공통된 명명 규칙을 다룰 것이다. 그리고 재사용성을 향상시키는 인스턴스 변수와 상수로 접근하는 방법을 살펴볼 것이다. 그 다음으로는 코딩과 관련된 일반적인 조언과 메서드를 구조화하는 방법 등을 고려할 것이다. 마지막으로 주석을 사용하는 방법을 다룬 다음 효율적인 코드의 작성에 관해 논함으로써 마무리하겠다.
스타일이 있는 스몰토크
지금쯤 깨달았겠지만, 스몰토크에서 프로그래밍을 하는 행위는 사실 당신이 원하는 일을 코드의 본문(body)을 확장시켜 (표준 클래스 라이브러리) 대신 시키는 행위다. 표준 클래스 라이브러리는 다년간 개발되어 왔다 (사실상 스몰토크가 1972년부터 존재했다는 점을 감안하면 당신이 생각하는 것보다 오랜 시간일 것이다!). 결국 스몰토크 언어를 시간 특정적인 방식으로 사용하는 방법 또한 진화해왔다. 공통적인 스몰토크 '관용구(idiom)'가 있다고 말할 수 있겠다.
스몰토크 관용구는 프로그래밍 아이디어를 표현하기 위해 언어를 사용하는 특정 방법이다. 이것은 클래스 라이브러리 내 대부분의 코드에서 공유된다. 클래스 라이브러리는 많은 사람이 개발해왔지만 스타일은 줄곧 놀라울 정도로 일관성을 보여왔다. 이러한 특징은 매우 유용한데, 스몰토크에서 가장 중요한 일-소스 코드만 읽어도 클래스 또는 메서드가 하는 일을 이해하는 일-을 행하도록 도와주기 때문이다. 또한 특정 프로그래밍 구성-강력하고, 이해가 쉬우며, 수정 및 관리가 수월한 구성-을 표현하는 방식을 처음부터 제공한다.
하지만 때로는 아이디어를 표현할 수 있는 다른 방식들이 떠오를 때가 있을 것이다. 이런 경우 꽤 혼란스러울 수 있으므로 스몰토크를 사용하는 데 있어 일관성의 중요성을 설명할 필요가 있겠다. 표준 사용 스타일을 이용할 경우 당신의 코드는 그것이 확장하는 클래스 라이브러리 내 코드와 잘 들어맞을 것이다. 다른 프로그래머들도 더 쉽게 읽고 이해할 수 있을 것이다. 뿐만 아니라 재사용 가능성도 향상된다.
명명 규칙
스몰토크에는 클래스, 변수, 메서드, 카테고리, 프로토콜을 명명하는 것과 관련해 규칙이 매우 적다. 그나마 있는 규칙은 제 4장, 스몰토크 언어에서 다루었다. 여기서 다루고자 하는 내용은 스몰토크 프로그래머들이 그들의 클래스, 변수, 그 외 엔티티(entity)를 명명하는 방식을 제어하는 규칙들이다. 이러한 규칙들은 스몰토크 코드를 특정 언어, 대부분 영문 식으로 읽을 수 있도록 시간이 지나면서 진화되어 왔다.
변수
우선 전역 변수, 풀 변수, 클래스 변수는 모두 대문자로 시작되지만 인스턴스와 임시 변수는 소문자로 시작되는 규칙은 항상 준수해야 한다 (시스템에서 완전히 강요하는 것은 아니지만). 그러고 나서 변수명은 가능한 한 기술적(descriptive)으로 만들도록 하라.
변수명은 원하는 만큼 길게 만들 수 있다. 변수의 기능과 함께 변수가 포함해야 할 객체 타입을 표현할 수도 있겠다. 가령 occupiedRectangle, labelText, examResult 와 같이 명명할 수 있다. 마지막 예는 복수로서 examResult 객체들의 컬렉션을 나타내는 데 공통된 규칙을 사용한 것이다.
부울 상태(Boolean state)를 보관하는 변수를 명명하고 싶다면 Isbig.wasConverted, hasBeenEdited와 같은 이름을 사용하라. 이러한 종류의 이름을 사용하면 다음과 같은 전형적인 스몰토크 표현식을 작성할 수 있다:
occupiedRectangle hasBeenEdited ifTrue:
["DoSomething"].
이러한 경우, hasBeenEdited는 occupiedRectangle 객체를 인스턴스로 가진 어떤 클래스에서 hasBeenEdited 라 불리는 인스턴스 변수에 대한 accessing 메서드에 해당한다.
매개변수
메서드 정의부에서 매개변수를 명명하는 일반 규칙으로는, 전달될 것으로 예상되는 객체의 클래스명 앞에 'a' 혹은 'as'를 붙이는 것이 적절하다. 예를 들자면 anOrderedCollection, aDictionary, aString과 같다. 메서드 내 매개변수가 여러 다른 클래스의 인스턴스일 가능성이 있다면 최하위 공통 슈퍼클래스를 사용하라. 가령 aCollection이라거나, 극단적인 경우 anObject가 되겠다. 후자가 만일 어떤 클래스의 객체든 매개변수로 전달할 수 있다는 사실을 알리는 경우, 생각하는 것만큼 의미가 크지는 않다.
메서드명
변수명과 마찬가지로 메서드명도 (또는 기술적으로 알려진 경우 선택자명) 목적을 기술하도록 하라. 길이에는 제한이 없으므로 원하는 만큼 (물론 온당한 범위 내에서) 가능하다. 클래스 계층구조를 살펴보면 정말로 엄청나게 긴 메서드를 몇 개 찾을 수 있을 것이다!
변수로의 접근이 유일한 목적인 메서드는 그것이 접근하는 변수의 이름을 따야 한다. 예를 들어, size라는 인스턴스 변수가 있다면 그 값을 리턴하는 메서드는 returnSize, getSize, giveSize와 같은 이름이 아니라 size로 명명해야 한다. 이와 비슷하게, 값을 설정하는 메서드 또한 setSize: 와 같은 이름 대신 size: 로 명명되어야 한다.
이와 비슷한 맥락에서 메서드 또한 명령형(imperative)보다는 선언적(declarative)으로 명명하도록 한다. 다시 말해 computeTotalArea보다는 totalArea를 사용하는 편이 낫다. 이 규칙은 메서드명이 메서드가 리턴하는 값의 종류를 알리도록 해준다. 메시지를 캐스케이드(cascade) 시 특히 중요하다. 아래 두 표현식을 비교해보라:
CricketPitch area asSquareMetres.
CricketPitch giveArea convertToSquareMetres.
언뜻 보면 그 차이가 미묘할지 모르지만 스몰토크 프로그래머에겐 첫 번째 표현식이 두 번째 보다 훨씬 더 명확하게 읽힌다. 'covert'와 같은 단어를 정말로 사용하고 싶거든 대신 과거분사형을 사용하라-convertedToSquareMetres.
여러 개의 매개변수를 취하는 메서드를 명명시에는 목적을 전달하고, 가능하다면 각 매개변수의 타입을 전달하는 이름을 생성하라. 다음을 예로 들어보겠다:
PaintBox drawLineFrom: x to: y
usingPen: aPen inColour: ftred.
이러한 경우, 메서드명을 선언적으로 생성해야 한다는 규칙을 어겼음을 주목하라 (lineFrom 대신 drawLineFrom...로 명명). 그 이유는 우리가 메서드의 리턴 값이 아니라 부가적 효과에 (그림이 그려지는 선) 관심이 있기 때문이다.
마지막으로 메서드를 명명할 때는 메서드가 제공하는 서비스를 생각해야지 메서드가 제공하는 방식을 생각해선 안 된다. 다시 말하자면, 메서드명은 구현부가 아니라 인터페이스부를 따라 명명해야 한다. 그러면 구현부가 작용하는 방식과 관련된 사항을 메서드를 명명하는 방식에 따라 노출하는 것을 피할 수 있을 것이다.
클래스
클래스명은 사실 전역 변수임을 기억하고, 그에 따라 대문자로 시작되어야 한다는 규칙을 지켜야 한다. 앞에서와 같이 클래스의 목적을 전달하는 것이 목표다. 하지만 클래스 계층구조와 관련해 중요한 내용을 전달하길 원할 수도 있다. 예를 들어, OrderedCollection은 Collection의 서브클래스다. (일례로) OptimizedOrderedCollection이라 불리는 서브클래스를 생성할 수 있을 것이다. 이러한 명명 규칙이 항상 필요하거나 바람직한 것은 아니며, 사실 판단의 문제다. 가령 Set 또한 Collection의 서브클래스이지만 이번 경우 'Set'라는 단어는 어떤 부자연스러운 이름보다도 클래스의 목적을 잘 전달한다.
자신의 이니셜, 프로젝트명 또는 회사명을 클래스명 앞에 붙이고 싶은 유혹에 빠지지 말길 바란다. 다른 시스템 클래스로부터 쉽게 구분할 수 있을 것 같지만 사실 서로 구별하기가 더 힘들다. 어떻게 해서든 당신의 클래스를 시스템에 통합시키는 것을 목표로 삼아야 한다.
카테고리와 프로토콜
카테고리와 프로토콜은 당신이 클래스와 메서드를 조직하도록 돕기 위해 존재한다. 이를 용이하게 하는 이름을 사용하라. 간격 문자를 이용할 수 있음을 기억하라. 어떤 사람들은 카테고리에서 클래스를 누가 작성하였는지를 나타내는 접두사를 붙이는 것을 좋아한다. 카테고리명에는 이것이 허용되는데 그 이유는 코드에 표시되지 않기 때문이다. 클래스 라이브러리 내 카테고리는 일반적으로 2 수준(two-level)의 명명 방식을 이용하는데, 자신의 프로젝트에 이 방식을 이용할 것인지 고려해볼 필요가 있다. Server-Views 또는 Framework-Datastore를 예로 들 수 있겠다.
앞에서 우리는 시스템 라이브러리 내 클래스들이 사용하는 '표준' 프로토콜명을 살펴본 바 있다. accessing, updating, displaying, private과 같은 이름은 스몰토크 관용구의 기본을 이룬다. 본문에서 논한 명명 규칙을 사용한다면 이것을 사용하라. 이는 다른 사람들이 당신의 코드를 쉽게 읽도록 만드는 데 가장 큰 역할을 하는 요인들 중 하나일 것이다. 하지만 이름의 사용에 그치는 것이 아니라 적절하게 사용해야 한다. 가령 accessing 에는 인스턴스 변수로 접근성을 제공하는 메서드 외에는 어떤 것도 넣지 말라. 메서드가 private할 경우 (본인만 사용 시) private 프로토콜에 넣어라. 새 프로토콜명을 생성해야 한다면 당신 또는 코드를 이해하려는 다른 사람의 작업을 항상 유념하여 이름을 지정하도록 하라. 프로토콜명으로 현재 분사-'ing'로 끝나는 단어-를 사용하는 지침을 따르는 것이 좋겠다. calculating이나 printing을 예로 들 수 있겠다.
인스턴스 변수 접근하기
스몰토커들 사이에서 종종 뜨거운 논쟁 주제가 되는 스타일 문제는 인스턴스 변수의 접근과 관련된다. 클래스가 인스턴스 변수를 정의하거나 상속할 경우, 그 변수들은 단순히 변수를 명명함으로써 클래스에서 정의된 메서드 내에서 접근 가능하다. 이는 변수에 값을 할당할 때나 표현식 내 변수의 값을 이용할 때에도 적용된다. 예를 들어, 아래 표현식은 area, width, height라 불리는 인스턴스 변수들을 가진 클래스의 메서드 내에서 적합(legal)하다:
area := width * height.
일반적으로 프로그래머는 다른 객체들이 메시지를 전송함으로써 변수로 접근하도록 허용하는 accessing 메서드들도 정의할 것이다. 이러한 경우, 위의 인스턴스 변수는 아래와 같은 표현식을 이용해 그들 자체의 객체 외부에서부터 접근할 수 있다:
MyObject area.
MyObject with: 25.
하지만 가끔은 객체가 직접 명명하지 않고 자신(itself)에게 메시지를 전송함으로써 자신의 인스턴스 변수로 접근할 것을 권하기도 한다. 이러한 경우, 앞의 메서드 단편은 아마 아래와 같이 변할 것이다 (명확성을 위해 괄호를 추가했다):
self area: (self width) * (self height).
area:, with, height 메서드가 올바르게 정의되었다면 (변수로의 접근 이외에는 어떤 일도 하지 않는다), 위의 표현식은 첫 번째 표현식과 정확히 같은 효과를 보일 것이다. 그렇다면 왜 이것을 사용하는 사람이 있을까? 바로 인스턴스 변수로의 직접 접근을 피함으로써 잠재적으로 유연성을 증가시키고 그 결과 재사용 가능성을 향상시킬 수 있기 때문이다. 그렇다면 대체 어떻게 가능할까?
인스턴스 변수는 실제로 객체의 프로퍼티를 나타낸다. 구체적으로 말하자면, 그들은 필요 시 계산되는 것이 아니라 실제 값이 객체에 저장된 (혹은 캐시 저장된) 프로퍼티들이다. 하지만 인스턴스 변수로서 생을 시작하게 만드는 것이 추후 계산된 값이 되어야 하는 경우도 있다. 여기서 그 인스턴스 변수가 메서드를 통해서만 접근이 가능하다면, 이는 accessing 메서드를 동일한 이름이지만 단순히 값을 리턴하는 대신 임의로 복잡하게 값을 계산하는 메서드로 교체하는 간단한 작업이 된다. 직접 명명을 통해 변수로 접근한 경우 상황은 더 복잡해진다. 새 메서드가 생성되어야 하고, 클래스와 그 모든 서브클래스에서 변수에 대한 모든 참조는 새 메서드를 호출하는 메시지로 변경되어야만 한다.
이 과정은, 변수가 슈퍼클래스에서 정의되어 있는데 서브클래스에서 계산된 값으로 만들길 원하는 경우에 특히 복잡하고 지저분해진다. 슈퍼클래스로 가서 수정하지 않는 한 (아마도 허용하지 않겠지만) 서브클래스에서 변수로 접근하는 슈퍼클래스의 모든 메서드를 오버라이드하여 변수에 대한 모든 참조를 메시지 표현식으로 대체해야 한다. 슈퍼클래스의 원 작성자가 애초에 accessing 메시지를 사용했다면 자신의 서브클래스에서 accessing 메서드만 오버라이드하면 된다.
위의 예를 다시 살펴보면, area라는 인스턴스 변수가 있다고 상상할 수 있겠다. 우리는 단순히 area 변수의 값을 리턴하는 accessing 메서드를 정의할 수 있다:
area
^area.
이제 나머지 코드에서 일관되게 사용한다면 후에 (서브클래스에서, 또는 동일한 클래스의 향후 버전에서) 'area' 개념을 캐시 저장된 값 대신 계산된 값으로 대체하고 싶을 때마다 단순히 area 메서드를 재정의하면 되는데, 그 모습은 아래와 같겠다:
area.
(width * height).
이제 area로 얼마나 많은 참조가 있는지는 중요하지 않다. 그들은 메시지 표현식일 뿐 새로 계산된 값에 자동으로 접근할 변수 참조가 아니기 때문이다. 그보다도 변수에서 계산된 값으로 변경되는 일이 만일 서브클래스에서 발생할 경우, 메서드 검색의 작용 방식 때문에 슈퍼클래스의 인스턴스 내 area를 향한 참조는 계속해서 오래된 메서드를 호출하는 반면 (인스턴스 변수의 값을 리턴), 서브클래스의 인스턴스는 새 메서드를 호출할 것이다 (계산된 값을 리턴).
메시지를 통해서만 인스턴스 변수로 접근 시 주장하는 이익은 이쯤에서 그만해 두고 단점으로는 무엇이 있을까? 가장 눈에 띄는 단점은 성능이겠다. 메시지를 통해 자신만의 인스턴스 변수로 접근할 경우 직접 참조 시보다 훨씬 느리다. 접근하는 메시지는 당신이 직접 할 때와 동일한 변수 참조를 실행하므로, 메시지 전달을 위한 시간이 추가되어야 한다.
이것이 자신의 코드 성능에 영향을 미치는지, 그 영향이 앞에서 논한 이점을 포기할만한 가치가 있는지에 대한 결정은 본인에게 달려 있다. VisualWorks Advanced Programming ObjectKit 의 일부로 제공되는 프로파일러를 이용해 직접 참조할 변수가 무엇인지, accessing 메서드는 어디에 사용해야 하는지를 결정할 수 있겠다. 또한 accessing 메시지의 사용을 향상시킬 수도 있다 (예: 임시 변수를 반복 접근하는 대신 루프 내에서 임시 변수 내 값을 캐시 저장함으로써).
메시지 표현식을 통한 변수 접근은 직접 접근에 비해 가독성이 떨어진다는 단점도 있다. 하지만 어떻게 보면 요점의 일부이기도 하다. 즉, 여기서 목적은 값의 사용자를 인스턴스 변수 또는 계산된 값으로서 그 값의 구현부로부터 분리시키는 것이다. 다시 말하지만, 코드의 명료성과 캡슐화 중 무엇이 중요한지는 개인의 판단이자 의견의 문제다.
마지막으로, 'private' 변수에 대한 (클래스의 인스턴스가 정의되거나 상속된 곳 외부에서 접근하도록 의도하지 않은 변수) accessing 메서드를 정의해야 한다는 사실도 불편하게 느낄지 모르겠다. 하지만 스몰토크에선 사실상 어떤 것도 private하지 않다는 사실을 기억하라. 당신이 할 수 있는 일은 프로토콜을 올바르게 명명함으로써 자신의 의도를 표시하는 것이다 (예: private-accessing). 결국 다른 사람이 항상 당신의 클래스에 그들만의 accessing 메서드를 정의하거나 object에서 정의된 instVarAt;을 이용할 수 있다. (브라우저를 이용해 해당 메서드가 하는 일을 살펴보되 '해커'라는 낙인을 원치 않는 이상 사용하지 말라!)
상수 접근하기
위의 모든 내용은 클래스 변수, 전역 변수, 상수에 동일하게 적용됨을 기억하라. 메서드를 이용해 어떤 방식으로든 다른 객체로 연결된 클래스를 정의 및 접근하는 경우가 훌륭한 예가 되겠다. 이는 보통 뷰와 함께 사용되는 컨트롤러의 클래스를 리턴하기 위해 defaultController-Class를 구현하는 다수의 뷰 서브클래스에서 발생한다 (이 때문에 컨트롤러가 MVC 삼자관계의 배경에 숨는 것처럼 보이는 것이다). 이는 뷰 클래스에 숨겨진 컨트롤러 클래스에 대한 모든 유형의 직접 참조보다는 클래스가 변경될 수 있는 한 장소를 제공한다. 이러한 사용 유형의 예를 확인하려면 default 의 구현자(implementor)를 살펴보라.
재래식 프로그래밍에서와 같이 당신이 작성하는 클래스에 중요한 다른 기본 상수가 있다면 자신의 메서드 곳곳에 하드코딩하는 대신 상수를 메서드에 넣어 위의 방식으로 접근하라. 가령, 아레와 같은 메서드를 정의하여:
minimumSize
"259100.
필요 시 아래를 이용해 minimumSize로 접근하라:
self minimumSize.
메서드 구성하기
클래스에 메서드를 작성 시 실행해야 하는 중요한 일들 중 하나는 되도록 작게 유지해야 한다는 점이다. 10행이 넘으면 너무 큰 것으로 간주될 것이다 (표준 클래스 라이브러리에서 평균은 약 7행정도다). 너무 긴 메서드의 기능은 두 개 또는 그 이상의 메서드로 뽑아내도록 하라. 중간 결과를 보유하기 위해 임시 변수를 사용하는 것처럼 이 또한 코드를 더 읽기 쉽고, 수정하기 쉬우며, 효율성의 급격한 감소를 막아준다. 이러한 메서드를 가능한 한 일반 목적으로 만들어, 주 메서드에서 실제로 당신이 필요로 하는 기능을 '구성'하도록 하라. 그러면 코드를 수정해야 할 때 훨씬 더 많은 유연성을 제공할 것이다.
자신의 코드를 일관된 방식으로 포맷하도록 하라. 어떤 사람들은 내장된 포매터를 사용한다 (코드 페인의 operate 메뉴에서 format 선택). 또 내장된 포매터가 생성하는 포맷을 싫어하는 사람도 있다 (이론상 본인이 변경할 수도 있지만!). 어떤 방식이든, 일관된 포맷을 이용하는 것이 코드의 읽기, 디버깅하기, 수정하기 등 모든 작업을 수월하게 만들 것이다. 추가 괄호를 사용 시 물론 본인만 볼 수 있지만 코드를 읽는 데 더 수월하다면 마음껏 추가하도록 하라. 단, 포매터가 (당신이 사용 시) 다시 제거할 것이다!
메서드는 명시적으로 어떤 값도 리턴되지 않을 경우 자동으로 self를 리턴한다. 하지만 메서드가 self를 리턴한다는 사실이 디자인에서 중요한 기능일 경우, self를 이용해 명시적으로 만드는 것을 고려해볼 수 있겠다. self는 메서드에서 리턴하고 싶은 지점에 도달했으나 리턴 값을 신경 쓰지 않는 경우 리턴해야 하는 값이기도 하다 (가령 nil과 같은 값 대신). self는 캐스케이드에서 nil로 전송된 다음 메시지를 받는 (가상적으로 보장하건대 이해하지 못할 것이다) 대신 당신의 객체로 메시지를 캐스케이드(cascade)하도록 허용한다.
조건부 표현식을 사용하고자 할 경우 (if True:, if False: 등) 다른 객체로 메시지를 전송하기 위해 자신의 메서드를 구성할 때와 같은 효과를 얻는지 생각해보라. 다시 말하자면, 메서드 검색 메커니즘과 그 다형적인 효과가 동일한 결과를 이끌어내도록 하라. 특히 자신이 원하는 것을 결정하기 전에 객체의 클래스를 테스트하고 있는 자신의 모습을 발견할 때 두드러진다. 이것은 자신의 디자인이 올바로 구성되지 않았다는 확실한 징조다.
하지만 객체의 클래스보다는 객체의 기능성을 테스트하는 것이 바람직하다. 예를 들어, implementsLookAhead 라는 메서드는 여러 개의 클래스에서 정의할 수 있을 것이다. 메서드는 특정 클래스가 (대상이 무엇이든) 'look ahead(미리보기)' 를 구현하는지 여부에 따라 true 또는 false를 리턴할 것이다. 그러면 다음과 같은 표현식을 사용할 수 있다:
MyObject implementsLookAhead
ifTrue: ["do one thing"]
ifFalse: ["do another thing"].
여기서 핵심은 무엇을 할 것인지 결정이 객체의 클래스와 무관하게 이루어진다는 사실이다. 따라서 프로그래머에게 클래스 구조를 수정할 필요 없이 자신의 선택이 작용하는 방식을 수정할 수 있는 자유가 부여된다. (정말로 급해지면 사실 object 객체 상에서 정의된 respondsTo: 메서드를 사용하여 특정 객체가 메시지를 이해하는지 테스트할 수 있다. 하지만 독한 해커가 아니라면 권장하지 않는다.)
마지막으로, 여느 프로그래밍 언어에서와 마찬가지로, 변수를 사용하기 전에 모두 확실히 초기화시켜야 한다. 시스템에 의해 nil로 초기화되긴 하지만, 정작 nil의 값을 원한다면 명시적으로 발생하도록 할당문에 넣는 편이 독자에게 분명할 것이다.
기타 코딩 지침
스몰토크 명명 규칙, 스몰토크에서 변수와 상수로 접근하는 방식과 메서드를 구성하는 방식을 살펴보았으니 기타 코딩 지침들을 살펴볼 것이다. 이 모든 것은 상식 범주에 속한다. 이는 일반적으로 유용하면서도 뭔가 다른 것을 시도하고자 하는 특정 상황이 항시 존재할 것을 의미한다.
이질적 상태의 변수
부울 상태를 처리할 때는 0과 1이나 nil과 non-nil(nil이 아닌) 객체가 아니라 항상 false와 true를 사용하라. 이러한 값에 대한 테스트는 컴파일러에 의해 최적화된다 (따라서 그 정의부는 당신이 변경할 수 없는 몇 안 되는 코드 조각임을 의미). 이는 당신이 원하는 것을 코드가 말하도록 만들기도 하는데, 특히 변수를 적절하게 명명 시 그러하다 (isCooked, wasRaw 등).
고정된 수의 값을 취하는 변수가 필요하다면 가령 값을 인코딩해야 할 숫자가 아니라 기호를 사용해보라 (문자열이 아니라 기호다. - 기호는 유일하고 == 와 비교가 가능하므로 더 효율적이다). 예를 들어, 어떤 대상을 인코딩하기 위한 크기가 3, 2, 1임을 설명하기 위해서는 #large, #medium, #small 을 사용하라. 아래 세 개의 표현식 중 첫 번째가 하는 일보다 두 번째 표현식이 하는 일을 이해하기가 얼마나 쉬운지 생각해보라. 세 번째 표현식도 쉬우나 size=#large 테스트를 캡슐화하고 true 또는 false를 리턴하는 isLarge라는 메서드를 작성할 때 이용 가능해진다.
MyObject size = 3 ifTrue; ["do something"1.
MyObject size = ftlarge ifTrue: ["do something"].
MyObject isLarge ifTrue; ["do something"].
Dictionaries 사용하기
Dictionaries는 표준 클래스 라이브러리에서 가장 유용한 기본 요소(building block) 중 하나이다. 한 쌍의 객체를 보유하고 'name'과 'object'로 구성되는 단순한 데이터 구조로서 사용하는 모습을 쉽게 상상할 수 있다. 하지만 dictionaries는 어떤 종류의 객체도 유지할 수 있음을 기억하라. 이에 따라 훨씬 더 복잡하고 강력한 사용이 가능해진다. 예를 들어, 블록을 값으로 넣고 다른 객체를 키로 넣음으로써 dictionary를 제어 구조의 한 종류로 사용할 수 있다. 이는 일종의 'case'문을 원하는 대로 구성하도록 해준다. 아래에서 첫 번째 표현식은 이러한 방법으로 초기화된 dictionary를 (한 번만 발생해야 한다) 보여주고, 두 번째 표현식은 그것을 사용할 수 있는 방법을 보여준다:
MyDict at: #Small put: ["do a small thing"];
ati ^Medium put: ["do a medium thing"];
at: #Large put: ["do a large thing"].
(MyDict at: case) value.
유일한 객체 관리하기
프로그램에서 특정 타입으로 된 객체 하나만으로 충분할 것 같은 때가 종종 있다. 예를 들어, 유일한 자원의 유형-파일시스템, 중앙 검색 테이블(central look-up table) 또는 어쩌면 외부 인터페이스까지-을 관리하는 임무를 가진 객체가 필요할지 모른다. 이러한 상황에서는 이 객체를 클래스로 만들고 싶어진다. 다시 말하자면, 특수 클래스를 생성하고 이 객체의 일을 수행하는 클래스 메서드를 작성하는 것이 논리적으로 보인다는 말인데, 특히 객체가 시스템에 걸쳐 잘 알려진 유일한 이름을 필요로 하는 경우 더 그러하겠다 (예: classname).
하지만 그러한 객체를 생성하는 방법이 최선은 아니다. 오히려 클래스를 생성하고 그 일을 수행하는 단일 인스턴스를 생성하는 접근법이 훨씬 더 낫다. 가장 큰 이유는 어느날 당신이 (혹은 다른 누군가가) 이렇게 분명히 유일한 객체의 인스턴스를 하나 이상 필요로 하게 될 것인지 절대 확신할 수 없기 때문이다. 유일한 객체는 인스턴스의 기능성만 상속할 뿐, 클래스의 부적절한 기능성까지 상속하지는 않기 때문이기도 하다. 적절한 기능성만을 상속하는 것은 클래스를 디자인할 때 사용해야 하는 지침들 중 하나라는 사실을 기억하라.
실제에서 이러한 상황에 대처하는 올바른 방법은, 클래스를 생성하고 클래스에게 Default라는 클래스 변수를 제공하는 것이다. 그리고 필요한 클래스의 단일 인스턴스를 보유하도록 클래스 변수를 초기화하는 initialize 라는 클래스 메서드를 생성하라. 마지막으로, Default 클래스 변수의 값을 리턴하는 default 라는 클래스 메서드를 생성하도록 한다. 그러고 나서 나머지 코드가 이 객체를 사용하길 원하면 MyClassName default 라는 표현식을 이용해 참조할 수가 있다. 당신 또는 다른 누군가가 클래스의 인스턴스를 더 생성하고 싶다면 언제든 생성할 수 있다. 이러한 인스턴스를 보관할 추가 클래스 변수를 생성하길 원할지도, 그렇지 않을지도 모른다. 이러한 방식으로 코드를 구성하는 클래스의 예를 보려면 런처의 Browse->implementors of... 명령을 이용해 default의 구현자를 확인하라.
액션 실행취소(unwinding)
때로는 당신이 실행한 액션을 취소하는 것이 중요한데, 특히 예외가 발생 시 더하다. 전형적인 예가 바로 열린 파일의 닫기인데, 스몰토크에서는 시작된 프로세스를 종료하는 것도 똑같이 중요하다. 이를 해결하기 위해 BlockClosure 클래스는 valueNowOrOnUnwindDo: 라는 메서드 형태를 제공한다. 해당 메시지를 블록으로 전송하되 다른 블록을 매개변수로 할 경우, 시스템은 첫 번째 블록을 실행한 후 심지어 첫 번째 블록이 예외를 발생시키더라도 두 번째를 실행하며 실행한다. 이는 개발 도중에 깔끔하게 정돈 시 매우 유용하다-수백 개의 열린 파일이나 창이나 프로세스가 실행되는 일이 발생하지 않도록 보장한다.
이러한 문제를 피하기 위해, 파일의 열기 또는 프로세스의 파생(spawning)을 고유의 메서드로 캡슐화하는 방법이 있다. 파일이나 프로세스에 대한 참조를 (예: 인스턴스 변수 내에) 유지하도록 하고, 메서드를 호출할 때마다 파일이 열리거나 프로세스가 실행되는지 확인한 후 다른 파일이나 프로세스가 열리기 전에 닫거나 종료(kill)시켜라.
시스템 클래스 수정하기
스몰토크가 구현부를 위해 제공하는 전체 소스코드의 집합은 시스템 클래스를 수정할 수 있는 권한을 명시적으로 제공한다. 이것은 매우 강력한 기능으로, 여느 비슷한 기능과 마찬가지로 조심히 취급해야 한다. 하지만 시스템 클래스를 절대로 수정해선 안 된다는 의미는 전혀 아니다.
개발 툴에서 새 기능처럼 간단한 기능성을 추가하길 원한다면 그렇게 해야 한다. 프로그래머에 의한 이러한 수정이 바로 개발 환경을 지금과 같이 강력하게 만든 것이므로, 더 향상시킬 수 있다면 망설이지 마라. 무언가 마음에 들지 않는다면 바꿔라.
Object 또는 Behavior 처럼 좀 더 기본적인 클래스를 수정할 계획이라면 좀 더 신중해야 할 것이다. 여기에는 두 가지 위험이 있다-시스템을 파괴할 수 있고, 타인의 변경 내용과 호환되지 않는 변경내용을 추가할 수 있다.
시스템 클래스를 수정하지 않고 당신이 원하는 효과를 얻을 수 있는지 살펴보라. 또한 당신이 원하는 수정이 다른 변경사항과 어떻게 상호작용할 것인지도 확인하라. 마지막으로, 근본적으로 스몰토크가 행동하는 방식을 얼마나 변경할 예정인지 고려해보라. 이러한 사항들을 고려한 후에도 여전히 시스템 클래스를 수정하고 싶다면 주의를 기울여 진행하라. 스몰토크의 가장 강력한 기능 중 하나로 당신이 원하는 언어를 확장할 수 있는 능력을 꼽을 수 있다. 당신은 (또한 사람들은) 시스템 클래스의 수정을 통해 영구 기억 메커니즘을 추가하고, 분산 객체 시스템을 생성하며, UI가 작동하는 방식을 변경할 수 있고, 심지어 상속이 작용하는 방식을 변경할 수도 있다. 이 모든 것은 스몰토크 디자이너들이 의도한 바이다. 당신이 맡은 책임을 기억하라.
기타 유의해야 할 점
클래스 계층구조, 특히 Object, Behavior, Class를 살펴보고 있다면 모든 종류의 유용한 메서드를 발견할 것이다. 하지만 사용 시 주의해야 하는 메서드가 한두 개 있다. 무엇보다 perform: 과 become: 을 주의해야 한다. 이 둘 모두 매우 강력한 메서드에 해당한다-perform: 이 '플러그인 가능한' 클래스 작성을 가능하게 하고 허용한다는 사실은 이미 살펴보았다. become: 메서드는 메시지를 수신하는 객체의 상태를 매개변수로서 전송된 객체와 바꾼다. 두 메서드 모두 중요하지만 사용하고 싶다면 잠시 멈추고 '이것을 해야 할 충분한 이유가 있는가?'라고 질문해보아야 한다. 답이 '그렇다'라면 사용하라. 답이 '아니다'라면 사용 전에 주의 깊게 생각해봐야 한다. perform: 메서드는 속도가 느릴 수 있고 코드를 근본적으로 추적할 수 없게(untraceable) 만든다. become: 메서드는 당신이 기대하는 일을 하는 법이 절대로 없으며, 자원을 많이 필요로 한다.
주석 사용하기
거의 모든 프로그래밍 언어와 마찬가지로 스몰토크 역시 주석을 코드로 넣는 방법을 제공한다. 또 다른 프로그래밍 언어들과 같이 주의 깊게 선택한 주석들은 다른 사람들이 당신의 코드를 이해하는 능력을 크게 향상시킨다 (자신의 코드 이해 능력도 마찬가지로, 몇 주, 몇 달, 또는 몇 년이 지나도!). 하지만 그 반대도 마찬가지다. 부적절한 주석은 유해무익할 수 있다. 스몰토크는 주석을 넣을 수 있는 두 개의 장소를 제공하는데, 메서드 내부와 클래스에 덧붙이는 방법이다.
주석은 큰 따옴표("") 한 쌍을 이용해 메서드의 코드로 내포시킬 수 있다. 일반적으로는 각 메서드의 바로 상단에 주석을 넣어 메서드의 목적이 무엇인지, 어쩌면 메서드가 어떤 매개변수를 취하는지 (메서드와 그 매개변수를 신중히 명명했다면 분명하겠지만), 어떤 값을 리턴하는지를 비롯해 그것이 구현된 방법에 관한 특별한 내용을 설명하는 편이 바람직하다. 좀 더 형식적이길 원한다면 생성자(creator)나 일자(date)와 같은 것을 포함하는 수도 있다.
메서드 상단에서 주석을 이용하는 데 있어 일반적이면서 매우 유용한 방법은 메서드의 이용 예제에 넣는 것이다. 이는 누구든 마우스로 예제를 선택하여 do it만 실행하면 당신의 코드가 작용하는 것을 볼 수 있도록 해준다. 이는 종종 클래스 라이브러리, 특히 instancecreation과 initialize 프로토콜 내 클래스 메서드에서 발견될 것이다.
메서드 내 주석은 어떤 일이 어떻게 발생하는지가 아니라 왜 어떠한 일이 발생하는지를 말해야 한다. 'increment index'는 index := index + inc 를 주석으로 하기엔 유용한 방법이 아니다. 약간만 신경 쓰면 스몰토크 코드 자체를 매우 가독성 있게 만들 수 있다. 조그만 주석을 너무 많이 삽입하면 가독성을 해칠 수 있다.
스몰토크 주석은 중첩(nest)되지 않음을 인지하라. 즉, 메서드 내 전체 코드 덩어리에 단순히 큰 따옴표를 두른다고 해서 꺼낼 수는 없다는 말이다. 중첩하는(embedding) 주석이 있을 경우, 컴파일러는 예측 가능한 결과와 함께 스몰토크 코드로서 취급할 것이다!
기타 스몰토크 주석 메커니즘은 텍스트의 기술적(descriptive) 조각을 각 클래스에 넣도록 해준다. 이는 시스템 브라우저의 (좌측 두 번째) 클래스 페인에서 operate 메뉴의 comment를 선택하면 된다. 기존 클래스의 주석을 살펴볼 수도 있고, 자신만의 클래스에 추가할 수도 있다. 원하는 것을 입력한 후 accept를 사용하라. 시스템 클래스가 하는 일을 이해하는 데 도움이 되도록 클래스 라이브러리 내 클래스 주석을 살펴보는 데 익숙해지고 나면 클래스의 다른 사용자들이 유사한 방법으로 주석을 작성한 것에 대해 얼마나 고마워하는지 깨달을 것이다!
효율적인 코드 작성하기
모든 프로그래밍 언어와 마찬가지로 스몰토크에서 작성된 코드의 효율성도 크게 달라진다. 하지만 역사적인 이유로 다른 언어로 작성된 시스템보다 스몰토크 시스템의 효율성과 관해 많은 염려를 표해왔다. 다행히 몇 가지 고찰사항을 고려한다면 다른 언어로 작성된 코드만큼 반응적인 코드를 작성할 수 있다. 스몰토크에만 특정적인 고찰사항은 많지 않다.
첫째, '효율성'의 의미를 고려하라. 코드가 빠르게 실행되길 원하는가, 아니면 메모리를 적게 소모하길 원하는가? 효율성을 위해 가독성을 (또는 재사용 가능성을) 포기할 준비가 되어 있는가? 둘째, 올바른 알고리즘의 사용을 대체할 만한 것은 없음을 기억하라. 실제로 기능성을 효율적으로 구성했는지를 조심스럽게 생각해보라. 셋째, 스몰토크는 코드의 효율성을 평가하고 비효율적인 구현부를 찾아나는 데 매우 유용한 툴을 제공한다. 이러한 기법은 다음 장에서 살펴보겠다.
무슨 언어를 사용하든 항상 비효율적인 특정 연산은 피하는 것이 도움이 된다. 가령 필요로 할 일이 절대 없는 선계산(precomputing) 값은 피해야 한다. 느긋한 평가(lazy evaluation) 는 일부 값의 계산을 실제로 필요할 때까지 늦추는 기법에 붙여진 명칭이다. 객체 지향 프로그래밍은 이것을 캡슐화할 수 있도록 하여 값의 소비자(consumer)가 무엇이 일어나는지 인식하지 못하도록 한다. 하지만 값을 일단 계산하고 나면 다시 필요할지도 모르기 때문에 버리지 말아야 한다. OOP는 이러한 캐싱(caching)을 캡슐화하도록 허용하기도 한다. 이와 비슷하게, 루프 안에서 값을 여러 번 재계산하는 것도 피하라. 계산은 루프 밖으로 이동시킨다.
마지막으로, 스몰토크에서 비효율적으로 알려진 특정 연산들이 있다. Dictionary 클래스는 identityDictionary 보다 속도가 느린데, 훨씬 빠른 = 를 대신해 사용하기 때문이다. Set와 identitySet도 비슷하다. 느린 클래스 대신 이와 같이 빠른 클래스를 골라서 이용할 수 있는데, 단, 키 검색과 세트 멤버십(set membership)의 비교 연산이 상등관계(equality)보다 동등관계(equivalence)를 이용해 이루어지는 데 만족해야 한다. 의존성 메커니즘은 직접 참조보다 느린 객체들 간 의사소통이다. 따라서 속도가 중요하다면 의존성의 이점을 희생할 준비가 되어 있어야 한다. 메모리의 할당은 느리기 때문에 반복해서 큰 객체를 생성하여 머지않아 버리는 행위는 하지 않는다.
이 모든 비효율성은 모두 검증된 것으로, 개발 환경에서 제공되는 벤치마킹, 타이밍, 프로파일링 툴을 이용해 잠재적인 비효율성이 관찰되었다. 자신의 코드에서 비효율성이 의심된다면 이러한 툴을 활용하라. 코드의 기능이 엄청 향상될 것이다.
이번 장에서는 객체 디자인을 작동하는 스몰토크 코드로 해석하는 데 있어 몇 가지 측면에 대한 지침을 살펴보았다. 대부분은 숙련된 스몰토커들의 코딩 스타일을 반영하였기 때문에 표준 클래스 라이브러리에 걸쳐 사용되고 있음을 목격할 것이다. 사실 시스템 클래스 라이브러리는 훌륭한 스몰토크 스타일의 예를 살펴보기에 좋은 장소에 속한다.
본문을 통해 우리는 스몰토크 코드를 좀 더 읽기 쉽도록 해주는 명명 규칙을 비롯해 인스턴스 변수, 상수, 클래스로의 접근을 분리시켜 모듈성을 향상시키는 방법, 메서드를 우아하게 구성하는 방법을 살펴보았다. 그리고 많은 디자인에 나타나는 요소의 표준 코딩 방법을 몇 가지 고려하고, '알아야 할 점'을 살펴보았으며, 유용한 주석을 작성하는 방법을 설명하고 효율적인 코드를 작성하는 방법을 논해보았다.
이러한 지침을 따른다면 코드를 클래스 라이브러리에 더 잘 통합되고 본인을 비롯해 다른 사람에 의해 재사용될 가능성이 향상됨을 발견할 것이다. 대부분의 스몰토커들은 우리가 논한 기법들이 대부분의 상황에 잘 들어맞는다고 생각할 것이다. 하지만 무언가 다른 것을 시도해볼만한 이유가 있다면 그것을 지침으로 삼아야 한다.
다음 장에서는 시스템이 무엇을 하는지 알아내고 자신의 코드를 그곳에 통합하기 위해 시스템을 어떻게 작업해야 하는지 고려할 것이다.