dependency-management

3 개의 포스트

코드 품질 개선 기법 27편: 티끌이 모여 태산이 되듯 의존성도 쌓이면 (새 탭에서 열림)

의존성 주입(Dependency Injection)은 코드의 유연성을 높이는 강력한 도구이지만, 명확한 목적 없이 모든 요소를 주입 대상으로 삼는 것은 오히려 코드 복잡도를 높이고 유지보수를 어렵게 만듭니다. 참조 투명한 유틸리티나 단순한 모델 클래스까지 외부에서 주입받기보다는, 복잡도가 낮거나 변경 가능성이 희박한 객체는 내부에서 직접 생성하는 것이 더 효율적일 수 있습니다. 따라서 의존성을 주입할 때는 객체의 라이프사이클 관리, 구현체 전환, 테스트 용이성 등 구체적인 목적이 있는지 먼저 검토해야 합니다. **불필요한 의존성 주입의 사례와 개선** * **과도한 주입의 예시**: 뉴스 기사 스니펫을 생성하는 `LatestNewsSnippetUseCase` 클래스에서 데이터 모델인 `NewsSnippet`의 팩토리 함수나, 단순한 문자열 포매터인 `StringTruncator`까지 생성자로 주입받는 경우입니다. * **개선 방향**: 상태를 갖지 않는 단순한 유틸리티 구현체나 데이터 모델의 생성자는 클래스 내부에서 직접 호출하도록 변경합니다. * **단순화 결과**: 환경에 따라 달라지는 값(Locale)이나 네트워크 통신 등 복잡한 로직을 가진 리포지터리만 주입 대상으로 남겨 코드를 더 간결하게 유지할 수 있습니다. **의존성 주입이 필요한 명확한 목적** * **라이프사이클 및 범위 관리**: 객체의 상태를 공유해야 하거나, 사용하는 객체보다 더 긴 수명을 가진 객체를 활용해야 할 때 주입을 사용합니다. * **의존성 역전(DIP)**: 모듈 간의 순환 의존성을 해결하거나 아키텍처에서 정의한 계층 간 의존 방향을 준수하기 위해 필요합니다. * **구현체 전환 및 분리**: 테스트나 디버깅을 위해 Mock 객체로 교체해야 하는 경우, 혹은 빌드 속도 향상을 위해 독점 라이브러리를 분리해야 할 때 유용합니다. **무분별한 주입이 초래하는 문제점** * **추적의 어려움**: 인터페이스와 주입이 남발되면 특정 로직의 실제 동작을 확인하기 위해 생성자의 호출자를 거꾸로 추적해야 하는 수고가 발생합니다. * **호출자 책임 전가**: 하위 클래스의 모든 의존성을 상위 호출자가 해결해야 하므로, 의존성 해결이 연쇄적으로 전달되어 메인 클래스에 과도한 책임이 집중됩니다. * **연관 데이터의 일관성 파괴**: 예를 들어 동일한 `Locale` 값을 사용해야 하는 여러 객체를 각각 주입받을 경우, 실수로 서로 다른 로케일이 전달되어도 컴파일 타임에 이를 감지하기 어렵고 테스트 작성이 까다로워집니다. 의존성 주입은 '할 수 있기 때문'이 아니라 '필요하기 때문'에 수행해야 합니다. 복잡한 비즈니스 로직이나 외부 시스템 의존성은 주입을 통해 유연성을 확보하되, 단순한 값 객체(Value Object)나 유틸리티는 직접 인스턴스화하여 코드의 명확성을 높이는 것을 권장합니다.

LLM을 활용한 대규모 악성 풀 리퀘스트 탐지 (새 탭에서 열림)

Datadog은 AI 코딩 어시스턴트의 도입으로 급증한 코드 작업량과 이로 인한 보안 취약점 문제를 해결하기 위해, LLM 기반의 실시간 코드 리뷰 시스템인 'BewAIre'를 구축했습니다. 이 시스템은 기존의 정적 분석 도구가 탐지하기 어려운 공격자의 의도와 교묘한 난독화 패턴을 추론하여, 매주 수만 건에 달하는 풀 리퀘스트(PR)를 실시간으로 검사합니다. 이를 통해 보안 팀의 리뷰 피로도를 대폭 줄이면서도 높은 정확도로 악성 코드 삽입을 차단하는 성과를 거두고 있습니다. **AI 시대의 코드 보안 위협과 한계** * **급증하는 코드량과 공격 표면:** AI 어시스턴트 활용으로 매주 약 10,000개의 PR이 생성되면서 보안 팀이 검토해야 할 범위가 기하급수적으로 늘어났습니다. * **정적 분석의 한계:** 기존 SAST 도구는 정해진 규칙 기반으로 작동하여, 정상적인 의존성 업데이트나 권한 변경으로 위장한 지능형 공격(예: tj-actions 해킹)의 '의도'를 파악하지 못합니다. * **교묘한 난독화 기법:** 공격자들은 Base64 인코딩을 사용해 악성 스크립트를 숨기거나, 신뢰할 수 있는 봇 계정을 도용하여 보안 검사를 우회하는 전략을 사용합니다. **LLM 기반 보안 시스템 'BewAIre'의 구조** * **데이터 전처리 및 컨텍스트 강화:** 모든 PR의 코드 차이점(Diff)을 추출하고 작성자 정보, 저장소 유형 등의 메타데이터를 결합하여 모델이 분석할 수 있는 형태로 정규화합니다. * **의도 중심의 추론(Inference):** LLM은 단순한 문법 검사를 넘어 수정 사항 뒤에 숨겨진 의도를 분석하며, 특정 변경이 악의적인지 아니면 정상적인 패턴인지 분류합니다. * **실시간 경보 체계:** 분석 결과는 Datadog 보안 신호로 변환되어 대시보드에 즉시 반영되며, 위험도가 높은 경우 보안 엔지니어에게 즉각적인 알림(Paging)을 전송합니다. **실전 검증을 통한 성능 및 성과** * **높은 탐지 정확도:** 수백 개의 PR 데이터셋을 테스트한 결과 99.3% 이상의 정확도를 기록했으며, 실제 발생했던 tj-actions 및 Nx 공격 사례를 100% 탐지해냈습니다. * **낮은 오탐률(False Positive):** 프롬프트 엔지니어링과 데이터 튜닝, 안전한 패턴에 대한 억제 규칙을 적용하여 오탐률을 0.03% 수준으로 유지하며 개발 속도 저하를 방지했습니다. * **맥락 제한 극복:** 모델의 컨텍스트 제한으로 인한 성능 저하를 방지하기 위해 데이터를 효율적으로 정제하고 프롬프트를 최적화하는 기술적 노하우를 적용했습니다. 대규모 개발 환경에서 보안을 유지하려면 인간 리뷰어의 피로도를 줄여줄 수 있는 지능형 자동화 도구가 필수적입니다. LLM을 보안 리뷰에 도입할 때는 단순한 코드 분석을 넘어 작성자의 의도와 주변 맥락을 함께 파악하도록 설계해야 하며, 이를 기존의 보안 모니터링 워크플로우에 통합함으로써 실질적인 방어 체계를 구축할 수 있습니다.

코드 품질 개선 기법 14편: 책임을 부여하는 오직 하나의 책임 (새 탭에서 열림)

단일 책임 원칙(SRP)을 기계적으로 적용하여 클래스를 과도하게 분리하면, 오히려 시스템 전체의 복잡도가 증가하고 사양의 제약 조건을 파악하기 어려워질 수 있습니다. 코드 품질을 높이기 위해서는 개별 클래스의 응집도뿐만 아니라, 분리된 클래스들이 맺는 의존 관계와 호출자가 짊어져야 할 관리 부담을 종합적으로 고려해야 합니다. 결국 핵심적인 제약 조건을 한곳에서 관리할 수 있다면, 약간의 책임이 섞여 있더라도 초기 구현의 단순함을 유지하는 것이 더 나은 선택일 수 있습니다. **과도한 책임 분리가 초래하는 문제** * 동적으로 실행 로직이 변하는 '론치 버튼'을 구현할 때, 버튼 바인딩 책임과 로직 선택 책임을 별도 클래스로 분리하면 각 클래스는 단순해지지만 시스템 구조는 복잡해집니다. * 로직별로 별도의 바인더 인스턴스를 생성하고 `isEnabled` 상태를 통해 실행 여부를 제어하게 되면, 버튼 하나에 여러 개의 리스너가 등록되는 등 내부 동작을 추적하기 어려워집니다. * 결과적으로 "단 하나의 로직만 실행되어야 한다"는 비즈니스 제약 조건을 확인하기 위해 여러 클래스와 루프 문을 모두 훑어야 하는 비용이 발생합니다. **제약 조건의 분산과 상태 중복** * 책임을 분리하면 특정 사양이 코드 전체로 흩어지는 '책임 떠넘기기' 현상이 발생할 수 있습니다. * 예를 들어 어떤 로직이 활성화되었는지 나타내는 상태를 상위 클래스(Selector)에 추가하면, 하위 클래스(Binder)의 `isEnabled` 속성과 데이터가 중복되어 상태 불일치 문제가 생길 위험이 있습니다. * 이러한 중복은 코드의 신뢰성을 떨어뜨리며, 사양 변경 시 수정해야 할 포인트가 늘어나는 결과를 초래합니다. **의존성 비대화와 라비올리 코드(Ravioli Code)** * 세부 사항을 은닉하기 위해 의존 관계를 더 잘게 쪼개면, 이를 조합해야 하는 호출자(Caller)의 코드가 비대해지는 '갓 클래스(God Class)' 현상이 나타날 수 있습니다. * 너무 작은 단위로 쪼개진 클래스들이 서로 얽히면 전체 흐름을 파악하기 위해 수많은 파일을 오가야 하는 '라비올리 코드'가 되어 유지보수성이 저하됩니다. * 객체 지향의 핵심은 캡슐화인데, 제약 조건을 보장하는 로직을 분리해버리면 오히려 캡슐화가 깨지고 외부 의존성만 강해지는 부작용이 생깁니다. **실용적인 설계를 위한 제언** 클래스를 분할할 때는 응집도라는 단일 지표에만 매몰되지 말고, 분할 후의 의존성 그래프와 호출자의 편의성을 반드시 확인해야 합니다. 만약 특정 클래스가 내부에서 핵심 제약 조건을 깔끔하게 관리하고 있다면, 억지로 책임을 나누기보다 그 응집된 구조를 유지하는 것이 시스템 전체의 결합도를 낮추고 코드의 가독성을 높이는 길입니다.