


큰 데스크톱 미디어 앱을 UI 멈춤 없이, 실행 취소를 깨지 않고 고치기
파일과 미디어를 다루는 큰 데스크톱 앱에서 작업할 때 사용합니다. 무거운 작업을 UI 스레드 밖으로 옮기고, 취소와 경계 사례를 처리하며, 실행 취소가 정확히 되돌리게 하고, 실제로 돌린 테스트만 보고합니다.
이 AI 규칙을 사용하려면
## 호출 경로 전체를 따라가고 상태마다 소유자를 하나만 정하기
- 코드를 수정하기 전에 해당 기능을 소유한 모듈과 기존 추상화를 먼저 이해하고, UI・자동화・실행 취소・영속성・백그라운드 작업까지 전체 호출 경로를 추적한다.
- 사소하지 않은 변경이라면 코드를 작성하기 전에 단일 진실 공급원, 상태 소유자, 격리 도메인, 불변조건, 실패 및 취소 동작을 먼저 파악한다.
- 구현 세부사항보다 아키텍처를 먼저 설계하고, 서로 무관한 계층에 흩어진 땜질식 수정보다 하나의 일관된 변경을 우선한다.
- 변경 가능한 상태마다 권한을 가진 소유자를 하나만 둔다. 파생 상태는 계산하거나, 명시적인 무효화 규약을 가진 캐시로 유지한다.
- 기존 도메인 연산을 재사용한다. 호출자가 프리뷰, 뷰, 컨트롤러, 자동화 도구, 테스트라는 이유로 별도의 구현을 추가하지 않는다.
- 코드는 그것을 소유한 기능 안에 배치하고, 공유 유틸리티는 여러 독립 기능이 함께 쓰는 인프라에만 한정한다. 타입과 파일 이름은 소유권이 명확히 드러나도록 짓는다.
## 주석은 짧게 쓰고 죽은 코드 없애기
- 주석은 최소한으로 유지한다. 이유, 불변조건, 안전 제약, 우회 방법이 자명하지 않을 때만 작성하고, 한 줄 이내로 짧게 쓴다.
- 코드를 그대로 설명하거나, 이름을 반복하거나, 현재 패치 내용을 서술하거나, 삭제 흔적을 남기거나, 주석 처리된 코드를 남겨두지 않는다.
- 설명 주석보다 정확한 이름, 작은 타입, 분리된 연산을 우선한다.
- 계산식이나 비즈니스 규칙은 단일 진실 공급원을 하나만 가진다. 다른 파일이나 화면에 절대 복사하지 않는다.
- 작업을 마치기 전에 죽은 코드, 사용하지 않는 상태, 낡은 호환성 경로, 임시 진단 코드를 제거한다.
- 프로젝트가 지원하지 않는 플랫폼, 버전, 설정을 위한 호환성 코드를 추가하지 않는다.
## 무거운 작업은 UI 스레드 밖에서 하고 await 뒤에 상태 다시 확인하기
- UI 스레드는 희소 자원으로 취급한다. UI 상태와 가벼운 조정 상태는 가질 수 있지만, 파일 작업, 디코딩, 모델 추론, 이미지 처리, 인덱싱, 내보내기, 블로킹 호출, 대규모 변환은 수행해서는 안 된다.
- 격리가 곧 성능은 아니다. 작업을 비동기로 표시하거나 태스크로 감싼다고 해서 현재 스레드에서 벗어나는 것은 아니다. 실제 실행 주체를 따라가야 한다.
- 스레드 이동은 명시적으로 표현한다. 스레드를 벗어나기 전에 최소한의 불변 입력값을 스냅샷으로 만들고, 값을 반환한 뒤, 그 값이 여전히 최신 상태인지 확인하고 나서 적용한다.
- 서스펜션 지점을 지날 때마다 공유 상태가 바뀌었다고 가정한다. 결과를 커밋하기 전에 아이덴티티, 세대(generation), 선택 상태, 사전 조건을 다시 검증한다.
- 구조화된 동시성을 우선한다. 구조화되지 않은 태스크라면 명확한 소유자, 취소가 중요한 경우 저장된 핸들, 정의된 정리(teardown) 절차가 필요하다.
- 취소는 협조적으로 이루어진다. 긴 루프는 의미 있는 경계마다 취소 여부를 확인하고, 부분적이거나 오래된 결과를 절대 커밋하지 않는다.
- 병렬 작업은 실제 희소 자원을 기준으로 상한을 둔다. 동일한 진행 중 요청은 중복 제거하고, 게이트는 짝이 맞는 범위 안에서 획득・해제하며, 각 컨티뉴에이션이나 콜백은 정확히 한 번만 재개한다.
- 비동기 작업을 기다리기 위해 동기식 대기, 세마포어, 락, 폴링으로 UI 스레드를 절대 막지 않는다.
## 파일 작업은 UI 스레드 밖에서 하고 파일은 한 번에 교체하기
- 읽기, 쓰기, 존재・메타데이터 확인, 열거, 복사, 이동, 교체, 삭제, 디렉터리 생성을 포함한 모든 파일시스템 작업은 UI 스레드 밖에서 실행한다.
- 모든 볼륨은 느리거나, 분리 가능하거나, 외부에서 변경되거나, 네트워크 기반일 수 있다고 가정한다. 크기가 작거나 이전에 접근이 성공했다고 해서 동기식 접근이 안전해지는 것은 아니다.
- 파일 작업 전에 소유한 모델 데이터를 스냅샷으로 만들고, 파일 작업을 실행하는 주체에서 관찰 가능한 상태를 변경하지 않는다.
- 완성된 출력물은 실제 대상 밖에서 준비하고, 목적지에 교체본을 만든 뒤 원자적으로 적용한다. 직접 쓰는 대신 실제 쓰기는 하나의 코디네이터를 거치도록 한다.
- 저장, 임포트, 생성, 썸네일, 삭제, 내보내기, 닫기가 서로 경합하지 않도록 동일한 파일이나 목적지를 대상으로 하는 작업은 직렬화한다.
- 고유한 임시 경로를 사용하고 성공, 실패, 취소 시 모두 정리한다. 완전한 교체본이 준비되기 전까지는 목적지를 삭제하거나 교체하지 않는다.
- 사용자가 요청한 파일 작업의 실패는 반드시 드러낸다. 무시된 오류, 빈 결과, 성공처럼 보이는 응답 뒤에 숨기지 않는다.
## 빨라졌다고 말하기 전에 측정하고 자주 도는 경로는 가볍게 유지하기
- 렌더링, 재생, 입력 처리, 뷰 업데이트, 임포트, 인덱싱, 저장, 복원, 내보내기 등 성능에 민감한 경로를 식별하고 핫 패스로 취급한다.
- 항목 단위, 프레임 단위, 반복되는 인터랙션 경로 안에서는 파일시스템 접근, 로깅, 직렬화, 모델・디코더 설정 등 블로킹을 유발하는 초기화를 수행하지 않는다.
- 개선되었다고 주장하기 전에 먼저 측정한다. 변경 전후로 동일한 워크로드를 프로파일링하거나 벤치마크한다.
- 저수준 튜닝보다 먼저 알고리즘 복잡도와 불필요한 작업을 고친다. 중첩 스캔, 반복 정렬, 루프 안에서의 복사 변경, 중간 할당, 반복적인 스레드 이동을 주의 깊게 살핀다.
- 대량 변경은 배치로 묶어 처리하고, 스냅샷・실행 취소・내보내기・닫기, 그리고 최신 데이터를 반환해야 하는 읽기 작업 전에는 대기 중인 상태를 먼저 반영(flush)한다.
- 데이터는 지연 로딩으로 불러오고 채운다. 비용이 큰 재사용 가능한 작업을 캐시할 때는 명시적인 키, 용량, 무효화 규칙, 오래된 결과에 대한 정책이 있을 때만 캐시한다.
- 생애주기가 허용하는 한 비용이 큰 객체는 재사용하고, 관련 설정이 바뀌면 무효화한다. 진행률이나 미터 같은 고빈도 상태가 뷰 전체를 무효화하지 않도록 하고, 유지되는 메모리에는 상한을 둔다.
## 구현 전에 경계 사례와 실패 경로 나열하기
- 정상 흐름(happy path)만 처리해서는 부족하다. 구현하기 전에 해당하는 경계값, 생애주기, 동시성, 실패 케이스를 나열하고 각각을 어느 계층이 책임질지 정한다.
- 해당하는 경우 빈 값, nil, 0, 음수, 최댓값, 오버플로, 비유한값, 잘못된 형식, 중복, 누락, 오래된 값, 지원되지 않는 입력을 검증한다.
- 산술 연산, 인덱싱, 숫자 변환을 하기 전에 먼저 검증한다. 이후의 catch 구문이 산술 오류를 흡수해줄 것이라 믿지 말고, 소리 없이 값을 clamp하는 대신 정확한 반올림・clamp 규칙을 정의한다.
- 아무 동작도 하지 않는 경우와 반복된 작업도 다룬다. 이런 경우 정확하게 보고해야 하며, 변경 사항, 히스토리 항목, 중복 작업, 오해를 주는 성공 표시를 만들어서는 안 된다.
- 시작 전, 각 단계 도중, 작업이 끝났지만 커밋 전인 시점 모두에서 취소 가능성을 고려한다. 선택, 대상, 설정이 바뀐 뒤에 뒤늦게 완료되는 오래된 결과도 고려한다.
- 닫기, 종료, 절전, 절전 해제, 비활성화, 기기 변경, 작업이 진행 중인 상태에서의 다른 이름으로 저장 등 정리(teardown) 경로도 고려한다.
- 모든 실패 경로에서 불변조건을 지킨다. 부분적으로 성공한 경우 안전하게 재개 가능하다고 그대로 보고하거나, 아니면 롤백한다.
## 사용자의 한 동작을 한 번의 실행 취소로 되돌리게 하기
- 사용자 액션과 자동화된 액션 모두 동일한 도메인 변경 연산과 동일한 공유 히스토리를 거치게 한다.
- 하나의 일관된 사용자 의도는 실행 취소 가능한 액션 하나를 만든다. 각 하위 단계가 독립적으로 의미가 있지 않은 한, 내부 하위 단계를 별도의 히스토리 항목으로 노출하지 않는다.
- 히스토리 그룹을 열기 전에 인자와 사전 조건을 검증한다. 실패, 취소, 거부, 변경 없음으로 끝난 작업은 빈 항목을 만들어서는 안 된다.
- 중첩된 작업은 플랫폼이나 다른 서브시스템이 소유한 그룹을 닫지 않은 채 바깥쪽 액션으로 합쳐진다.
- 실행 취소는 누적 반올림 오차, 파생 상태의 어긋남, 고아가 된 관계, 오래된 선택 상태 없이 정확한 상태를 복원한다.
- 변경 사항이 히스토리에 영향을 준다면, 사용자 편집・자동화된 편집・플랫폼 이벤트 그룹화・동시 요청이 서로 뒤섞이는 상황도 테스트한다.
## 사용자가 이루려는 일을 기준으로 자동화 도구 설계하기
- 도구는 내부 API, 저장소 연산, 서비스 경계가 아니라 사용자 의도를 기준으로 설계한다. 스키마를 정하기 전에 결과, 성공 기준, 경고, 실패, 취소, 멱등성, 실행 취소 의미부터 정의한다.
- 도구는 하나의 일관된 동작을 수행한다. 보통 한 번의 호출로 원자적이고 이해하기 쉬우며 실행 취소 가능한 워크플로 하나를 완료한다. 서로 무관한 모드를 여럿 가진 광범위한 도구를 만들지 않으며, 시스템이 안전하게 스스로 처리할 수 있는 작업을 호출자가 저수준 도구를 여러 번 이어 붙이도록 강요하지 않는다.
- 파라미터는 사용자에게 보이는 도메인 개념으로 표현하고, 저장소 구조와 구현 세부사항은 숨긴다. 자동화에는 안정적인 엔티티 식별자를 사용하고, 위치 기반 인덱스와 라벨은 지속적인 식별자가 아니라 참고용 맥락으로만 다룬다.
- 모든 인자는 신뢰할 수 없는 값으로 취급한다. 정확한 타입, 유한한 숫자, 명시적인 범위, 유효한 식별자를 요구한다. 변경을 가하기 전에 요청 전체를 해석하고 검증하며, 여러 엔티티에 걸친 변경은 원자적으로 적용한다.
- UI와 동일한 도메인 연산을 재사용하고, 배치・연결・검증 로직을 중복 구현하지 않는다.
- 무엇이 바뀌었는지, 안정적인 식별자, 명시적인 무동작 상태, 경고, 실행 가능한 오류를 담은 구조화된 응답을 반환한다. 결과가 조정되었거나 달성되지 않았을 때는 성공처럼 보이는 응답을 절대 반환하지 않는다. 계약에서 약속하고 보고하는 경우가 아니라면 조용히 값을 clamp하거나, 대상을 바꾸거나, 폴백을 사용하지 않는다.
- 오래 실행되는 도구는 호출자가 확인할 수 있는 지속적인 작업(job)이나 최종 결과를 노출한다. 기계가 처리하는 계약은 지역화된 문구와 분리하여 안정적으로 유지한다. 설명 문구는 도구가 다른 도구와 언제, 왜, 어떻게 상호작용하는지 밝힌다.
## UI 상태는 UI 스레드에 두고 플랫폼 고유 동작 지키기
- 관찰 가능한 UI 상태는 UI 스레드에 유지하고, 백그라운드 결과는 불변 값으로만 경계를 넘게 한다.
- 관찰 범위는 해당 값이 필요한 가장 작은 뷰로 한정한다. 진행률, 호버, 재생 상태처럼 빈번하게 바뀌는 상태가 무관한 뷰 트리를 무효화해서는 안 된다.
- 렌더 본문에서 지속적인 부수 효과를 시작하지 않는다. 명시적인 취소와 정리 절차를 갖춘, 생애주기를 인식하는 태스크나 컨트롤러를 사용하고, 렌더 본문은 빠르고 부수 효과 없이 유지한다.
- 포커스, 키보드, Esc, 메뉴, 창 복원, 실행 취소, 드래그 상태, 닫기 확인 등 플랫폼 고유의 동작을 그대로 유지한다.
- 델리게이트와 완료 핸들러 계약은 성공, 실패, 취소, 대상 없음 등 모든 경로에서 정확히 한 번만 완료된다.
- API가 보장하지 않는 한, 프레임워크나 완료 콜백이 UI 스레드에서 호출될 것이라고 가정하지 않는다.
## 막힘 없이 미디어를 처리하고 시간과 프레임 값을 정확히 유지하기
- 외부 에셋에는 비동기 프로퍼티 로딩을 사용하고, 호출 스레드를 막을 수 있는 지원 중단된 동기식 접근자는 호출하지 않는다.
- 정밀한 시간, 프레임, 단위 정수 등의 값은 가능한 한 오래 원래의 도메인 형태로 유지하고, 부동소수점으로의 변환은 UI나 외부 포맷과 맞닿는 명시적인 경계에서만 한다.
- 기능이 명시적으로 바꾸지 않는 한, 타이밍, 스케일, 변환, 색상, 알파, 채널 레이아웃, 메타데이터 등 원본의 의미를 정의하고 그대로 보존한다.
- 생애주기가 허용하는 한 리더, 렌더 컨텍스트, 파이프라인을 재사용하고, 무효화가 필요한 경우가 아니라면 연속적인 인터랙션 도중에 다시 만들지 않는다.
- 동시에 실행되는 디코더, 리더, 내보내기, 추론, 추출 작업에는 상한을 두고, 취소는 모든 디코드・렌더・내보내기 루프에 전파한다. 하위 단계가 취소될 수 없는 경우 청크 사이마다 취소 여부를 확인한다.
- 트랙 누락, 특이한 컨테이너, 길이가 0이거나 무한정인 경우, 회전되었거나 알파 채널이 있는 입력, 비표준 레이트, 취소 상황을 함께 테스트한다.
## 스타일 값을 직접 쓰지 말고 디자인 토큰 쓰기
- 모든 스타일은 공유 디자인 토큰 시스템을 거쳐 적용한다. 여백, 크기, 글꼴 굵기, 모서리 반경, 테두리 두께, 불투명도, 색상, 그림자, 애니메이션 지속시간을 하드코딩하지 않는다.
- 필요한 토큰이 존재하지 않으면 값을 하드코딩하는 대신 먼저 토큰 시스템에 추가한다.
- 리소스 조회는 패키징된 애플리케이션, 로컬 실행, 테스트 환경 모두에서 동작해야 한다. 기능별로 개별 탐색을 만드는 대신 공유 리소스 조회 추상화를 사용하고, 각 번들 레이아웃을 검증이 필요한 별개의 설정으로 취급한다.
- 그 차이가 의도된 것이고 테스트된 경우가 아니라면, 디버그 전용 플래그로 근본적으로 다른 리소스 경로나 동작을 선택하지 않는다.
- 번들 리소스를 추가할 때는 패키지 매니페스트, 빌드 스크립트, 테스트를 함께 갱신하고, 최종 패키징 레이아웃을 검증하며, 선택적 기능 플래그 코드는 플래그가 있을 때와 없을 때 모두 빌드되도록 유지한다.
## 모든 작업이 눈에 보이는 결과로 끝나고 쓸모 있는 로그를 남기게 하기
- 사용자가 시작했든 자동화가 시작했든, 모든 작업은 관찰 가능한 성공, 실패, 거부, 취소 상태 중 하나에 도달해야 한다.
- 실패가 결과에 영향을 준다면 인터랙션 경계에서 발생한 오류를 무시하지 않는다. UI 상태, 도구 오류, 작업 실패, 조치 가능한 로그로 변환한다.
- Fire-and-forget(발사 후 방치) 방식의 작업은 명시적으로 최선 노력(best-effort)일 때만 허용된다. 반드시 필요한 영속성을 책임져서는 안 되며, 실패는 안전하게 무시되거나 기록되어야 한다.
- 실패를 로그로 남길 때는 작업 내용, 안정적인 식별자, 생애주기 단계를 함께 기록한다. 비밀 정보, 자격 증명, 사용자 입력 원문은 로그에 절대 남기지 않는다.
- 취소, 검증 실패, 리소스 사용 불가, 프레임워크 실패, 내부 불변조건 위반을 구분할 수 있을 만큼 충분한 진단 정보를 남긴다.
- 어서션은 프로그래머가 지켜야 할 불변조건에만 사용한다. 복구 가능한 사용자 입력, 외부 파일, 생애주기 경합, 자동화 요청에는 사용하지 않는다.
## 작고 결과가 늘 같으며 병렬로 돌 수 있는 테스트 쓰기
- 단위 테스트는 간결하고 동작 중심이어야 한다. 테스트 하나는 불변조건이나 회귀 하나를 검증하며, 이를 명확히 하는 데 필요한 준비 코드만 포함하고, 설명적인 이름을 쓰고 단계를 서술하는 주석은 쓰지 않는다.
- 경계값 행렬에는 파라미터화된 테스트를, 준비 코드에는 공유 픽스처 빌더를 사용하고, 여러 테스트 스위트에 준비 코드를 중복하지 않는다.
- 해당 동작을 입증할 수 있는 가장 작고 안정적인 지점을 통해 테스트하고, 비공개 세부사항이나 우연한 호출 횟수에 결합되지 않도록 한다.
- 모든 버그 수정에는 가능하다면 원래 결함에서 실패하는 회귀 테스트를 함께 작성하고, 검증, 무동작, 부분 실패, 취소, 오래된 완료, 정리 등 실패 경로도 함께 다룬다.
- 동시성 테스트는 게이트, 컨티뉴에이션, 주입된 훅을 이용해 결정론적으로 흐름을 조율하며, sleep, 타이밍 운, 스케줄링 순서에 의존하지 않는다.
- 테스트는 병렬로 실행된다. 변경 가능한 전역 상태, 고정된 임시 이름, 포트, 디렉터리를 공유하지 않는다. 고유한 임시 디렉터리를 사용하고 정리하며, 실제 자격 증명, 외부 서비스, 실제 사용자 파일을 단위 테스트에서 배제한다.
## 정확성부터 리뷰하고 실제로 돌린 테스트를 정확히 보고하기
- 스타일보다 정확성을 먼저 리뷰한다. 변경된 동작을 호출자, 공유 상태, 백그라운드 작업, 영속성, 실행 취소, UI, 자동화 영역까지 추적한다.
- 데이터 손실, 손상, 충돌, 멈춤, 경합, 보안 노출, 잘못된 결과, 망가진 실행 취소, 오해를 주는 성공 표시를 유발하는 문제를 최우선으로 지적하고, 그다음 성능, 유지보수성, 다듬기 순으로 다룬다.
- UI 스레드 블로킹, 서스펜션 이후 재검증 없이 커밋된 상태 같은 동시성 실수, 소유자 없는 태스크, 취소 누락, 무제한 팬아웃, 균형이 맞지 않는 게이트, 두 번 재개된 콜백을 살핀다. 여기에 더해 중복된 도메인 로직, 안전하지 않은 캐시, 검증되지 않은 경계값 연산도 확인한다. 리뷰 범위는 이번 변경과 그것이 직접 영향을 미치는 부분으로 한정한다.
- 주장한 내용을 검증한다. 변경 사항에 맞는 빌드와 테스트를 실행하고, 동시성 관련 변경이라면 취소와 생애주기 경로를 직접 실행해보며, 성능 변화는 대표적인 워크로드로 측정한다.
- 무엇을 실행했고 무엇을 실행하지 않았는지 정확히 보고한다. 일부만 완료했을 때 전체 빌드, 테스트, 런타임 검증을 마쳤다고 주장하지 않는다.
- 커밋은 범위를 좁히고 정확한 카테고리 접두사를 붙이며 무관한 정리 작업을 섞지 않는다. 변경 사항은 수정한 순서가 아니라 요약, 접근 방식, 핵심 설계 결정, 테스트 근거로 설명한다.
