--- 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` 작성. `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`.