Files
llm-wiki/raw/interviews/archunit-static-analysis-limits.md
T

102 lines
10 KiB
Markdown

---
title: interview-prep / archunit-static-analysis-limits
source_type: interview-prep
status: raw
related_branches: [feature-architecture-enforcement-rules, feature-application-port-usecase-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, archunit, fitness-function, static-analysis, reflection]
created: 2026-05-28
status_label: collecting
---
# interview-prep: archunit-static-analysis-limits
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D11 (`ApplicationContext` 금지) + D12 (string-key bypass 한계) + Claims to Verify 의 violations-as-data 보완.
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton 의 boundary 자동 검증 정책 맥락.
## 질문 / Question
- 질문 원문: ArchUnit 같은 정적 분석 기반 fitness function 의 _한계_ 를 인지하면서 어떻게 _믿을 수 있게_ 만들었나요? runtime reflection 우회 / 빈 scope 의 vacuous pass / generated code 처리 같은 케이스는 어떻게 다뤘나요?
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- 정적 분석의 _한계__구체적_ 으로 인지하는지 (단순히 "있다" 가 아니라 _어떤_ 코드 패턴이 catch 되지 않는지).
- vacuous pass 함정 (scope 가 비어 있을 때도 SUCCESS 반환) 을 인지하고 _negative test 로 보완_ 했는지.
- runtime bypass 를 _code review checklist_ / Sonar / Spring Modulith 같은 _보완 도구_ 로 메우는 감각.
- generated code (MapStruct, Lombok, Spring AOT) 와 fitness function 의 충돌 처리.
- 함정 / 흔히 빠지는 답변 패턴:
- "ArchUnit 으로 다 막을 수 있다" — reflection / `ApplicationContext#getBean(String)` / `Class.forName(String)` 의 catch 불가 인식 없음.
- "rule 이 있으면 catch 된다고 믿는다" — vacuous pass 가능성 인지 못함.
- generated code 를 rule 의 _예외_ 로 처리하지 못해 build 가 깨지는 시나리오.
- 따라올 만한 후속 질문:
- `noClasses().that(...)` rule 이 빈 scope 에서 어떤 동작인가요? 어떻게 _vacuous pass_ 를 막을 수 있나요?
- `ApplicationContext#getBean(String)` 은 왜 ArchUnit 이 못 잡나요? `getBean(Class)` 는 어떻게 다른가요?
- MapStruct generated mapper 를 mapper boundary rule 에 어떻게 _예외_ 처리하나요? Spring AOT 와는?
- custom `ArchCondition` 은 언제 필요하나요? 예시?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-architecture-enforcement-rules.md` D11): ArchUnit 의 banned-class rule (`noClasses().that(pkg).should().dependOnClassesThat().haveFullyQualifiedName("...ApplicationContext")`) 은 _class literal_ 이 bytecode 에 박힌 의존만 catch. ca-tmpl 의 `application_does_not_depend_on_application_context` 가 이 패턴.
- 사실 2 (근거: `feature-architecture-enforcement-rules.md` D12 + `raw/official-docs/archunit-user-guide.md` 의 negative claim): ArchUnit 은 bytecode 의 method/constructor call 만 본다. _string content_ 자체는 bytecode 에 노출되지만 의미 분석은 안 한다. 결과적으로 `getBean("repository")` 같은 string-key bean lookup 과 `Class.forName(System.getenv("FOO"))` 같은 dynamic target 은 catch 불가.
- 사실 3 (근거: 파생 에러 [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]]): ArchUnit 의 vacuous pass 함정 — `@AnalyzeClasses(packages = ...)` 가 패키지 _필터_ 이고 _scan source_ 가 아니다. 분석 대상이 0개일 때도 rule 은 SUCCESS. ca-tmpl 의 첫 시도에서 `app-bootstrap` 의 test classpath 가 `sample-ticket` 을 안 보아 새 rule 이 vacuously pass 한 사례.
- 사실 4 (근거: `feature-architecture-enforcement-rules.md` Claims to Verify 마지막 행 `actually-implemented` + `feature-application-port-usecase-contract.md` 구현 결과 round 2): ca-tmpl 의 보완 — Spring Modulith `example/ninvalid` 패턴 차용. `src/app-bootstrap/src/test/java/.../violations/` 에 6 fixture + `ArchitectureViolationFixtureTest` 에 6 negative test. 각 rule 의 _실 catch 동작_ 을 commit 으로 박음.
- 사실 5 (근거: `feature-architecture-enforcement-rules.md` D9 + `raw/official-docs/mapstruct-generated-annotation-official.md#MS-ANNOT-C1`): MapStruct generated mapper 는 `javax.annotation.processing.Generated` 어노테이션 부착. ArchUnit `.and().areNotAnnotatedWith(Generated.class)`_annotation-based exemption_ 가능. annotation FQN 주의 — MapStruct 와 Spring AOT (`org.springframework.aot.generate.Generated`) 가 다른 클래스.
- 사실 6 (근거: `feature-application-port-usecase-contract.md` D14 + `CleanArchitectureTest#notDeclareKeyedIdempotency`): annotation parameter 의 enum value 검사는 ArchUnit DSL 로 표현 불가 → custom `ArchCondition<JavaClass>` 작성. `JavaAnnotation.get("idempotency")``JavaEnumConstant` 를 반환하므로 _reflection 없이 bytecode 만으로_ enum 값 catch.
- 내가 직접 한 경험:
- ca-tmpl 의 14개 ArchUnit rule 중 `D11` 의 banned-class rule + `D14` 의 custom `ArchCondition` 작성.
- vacuous pass 사례 발견 → `testImplementation project(':sample-ticket')` 으로 scope 확장 → `ArchitectureViolationFixtureTest` 6 negative test 로 catch 동작 보증.
- Lombok 금지 rule (`lombok..` 추가 to `domain_is_pure`) — Lombok 이 generated bytecode 를 만들어서 domain 의 framework 독립성을 흐릴 위험 차단.
- 트레이드오프:
- **정적 분석 한계 인정 vs 만능 도구화**: ArchUnit 으로 _대부분_ 의 boundary 위반은 catch 가능. 단 string-key bypass / reflection / DI runtime lookup 은 catch 불가 — _code review checklist_ + Sonar custom rule 로 보완. ca-tmpl 은 후자를 documented-only 로 유지.
- **rule 작성 비용 vs 위반 catch 정밀도**: 단순 DSL rule 은 빠르지만 vacuous pass 위험. custom condition + negative test fixture 는 catch 정밀도 ↑ 이지만 작성/유지 비용 ↑. ca-tmpl 은 _core rule 14개_ 에만 fixture 적용 (정밀도 우선).
- **generated code exemption**: 너무 넓은 exemption (예: `package..mapper..` 통째 제외) 은 hand-written 위반도 함께 통과. annotation-FQN 기반 exemption 이 _좁고 안전_ — MapStruct `@Generated` vs Spring AOT `@Generated` 의 FQN 차이 인식.
- 한계 / "이건 안 해봤다":
- Sonar custom rule / IDE inspection 으로 string-key bypass 를 _얼마나_ 보완할 수 있는지 정량 측정 미수행.
- Spring Modulith verifier 의 named interface 검증과 ca-tmpl 의 ArchUnit rule 의 _중복/대체_ 비교 미수행.
- `ApplicationContext#getBean(Class)` class-literal 호출이 ca-tmpl 의 D11 rule 로 _실제_ catch 되는지는 negative test 로 보증했지만, 실 사업 도메인에서의 false-positive 비율 측정 안 함.
## Sources / 근거
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D9, D11, D12 + Claims to Verify status.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — D14 custom ArchCondition + violations-as-data round 2.
- [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] — vacuous pass 의 실 사례.
- [[raw/official-docs/archunit-user-guide]] — `JavaAnnotation` / `JavaEnumConstant` / `EvaluationResult` 공식 (`ARCHUNIT-UG-C5`, `ARCHUNIT-UG-C6`).
- [[raw/official-docs/mapstruct-generated-annotation-official]] — `@Generated` FQN (`MS-ANNOT-C1`, `MS-ANNOT-C2`).
- [[raw/company-tech-blogs/spring-modulith-archunit-generated-exemption-and-violations-as-data]] — `annotatedWith(Generated.class)` predicate + `example/ninvalid` 패턴 (`SPRING-MOD-AU-C1`, `SPRING-MOD-AU-C2`).
## 미해결 / Unknown
- 모르는 것: Sonar / SpotBugs custom rule 이 _string-key bean lookup_ 류를 얼마나 잘 catch 하는지 — _Sonar Quality Profile_ 의 표준 rule set 보강 필요.
- 모르는 것: Spring Modulith named interface 검증의 internal model 이 ArchUnit 의 `JavaClass` 와 어떻게 다른지 — Modulith 도입 시 중복 rule 청산 비용.
- 확인 방법: `feature-ci-quality-gates-contract` 후속 branch 에서 Sonar custom rule + Modulith verifier 도입 PoC.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl 의 14 ArchUnit rule + 6 negative test fixture 의 _직접 구현_ 범위.
- vacuous pass 함정 두 갈래 (production 0개 매칭 vs scope 0개 매칭) 의 _구체 사례__보완 방법_.
- custom `ArchCondition` 으로 annotation parameter (enum value) catch 한 D14 의 구현 패턴.
- MapStruct `@Generated` exemption 의 annotation-FQN 기반 패턴 (구현은 안 했지만 `adapter-persistence/CLAUDE.md` 에 example 명시).
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- Sonar custom rule 작성의 _Quality Profile_ 표준 운영.
- Spring Modulith verifier 의 named interface 구체 configuration (도입 안 했음).
- 운영 환경에서 ArchUnit rule 변경의 _CI 차단_ 정책 (개인 경험 없음, `feature-ci-quality-gates-contract` 후속).
- **절대 과장하지 말 것**:
- "ArchUnit 으로 모든 boundary 위반을 catch 한다" 표현 금지 — D12 의 string bypass 한계가 명시됨.
- "violations-as-data 가 fitness function 의 _모든_ regression 을 잡는다" 표현 금지 — negative test 자체도 정적이라 reflection bypass 는 못 잡음.
- 운영 환경 검증 경험인 것처럼 표현 금지 — `locally-verified` 등급. ca-tmpl 은 template repository.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-boundary-enforcement]] (선행 — boundary 자동 검증의 자매), [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리의 _왜_), [[raw/interviews/transaction-port-vs-spring-transactional]] (자매 — application framework 격리).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/archunit-violations-as-data-pattern-2026-05-28]] (같은 작업의 글감).
- 답변 derive 후 위치: 생성 전. 후보 `wiki/interview/architecture/archunit-static-analysis-limits.md`.