


큰 Java 백엔드에서 AI가 코드 스타일과 모듈 규칙을 지키게 한다
모듈과 프로바이더로 조립된 큰 Java 서버에서 AI에게 작업을 시킬 때 쓴다. 먼저 진입점 코드를 읽고, 자동으로 검사되는 코드 스타일과 지원하는 가장 오래된 Java 버전을 지키며, 사용하는 모듈을 모두 선언하고, 소스로 확인한 뒤 답하고, 푸시 전에 로컬에서 테스트한다.
이 AI 규칙을 사용하려면
## 수정하기 전에 모듈과 프로바이더가 어떻게 맞물리는지 읽는다
이런 종류의 대규모 서버는 하나의 애플리케이션으로 작성되는 것이 아니라 여러 모듈을 조립해서 만들어진다. 모듈 선언은 모듈의 이름과 그 모듈이 노출하는 서비스 인터페이스를 지정하고, 프로바이더는 그 서비스들을 하나의 특정 기술로 구현하며, 다른 모듈은 선언된 인터페이스만 사용한다. 프로바이더는 플랫폼의 서비스 로더 메커니즘을 통해 발견되며 고정된 생명주기를 따른다. 먼저 객체를 만들고 설정을 읽는 준비 단계가 있고, 그다음 서비스를 활성화하는 시작 단계가 있다.
### 어떤 단계를 건드리기 전에 파이프라인을 추적한다
컬렉터가 전송 계층을 통해 텔레메트리를 보내고, 리시버 플러그인이 이를 파싱하고 검증하며, 분석 엔진이 프로젝트 자체의 도메인 언어를 통해 이를 집계하고, 스토리지 플러그인이 결과를 영속화하며, 쿼리 플러그인이 이를 인터페이스에 제공한다. 한 단계를 변경하면 대개 다른 단계에도 대응하는 변경이 필요하다.
### 진입점을 먼저 읽는다
편집하기 전에 코어 모듈 정의, 모듈 시스템 기반 클래스, 도메인 특화 언어의 문법 파일, 애플리케이션 진입점, 그리고 아키텍처 문서를 읽는다. 이름만 보고는 기능이 어떻게 동작하는지 알 수 없다.
## 빌드에서 자동으로 검사되는 코드 스타일을 지킨다
여기서 스타일은 기계가 검사하므로, 위반은 논쟁의 시작이 아니라 빌드의 실패로 이어진다. 표준 출력으로 출력하지 말고 프로젝트의 로깅 파사드를 사용한다. author 애너테이션을 추가하지 않는다. 탭 문자, 별표 임포트, 사용되지 않거나 중복된 임포트, 빈 문장, 연속된 여러 개의 빈 줄을 사용하지 않는다. 한 줄에 한 문장을 유지하고, 쉼표·세미콜론·캐스트 뒤의 공백을 유지하며, 연산자 주변의 공백을 유지한다.
### 필수 패턴
오버라이드한 메서드에는 애너테이션을 붙인다. 동등성과 해시는 함께 오버라이드한다. 모든 문서화 파라미터·반환·예외 태그에 실제 설명을 붙인다. long 상수는 대문자 접미사 문자를 붙여 작성한다. switch에서는 default 분기를 맨 마지막에 둔다. 최대 파일 길이를 지키고, 그 한계에 근접하는 파일은 분리한다.
### 이름 규칙
상수와 정적 변수는 밑줄을 쓴 대문자, 타입 파라미터는 대문자, 타입은 파스칼 케이스, 지역 변수·파라미터·멤버는 카멜 케이스를 사용한다. 손으로 쓴 보일러플레이트보다 불변 컬렉션 팩토리와 프로젝트의 애너테이션 프로세서를 선호하고, 지역 변수와 파라미터는 final을 선호한다.
### 완전 정규화된 클래스 이름을 인라인으로 쓰지 않는다
임포트를 추가하고, 필드 선언·메서드 시그니처·지역 변수·제네릭 인자에서는 짧은 이름을 사용한다. 허용되는 예외는 두 개의 임포트된 클래스 사이에 실제로 단순 이름 충돌이 있는 경우, 그리고 짧은 이름이 독자에게 모호해지는 단 하나의 문서 링크뿐이다.
### 한 줄짜리 위임 메서드를 작성하지 않는다
본문이 다른 클래스로 전달하기만 하는 래퍼는 가치 없이 한 단계만 늘린다. 호출 지점에서 인라인하거나, 메서드 참조를 포함해 기반 객체를 직접 호출한다.
### 라이선스 헤더
소스·마크업·설정 파일에는 프로젝트의 라이선스 헤더가 들어간다. 주석을 담을 수 없는 형식은 라이선스 설정에서 제외되어 있으니, 헤더가 들어가면 안 되는 곳에 손으로 추가하지 말고 그 설정을 따른다.
## 클래스와 메서드에 비유가 아닌 하는 일로 이름을 붙인다
클래스와 메서드는 추상적인 비유가 아니라 기능에 따라 이름을 붙인다. seed, hydrate, bootstrap, prime 같은 비유적 동사보다 load, apply, unregister, compile, verify, commit, roll back 같은 구체적인 동사를 선호한다.
### 이름이 하는 일을 말하게 한다
정적 규칙을 로드하는 클래스는 인상적인 이미지가 아니라 정적 규칙을 로드한다는 것으로 이름 짓는다. 정해진 주기로 데이터베이스 상태를 동기화하는 타이머는 tick이 아니라 그 동기화로 이름 짓는다.
### 비유는 분리하라는 신호다
비유에 기대지 않고는 메서드 이름을 지을 수 없다면, 그 메서드는 아마 너무 많은 일을 하고 있다. 각 조각이 평범한 기능적 이름을 가질 때까지 분리한다.
## 코드만으로 알 수 없는 것만 주석으로 쓴다
코드가 이미 말하고 있는 것을 설명하지 않는다. 코드를 읽는 것만으로 충분하지 않은 곳에만 주석을 단다. 이 메서드에서는 보이지 않는 불변 조건, 여러 곳에 걸친 순서 제약, 자명하지 않은 분기가 왜 존재하는지, 겉보기에 불필요해 보이는 호출이 왜 필요한지 같은 것들이다. 코드가 명확하다면 주석을 쓰지 않는다.
### 떨어져 있는 로직을 연결한다
다른 곳에서 보장하기 때문에만 존재하는 검사에는 그 보장을 가리키는 한 줄짜리 포인터가 필요하다. 다른 곳의 알려진 결함에 대한 우회책에는 그 결함으로 향하는 링크가 필요하다.
### 다시 진술하는 문서는 걷어낸다
메서드 문서는 본문이 아니라 계약을 설명한다. 여러 문단에 걸친 근거 설명은 소스 파일이 아니라 설계 문서에 들어간다.
## 프레임워크 단언 메서드를 쓰고, 테스트를 바꾸기 전에 물어본다
항상 테스트 프레임워크의 단언 메서드를 사용한다. 테스트에서 언어에 내장된 단언문을 절대 사용하지 않는다. 테스트 러너가 마침 그 단언문을 활성화하고 있더라도, 프레임워크 단언이 코드베이스의 다른 곳에서 쓰이는 관례이고, 어떤 러너 설정에서도 살아남으며, 맥락 없는 오류 대신 실제 값을 알려주는 구조화된 실패를 만들어낸다.
### 실패하는 테스트는 고치고, 조용히 바꾸지 않는다
기존 단위·통합·엔드투엔드 테스트가 실패하면 고치려고 시도한다. 테스트 케이스를 수정해야 하거나 테스트 코드를 삭제해야 한다면, 그 변경은 적용되기 전에 사람이 다시 확인해야 한다. 제안하는 수정 또는 삭제와 그 이유를 드러내 보이고 명시적인 승인을 기다리며, 테스트를 직접 바꾸거나 제거하지 않는다.
### 테스트 종류를 범위에 맞춘다
단위 범위와 통합 범위는 서로 다른 패턴과 서로 다른 하네스를 사용한다. 같은 단언을 모든 레벨에서 중복하지 말고, 동작이 실제로 존재하는 범위에서 그 동작을 증명한다.
## 지원하는 가장 오래된 Java 버전에서 컴파일되는 코드를 쓴다
여러 장기 지원 릴리스를 지원하는 프로젝트는, 빌드 머신에 더 새로운 컴파일러가 있더라도 가장 오래된 릴리스에서 컴파일되고 실행되어야 한다. 에디터가 받아주는 것이 아니라 최소 버전을 기준으로 삼는다.
### 최소 버전이 오래되었을 때 피해야 할 구문
화살표 형식의 switch 표현식, 리스트를 직접 반환하는 스트림 종단 연산, 텍스트 블록, 레코드 타입, 타입 검사에서의 패턴 매칭, 봉인 타입은 모두 오래된 기준선보다 새로운 기능이다. 대신 명시적인 case와 break를 쓴 전통적인 switch, 리스트를 만드는 컬렉터, 문자열 연결, 생성된 접근자를 가진 일반 클래스, 타입 검사 뒤의 명시적 캐스트, 그리고 일반 클래스나 인터페이스를 사용한다.
### 안전한 구문
불변 컬렉션 팩토리, 더 새로운 옵셔널 메서드들, 람다 표현식, 메서드 참조, 그리고 스트림 인터페이스 자체는 오래된 기준선에서도 사용할 수 있으며 우회책이 필요 없다.
### 빌드를 믿지 말고 검색해서 확인한다
금지된 구문 하나하나를 소스 트리에서 검색하고, 일치하는 것이 없음을 확인한 뒤에 호환성을 주장한다. 더 새로운 컴파일러에서의 빌드는 이런 것들을 대신 잡아주지 않는다.
## 서브모듈과 생성된 코드를 빌드와 맞춰 둔다
프로토콜 정의와 웹 인터페이스는 서브모듈에 있을 수 있다. 항상 서브모듈을 포함해서 클론하고, 프로토콜 변경이 필요할 때는 서브모듈을 명시적으로 업데이트한다. 서브모듈 포인터에 반영되지 않은 프로토콜 변경은 다른 사람에게서는 빌드되지 않는다.
### 분석하기 전에 생성한다
생성된 코드를 분석하기 전에 컴파일 단계를 실행하고, 테스트를 실행하기 전에 프로젝트를 설치해서 프리컴파일러와 생성된 클래스가 내보내지도록 한다. 생성된 클래스를 절대 손으로 편집하지 말고, 그것을 만들어내는 정의를 바꾼 다음 다시 빌드한다.
### 모듈 간 변경 후에는 프로젝트 전체를 다시 빌드한다
두 개보다 많은 모듈을 변경했거나 원격에서 풀 또는 리베이스를 했다면, 개별 모듈을 골라 빌드하지 말고 프로젝트 전체를 빌드한다. 증분식 단일 모듈 빌드는 아카이브 크기는 그대로인데 내용이 바뀐 경우 로컬 저장소나 라이브러리 디렉터리에 오래된 아카이브를 남길 수 있고, 이는 추적하기 매우 어려운 런타임 실패를 만들어낸다.
## 문서와 변경 이력을 정확하게 쓰고 올바르게 보이게 한다
문서는 마크다운 엔진으로 렌더링되므로, 소스가 어떻게 읽히는지만이 아니라 어떻게 보일지도 고려한다. 상대 파일 경로는 유효하며 저장소에서도 문서 사이트에서도 동작하니, 절대 링크 대신 계속 상대 경로를 사용한다. 동작이 바뀔 때마다 변경 이력을 업데이트한다.
### 변경 이력의 각 항목은 한 줄에 유지한다
항목 안에서 문장을 여러 소스 줄로 강제 줄바꿈하지 않는다. 중첩된 하위 항목은 실제 목록 항목이므로 괜찮다. 변경 이력은 릴리스 본문이 되는데, 릴리스 렌더러는 이어지는 하나의 개행을 줄바꿈으로 바꾸기 때문에, 문서 사이트는 줄을 다시 흘려보내 문제를 감추더라도 릴리스 페이지에서는 줄바꿈된 항목이 문장 중간에서 들쭉날쭉하게 끊겨 보인다.
### 설계 제안서는 출시되면 동결한다
설계 제안서의 구현이 반영되면, 제안서를 실제로 만들어진 것에 맞춰 한 번 동기화한다. 제안된 모든 변경은 과거 시제로 쓰고, 그 범위 안의 어떤 것에도 남은 작업·향후 작업·미해결 의존성·비어 있음 표기 같은 표현을 남기지 않는다. 그 동기화 이후 제안서는 동결된다. 그 이후의 지표, 대시보드, 점진적 개선은 대신 살아 있는 운영자 문서에 들어간다. 정말로 작고 선택적인 후속 작업만 향후 작업으로 남을 수 있으며, 큰 변경은 절대 안 된다.
### 앵커 링크로 상호 참조한다
다른 절을 참조할 때는 마크다운 앵커 링크를 쓰고, 절 기호나 맨 절 번호는 절대 쓰지 않는다. 깨진 앵커는 조용히 일반 텍스트로 렌더링되므로, 모든 앵커가 사이트의 슬러그 규칙에 따라 실제 제목으로 해석되는지 검증한다.
## 코드가 찾는 모듈을 모두 선언한다
다른 모듈의 서비스를 조회할 때는 반드시 선언이 뒷받침되어야 한다. 코드가 직접이든 헬퍼를 통해서든 모듈 매니저에게 모듈을 찾아달라고 요청한다면, 그 모듈의 이름이 프로바이더의 필수 모듈 목록에 나타나야 한다. 선언이 없어도 부팅 시에는 실패하지 않고, 그 코드 경로가 처음 실행될 때 예외를 던진다. 호출을 넓은 catch로 감싸는 것은 대안이 되지 않는다. 모듈을 선언하고, 방어적 처리는 일시적인 프로바이더 장애에 대해서만 남긴다. 브랜치를 점검할 때는 건드린 모든 모듈에서 모듈 매니저 조회를 검색하고, 각 대상이 선언되어 있는지 확인한다.
### 메인 구현만이 아니라 모든 구현을 업데이트한다
코어 모듈의 서비스 목록에 서비스를 추가할 때는, 독립 실행 도구와 테스트 하네스가 사용하는 목 프로바이더를 포함해 그 모듈의 모든 프로바이더 구현을 업데이트한다. 하나뿐이라고 가정하지 말고 트리에서 서브클래스를 검색한다. 메인 서버가 유일한 패키징이 아니다. 독립 실행 도구는 목 프로바이더와 모듈의 일부만으로 부팅하므로, 코어 모듈 계약의 변경은 그쪽에도 도달해야 하며 그렇지 않으면 그 도구들이 시작 시 실패한다.
### 코디네이션 모듈보다 이미 채워져 있는 서비스를 선호한다
리시버와 애널라이저 모듈은 보통 클러스터 코디네이션 모듈을 선언하지 않으므로, 그것을 직접 조회하면 런타임에 예외가 발생한다. 코디네이션 계층이 이미 채워 두었고 대부분의 컴포넌트가 필요로 하는 피어 목록을 노출하는, 코어 모듈의 피어 관리 서비스를 거쳐 간다. 어떤 모듈이 정말로 코디네이션 프리미티브를 필요로 한다면, 그 모듈을 명시적으로 선언한다.
### 의도를 스레드 로컬 사이드 채널로 전달하지 않는다
호출자의 의도를 하위 코드가 읽는 스레드 로컬로 넘기면, 멀리 떨어진 코드 경로 사이에 보이지 않는 결합이 생기고, 실행기나 이벤트 루프 같은 비동기 인계 지점에서 누수되며, 메서드 시그니처만으로는 동작을 이해할 수 없게 된다. 대신 인터페이스를 확장한다. 줄 수가 더 늘더라도 시그니처에 나타나는 파라미터, 메서드, 또는 모드 값을 추가한다. 드문 예외는 업계 전체가 스레드 로컬로 표준화한 컨텍스트 전파, 그리고 프레임워크가 강제하는 보안 주체다.
## 스키마 변경이 모든 노드에 닿을 때까지 기다린 뒤 데이터를 쓴다
스키마나 메타데이터 변경이 코디네이터 노드에 브로드캐스트되고 데이터 노드로 비동기 전파되는 경우, 정의 호출은 코디네이터가 이를 수락하는 즉시 리비전 표시자를 담아 반환하며 다른 노드를 기다리지 않는다. 정의가 아직 전파되지 않은 상태의 데이터 쓰기는 실행 노드에서 버려지고 로그에 남은 뒤 건너뛰어지므로, 펜스가 필요하다.
### 리비전 워터마크에서 대기한다
정의 변경을 발행한 뒤에는, 살아 있는 모든 노드가 반환된 리비전 이상을 적용했다고 보고할 때까지 클라이언트의 스키마 워처에서 타임아웃을 걸고 블로킹한 다음, 의존하는 쓰기를 풀어준다. 레지스트리에 대한 쓰기 후 읽기 확인은 전파를 증명하지 않는다.
### 삭제와 동기식 스토어를 처리한다
삭제가 쓸 만한 리비전 표시자를 반환하지 않을 때는 전용 삭제 대기 호출로 폴백한다. 정의 문장이 코디네이터에서 동기식으로 처리되는 스토어는 펜스가 전혀 필요 없다. 자기가 쓴 것을 읽을 수 있을 때까지 조회 호출을 폴링하던 예전 관용구를 다시 들여오지 않는다. 그것은 배리어 인터페이스보다 앞선 방식이고 이미 대체되었다.
### 어느 클라이언트 라이브러리를 읽고 있는지 확인한다
배리어와 워처 타입은 독립적으로 배포된 클라이언트가 아니라 트리 안의 클라이언트 포크에 있을 수 있다. 배포된 클라이언트에 없다는 것이 그 인터페이스가 사라졌다는 증거는 아니다.
## 추측하지 말고 소스와 문서를 읽어 확인한다
절대 추측하거나 짐작하지 않는다. 모든 분석은 소스 코드, 문서, 또는 관찰된 동작에 근거해야 한다. 이름이나 관례를 보고 기능이 어떻게 동작하는지 가정하지 말고 구현을 읽는다. 설계를 제안하기 전에 프로젝트 문서, 서브모듈 안의 가이드 파일, 그리고 리드미 파일을 읽는다.
### 설정과 플래그를 검증한다
어떤 플래그와 환경 변수가 실제로 존재하는지, 기본값이 무엇인지, 어떻게 파싱되는지 확인한다. 플래그를 환경 변수에 자동으로 연결하는 접두사 관례가 있다면 그것도 확인한다.
### 의존하는 프로젝트를 읽는다
어떤 기능이 있다고 가정하기 전에, 관련된 의존성과 동반 프로젝트의 소스와 문서를 읽는다. 지원되는 필드는 배포 차트에서, 지원되는 옵션은 테스트 인프라에서, 사용 가능한 호출은 클라이언트 라이브러리에서 확인한다. 동반 프로젝트가 로컬에 체크아웃되어 있을 가능성이 있다면 로컬 경로를 물어본다. 원격 인터페이스에서 파일을 하나씩 가져오는 것보다 로컬 클론을 검색하는 편이 훨씬 빠르기 때문이다.
## 지속적 통합(CI)에 맡기기 전에 코드와 테스트를 로컬에서 돌린다
푸시하기 전에 코드를 실행하고, 컨테이너를 띄우고, 테스트를 로컬에서 수행한다. 지속적 통합은 시행착오를 위한 환경이 아니다.
### 경로와 디렉터리 구조를 검증한다
플래그 이름을 보고 디렉터리 이름을 가정하지 말고 데이터가 실제로 어디에 떨어지는지 확인한다. 루트 경로 옵션은 그 아래에 다른 이름의 하위 디렉터리를 만드는 경우가 흔하다.
### 생성했거나 편집한 설정을 검증한다
설정 파일을 편집한 뒤에는, 특히 스트림 편집기나 스크립트로 편집했다면, 커밋하기 전에 실제 파서로 검증한다. 손상된 설정은 자동화에서 조용한 실패를 일으킨다.
### 이미지와 바이너리가 존재하는지 확인한다
컨테이너 이미지에 의존하는 파이프라인이나 엔드투엔드 설정을 작성하기 전에 그 이미지가 실제로 배포되어 있는지 검증하고, 이미지 안에서 어떤 바이너리·셸·디렉터리가 존재하는지 확인한 뒤에 그것들을 사용하는 명령을 작성한다. 이미지의 가용성은 배포 워크플로가 완료되었는지에 달려 있다. 이미지가 어디에 있는지도 유의한다. 자체 프로젝트 이미지는 전체 커밋 식별자를 태그로 쓰는 코드 호스트 레지스트리에 있을 수 있고, 공식 이미지와 서드파티 이미지는 관례적인 태그와 함께 공개 레지스트리에 있다.
## 프로젝트의 전용 제출 워크플로와 승인된 자동화 단계로 푸시한다
리뷰 브랜치로 커밋하고 푸시할 때는 프로젝트의 전용 제출 워크플로를 사용한다. 이 워크플로는 매 푸시 전에 컴파일, 스타일 검사, 라이선스 헤더 검증 같은 사전 점검을 실행하고, 리뷰 요청이 아직 없으면 새로 연다.
### 자동화 허용 목록을 지킨다
조직은 서드파티 자동화 단계에 대해 허용 목록을 강제하고, 각 단계를 버전 태그가 아니라 승인된 리비전 식별자에 고정하도록 요구할 수 있다. 어떤 단계가 허용되지 않아 리뷰가 막혔다면, 승인된 목록을 확인하고 승인된 고정값을 쓰도록 워크플로 파일을 업데이트한다. 플랫폼 벤더나 조직 자체가 소유한 단계는 일반적으로 고정 없이 허용된다.
### 추측하지 말고 무엇이 없는지 말한다
소스를 찾을 수 없거나, 무언가를 어떻게 실행하는지 모르거나, 코드만으로 답이 분명하지 않다면, 짐작하지 말고 물어본다. 런타임 동작을 조사할 때는 실제 데이터로 로컬 테스트를 구성하고, 결과를 예측하는 대신 실제 출력을 관찰한다.
