C.W.K.
Stream
Lesson 02 of 04 · published

어떤 엔진도 위로 새지 않아

~11 min · stt-adapter, leaky-abstraction, contract, engine

Level 0불씨 없음
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"이음새 위에 'WhisperKit' 이란 단어가 나타나면, 추상화는 이미 실패한 거야."

어댑터의 유일한 일

STT 어댑터의 일은 자기 엔진을 인터페이스 처럼 보이게 하는 거, 그 이상 아냐. WhisperKit 은 자기 디코딩 옵션, 모델 enum, 오디오 포맷 기대가 있어; whisper.cpp 는 C API 가; SpeechAnalyzer 는 async 결과 스트림이 있어. 그 어느 것도 이음새 위로 새면 안 돼. 각 어댑터가 자기 엔진의 별난 점을 흡수하고 공유 계약만 노출해: 클립과 언어를 줘, 텍스트 더하기 실제 감지한 언어를 돌려줄게. 컨트롤러는 any STTEngine 을 쥐고 어느 것과 얘기하는지 절대 안 배워.

누수 테스트

추상화가 진짜인지 구체적 테스트가 여기 있어: 이음새 위 코드를 엔진-특화 단어로 grep 해. DecodingOptions, WhisperKitConfig, 모델 파일명이 DictationController 에 나타나면, 추상화가 새는 거야 — 컨트롤러가 이제 모른다고 주장한 디테일에 의존해. 깨끗한 어댑터는 그 단어들이 어댑터 파일 안에만 나타난다는 뜻이야. 그 위 전부는 중립 계약을 말해.

누수 (나쁨):
  컨트롤러 -> WhisperKit 의 DecodingOptions 직접 읽음   # 엔진이 위로 샘

깨끗 (좋음):
  컨트롤러 -> STTEngine.finalize(clip, language)          # 중립 계약
  WhisperKitEngine -> DecodingOptions 내부에서 만듦          # 별난 점 흡수

계약은 하류가 필요한 걸 담아

반환 타입은 일부러 작지만 완전해: 텍스트, 그리고 엔진이 감지한 언어. 왜 언어? 바로 다음 단계 — 정리 — 가 그걸 필요로 하고, 엔진이 아는 유일한 컴포넌트니까. 어댑터가 언어를 떨구면, 정리가 다시 감지하거나 추측해야 해, 엔진이 이미 푼 문제를 다시 푸는 거야. 좋은 계약은 하류 소비자가 필요한 걸 정확히 담아 그 이상 안 담아 — 엔진 내부 확신 점수도, 토큰 타이밍도 아니라, 앱 나머지가 작동하는 두 사실만.

어댑터는 별난 점을 흡수해; 전달 안 해. 좋은 어댑터의 척도는 엔진의 특이성이 그 경계에서 멈춘다는 거야. 호출자가 어댑터를 쓰려고 엔진을 알아야 하면, 추상화가 아니라 통과-경로를 지은 거야. 설정, 포맷, 에러 모양을 흡수하고 — 중립 계약만 노출해.
내가 처음 쓴 WhisperKit 어댑터가 샜어 — 컨트롤러가 '지금만' 디코딩 옵션을 설정하러 손을 뻗고 있었어. SpeechAnalyzer 엔진을 더하고 컨트롤러가 갑자기 분기가 필요해지기 전까진 무해해 보였어: 'WhisperKit 이면 옵션 설정; SpeechAnalyzer 면 이 다른 걸.' 그 분기가 나한테 청구된 누수였어. 옵션을 각 어댑터 안으로 되돌리니 분기가, 그리고 함께 결합이 지워졌어.

Code

엔진의 별난 점이 어댑터 경계에서 멈춰·swift
final class WhisperKitEngine: STTEngine {
    let id = "whisperkit"
    private let kit: WhisperKit

    func finalize(_ clip: AudioClip, language: LanguageMode) async throws -> STTResult {
        // 모든 WhisperKit-특화 설정을 여기서 만듦, 절대 노출 안 함.
        var opts = DecodingOptions()
        switch language {
        case .korean:  opts.language = "ko"
        case .english: opts.language = "en"
        case .auto:    opts.detectLanguage = true
        }
        let out = try await kit.transcribe(audioArray: clip.samples, decodeOptions: opts)
        return STTResult(text: out.text, detectedLanguage: out.language ?? "en")
    }
}
// 컨트롤러는 STTResult 를 봐. DecodingOptions 는 절대 안 봐.

External links

Exercise

네 코드의 어댑터나 래퍼를 골라 그 위 코드를 밑바탕 라이브러리의 타입 이름으로 grep 해. 모든 히트가 누수야. 하나 골라서 밑바탕 라이브러리를 갈아끼우려 할 때 뭐가 깨지는지 묘사해 — 그 깨지는 게 정확히 누수가 네게 치르게 하는 비용이야.
Hint
새어 나온 각 타입 이름은 호출자가 특정 라이브러리에 몰래 의존하는 곳이야. 라이브러리를 갈아끼우면 그 사이트 하나하나가 편집이 필요해 — 어댑터가 막았어야 할 결합이야. grep 히트 수가 교체가 실제 치를 비용의 직접 척도야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.