검색/이벤트 기반 상태 머신을 동작을 깨뜨리지 않고 만들고 고치기

이벤트 기반 상태 머신을 동작을 깨뜨리지 않고 만들고 고치기

상태 머신 라이브러리를 만들거나 고칠 때 쓴다. AI가 이벤트 하나를 끝까지 처리한 뒤 다음으로 넘어가고, 오류가 나도 정리 콜백을 건너뛰지 않으며, 스레드와 자식 머신을 안전하게 지킨다. 테스트를 먼저 써서 동기·비동기 엔진에서 돌리고, 코드·문서·스키마를 맞춘다.

이 AI 규칙을 사용하려면
## 상태 머신의 각 로직을 어느 모듈에 둘지 정하기 코드를 변경하기 전에 책임을 매핑하여, 동작이 필요한 지식을 보유한 컴포넌트에 남도록 한다. ### 명확한 경계 정의 - 상태 및 이벤트 정의, 전이 구성, 콜백 디스패치, 시그니처 조정, 실행 엔진, 레지스트리, 통합, 직렬화를 분리한다. - 디스패치와 시그니처 조정 같은 일반 인프라는 상태 머신 도메인과 독립적으로 유지한다. - 각 모듈, 클래스, 함수에는 변경해야 할 명확한 이유를 하나만 부여한다. - 편의를 위해 관련 없는 모듈을 확장하기보다 명시적 인터페이스를 갖춘 작고 집중된 모듈을 선호한다. ### 논리를 의도적으로 배치 - Information Expert를 적용한다. 필요한 지식이 이미 있는 곳에서 결과를 계산한 뒤 반환하거나 신호를 보내며, 다른 곳에서 다시 계산하지 않는다. - Law of Demeter를 적용한다. 협력자가 필요로 하는 구체적인 값을 전달하고 그 상위 객체를 전달하지 않는다. - Interface Segregation을 적용한다. 헬퍼가 전체 객체를 필요로 하지 않는다면 범위가 좁은 필드나 프로토콜을 받는다. - 리팩터링할 때 공개 동작을 보존하고, 논리를 옮기기 전에 그 동작을 규정하는 테스트를 식별한다. ## 이벤트 하나를 끝까지 처리한 뒤 다음 이벤트로 넘어가기 이벤트 처리를 서로 다른 두 수준으로 모델링하고 구현 중에 두 수준을 합치지 않는다. ### 처리 수준 구분 - 마이크로스텝은 선택된 전이 집합 하나를 정의된 순서대로 원자적으로 실행한다. 순서는 사전 콜백, 이탈 콜백, 전이 액션, 진입 콜백, 사후 콜백이다. - 매크로스텝은 외부 이벤트 하나를 처리하고 머신이 안정 구성에 도달할 때까지 마이크로스텝을 반복한다. - 무이벤트 전이가 하나도 활성화되지 않고 내부 큐가 비어 있을 때만 구성이 안정적이다. ### 큐와 이벤트 의미론을 명시적으로 유지 - 외부에서 전송된 이벤트는 외부 큐에 들어가 현재 매크로스텝이 끝날 때까지 기다린다. - 내부에서 발생한 이벤트는 내부 큐에 들어가 이후 외부 이벤트보다 먼저 현재 매크로스텝 안에서 실행된다. - 할당되지 않은 전이 선언은 가드가 참이 될 때 실행되는 무이벤트 전이를 나타낼 수 있다. - 할당된 전이는 이름 있는 이벤트를 만든다. 명명 규칙에서 별칭을 파생하는 경우에는 명시적 식별자가 우선한다. - 큐 순서, 무이벤트 진행, 안정 구성에서의 종료를 서로 별개의 동작으로 테스트한다. ## 오류를 이벤트로 다루고 정리 콜백을 건너뛰지 않기 오류를 가로채는 위치와 실패 후에도 실행되는 수명 주기 콜백을 정의한다. ### 오류 경계 설정 - 각 머신 유형에 대해 오류를 이벤트로 포착하는 정책을 명시한다. 차트 지향 변형과 기본 변형은 의도적으로 서로 반대되는 기본값을 사용할 수 있다. - 전체 마이크로스텝을 하나의 포착 경계로 취급하지 말고 액션 블록 수준에서 오류를 포착한다. 수명 주기가 보장한다면 성공과 실패 모두에서 마무리 콜백도 계속 실행한다. - 포착한 실행 오류를 내부 이벤트로 디스패치하여 상태 차트가 선언된 전이로 처리할 수 있게 한다. - 내부 실행 오류 이벤트를 처리하는 중에 발생한 오류를 억제하거나 종료하여 재귀적 실패 루프를 방지한다. ### 콜백의 구분 보존 - 일반 수명 주기 콜백을 이벤트별 콜백 및 상태별 콜백과 분리한다. - 이름이 `error`인 콜백이 자동으로 일반 콜백이 되는 것은 아니다. 관례에 따라 호출하기 전에 대응하는 오류 전이가 있어야 한다. - 오류 이벤트 처리 중에 전이 액션이 로컬 오류 가로채기를 비활성화한다면, 언제나 자체 오류 처리기를 사용하는 진입 및 이탈 블록의 규칙과 이 규칙을 구분한다. - 성공, 일반 실패, 오류 처리 내부의 실패, 마무리 순서를 각각 독립적으로 테스트한다. ## 큐나 잠금을 바꿀 때도 스레드와 자식 머신을 안전하게 지키기 큐, 잠금, 자식 머신 관리를 변경할 때 스레드 안전성과 호출 라우팅 보장을 그대로 유지한다. ### 안전한 동시 처리 유지 - 한 번에 최대 한 개의 스레드만 머신 인스턴스의 전이를 실행하도록 허용한다. - 동시 발신자를 지원할 때는 스레드 안전 우선순위 큐를 사용한다. - 동시성 계약을 다시 설계하고 입증하지 않은 채 스레드 안전 큐를 일반 목록이나 스레드 안전성이 없는 덱으로 바꾸지 않는다. - 조율된 발신 스레드와 배리어를 사용해 실제 경합 상황을 스트레스 테스트한다. - 블로킹 스레드 테스트에서는 정리 중에 스레드가 남을 수 있는 고정 sleep 대신 명시적인 시간 제한이 있는 중단 가능한 이벤트 대기를 사용한다. ### 호출된 자식을 정밀하게 관리 - 자식 호출이 종료되었고 취소되었을 때만 제거한다. - 종료되었지만 취소되지 않은 자식은 이를 소유한 상태가 활성 상태인 동안 라우팅 가능한 상태로 유지한다. - 처리 루프 안에서 자식 생성이 블로킹된다면 루프가 계속되기 전에 초기 수명 주기 리스너를 통해 자식 참조를 확보한다. - 수명 주기 제어를 우회하지 말고 호출 관리자와 자식 처리기를 통해 자식 대상 이벤트를 라우팅한다. - 취소, 자연 종료, 유지된 라우팅, 지연 이벤트 전달을 서로 다른 사례로 테스트한다. ## 정해진 개발 환경에서 테스트를 돌리고 브랜치 커버리지 측정하기 프로젝트에 고정된 환경과 태스크 러너를 통해 설치, 테스트, 품질 검사, 문서화를 실행한다. ### 테스트를 안전하게 실행 - 전체 스위트에 명시적인 시간 제한을 설정하여 남아 있는 스레드나 무한 루프를 드러낸다. - 테스트에 의도적인 대기나 대체 시간 제한이 포함되어 있다면 전체 스위트에 지원되는 병렬 실행을 사용한다. - 경로를 제한한 실행이 모듈 또는 문서 doctest를 누락할 수 있다면 프로젝트 루트에서 정식 스위트를 호출한다. - 확립된 기준 시간을 초과하는 실행은 진단 없이 계속 다시 실행하지 말고 원인을 조사한다. - 전역 디버그 로깅이 성능 측정값을 바꾸는 경우, 자세한 디버그 로깅은 표적 조사에만 활성화한다. - 방대한 출력은 한 번만 캡처한 뒤 저장된 결과를 분석하며, 동일한 고비용 스위트를 반복 실행하지 않는다. ### 의도한 커버리지 측정 - 필요한 경우 커버리지 수집을 명시적으로 활성화한 뒤 영향받는 모듈에 대해 누락 라인 보고서나 머신이 읽을 수 있는 보고서를 생성한다. 보고서 형식만 선택해서는 수집이 활성화되지 않을 수 있다. - 커밋하기 전에 라인 커버리지만이 아니라 브랜치 커버리지도 확인한다. ## 실패하는 테스트를 먼저 쓰고 동기·비동기 엔진에서 돌리기 테스트를 코드 작성 후에 수행하는 정리 작업이 아니라 구현 요구 사항으로 취급한다. ### 테스트 주도 반복 과정 따르기 1. 계획 단계에서 새로운 분기, 경계 사례, 오류 경로, 동시성 위험을 식별한다. 2. 누락되었거나 잘못된 동작 때문에 실패하는 집중 테스트를 추가한다. 3. 테스트를 통과시키는 최소한의 동작 변경을 구현한다. 4. 집중 테스트, 영향받는 모듈의 커버리지, 필요한 더 넓은 검사를 실행한다. 5. 동작과 커버리지가 보호된 뒤에만 리팩터링한다. ### 테스트 인프라 재사용 - 공유 매개변수화 러너를 사용하여 동일한 상태 차트를 동기 및 비동기 엔진에서 테스트한다. - 테스트 클래스를 복제하거나 아무 동작도 하지 않는 리스너를 추가하지 말고, 러너가 엔진 시작과 이벤트 전달을 추상화하게 한다. - 임시 경로, 환경 변경, 변경 가능한 상태에는 fixture를 사용한다. 경로를 하드코딩하거나 변경 가능한 전역 변수에 의존하지 않는다. - 도달 가능한 방어적 분기는 언제나 테스트로 다룬다. 불가능함이 확립된 경우에만 커버리지에서 분기를 제외한다. - 표준 커밋 검사가 필요한 브랜치 커버리지 임곗값을 계속 강제하도록 한다. ## 코드 검사를 돌리고 파서와 스키마 맞추기 구현, 예제, 생성된 계약, 개발자 문서에 일관된 하나의 품질 체계를 적용한다. ### 코드 규칙 적용 - 정식 도구 체인을 통해 프로젝트의 포매터, 린터, 임포트 정렬기, 타입 검사기, 테스트를 실행한다. - 정렬된 최상위 임포트를 선호한다. 순환 의존성을 끊는 데 필요한 경우에만 지연 임포트를 사용한다. - 프로젝트의 명명 및 docstring 규칙을 따르고, 명백한 이름이나 타입을 반복하지 말고 숨은 의미론을 문서화한다. - 문서 생성기가 요구한다면 속성 바로 아래에 속성 docstring을 사용한다. 인라인 주석은 명확하지 않은 논리에만 사용한다. ### 문서와 선언적 형식 테스트 - 가능한 경우 문서 예제를 실행 가능한 doctest로 만든다. - 실제 동시성이 필요한 예제는 단위 테스트에 넣고 문서에서 참조한다. - 선언적 어휘가 바뀔 때마다 파서, 런타임 동작, 게시된 스키마를 동기화된 상태로 유지한다. - 파서가 허용하는 키 집합을 신뢰할 수 있는 기준으로 삼고, 대응하는 스키마 속성과 대조하여 테스트한다. - 모든 구조화된 예제와 fixture를 스키마에 대해 검증하여 파서와 스키마의 불일치가 테스트 스위트를 실패하게 한다. ## 커밋 훅을 건너뛰지 않고 비밀 정보도 넣지 않은 채 변경 마무리하기 리포지터리의 기여 절차를 사용하여 추적 가능성, 생성된 산출물, 보안 경계를 보존한다. ### 품질 게이트 완료 - 리포지터리가 지정한 개발 브랜치를 대상으로 하고 커밋 메시지 규칙을 따른다. - 프로젝트가 선호한다면 서명된 커밋을 사용한다. - 커밋 훅을 우회하거나 필수 검사를 비활성화하지 않는다. - 훅이 파일을 다시 생성하거나 수정하면 그 결과를 검토하고 스테이징한 뒤, 모든 훅이 통과할 때까지 다시 커밋한다. - 최종 diff를 검토하고 구현, 테스트, 문서, 생성된 계약이 서로 일치하는지 확인한다. ### 신뢰 경계 보호 - 비밀, 자격 증명, 환경 파일을 절대 커밋하지 않는다. - 시스템 경계에서 신뢰할 수 없는 데이터를 검증하고 내부 불변 조건을 명시적으로 유지한다. - 단지 변경을 통과시키기 위해 큐 안전성, 오류 루프 방지, 스키마 검증, 커버리지 강제를 약화하지 않는다. - 실행하지 못한 필수 검사를 보고하고 직접적인 결과 없이 완료를 주장하지 않는다.