DeepintoPharo:Chapter 12

From 흡혈양파의 번역工房
Revision as of 02:58, 28 February 2014 by Onionmixer (talk | contribs) (DIP 제 12 장 Mondrian을 이용해 시각화 스크립팅하기 페이지 추가)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
제 12 장 Mondrian을 이용해 시각화 스크립팅하기

Mondrian을 이용해 시각화 스크립팅하기

많은 양의 데이터에 의미를 부여하기란 적절한 툴이 없이는 힘들다. 텍스트 출력(textual output)은 그 표현의 유연성(expressiveness)과 상호작용의 지원에 제한이 있는 것으로 알려져 있다.


Mondrian은 시각화의 스크립팅에 있어 도메인 특정 언어에 해당한다. 그 구현은 Roassal 위에서 실행된다 (제 11장 참고). 이는 객체와 그 관계와 관련해 정의된 임의의 데이터를 시각화하고 그와 상호작용한다. Mondrian은 소프트웨어 평가 활동에 주로 사용된다. Mondrian은 소프트웨어 소스 코드의 시각화에 뛰어난 기능을 보인다. 이번 장에서는 Mondrian의 원리를 소개하고 재빠르게 데이터를 구성하기 위한 그 표현식 명령을 설명하고자 한다. 더 상세한 정보는 해당 주제를 본격적으로 다룬 Moose book[1]의 관련 장을 참고하길 바란다. 본문을 읽고 나면 상호작용적이고 시각적인 표현을 생성할 수 있을 것이다.


설치 및 첫 시각화

Mondrian은 Roassal의 일부이다. 설치 절차는 Roassal을 다룬 장을 확인한다. Pharo의 Moose 배포판[2]을 이용할 경우 이미 Roassal이 설치되어 있을 것이다.

첫 시각화

첫 번째 시각화는 워크스페이스로 들어가 아래 코드를 실행하면 얻을 수 있다. 워크스페이스에 아래 코드를 실행하면 Collection 클래스 계층구조가 보일 것이다.

| view |
view := ROMondrianViewBuilder new.
view shape rectangle
    width: [ :cls | cls numberOfVariables*5 ];
    height: #numberOfMethods;
    linearFillColor: #numberOfLinesOfCode within: Collection withAllSubclasses.

view interaction action: #browse.

view nodes: ROShape withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.
view open


시각화는 아래와 같이 읽힐 것이다.

  • 각 클래스는 도표에 상자로 표현된다.
  • 상속은 상자 사이의 edge로 표현된다. 슈퍼클래스는 그 서브클래스들 위에 위치한다.
  • 각 클래스의 width(너비)는 인스턴스 변수의 양을 나타낸다.
  • 클래스의 height(높이)는 클래스에 정의된 메서드의 양을 나타낸다. 클래스가 길수록 클래스가 정의한 메서드가 많음을 의미한다.
  • 클래스 shading(음영)은 클래스가 포함하는 코드 행의 개수를 나타낸다. 검정색으로 칠해진 클래스는 거의 모든 코드 행을 포함한다. 흰색 클래스는 소량의 코드 행을 포함한다.


시각화에 수반되는 메커니즘은 모두 후에 상세히 살펴볼 것이다.


Mondrian 시작하기

ROMondrianViewBuilder는 Mondrian 도메인 특정 언어(DSL)를 모델화한다[3]. ROMondrianViewBuilder는 내부적으로 ROView의 인스턴스를 포함하는데, 이는 raw view라고 불린다. 그 접근자는 raw이다. ROMondrianViewBuilder를 이용한 스크립팅은 모두 스크립트가 설정한 상호작용과 모양으로 된 ROElements를 생성하여 raw view로 추가되는 결과를 야기한다. 빌더를 이용해 시각화를 시작하려면 아래 코드를 이용하면 된다.

view := ROMondrianViewBuilder new.
view open.


Mondrian 빌더는 ROView의 인스턴스를 이용해 초기화할 수도 있다. 하지만 빌더는 기본적으로 고유의 raw view를 생성할 것이기 때문에 이는 바람직하지 않음을 이해하는 것이 중요하겠다. 빌더로 작업 시에는 Mondrian DSL을 이용해 ROMondrianViewBuilder의 인스턴스로 메시지를 전송하거나 raw view를 직접 작업하는 것이 가능하다.

rawView := ROView new.
view := ROMondrianViewBuilder view: rawView.
view open.


추후 내부적으로 ROElement로서 전환(translate)되는 노드를 시각화에 추가하기 위해서는 나타내고자 하는 객체와 함께 node: 선택자를 이용한다. 기본적으로 각 요소마다 작은 사각형이 그려진다.

view := ROMondrianViewBuilder new.
view node: 1.
view open.


모양을 정의하기 위해선 노드(들) 정의 앞에 shape 메시지를 이용한 후 원하는 모양과 특성을 이용한다. 이는 노드에 대한 모양을 국부적으로 정의할 것이다.

view := ROMondrianViewBuilder new.
view shape rectangle
    size: 10;
    color: Color red.
view node: 1.
view open.


객체의 컬렉션과 함께 nodes: 메시지를 이용하면 여러 개의 노드를 생성할 수 있다.

view := ROMondrianViewBuilder new.
view shape rectangle
    size: 10;
    color: Color red.
view nodes: (1 to: 5).
view open.


노드(들이)가 중첩된 노드들을 가진 경우 그들을 추가하기 위해선 node:forIt: 이나 nodes:forEach: 메시지를 사용하라. 두 번째 매개변수는 블록으로서, 아래 코드와 같이 중첩된 노드들을 추가할 것이다.

view := ROMondrianViewBuilder new.
view shape rectangle
    size: 10;
    color: Color red.
view
    nodes: (1 to: 5)
    forEach: [ :each |
        view shape rectangle
            size: 5;
            color: Color yellow.
        view nodes: (1 to: 2) ].
view open.


노드의 모델 간 연관의 컬렉션과 함께 edgesFromAssociations: 메시지를 이용하면 edges를 생성할 수 있다.

view := ROMondrianViewBuilder new.
view shape rectangle
    color: Color red.
view nodes: (1 to: 4).
view
    edgesFromAssociations: (Array with: 1-> 2 with: 2 -> 3 with: 2 -> 4).
view open.


본 장이 시작될 때 소개한 Collection 계층구조의 예제와 마찬가지로 적절한 레이아웃이 필요하다. 기본적으로 빌더는 가로 직선 레이아웃을 적용하며, 우리에겐 트리 레이아웃이 필요하다. 따라서 treeLayout을 이용해 적용한다.

view := ROMondrianViewBuilder new.
view shape rectangle
    size: 10;
    color: Color red.
view nodes: (1 to: 4).
view edgesFromAssociations: (Array with: 1-> 2 with: 2 -> 3 with: 2 -> 4).
view treeLayout.
view open.


Collection 계층구조 예제

Mondrian DSL은 Collection 계층구조 시각화에 대해 이번 장에 걸쳐 구성된 스크립팅보다 단순한 스크립팅을 허용한다. 각 요소와 edge를 어떻게 생성할 것인지 설정하면 수동으로 생성할 필요가 없어진다. 앞서 소개한 버전을 아래 코드로 대체할 수 있겠다.

view := ROMondrianViewBuilder new.
view shape rectangle
    width: [ :cls | cls instVarNames size ];
    height: [ :cls | cls methods size ].
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.
view open.


Mondrian을 작업하는 방법에는 두 가지가 있는데, easel을 이용하는 방법과 view renderer를 이용하는 방법이다. easel은 사용자가 스크립트를 이용해 시각화를 상호작용적으로, 그리고 점진적으로 빌드할 수 있는 툴이다. easel은 특히 prototyping에 유용하다. MOViewRenderer는 시각화를 비상호작용적인 방식으로 계획에 따라 빌드될 수 있도록 해준다. 자신의 애플리케이션에 시각화를 내장할 경우 해당 클래스를 사용하길 원할 것이다.


우리는 먼저 가장 간단한 방식, 즉 easel을 이용해 Mondrian을 사용해볼 것이다. easel을 열기 위해선 World 메뉴("Mondrian Easel" 엔트리를 포함할 것이다)를 이용하거나 아래 표현식을 실행한다.

ROEaselMorphic open.


방금 열린 easel에서 두 개의 패널을 볼 수 있을 것인데, 상단의 패널은 시각화 패널이고, 나머지는 스크립트 패널이다. 스크립트 패널에서 아래 코드를 입력하고 generate 버튼을 누른다.

view nodes: (1 to: 20).


패인에 20개의 작은 상자가 상단 좌측 모서리부터 줄을 지어 있음을 볼 수 있다. 당신은 방금 1부터 20까지 숫자 집합을 렌더링한 것이다. 각 상자는 하나의 숫자를 나타낸다. 당신이 실행할 수 있는 상호작용의 양은 현재 꽤 제한된다. 한 숫자를 드래그 앤 드롭하면 그 값을 나타내는 툴팁(tooltip)을 얻을 것이다. 상호작용을 정의하는 방법은 곧 살펴볼 것이다. 당장은 Mondrian의 기본 그리기 기능을 살펴보자.


이전에 그린 노드들 사이에 edges를 추가할 수 있겠다. 두 번째 행을 추가하라.

view nodes: (1 to: 20).
view edgesFrom: [ :v | v*2 ].


각 숫자는 그 double과 연결된다. 모든 double이 눈에 보이는 것은 아니다. 가령 20의 double은 40인데, 이는 시각화에 해당하지 않기 때문에 이런 경우 edge가 그려지지 않는다.


edgesFrom: 메시지는 edge의 정의가 가능한 경우 각 노드 당 하나의 edge를 정의한다. 시각화에 각 노드가 추가되면 edge는 해당 노드와 제공된 블록에서 검색된 노드 사이로 정의된다.


Mondrian은 노드를 정렬하기 위한 다수의 레이아웃을 포함한다. 여기선 원형 레이아웃을 사용해보도록 하겠다.

view nodes: (1 to: 20).
view edgesFrom: [ :v | v*2 ].
view circleLayout.


다음 절에서는 소프트웨어 코드를 시각화할 것이다. 소스 코드의 시각화는 보통 패턴을 발견하는 데에 사용되며, 코드의 질을 평가 시 유용하다.


컬렉션 프레임워크 시각화하기

이제 Pharo 클래스를 시각화할 것이다. 이 시점부터는 Pharo의 반영적 기능을 집중적으로 이용하여 Collection 클래스 계층구조를 들여다볼 것인데, 이는 설득력 강한 예제가 된다. Collection 프레임워크에 포함된 클래스의 계층구조를 시각화 해보자.

view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


트리 레이아웃을 이용해 클래스 계층구조를 시각화하였다. 스몰토크는 단일 상속 지향적이기 때문에 이 레이아웃이 적절하다. Collection은 Pharo 컬렉션 프레임워크 라이브러리의 루트 클래스가 아니다. withAllSubclasses 메시지는 Collection과 그 서브클래스의 리스트를 리턴한다.


클래스는 상속 링크를 따라 수직으로 정렬된다. 슈퍼클래스는 그 서브클래스의 위에 위치한다.


노드 모양 변경하기

Mondrian은 객체에 대한 그래프를 시각화한다. 도메인의 각 객체는 그래프 요소, 노드, 또는 edge에 연관되어 있다. 그래프 요소는 그들의 그래프 표현에 대해 알지 못한다. 그래프 면은 shape에 의해 주어진다.


지금까지는 노드와 edge를 표현하기 위해 기본 모양만 사용해왔다. 노드의 기본 모양은 5 픽셀 너비의 사각형이며, edge의 기본 모양은 회색의 얇은 직선이다.


수많은 치수(dimension)가 shape의 외관을 정의하는데, 직사각형의 너비와 높이, 선(line dash)의 크기, 테두리와 안쪽 색상을 예로 들 수 있겠다. 우리는 시각화하는 클래스의 내적 구조에 관해 더 많은 정보를 제공하기 위해 시각화의 노드 모양을 변경할 것이다. 아래를 고려해보자.

view shape rectangle
width: [ :each | each instVarNames size*3 ];
height: #numberOfMethods.
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


이 결과는 그림 12.1에 실려 있으며, 각 클래스는 상자로 표현된다. Collection 클래스, 즉 계층구조의 루트는 가장 위에 위치한 상자다. 클래스의 너비는 클래스가 가진 인스턴스 변수의 양을 알려준다. 해당 수치에 3을 곱하면 좀 더 대조적인 결과가 야기된다. 높이는 메서드의 개수를 알려준다. 다른 클래스에 비해 메서드가 많은 클래스들, Collection, SequentiableCollection, String, CompiledMethod를 즉시 알아챌 수 있을 것이다. 다른 클래스에 비해 변수의 개수가 많은 클래스로 RunArray, SparseLargeTable을 들 수 있다.

그림 12.1: Collection 클래스 계층구조의 시스템 복잡성


다수의 edge

edgesFrom: 메시지는 기껏해야 하나의 노드 당 하나의 edge만 그리는 데 사용된다. 그 변형체로 edges:from:toAll: 이란 메시지가 있는데, 이는 주어진 노드에서 시작해 여러 개의 edge 정의를 지원한다. 클래스들 간 의존성을 고려해보자.

view shape rectangle
    size: [:cls | cls referencedClasses size ];
    withText.
view nodes: ArrayedCollection withAllSubclasses.
view shape arrowedLine.
view
    edges: ArrayedCollection withAllSubclasses from: #yourself toAll: #referencedClasses.
view circleLayout.


위의 스크립트에서 얻은 시각화를 그림 12.2에 제시하겠다.

그림 12.2: 클래스 간 직접 참조.


String과 CompiledMethod가 눈에 띈다. 두 클래스는 다른 클래스에 대한 다수의 참조를 포함한다. text: 는 shape가 텍스트를 포함하도록 만드는 것도 볼 수 있다.


Mondrian은 요소를 쉽게 생성하도록 수많은 유틸리티 메서드를 제공한다. 아래 표현식을 고려해보자.

view edgesFrom: #superclass


edgesFrom: 는 edges:from:to: 와 같다.

view edges: Collection withAllSubclasses from: #superclass to: #yourself.


그리고 그 자체는 아래와 동일하다.

view
    edges: Collection withAllSubclasses
    from: [ :each | each superclass ]
    to: [ :each | each yourself ].


색이 칠해진 모양

모양은 여러 다양한 방식으로 색상을 입힐 수 있다. 노드 모양은 fillColor:, textColor:, borderColorder: 메시지를 이해한다. 직선 모양은 color: 을 이해한다. 이제 Collection 계층구조의 시각화에 색을 칠해보자.

view shape rectangle
    size: 10;
    borderColor: [ :cls | ('*Array*' match: cls name)
        ifTrue: [ Color blue ]
        ifFalse: [ Color black ] ];
    fillColor: [ :cls | cls hasAbstractMethods ifTrue: [ Color lightGray ] ifFalse: [ Color white]
        ].
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


생성된 시각화는 그림 12.3에 주어진다. 이는 "Array"로 명명되지 않은 추상적 클래스들과, 추상적 메서드가 없는 추상적인 클래스를 식별하도록 도와준다.


height:와 width: 와 유사하게 색상을 정의하는 메시지들도 기호, 블록, 또는 상수 값을 인자로서 취한다. 인자는 그래픽 요소가 표현하는 도메인 객체에 대해 평가된다 (이중 디스패치가 moValue: 메시지를 인자로 전송한다). ifTrue:ifFalse: 는 그다지 실용적이지 못하다. 특정 조건에서 색상을 쉽게 선택하기 위한 용도로 유틸리티 메서드들이 제공된다. 모양은 아래와 같이 간단하게 정의할 수 있다.

view shape rectangle
size: 10;

그림 12.3: 추상 클래스는 회색으로, 이름이 "Abstract"라는 단어로 된 클래스는 파란색으로 표시된다.


    if: [ :cls | ('*Array*' match: cls name) ] borderColor: Color blue;
    if: [ :cls | cls hasAbstractMethods ] fillColor: Color lightGray;
...


hasAbstractMethods 메서드는 Pharo에서 Behavior 와 Metaclass 에서 정의된다. 클래스로 hasAbstractMethods를 전송하면 부울 값을 리턴하면서 클래스가 추상적인지 여부를 알려준다. 스몰토크에서 추상적 클래스란 최소한 하나의 추상적 메서드를 (예: self subclassResponsibility를 포함하는) 정의하거나 상속하는 클래스임을 기억할 것이다.


색상에 관한 추가 정보

색상은 프로퍼티를 지정하는 데에 꽤 유용하다 (예: 클래스가 추상적일 경우 회색). 이는 연속적인 분포를 표현할 때 사용되기도 한다. 예를 들어, 채도(color intensity)는 측정(metric)의 결과를 나타낼 수 있다. 앞에서 노드 채도가 코드 행 수에 관해 알려주는 스크립트를 생각해보자.

view interaction action: #browse.
view shape rectangle
    width: [ :each | each instVarNames size*3 ];
    height: [ :each | each methods size ];
    linearFillColor: #numberOfLinesOfCode within: Collection withAllSubclasses.
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


그림 12.4는 결과 그림을 보여준다. linearFillColor:within: 메시지는 첫 번째 인자로서 숫자형 값을 리턴하는 블록 함수를 취한다. 두 번째 인자는 각 노드의 명암도(intensity)를 변경하는 데에 사용되는 요소의 그룹이다. 블록 함수는 그룹의 각 요소에 적용된다. 음영은 흰색에서 (코드의 0행) 검정색으로 (코드의 최대 숫자 행) 측정된다. 최대 명암도는 Collection의 모든 서브클래스에 대한 최대 #numberOfLInesOfCode에 의해 주어진다. linearFillColor:within: 의 변형체로 linearXXXFillColor:within: 이 있는데, 여기서 XXX는 Blue, Green, Red, Yellow 중에 하나가 해당한다.


여기서 얻는 시각화[4]는 메서드의 개수, 인스턴스 변수의 개수, 코드의 행 수에 대한 각 클래스의 관계를 넣는다. 클래스들 간 크기 차이는 관리 활동이 필요함을 제시할지도 모른다.

그림 12.4: 시스템 복잡성 시각화: 노드는 클래스이고, 높이는 메서드의 행 수, 너비는 변수의 개수, 색상은 코드 행의 개수를 말한다.


색상은 identifyFillColorOf: 를 이용해 객체 정체성으로 할당될 수 있다. 인자는 블록이거나 기호로서, 도메인 객체에 대해 평가된다. 색상은 인자의 결과에 연관된다.


팝업 뷰

추상 클래스 예제로 돌아가보자. 아래 스크립트는 추상 클래스, 그리고 그들이 정의하는 추상 메서드 개수를 나타낸다.

view shape rectangle
    size: [ :cls | (cls methods select: #isAbstract ) size*5 ] ;
    if: #hasAbstractMethods fillColor: Color lightRed;
    if: [:cls | cls methods anySatisfy: #isAbstract ] fillColor: Color red.
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.

그림 12.5: 상자는 클래스, 링크는 상속 관계다. 추상 메서드의 양은 클래스 크기에 따라 표현된다. 빨간색 클래스는 추상 메서드를 정의하고, 분홍색 클래스는 추상 클래스에서만 상속된다.


그림 12.5는 추상 클래스의 정의 또는 상속에 따라 추상적인 클래스를 나타낸다. 클래스 크기는 정의된 추상 메서드의 개수를 나타낸다.


팝업 메시지는 추상 메서드를 열거하도록 개선할 수 있다. 클래스 위에 마우스를 놓으면 그 이름 뿐만 아니라 클래스 내의 추상 메서드 리스트까지 제공한다. 아래 코드 조각을 앞에 추가해야 한다.

view interaction popupText: [ :aClass |
    | stream |
    stream := WriteStream on: String new.
    (aClass methods select: #isAbstract thenCollect: #selector)
        do: [:sel | stream nextPutAll: sel; nextPut: $ ; cr].
    aClass name printString, ' => ', stream contents ].
...


지금까지 그래픽 표현을 설명하기 위해 모양을 가진 요소를 살펴보았다. 요소는 이벤트 핸들러를 포함하는 상호작용도 포함한다. popupText: 메시지는 블록을 인자로 취한다. 블록은 도메인 객체를 인자로 하여 평가된다. 블록은 팝업 텍스트 내용을 리턴해야 하는데, 본문의 예제에서는 단순히 메서드의 리스트에 해당한다.


텍스트 내용에 더해 Mondrian은 뷰의 팝업을 허용하기도 한다. 이 요점을 설명하기 위해 앞의 예제를 확장해보겠다. 마우스가 노드로 들어가면 새 뷰가 정의되고 노드 옆에 표시된다.

view interaction popupView: [ :element :secondView |
    secondView node: element forIt: [
        secondView shape rectangle
            if: #isAbstract fillColor: Color red;
            size: 10.
        secondView nodes: (element methods sortedAs: #isAbstract).
        secondView gridLayout gapSize: 2
    ]].

view shape rectangle
    size: [ :cls | (cls methods select: #isAbstract ) size*5 ] ;
    if: #hasAbstractMethods fillColor: Color lightRed;
    if: [:cls | cls methods anySatisfy: #isAbstract ] fillColor: Color red.
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


popupView: 의 인자는 2 인자 블록이다. 블록의 첫 번째 매개변수는 마우스 아래 위치한 노드가 표현하는 요소다. 두 번째 인자는 열리게 될 새 뷰가 된다.


예제에서 우리는 메서드를 표현하는 노드를 정렬하기 위해 sortedAs: 를 이용했다. 해당 메서드는 Collection에서 정의되고 Mondrian에 속한다. sortedAs: 의 사용 예제를 보고 싶다면 해당하는 단위 시험(unit test)을 훑어보도록 한다.


마지막 예제에서는 하위뷰를 정의하기 위해 팝업 뷰에서 node:forIt: 을 사용한다.


하위뷰

노드는 그 자체 내의 뷰이다. 이는 그래프가 어떤 노드로든 내장될 수 있도록 해준다. 내장된 뷰는 캡슐화하는 노드에 의해 바인딩된다. 내장은 nodes:forEach: 와 node:forIt: 키워드를 통해 실현된다.


아래 예제는 서로 호출이 가능한 메서드를 연결함으로써 메서드들 간 의존성을 계산한다. 메서드 m1이 선택자 #m2로의 참조를 포함할 경우 m1은 메서드 m2로 연결된다. 이는 간단하지만 메서드들 간 의존성을 효율적으로 바라보는 방법이다. 아래를 고려해보자.

view nodes: ROShape withAllSubclasses forEach: [:cls |
    view nodes: cls methods.
    view edges: cls methods from: #yourself toAll: [ :cm | cls methods select: [ :rcm | cm
        messages anySatisfy: [:s | rcm selector == s ] ] ].
    view treeLayout
].
view interaction action: #browse.
view edgesFrom: #superclass.
view treeLayout.


하위뷰는 고유의 레이아웃을 포함한다. 하위뷰에서 정의된 상호작용과 모양은 중첩(nesting) 노드에서 접근할 수 없다 (그림 12.6).

그림 12.6: 큰 상자는 클래스들이다. 그 내부의 상자들은 메서드에 해당한다. edges는 둘 사이에 가능한 호출을 표시한다.


이벤트 전달하기

앞 절에서 주어진 시각화의 메서드들은 간단한 드래그 앤 드롭을 통해 이동할 수 있다. 하지만 메서드의 위치는 고정되어 있고 클래스만 드래그 앤 드롭이 가능하길 원하는 경우도 있는데, 이럴 때는 상호작용으로 forward 메시지가 전송되어야 한다. 아래를 살펴보자.

view nodes: ROShape withAllSubclasses forEach: [:cls |
    view interaction forward.
    view shape rectangle
        size: #linesOfCode.
    view nodes: cls methods.
    view edges: cls methods from: #yourself toAll: [ :cm | cls methods select: [ :rcm | cm
        messages anySatisfy: [:s | rcm selector == s ] ] ].
    view treeLayout
].
view interaction action: #browse.
view edgesFrom: #superclass.
view treeLayout.


메서드를 이동하면 클래스를 대신 이동시킬 것이다. 때로는 하나 이상의 요소를 드래그 앤 드롭하는 것이 편리하기도 하다. 대부분의 운영체제에서 Mondrian은 Ctrl 키나 Cmd 키를 이용해 다중 선택을 제공한다. 기본 행위는 모든 노드를 대상으로 이용할 수 있다. 다중 선택은 노드 그룹의 이동을 가능하게 한다.


이벤트

각 마우스 이동, 클릭, 키보드 키누름은 특정 이벤트에 들어맞는다. Mondrian은 풍부한 이벤트 계층구조를 제공하며, 계층구조의 루트는 MOEvent이다. 특정 액션을 이벤트로 연관시키고 싶다면 객체 상호작용에서 핸들러를 정의해야 한다. 아래 예제의 경우, 클래스를 클릭하면 코드 브라우저가 열린다.

view shape rectangle
    width: [ :each | each instVarNames size*5 ];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color lightRed;
    if: [:cls | cls methods anySatisfy: #isAbstract ] fillColor: Color red.

view interaction on: ROMouseClick do: [ :event | event model browse ].

view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.


블록 핸들러는 하나의 인자, 생성된 이벤트를 수락한다. 이벤트를 트리거한 객체는 이벤트 객체로 modelElement를 전송하여 얻는다.


상호작용

Mondrian은 컨텍스트의 다수의 상호작용 메커니즘을 제공한다. 상호작용 객체는 그러한 용도의 키워드를 많이 포함한다. highlightWhenOver: 메시지는 블록을 인자로서 취하는데, 해당 블록은 마우스가 노드로 진입 시 강조해야 하는 노드의 리스트를 리턴한다. 아래 예제를 살펴보자.

view interaction
    highlightWhenOver: [:v | {v - 1 . v + 1. v + 4 . v - 4}].
view shape rectangle
    width: 40;
    height: 30;
    withText.
view nodes: (1 to: 16).
view gridLayout gapSize: 2.


노드 5로 진입하면 노드 4, 6, 1, 9가 강조된다. 이러한 메커니즘은 연결 edge의 오버로딩을 피하는 데 꽤 효율적이다. 관심 노드에 대한 정보만 표시되기 때문이다.


아래 예제에서는 좀 더 강력한 highlightWhenOver: 의 적용 예제를 제시한다. 클래스의 계층구조는 좌측에 표시된다. 우측에는 unit test의 계층구조가 표시된다. 마우스 포인터를 unit test로 갖다 대면 해당 unit test 메서드 중 하나가 참조하는 클래스들을 강조한다. (길지만) 아래 스크립트를 고려해보라.

"System complexity of the collection classes"
view shape rectangle
    width: [ :each | each instVarNames size*5 ];
    height: [ :each | each methods size ];
    linearFillColor: #numberOfLinesOfCode within: Collection withAllSubclasses.
view nodes: Collection withAllSubclasses.
view edgesFrom: #superclass.
view treeLayout.

"Unit tests of the package CollectionsTest"
view shape rectangle withoutBorder.
view node: 'compound' forIt: [
    view shape label.
    view node: 'Collection tests'.

view node: 'Collection tests' forIt: [
    | testClasses |
    testClasses := (PackageInfo named: 'CollectionsTests') classes reject: #isTrait.
    view shape rectangle
        width: [ :cls | (cls methods inject: 0 into: [ :sumLiterals :mtd | sumLiterals + mtd
            allLiterals size]) / 100 ];
        height: [ :cls | cls numberOfLinesOfCode / 50 ].
    view interaction
        highlightWhenOver: [ :cls | ((cls methods inject: #()
            into: [:sum :el | sum , el allLiterals ]) select: [:v | v isKindOf: Association ]
        thenCollect: #value) asSet ].
    view nodes: testClasses.
    view edgesFrom: #superclass.
    view treeLayout ].

    view verticalLineLayout alignLeft
].


위 스크립트는 두 가지 부분을 포함한다. 첫 번째는 컬렉션 프레임워크에서 아주 흔하게 발견되는 시스템 복잡성이고, 두 번째는 CollectionsTests에 포함된 테스트를 제공한다. 클래스 너비는 클래스에 포함된 리터럴 개수를, 높이는 코드의 행 수를 나타낸다. 컬렉션 테스트는 코드의 재사용을 위한 특성을 많이 사용하기 때문에 이러한 측정(metrics)은 축소되어야 한다. 마우스를 test unit 위로 갖다 대면 해당 클래스 내에서 참조된 컬렉션 프레임워크의 모든 클래스들이 강조된다.

그림 12.7: 상호작용적 시스템 복잡성.


요약

Mondrian은 객체의 어떤 그래프든 시각화하도록 해준다. 본 장에서는 아래와 같은 Mondrian의 주요 특징들을 살펴보았다.

  • 노드를 정의할 때는 nodes: 를, edges를 정의할 때는 edgesFrom:, edges:from:to:, edges:from:toAll: 을 이용하는 방법이 가장 일반적이다.
  • 레이아웃의 전체 범위가 제공된다. 가장 일반적인 레이아웃은 circleLayout, treeLayout, gridLayout을 뷰로 전송하면 접근할 수 있다.
  • Shape은 요소의 그래픽적 측면을 정의한다. 요소의 높이와 너비는 보통 각각 height: 과 width: 를 이용해 설정된다.
  • 모양은 borderColor: 와 fillColor: 로 칠한다.
  • 정보는 popupText: 와 popupView: 를 이용해 팝업할 수 있다.
  • 하위뷰는 nodes:forEach: 와 node:forIt: 을 이용해 정의된다.
  • 하위 노드의 이벤트는 forward: 와 forward: 를 이용해 그 부모 노드로 전달된다.
  • 강조(highlighting)는 highlightWhenOver: 를 통해 이용 가능한데, 이는 1 인자 블록을 취하며 강조해야 할 노드의 리스트를 리턴한다.


본 장은 Mondrian 도메인 특정 언어를 집중적으로 다루었다. Mondrian은 Tudor Girba와 Michael Meyer이 2005년에 개발한 오래된 시각화 프레임워크다. Mondrian은 2008년부터 2009년까지 Alexandre Bergel에 의해 관리되었다.


감사의 말. 본 장의 초판을 검토해준 Nicolas Rosselot Urrejola에게 감사드린다.


노드 색상은 노드의 높이와 너비로서 중요한 정보 지원이다. 색상은 특정 상태를 표현하도록 선택하기 쉬워야 한다.


if:fillColor: 키워드는 특정 상태에 대해 색상을 할당하도록 해준다. 앞의 예제를 확장시켜 추상 클래스를 빨간색으로 칠한다고 가정하자.

view shape rectangle
    width: [ :each | each instVarNames size*3 ];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color red.
view nodes: Collection withAllSubclasses.

view edges: Collection withAllSubclasses from: #yourself toAll: #subclasses.

view treeLayout.


if:fillColor: 메시지는 조건적으로 색상을 설정하도록 shape로 전송될 수 있다.

view shape rectangle
    width: [ :each | each instVarNames size*3];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color red.
view nodes: Collection withAllSubclasses.

view edgesFrom: #superclass.

view treeLayout.


빨간색 노드는 모두 추상적 클래스를 표현한다. 노드 위에서 마우스(moose라고 되어 있는데 오타 같아서 mouse로 번역해드립니다만 확인 바랍니다.)를 왔다 갔다 하면 이름을 표시하는 텍스트 툴팁에 나타난다.


상호작용을 정의하기 위한 확장된 기능들도 존재한다. 이는 추후 살펴볼 것이다. 당장은 노드에서 직접 시스템 브라우저를 열고 싶다면 아래 상호작용을 정의한다.

view interaction action: #browse.
view shape rectangle
    width: [ :each | each instVarNames size*3];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color red.
view nodes: Collection withAllSubclasses.

view edgesFrom: #superclass.

view treeLayout.


서브클래스를 갖고 있지 않은 빨간색 노드가 몇 개 눈에 띌 것이다. 추상 클래스는 서브클래스를 가져야 하므로 이는 설계 흐름을 나타낸다. 인스턴스화 되어선 안 되는 (추상적이므로) 클래스가 서브클래스를 갖지 않는다는 것은 말이 안 된다.


자신의 클래스에서도 이와 같은 분석을 실현할 수 있다.

view interaction action: #browse.
view shape rectangle
    width: [ :each | each instVarNames size*3];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color red.
view nodes: (PackageInfo named: 'Mondrian') classes.

view edgesFrom: #superclass.

view treeLayout.


shape은 하나 이상의 상태를 포함할 수도 있다. 추상 메서드를 정의하는 클래스로부터 추상 클래스를 구별하자.

view shape rectangle
    width: [ :each | each instVarNames size*3];
    height: [ :each | each methods size ];
    if: #hasAbstractMethods fillColor: Color lightRed;
    if: [:cls | cls methods anySatisfy: #isAbstract ] fillColor: Color red.
view nodes: Collection withAllSubclasses.

view edgesFrom: #superclass.

view treeLayout.


모든 빨간색 노드는 여전히 추상적 클래스이다. 연한 빨간색은 추상 메서드를 정의하지 않는 클래스를 나타내고, 진한 빨간색은 최소 하나의 추상 메서드를 정의하는 클래스를 의미한다.


Notes

  1. http://themoosebook.org/book/internals/mondrian
  2. http://www.moostechnology.org/
  3. http://www.moosetechnology.org/tools/mondrian
  4. 시각화는 '시스템 복잡성'으로 명명되는데 이에 관해 더 알고 싶다면 'Polymetric ViewsㅡA Lightweight Visual Approach to Reverse Engineering'(Transactions on Software Engineering, 2003)을 참고한다.