- 발행일
표준데이터 구조를 깊이마다 한 번씩만 읽었다 — 좌→우 계층 트리 뷰어
표준데이터 구조를 깊이마다 한 번씩만 읽었다
메시지 하나가 무엇으로 이뤄졌는지 보려면 상세 화면을 계속 타고 들어가야 했습니다. 메시지 상세의 구조 표는 한 단계만 보여주거든요. 그 아래 데이터 프레임이 또 무엇을 담고 있는지 보려면 그 프레임의 상세로 가야 하고, 거기서 또 들어가야 합니다.
세 단계쯤 내려가면 처음 어디서 출발했는지 잊습니다. 표준데이터의 구조는 원래 깊어요 — 메시지 → 필드 → 데이터 프레임 / 데이터 엘리먼트 → 데이터 유형으로 이어지고, 프레임 안에 프레임이 또 들어갑니다.
전체를 한 장에 좌→우 계층 트리로 그리는 뷰어를 만들었습니다.
관계도가 있는데 왜 또 그리나
이 시스템에는 이미 관계도가 11개 화면에 있습니다. 노드와 엣지로 연결을 그리는 화면이에요. 그런데 두 화면은 답하는 질문이 다릅니다.
- 관계도 — "이것이 무엇과 이어져 있나"
- 구조 트리 — "이것이 무엇으로 이뤄져 있나"
두 번째 질문에는 순서·의무 수준(M/O/C)·반복 범위가 딸려 옵니다. 힘 시뮬레이션으로 배치하는 그래프에서는 이걸 표현할 자리가 없어요. 순서가 있는 것을 순서 없이 그려 놓고 옆에 배지를 붙이는 건 답이 아닙니다.
| 방법 | 장점 | 포기하는 것 | 판단 |
|---|---|---|---|
| ① 상세 화면 구획 안에 인라인 | 이동 없이 바로 봄 | 깊이 하나당 270px라 본문 폭 안에서는 늘 잘린 채 보임 | 기각 |
| ② 기존 관계도에 합치기 | 화면 하나로 끝 | 순서·의무·반복을 힘 배치로 못 그림. 두 질문이 섞여 둘 다 흐려짐 | 기각 |
| ③ 전용 뷰어를 새 탭으로 | 전체 화면 한 장. 거기서 검색·이동 | 화면 이동이 한 번 필요 | 채택 |
④ 구획 디스플레이 유형에 G(그래프)를 더해 켜기 | 기존 설정 체계 재사용 | (아래에 적음 — 실제로 넣었다가 같은 날 걷어냈습니다) | 기각 |
재귀인데 쿼리는 깊이당 한 번
구조를 따라 내려가는 코드는 모양만 보면 재귀입니다. 그대로 짜면 노드 하나마다 쿼리가 나가요.
"""표준데이터 구조 시각화 — 좌→우 계층 트리의 데이터.
...
⚠️ 재귀지만 쿼리는 **깊이당 한 번**이다. 단계마다 쿼리를 날리면 노드 수만큼 폭발한다.
"""
깊이 우선이 아니라 너비 우선으로 갑니다. 한 깊이의 노드를 전부 모아 놓고, 그 깊이에 필요한 것을 한 번에 읽어요.
def _literal_values(instance_ids, curies):
"""{인스턴스 id: {CURIE: 값}} — 한 단계에 필요한 리터럴을 한 쿼리로."""
def _child_links(source_ids, slots):
"""{출처 id: [대상 인스턴스]} — 한 단계의 링크를 한 쿼리로 모은다."""
while frontier and depth < MAX_DEPTH: 로 돌면서 다음 깊이의 노드 집합을 만들고, 그 집합에 대해 리터럴 한 번·링크 한 번. 노드가 몇 개든 깊이가 같으면 쿼리 수가 같습니다.
이건 카드 목록의 N+1을 39개에서 19개로 줄였던 것과 같은 이야기인데, 그때는 이미 있는 코드를 고친 거고 이번엔 처음부터 그렇게 짰습니다. 트리는 노드 수를 예측할 수 없어서 나중에 고치면 늦어요.
순환은 자르되 잘랐다고 말한다
구조 데이터에는 순환이 실제로 있습니다. 프레임 A가 프레임 B를 담고 B가 다시 A를 담는 정의가 저장돼 있어요.
def _descend(node, instance, ancestors, next_frontier):
"""조상 경로에 없는 자식만 다음 단계로 넘긴다 — 돌아오는 링크가 실제로 있다."""
방문 여부를 전역으로 기억하지 않고 조상 경로만 봅니다. 같은 자원이 트리의 다른 가지에 또 나오는 건 순환이 아니라 정상이거든요. 전역 visited로 막으면 두 번째부터는 안 그려져서 구조가 실제와 달라집니다.
잘린 자리는 화면에 적습니다.
node["description"] + " (순환 — 위에서 이미 나온 자원)"
여기서 멈춘 게 데이터가 없어서인지 순환을 잘라서인지, 보는 사람이 알아야 하니까요. 조용히 멈추면 "이 프레임은 비었다"로 읽힙니다.
깊이 상한도 뒀습니다 — MAX_DEPTH = 12.
ASN.1 원문에서 필드를 뽑고, 없으면 물러난다
필드 이름·M/O/C·반복 범위는 kadif:asn1Code에 든 ASN.1 원문에서 뽑습니다.
def parse_asn1_members(asn1_code: str) -> list:
"""`X ::= SEQUENCE { ... }` 첫 정의의 **멤버 한 겹**을 뽑는다."""
def _top_level_members(body: str) -> list:
"""중괄호 깊이를 세며 최상위 콤마로 자른다."""
정규식 하나로 자르면 중첩된 SEQUENCE 안의 콤마까지 잘립니다. 중괄호 깊이를 세면서 최상위 콤마만 경계로 삼아야 해요. 한 겹만 뽑는 것도 의도인데, 더 깊은 건 그 자원의 자기 원문이 답하니까요.
원문에서 뽑은 멤버와 실제 자식 인스턴스는 타입 이름으로 짝짓습니다.
def match_asn1_children(members, children, values):
"""ASN.1 멤버 ↔ 자식 인스턴스를 **타입 이름**으로 짝짓는다."""
원문이 없는 자원은 자식 자원만 그리는 폴백으로 물러납니다. 필드 이름과 배지는 빠지고 구조만 남아요.
오전에 넣은 G를 오후에 걷어냈다
이 시스템의 상세 구획은 모양(리스트·표·카드)을 클래스별로 지정할 수 있습니다. constance 설정 하나로 정하고 스키마 변경은 없어요.
같은 날 오전 판에서는 이 설정에 G(그래프)를 하나 더 만들어 구조 시각화를 켰습니다. 기존 체계를 재사용하는 깔끔한 방법 같았는데, 세 가지가 어긋났습니다.
- 표가 사라졌다.
G면 구조 표 대신 진입 상자가 섰는데, 시안은 둘 다 있습니다. - 이름이 셋 다 따로 놀았다. 설정은
DataFrame, 구획 제목은 먼저 온 슬롯 이름(데이터 엘리먼트), 내용은 메시지 구조. "왜 데이터 엘리먼트 구획에 그래프가 뜨나"를 아무도 설명할 수 없는 상태가 됐습니다. - 개념이 다르다.
L·T·C는 "연결 자원 N건을 어떻게 늘어놓나"인데G는 "이 인스턴스의 구조로 가는 문"입니다. 자원 클래스에 걸 값이 아니에요.
걷어냈습니다. 설정에 G가 남아 있어도 코드 목록에 없으니 조용히 버려집니다.
되돌아보면 "기존 체계에 값 하나만 더하면 된다"가 위험 신호였습니다. 값 하나로 되면 그 체계가 표현하려던 축과 같은 축이라는 뜻인데, 여기서는 축 자체가 달랐어요.
검증
- 파서·구획 테스트 34건. 중첩
SEQUENCE의 최상위 콤마 분리, 확장 표식, 반복 범위 파싱이 각각 잠겨 있습니다. - e2e 시나리오 2건 — 뷰어가 열리고 트리가 그려지는 것, 검색이 노드를 찾아 가운데로 옮기는 것.
- 순환 데이터(
f1 → f2 → f1)를 넣어 트리가 멈추고 "(순환 — 위에서 이미 나온 자원)"이 붙는 것 확인. kadif:asn1Code가 없는 자원으로 열어 폴백이 도는 것 확인.- 깊이별 쿼리 수 확인 — 노드를 늘려도 같은 깊이면 쿼리가 안 느는 것.
- 변경 범위 22개 파일 4430줄 추가. 렌더러
messageStructureTree.js가 544줄입니다.
남은 것 · 한계
MAX_DEPTH = 12는 근거가 없는 숫자입니다. 실제 데이터의 최대 깊이를 재보고 정한 게 아니라 "이 정도면 되겠지"로 둔 값이에요. 12에서 잘린 트리가 있는지 지금은 모릅니다.- 깊이당 한 번이지 넓이는 안 봅니다. 한 깊이의 노드가 수천 개면
IN절이 그만큼 커집니다. 지금 데이터에서는 문제가 안 되는데, 문제가 될 크기가 얼마인지도 안 재봤습니다. - 원문이 없을 때 화면이 그 사실을 말하지 않습니다. 폴백으로 물러나면 필드 이름과 배지가 그냥 안 보이는데, 보는 사람은 "이 자원은 원래 필드가 없나 보다"로 읽습니다. 이 폴백이 실제로 무엇을 감췄는지는 며칠 뒤에 드러났습니다.
- 멤버와 자식을 타입 이름으로 짝짓습니다. 같은 타입이 한 정의에 두 번 나오면 어느 쪽에 붙을지가 이름만으로는 안 갈립니다. 지금 데이터에서 그런 경우를 안 만났을 뿐이에요.
- 데이터 프레임·엘리먼트 상세에는 뷰어로 가는 문이 없습니다. URL은 어느 인스턴스로든 열리는데 도달 경로는 메시지에만 있어요. 시안이 그래서 따랐는데, 문 없는 화면이 남아 있는 건 사실입니다.
- 검색 결과 목록 패널은 안 만들었습니다. 앞뒤 이동은 되지만 "몇 개 찾았고 어디에 있나"는 못 봅니다.