1. 시험 출제와 교안 개정의 악몽: "1글자 차이로 문항 오류가 갈리는 교단 현장"
학교 현장의 정기 고사 출제나 소프트웨어 실습 수업에서는 두 텍스트의 미세한 차이를 비교(Diff)해야 하는 상황이 일상적으로 발생합니다:
- 시험 문항 오류의 치명성: 지문의 쉼표 하나, 문제 배점의 숫자 하나가 달라지는 것만으로도 재시험 사태가 발생하고 교권 신뢰가 무너집니다.
- 프로그래밍 실습 채점: 학생들이 제출한 수백 개의 파이썬, C++ 소스코드와 표준 정답 코드를 비교할 때, 공백이나 들여쓰기(Whitespace) 차이 때문에 정답이 오답 처리되는 혼란이 빈번합니다.
- 클라우드 Diff 사이트의 보안 누출:
diffchecker.com같은 무료 웹사이트에 공문서나 시험지를 올리는 행위는 교육청 보안 지침 위반이자 엄연한 기밀 유출 사고입니다.
2. O(ND) 복잡도의 Myers Diff 엔진과 비동기 백그라운드 태스크 격리
두 텍스트 사이의 차이점을 찾는 고전 알고리즘인 LCS(Longest Common Subsequence)는 $O(M \times N)$의 연산량을 가집니다. 수천 줄의 문서에서는 연산량이 기하급수적으로 폭증하여 UI 스레드를 수 초간 얼려버립니다.
ZeliDesk의 ZeliDiff는 Git의 표준 알고리즘인 Myers Diff 알고리즘 (DiffPlex 엔진)을 채택하여, 차이점(Edit Distance $D$)에 비례하는 $O(N \cdot D)$의 압도적인 속도로 양방향 텍스트를 대조합니다:
시험 초안 / 기존 코드"] TextB["비교 문서 (TxtRight)
최종 수정본 / 학생 과제"] end subgraph BgWorker["비동기 백그라운드 태스크 (Task.Run)"] direction TB Differ["Myers Differ Engine
O(N·D) 최소 편집 거리"] Builder["SideBySideDiffBuilder
공백/대소문자 정규화 필터"] LineAlign["ChangeType 분류
Equal / Added / Removed
Imaginary(유령 행 삽입)"] Differ --> Builder --> LineAlign end subgraph UIThread["WinUI 3 가상화 렌더링"] ListL["ListViewLeft
(단방향 복사 툴팁 바인딩)"] ListR["ListViewRight
(원클릭 SyncLine 패치)"] end InputState --> BgWorker LineAlign --> UIThread
특히 UI 디스패처 스레드가 블로킹되는 것을 원천 차단하기 위해, 전체 텍스트 토큰화 및 모델 빌드 과정을 Task.Run 백그라운드 스레드로 완전 격리했습니다:
// 무거운 diff 계산은 백그라운드 스레드에서 전담하여 UI 프리징 0% 달성
var (leftLines, rightLines, added, removed, lineCount) = await Task.Run(() =>
{
var differ = new DiffPlex.Differ();
var diffBuilder = new DiffPlex.DiffBuilder.SideBySideDiffBuilder(differ);
// 사용자가 선택한 공백 무시(ignoreWhitespace) 및 대소문자 무시(ignoreCase) 옵션 적용
var result = diffBuilder.BuildDiffModel(leftText, rightText,
ignoreWhitespace: ignoreWhiteSpace, ignoreCase: ignoreCase);
var left = new List<DiffLine>(result.OldText.Lines.Count);
var right = new List<DiffLine>(result.NewText.Lines.Count);
for (int i = 0; i < result.OldText.Lines.Count; i++)
{
var lPiece = result.OldText.Lines[i];
var rPiece = result.NewText.Lines[i];
// ... DiffLine 모델 생성 및 행 상태(Equal/Removed/Added/Empty) 매핑 ...
}
return (left, right, addedCount, removedCount, result.OldText.Lines.Count);
});
3. 실시간 타이핑 중 스레드 재진입 방지: _isComparing & _pendingCompare 펜딩 루프
사용자가 텍스트를 빠르게 편집하거나 붙여넣기를 연타할 때, 비동기 메서드가 무분별하게 병렬 호출되면 스레드 경합(Race Condition)과 메모리 낭비가 발생합니다.
ZeliDiff는 불필요한 뮤텍스 락이나 세마포어 없이, 가벼운 두 개의 불리언 플래그를 활용한 펜딩 루프(Pending Loop) 아키텍처를 구현했습니다:
private bool _isComparing;
private bool _pendingCompare; // 진행 중 들어온 최신 요청 보류
private async Task DoCompareAsync()
{
// 이미 계산 중이면 다음 턴에 실행하도록 플래그만 세우고 즉시 반환
if (_isComparing) { _pendingCompare = true; return; }
_isComparing = true;
try
{
do
{
_pendingCompare = false;
// UI 스레드에서 최신 텍스트 캡처 (Task.Run 내부에서는 UI 요소 접근 불가)
var leftText = TxtLeft.Text ?? "";
var rightText = TxtRight.Text ?? "";
// 비동기 계산 수행 및 UI 컬렉션 갱신
// ... (await Task.Run) ...
} while (_pendingCompare); // 진행 중 들어온 최신 요청이 있다면 완료 후 즉시 1회 더 실행
}
finally
{
_isComparing = false;
}
}
이 패턴은 사용자가 타이핑하는 동안 이전 계산을 안전하게 완료하면서도, 타이핑이 끝나는 즉시 가장 최신의 텍스트 상태를 정확히 반영하여 UI와 데이터가 어긋나는 현상을 완벽히 방어합니다.
4. 높이 불일치를 극복하는 ChangeType.Imaginary 유령 행(Phantom Line) 가상화 정렬
텍스트를 좌우(Side-by-Side)로 비교할 때 가장 까다로운 점은 비대칭 변경(Asymmetric Chunk)입니다.
문서 A의 다음 줄이 그대로 올라와 붙어버리면, 좌우의 서로 다른 내용이 같은 높이에 나란히 놓여 비교가 불가능해집니다."
ZeliDiff는 이를 ChangeType.Imaginary라는 유령 행(Phantom Line)으로 해결합니다. 한쪽에만 존재하는 추가/삭제 블록이 있을 때, 반대편 패널에 내용이 비어 있는 Status = "Empty" 가상 행을 삽입하여 좌우의 행 번호와 수직 높이를 1:1로 물리적 일치시킵니다:
private void ApplyBrushes(DiffLine dl, bool isLeft)
{
switch (dl.Status)
{
case "Equal": // 변경 없는 행: 투명 배경, 기본 텍스트
dl.BackgroundBrush = _brushEqualBg; dl.ForegroundBrush = _brushEqualFg;
break;
case "Removed": // 삭제된 행: 소프트 레드 배경 (#ef5350)
dl.BackgroundBrush = _brushRemovedBg; dl.ForegroundBrush = _brushRemovedFg;
break;
case "Added": // 추가된 행: 소프트 에메랄드 그린 배경 (#66bb6a)
dl.BackgroundBrush = _brushAddedBg; dl.ForegroundBrush = _brushAddedFg;
break;
case "Empty": // 유령 행: 반대편의 변경 블록 높이와 정확히 맞물리도록 대칭 배경 처리
dl.BackgroundBrush = isLeft ? _brushAddedBg : _brushRemovedBg;
dl.ForegroundBrush = _brushEqualFg;
break;
}
}
5. 단 1회의 클릭으로 줄 단위 원자적 머지: SyncLine과 상태 전이(State Transition)
차이점을 발견한 후 교사가 일일이 복사해서 붙여넣을 필요가 없습니다. ZeliDiff는 단 한 번의 마우스 클릭으로 반대편 패널에 변경 사항을 즉각 이식하는 SyncLine 엔진을 내장하고 있습니다:
private async Task SyncLine(int sourceIndex, bool leftToRight)
{
// 1. 클릭된 행의 텍스트 추출
string textToCopy = leftToRight ? _diffLeft[sourceIndex].Text : _diffRight[sourceIndex].Text;
// 2. 대상 컬렉션의 대응 위치에 텍스트 주입
if (leftToRight) _diffRight[sourceIndex].Text = textToCopy;
else _diffLeft[sourceIndex].Text = textToCopy;
// 3. 유령 행(Empty)을 제외한 실제 유효 텍스트만 필터링하여 원본 텍스트박스 복원
var newLeft = _diffLeft.Where(d => d.Status != "Empty" || (!leftToRight && sourceIndex == d.SourceIndex))
.Select(d => d.Text).ToList();
var newRight = _diffRight.Where(d => d.Status != "Empty" || (leftToRight && sourceIndex == d.SourceIndex))
.Select(d => d.Text).ToList();
TxtLeft.Text = string.Join("\r\n", newLeft);
TxtRight.Text = string.Join("\r\n", newRight);
// 4. 즉시 재비교하여 동기화 완료 상태("Equal")로 뷰포트 갱신
await DoCompareAsync();
}
빨간색(삭제)이나 초록색(추가)으로 강조된 행을 클릭하기만 하면, 반대편으로 텍스트가 즉시 병합되며 색상이 회색("Equal")으로 바뀌어 병합 완료를 시각적으로 즉시 인지할 수 있습니다.
6. 기존 웹 Diff 사이트 vs WinMerge vs ZeliDesk ZeliDiff 심층 비교
| 비교 항목 | 온라인 텍스트 비교 웹사이트 | WinMerge (설치형 전통 도구) | ZeliDesk ZeliDiff (엔지니어링 백서) |
|---|---|---|---|
| 폐쇄망(Air-Gap) 보안 | 불가 (클라우드 서버에 텍스트 유출) | 가능 | 100% 온디바이스 완전 격리 (네트워크 0바이트) |
| UI 모던 디자인 | 광고 배너 난립, 느린 반응 속도 | 구형 Win32 MFC 디자인, 복잡한 메뉴 | WinUI 3 Fluent 디자인 & 다크 오로라 테마 |
| 줄 단위 원클릭 병합 | 지원 안 함 (단순 눈 대조 전용) | 화살표 버튼을 정확히 클릭해야 함 | 행 자체를 클릭하는 직관적 SyncLine 머지 |
| 대용량 비동기 렌더링 | 브라우저 탭 멈춤 현상 발생 | C++ 고속 구동 | Task.Run 백그라운드 격리 & 펜딩 루프 방어 |
| 공백·대소문자 필터 | 유료 옵션이거나 부분 지원 | 세부 설정 필요 | 체크박스 원클릭 즉시 실시간 재정규화 |
7. 결론: "외부 유출 없는 100% 온디바이스에서 실현되는 무결점 문서 거버넌스"
단 한 줄의 문장, 단 하나의 숫자가 만드는 차이가 학생들의 성적을 바꾸고 행정의 신뢰를 좌우합니다.
ZeliDesk의 ZeliDiff는 중요한 공문서와 교육 자료를 외부 서버에 맡기는 타협을 용납하지 않습니다. 검증된 Myers Diff 알고리즘과 비동기 펜딩 루프, 그리고 직관적인 원클릭 델타 머지를 통해 교사와 실무자가 가장 안전하고 신속하게 문서의 무결성을 수호할 수 있도록 뒷받침합니다.