- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
593 lines
17 KiB
Markdown
593 lines
17 KiB
Markdown
# Five RDD Architecture Specification
|
||
|
||
> Harbour RDD 상속 아키텍처 분석 및 Go 재설계
|
||
> WAAREA → DBF → DBFNTX/DBFCDX 체인의 정밀 분석
|
||
>
|
||
> Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
||
> All rights reserved.
|
||
>
|
||
> Source reference: /mnt/d/harbour-core/include/hbapirdd.h, src/rdd/
|
||
|
||
---
|
||
|
||
## 1. Harbour RDD 상속 체인
|
||
|
||
```
|
||
WAAREA (base, 101 methods)
|
||
│ ~25 real implementations + ~76 unsupported stubs
|
||
│
|
||
├── DBF (overrides all 101 methods)
|
||
│ │ 파일 I/O, 필드 GET/PUT, 레코드 관리, 락
|
||
│ │
|
||
│ ├── DBFFPT (DBF + FPT memo)
|
||
│ │ │ 메모 5 methods override
|
||
│ │ │
|
||
│ │ ├── DBFNTX (+ NTX index)
|
||
│ │ │ ~30 methods override (movement, order, filter)
|
||
│ │ │
|
||
│ │ └── DBFCDX (+ CDX index)
|
||
│ │ ~20 methods override (order, CDX-specific)
|
||
│ │
|
||
│ └── DBFDBT (DBF + DBT memo, fallback)
|
||
│ │
|
||
│ ├── DBFNTX (fallback parent)
|
||
│ └── DBFCDX (fallback parent)
|
||
│
|
||
└── SDF, DELIM (flat file drivers, separate chain)
|
||
```
|
||
|
||
### 상속 해석 순서 (DBFNTX 예시)
|
||
|
||
```c
|
||
// DBFNTX 등록 시:
|
||
errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBFFPT", ...); // 1st: try DBFFPT
|
||
if (errCode != HB_SUCCESS)
|
||
errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBFDBT", ...); // 2nd: try DBFDBT
|
||
if (errCode != HB_SUCCESS)
|
||
errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBF", ...); // 3rd: fallback DBF
|
||
```
|
||
|
||
### hb_rddInheritEx 알고리즘
|
||
|
||
```
|
||
1. 부모 RDD를 이름으로 검색
|
||
2. 부모의 전체 메서드 테이블 (101개)을 복사 → pTable, pSuperTable 양쪽
|
||
3. 자식의 메서드 테이블을 순회:
|
||
- NULL이 아닌 항목만 pTable에 덮어씀 (override)
|
||
- NULL 항목은 부모 메서드가 그대로 유지됨 (inherit)
|
||
4. pSuperTable은 부모 원본 그대로 보존 (SUPER_* 호출용)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 101개 메서드 분류 + 드라이버별 오버라이드 현황
|
||
|
||
### Movement & Positioning (11)
|
||
|
||
| Method | WAAREA | DBF | DBFNTX | DBFCDX |
|
||
|--------|--------|-----|--------|--------|
|
||
| bof | real | override | override | inherit |
|
||
| eof | real | override | override | inherit |
|
||
| found | real | override | override | inherit |
|
||
| goBottom | unsup | override | override | **override** |
|
||
| go | unsup | override | override | inherit |
|
||
| goToId | unsup | override | override | inherit |
|
||
| goTop | unsup | override | override | **override** |
|
||
| seek | unsup | override | **override** | **override** |
|
||
| skip | real | override | override | **override** |
|
||
| skipFilter | real | override | override | inherit |
|
||
| skipRaw | unsup | override | override | **override** |
|
||
|
||
### Data Management (22)
|
||
|
||
| Method | WAAREA | DBF | DBFNTX | DBFCDX |
|
||
|--------|--------|-----|--------|--------|
|
||
| addField | real | override | inherit | inherit |
|
||
| append | unsup | override | inherit | inherit |
|
||
| createFields | real | override | inherit | inherit |
|
||
| deleterec | unsup | override | inherit | inherit |
|
||
| deleted | unsup | override | inherit | inherit |
|
||
| fieldCount | real | override | inherit | inherit |
|
||
| fieldInfo | real | override | inherit | inherit |
|
||
| fieldName | real | override | inherit | inherit |
|
||
| flush | unsup | override | override | **override** |
|
||
| getValue | unsup | override | inherit | inherit |
|
||
| putValue | unsup | override | inherit | inherit |
|
||
| goCold | unsup | override | override | **override** |
|
||
| goHot | unsup | override | override | **override** |
|
||
| reccount | unsup | override | inherit | inherit |
|
||
| recno | unsup | override | inherit | inherit |
|
||
| ... | | | | |
|
||
|
||
### Order Management (9) — 핵심 차이점
|
||
|
||
| Method | WAAREA | DBF | DBFNTX | DBFCDX |
|
||
|--------|--------|-----|--------|--------|
|
||
| orderListAdd | unsup | stub | **NTX** | **CDX** |
|
||
| orderListClear | unsup | stub | **NTX** | **CDX** |
|
||
| orderListFocus | unsup | stub | **NTX** | **CDX** |
|
||
| orderListRebuild | unsup | stub | **NTX** | **CDX** |
|
||
| orderCreate | unsup | stub | **NTX** | **CDX** |
|
||
| orderDestroy | unsup | stub | **NTX** | **CDX** |
|
||
| orderInfo | real | override | **NTX** | **CDX** |
|
||
|
||
### Filter & Scope (11)
|
||
|
||
| Method | WAAREA | DBF | DBFNTX | DBFCDX |
|
||
|--------|--------|-----|--------|--------|
|
||
| clearFilter | real | override | **override** | **override** |
|
||
| setFilter | real | override | **override** | **override** |
|
||
| clearScope | unsup | override | **NTX** | **CDX** |
|
||
| countScope | unsup | override | **NTX** | **CDX** |
|
||
| ... | | | | |
|
||
|
||
---
|
||
|
||
## 3. SELF_* / SUPER_* 디스패치 메커니즘
|
||
|
||
```
|
||
호출 체인 예시: USE customers VIA DBFNTX → CLOSE
|
||
|
||
User → SELF_CLOSE(workarea)
|
||
→ workarea->lprfsHost->close [DBFNTX의 hb_ntxClose]
|
||
│ NTX 인덱스 닫기
|
||
│ SUPER_CLOSE(area)
|
||
└→ ntxSuper->close [DBF의 hb_dbfClose]
|
||
│ DBF 파일 플러시/닫기
|
||
│ SUPER_CLOSE(area)
|
||
└→ dbfSuper->close [WAAREA의 hb_waClose]
|
||
│ 플래그 초기화
|
||
└→ HB_SUCCESS
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Go 재설계: Interface 분할
|
||
|
||
### 핵심 발견
|
||
|
||
```
|
||
Harbour: 101개 메서드의 거대한 단일 vtable
|
||
- 대부분 unsupported stub
|
||
- 새 드라이버 작성 시 101개를 모두 채워야 함
|
||
|
||
Go 철학: 작은 interface 조합
|
||
- 필요한 것만 구현
|
||
- 나머지는 임베딩으로 상속
|
||
```
|
||
|
||
### Go Interface 설계
|
||
|
||
```go
|
||
// 계층 1: 필수 (모든 드라이버가 구현)
|
||
type Driver interface {
|
||
Name() string
|
||
Open(params OpenParams) (Area, error)
|
||
Create(params CreateParams) (Area, error)
|
||
}
|
||
|
||
// 계층 2: 기본 Area (WAAREA 대응)
|
||
type Area interface {
|
||
io.Closer
|
||
|
||
// Movement (11)
|
||
BOF() bool
|
||
EOF() bool
|
||
Found() bool
|
||
GoTo(recNo uint32) error
|
||
GoTop() error
|
||
GoBottom() error
|
||
Skip(count int64) error
|
||
SkipFilter(count int64) error
|
||
|
||
// Data (core)
|
||
RecNo() uint32
|
||
RecCount() uint32
|
||
Deleted() bool
|
||
FieldCount() int
|
||
FieldInfo(index int) FieldInfo
|
||
GetValue(index int) (Value, error)
|
||
PutValue(index int, val Value) error
|
||
Flush() error
|
||
}
|
||
|
||
// 계층 3: 레코드 조작 (DBF가 구현)
|
||
type RecordManager interface {
|
||
Append() error
|
||
Delete() error
|
||
Recall() error
|
||
Pack() error
|
||
Zap() error
|
||
}
|
||
|
||
// 계층 4: 인덱스 (DBFNTX, DBFCDX가 각각 구현)
|
||
type Indexer interface {
|
||
OrderCreate(params OrderCreateParams) error
|
||
OrderListAdd(path string) error
|
||
OrderListClear() error
|
||
OrderListFocus(tag string) error
|
||
OrderListRebuild() error
|
||
OrderDestroy(tag string) error
|
||
OrderInfo(index int, info *OrderInfo) error
|
||
Seek(key Value, softSeek bool) (bool, error)
|
||
}
|
||
|
||
// 계층 5: 락 (DBF가 구현)
|
||
type Locker interface {
|
||
Lock(params LockParams) (bool, error)
|
||
Unlock(recNo uint32) error
|
||
RawLock(action int, recNo uint32) error
|
||
}
|
||
|
||
// 계층 6: 필터/관계
|
||
type Filterer interface {
|
||
SetFilter(expr string, block func() bool) error
|
||
ClearFilter() error
|
||
}
|
||
|
||
type Relater interface {
|
||
SetRelation(child Area, keyExpr func() Value) error
|
||
ClearRelation() error
|
||
ForceRel() error
|
||
SyncChildren() error
|
||
}
|
||
|
||
// 계층 7: 메모 (DBFFPT가 구현)
|
||
type MemoHandler interface {
|
||
OpenMemo(path string) error
|
||
CloseMemo() error
|
||
GetMemo(blockNo uint32) ([]byte, error)
|
||
PutMemo(data []byte) (uint32, error)
|
||
}
|
||
```
|
||
|
||
### Go 임베딩으로 상속 구현 (Harbour의 hb_rddInheritEx 대응)
|
||
|
||
```go
|
||
// WAAREA 대응: 기본 구현
|
||
type BaseArea struct {
|
||
fBof, fEof, fFound bool
|
||
fields []FieldInfo
|
||
alias string
|
||
filter *Filter
|
||
relations []*Relation
|
||
}
|
||
// BaseArea implements Area with default behaviors
|
||
|
||
// DBF 대응: BaseArea를 임베딩
|
||
type DBFArea struct {
|
||
BaseArea // ← WAAREA 상속 (Go 임베딩)
|
||
|
||
file *os.File
|
||
header DBFHeader
|
||
recBuf []byte
|
||
recNo uint32
|
||
// ...
|
||
}
|
||
// DBFArea implements Area + RecordManager + Locker + MemoHandler
|
||
|
||
// DBFNTX 대응: DBFArea를 임베딩
|
||
type NTXArea struct {
|
||
DBFArea // ← DBF 상속 (Go 임베딩)
|
||
|
||
indexes []*NTXIndex
|
||
curOrder int
|
||
// ...
|
||
}
|
||
// NTXArea adds Indexer to DBFArea's capabilities
|
||
|
||
// DBFCDX 대응: DBFArea를 임베딩
|
||
type CDXArea struct {
|
||
DBFArea // ← DBF 상속 (Go 임베딩)
|
||
|
||
indexes []*CDXIndex
|
||
curTag string
|
||
// ...
|
||
}
|
||
// CDXArea adds Indexer to DBFArea's capabilities (CDX-specific)
|
||
```
|
||
|
||
### SUPER_* 호출 → Go 임베딩 메서드 호출
|
||
|
||
```go
|
||
// Harbour:
|
||
// SUPER_CLOSE(&pArea->dbfarea.area) → ntxSuper->close(area)
|
||
|
||
// Go:
|
||
func (a *NTXArea) Close() error {
|
||
// NTX-specific: close all index files
|
||
for _, idx := range a.indexes {
|
||
idx.Close()
|
||
}
|
||
// SUPER call → DBFArea.Close() (임베딩으로 자동)
|
||
return a.DBFArea.Close()
|
||
}
|
||
|
||
func (a *DBFArea) Close() error {
|
||
// DBF-specific: flush and close data file
|
||
a.Flush()
|
||
a.file.Close()
|
||
// SUPER call → BaseArea.Close() (임베딩으로 자동)
|
||
return a.BaseArea.Close()
|
||
}
|
||
|
||
func (a *BaseArea) Close() error {
|
||
a.fBof = true
|
||
a.fEof = true
|
||
return nil
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 드라이버 등록 (Harbour hb_rddRegister 대응)
|
||
|
||
```go
|
||
var drivers = map[string]Driver{}
|
||
|
||
func RegisterDriver(d Driver) {
|
||
drivers[strings.ToUpper(d.Name())] = d
|
||
}
|
||
|
||
func init() {
|
||
RegisterDriver(&DBFDriver{})
|
||
RegisterDriver(&DBFNTXDriver{})
|
||
RegisterDriver(&DBFCDXDriver{})
|
||
}
|
||
|
||
// USE customers VIA DBFCDX
|
||
func OpenTable(path, driverName string) (Area, error) {
|
||
d, ok := drivers[strings.ToUpper(driverName)]
|
||
if !ok {
|
||
return nil, fmt.Errorf("unknown RDD: %s", driverName)
|
||
}
|
||
return d.Open(OpenParams{Path: path})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. DBSEEK → Index 호출 체인 정밀 분석
|
||
|
||
### 전체 흐름
|
||
|
||
```
|
||
User: SEEK "SMITH"
|
||
│
|
||
▼
|
||
dbcmd.c: HB_FUNC(DBSEEK)
|
||
│ pKey = "SMITH", fSoftSeek = SET SOFTSEEK value
|
||
│ SELF_SEEK(pArea, fSoftSeek, pKey, fFindLast)
|
||
▼
|
||
dbfntx1.c: hb_ntxSeek()
|
||
│ 1. GOCOLD (flush current record)
|
||
│ 2. Check lpCurTag != NULL (active index required)
|
||
│ 3. hb_ntxKeyPutItem() → convert "SMITH" to binary key
|
||
│ 4. Lock tag (read lock)
|
||
│ 5. hb_ntxTagKeyFind() → B-tree search
|
||
│ 6. Scope validation
|
||
│ 7. SELF_GOTO(recNo) → position data cursor
|
||
│ 8. SELF_SKIPFILTER() → apply SET FILTER / SET DELETED
|
||
│ 9. Set area.fFound flag
|
||
│ 10. Unlock tag
|
||
▼
|
||
Result: area.fFound = .T./.F., cursor at record or EOF
|
||
```
|
||
|
||
### 키 변환: hb_ntxKeyPutItem()
|
||
|
||
사용자가 전달한 값을 인덱스 키 바이너리 형식으로 변환:
|
||
|
||
```
|
||
타입 변환 방식 예시
|
||
──── ────────── ──────
|
||
C memcpy + 우측 공백 패딩 (KeyLength까지) "SMITH" → "SMITH " (8바이트 키)
|
||
코드페이지 변환 (hb_cdpnDup2) 적용
|
||
N hb_ntxNumToStr (정렬 가능 문자열) 123.45 → " 123.45"
|
||
D YYYYMMDD 문자열 (8바이트) Date → "20260328"
|
||
L 'T' 또는 'F' (1바이트) .T. → "T"
|
||
```
|
||
|
||
### B-tree 검색: hb_ntxTagKeyFind()
|
||
|
||
```
|
||
Phase 1: 루트→리프 순회
|
||
┌────────────────────────────────────────────┐
|
||
│ ulPage = root (page 0) │
|
||
│ WHILE ulPage != 0: │
|
||
│ page = LoadPage(ulPage) │
|
||
│ iKey = BinarySearch(page, searchKey) │
|
||
│ stack.push({page, iKey}) │
|
||
│ ulPage = page.children[iKey] │
|
||
│ END WHILE │
|
||
│ → 리프 페이지에 도착, iKey 위치에 결과 │
|
||
└────────────────────────────────────────────┘
|
||
|
||
Phase 2: 키 추출
|
||
CurKeyInfo = page[iKey]
|
||
recNo = CurKeyInfo.Xtra
|
||
|
||
Phase 3: FINDLAST 처리 (fFindLast=.T. 인 경우)
|
||
WHILE PrevKey() AND key matches:
|
||
이전 키로 이동 (마지막 일치 키 찾기)
|
||
|
||
Phase 4: 결과 판정
|
||
exact match → return TRUE (fFound 후보)
|
||
no match → return FALSE (SOFTSEEK에 따라 처리)
|
||
```
|
||
|
||
### 페이지 내 이진 검색: hb_ntxPageKeyFind()
|
||
|
||
```go
|
||
// Go 의사코드 (Harbour hb_ntxPageKeyFind 대응)
|
||
func pageKeyFind(tag *TagInfo, page *PageInfo, key []byte, keyLen int,
|
||
fNext bool, recNo uint32) (int, bool) {
|
||
lo, hi := 0, int(page.keyCount)-1
|
||
found := false
|
||
last := -1
|
||
|
||
for lo <= hi {
|
||
mid := (lo + hi) / 2
|
||
cmp := keyCompare(tag, key, keyLen, page.keyAt(mid), tag.keyLength, false)
|
||
|
||
// 레코드 번호로 타이브레이커 (fSortRec일 때)
|
||
if cmp == 0 && recNo != 0 && tag.fSortRec {
|
||
pageRec := page.recAt(mid)
|
||
if recNo < pageRec { cmp = -1 }
|
||
else if recNo > pageRec { cmp = 1 }
|
||
else { return mid, true } // 정확히 일치
|
||
}
|
||
|
||
// 내림차순 인덱스면 비교 반전
|
||
if cmp != 0 && !tag.ascendKey {
|
||
cmp = -cmp
|
||
}
|
||
|
||
if (fNext && cmp >= 0) || (!fNext && cmp > 0) {
|
||
lo = mid + 1
|
||
} else {
|
||
if cmp == 0 && recNo == 0 {
|
||
found = true
|
||
}
|
||
last = mid
|
||
hi = mid - 1
|
||
}
|
||
}
|
||
|
||
if last >= 0 {
|
||
return last, found
|
||
}
|
||
return int(page.keyCount), found
|
||
}
|
||
```
|
||
|
||
### 키 비교: hb_ntxValCompare()
|
||
|
||
```go
|
||
// Go 의사코드 (Harbour hb_ntxValCompare 대응)
|
||
func keyCompare(tag *TagInfo, val1 []byte, len1 int,
|
||
val2 []byte, len2 int, exact bool) int {
|
||
limit := min(len1, len2)
|
||
|
||
var result int
|
||
if tag.keyType == 'C' {
|
||
if tag.isBinSort() {
|
||
result = bytes.Compare(val1[:limit], val2[:limit])
|
||
} else {
|
||
// 코드페이지 기반 정렬 (collation)
|
||
result = codepageCompare(tag.codepage, val1[:len1], val2[:len2])
|
||
}
|
||
} else {
|
||
// N, D, L: 바이너리 비교
|
||
result = bytes.Compare(val1[:limit], val2[:limit])
|
||
}
|
||
|
||
if result == 0 {
|
||
if len1 > len2 { return 1 }
|
||
if len1 < len2 && exact { return -1 }
|
||
}
|
||
|
||
// 정규화: -1, 0, +1
|
||
if result > 0 { return 1 }
|
||
if result < 0 { return -1 }
|
||
return 0
|
||
}
|
||
```
|
||
|
||
### 페이지 스택과 SKIP
|
||
|
||
SEEK 후 스택이 유지되어 SKIP이 효율적:
|
||
|
||
```
|
||
SEEK 후 스택 상태:
|
||
stack[0] = {page=5, ikey=3} ← 루트
|
||
stack[1] = {page=12, ikey=7} ← 중간
|
||
stack[2] = {page=24, ikey=2} ← 리프 (현재 위치)
|
||
stackLevel = 3
|
||
|
||
SKIP +1 (hb_ntxTagNextKey):
|
||
IF stack[2].ikey+1 < page[24].keyCount:
|
||
stack[2].ikey++ ← 같은 페이지 내 이동 (I/O 없음)
|
||
ELSE:
|
||
stack[1].ikey++ ← 부모로 올라감
|
||
stack[2] = 새 리프의 첫 키 ← 새 페이지 로드 (I/O 1회)
|
||
|
||
SKIP -1 (hb_ntxTagPrevKey):
|
||
IF stack[2].ikey > 0:
|
||
stack[2].ikey-- ← 같은 페이지 내 이동
|
||
ELSE:
|
||
stack[1].ikey-- ← 부모로 올라감
|
||
stack[2] = 새 리프의 마지막 키
|
||
```
|
||
|
||
### SOFTSEEK / FOUND 판정 로직
|
||
|
||
```
|
||
hb_ntxTagKeyFind() 결과:
|
||
├── TRUE (정확한 키 일치)
|
||
│ ├── GOTO(recNo)
|
||
│ ├── SKIPFILTER (SET DELETED, SET FILTER 적용)
|
||
│ ├── 필터 후에도 키 일치 확인
|
||
│ │ ├── 일치 → fFound = TRUE
|
||
│ │ └── 불일치 (필터가 다른 레코드로 이동)
|
||
│ │ ├── SOFTSEEK ON → fFound = FALSE, 현재 위치 유지
|
||
│ │ └── SOFTSEEK OFF → GOTO 0 (EOF)
|
||
│ └── RETURN
|
||
│
|
||
└── FALSE (키 불일치, 다음 높은 키에 위치)
|
||
├── SOFTSEEK ON
|
||
│ ├── 스코프 범위 내 → fFound = FALSE, 현재 위치 유지 (다음 키)
|
||
│ └── 스코프 범위 밖 → GOTO 0 (EOF)
|
||
└── SOFTSEEK OFF
|
||
└── GOTO 0 (EOF), fFound = FALSE
|
||
```
|
||
|
||
### 성능 특성
|
||
|
||
```
|
||
인덱스 100만 키, 페이지당 50키 기준:
|
||
|
||
SEEK: ~4 페이지 로드 (log₅₀(1,000,000) ≈ 3.5)
|
||
SEEK+FILTER: +필터 평가 비용 (레코드별)
|
||
FINDLAST: +M번 역방향 이동 (M = 일치 키 수)
|
||
SKIP (순차): 같은 페이지면 I/O 없음, 페이지 넘어가면 1회 I/O
|
||
SKIP N: O(N × 페이지당 비용)
|
||
GO TOP: 루트→리프 좌측 최하단 = SEEK과 동일 비용
|
||
GO BOTTOM: 루트→리프 우측 최하단 = SEEK과 동일 비용
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Harbour vs Go 대응 요약
|
||
|
||
```
|
||
Harbour Go
|
||
────────── ────────
|
||
RDDFUNCS (101 function pointers) 여러 interface 조합
|
||
hb_rddInheritEx (memcpy + override) struct 임베딩
|
||
SELF_* macro (vtable dispatch) interface method call
|
||
SUPER_* macro (saved parent table) embedded struct method call
|
||
AREAP->lprfsHost interface 타입 assertion
|
||
hb_rddRegister("DBF") RegisterDriver(&DBFDriver{})
|
||
NULL entry = inherit 임베딩된 메서드 = 자동 상속
|
||
static RDDFUNCS dbfSuper Go 임베딩이 자동 처리
|
||
```
|
||
|
||
### 핵심 차이: Go가 더 나은 점
|
||
|
||
```
|
||
1. NULL 메서드 불필요 — 임베딩이 자동 상속
|
||
2. 타입 안전 — interface assertion으로 기능 확인
|
||
3. 작은 interface — 필요한 것만 구현 (Indexer 없이 DBF만 가능)
|
||
4. 테스트 용이 — mock interface 쉬움
|
||
5. 새 드라이버 작성 쉬움 — 101개 아닌 필요한 interface만 구현
|
||
```
|
||
|
||
---
|
||
|
||
## 변경 이력
|
||
|
||
| 날짜 | 변경 내용 |
|
||
|------|----------|
|
||
| 2026-03-28 | 초기 작성. Harbour RDD 101-method vtable 분석, Go interface 재설계 |
|