2.21 타입 시스템과 정적 분석
개요와 동기
대부분의 결함은 늦게, 런타임에, 테스트나 사용자나 인시던트에 의해 잡힙니다. 그중 한 부류 전체는 그렇게 멀리 갈 필요가 없습니다. 타입 시스템과 좋은 정적 프로그램 분석 도구는 코드가 실행되기 전에 읽고 특정 실수가 일어날 수 없음을 증명합니다. 숫자가 필요한 곳에 쓰인 문자열, 역참조된 null, 쓰이기 전에 읽힌 변수, 처리되지 않은 경우입니다. 이 장은 정확성을 왼쪽으로, 즉 줄을 쓰는 순간에 더 가깝게 밀어 넣는 것에 관한 것입니다. 거기서 수정은 인시던트 리뷰 한 페이지 대신 몇 초가 듭니다.
정적 분석은 코드를 실행하지 않고 소스나 컴파일된 코드를 검토하는 모든 기법입니다. 타입 검사가 가장 널리 퍼진 형태지만, 이 계열에는 린터(스타일과 정확성 패턴을 표시하는 도구), 데이터 흐름 분석기, 그리고 먼 끝의 형식 검증도 있습니다. 공통된 약속은 테스트를 쓸 필요도, 기억할 리뷰어도 없이 모든 빌드에서 영원히 공짜로 얻는 보장의 부류입니다. 그 약속이 이 규율을 코딩 표준(2.1장), 소프트웨어 설계 원칙(2.2장), 테스트 전략(2.4장) 옆에 놓는 이유입니다. 큰 코드베이스를 변경하기에 안전하게 만드는 또 하나의 자동화된 방법입니다.
큰 팀에서는 가치가 누적됩니다. 수백 명의 엔지니어가 공유 시스템을 만질 때, 타입 시그니처는 컴파일러가 그들 모두에게 시행하는 계약이고, 파이프라인의 검사기는 지치지 않고 편애하지 않는 리뷰어입니다. 기업 환경에서 이는 온보딩과 통합 비용을 줄입니다. 타입이 의도를 문서화하고 분석기가 신입이 하는 실수를 잡기 때문입니다. 틀린 답이 급여를 거부하거나 데이터를 노출할 수 있는 정부와 다른 고위험 시스템에서는 기계가 검사한 보장이 증거입니다. 단지 테스트되지 않은 것이 아니라 구조상 불가능한 결함 부류 전체가 있음을 감사자에게 보여 줍니다. 이는 소프트웨어 품질(2.11장)과 애플리케이션 보안(4.2장)에 직접 연결됩니다.
핵심 원칙
- 정확성을 왼쪽으로 미십시오. 결함을 프로덕션이 아니라 작성 시점에 잡으십시오.
- 사람이 기억해야 하는 관례보다 기계가 검사하는 보장을 선호하십시오.
- 불법 상태를 아예 표현할 수 없도록 의도를 타입에 담으십시오.
- 동적 코드에는 타입을 점진적으로 도입하십시오. 전부 아니면 전무일 필요가 없습니다.
- 경고를 오류로 다루고, 기준선이 개선만 되도록 래칫을 쓰십시오.
- 편집기와 파이프라인에서 같은 분석기를 동일한 규칙으로 실행하십시오.
- 규율 있고, 정당화되고, 리뷰 가능한 억제로 거짓 양성을 관리하십시오.
권장 사항
정적 타이핑과 동적 타이핑을 눈을 뜨고 고른다
정적 타입 언어에서는 프로그램이 실행되기 전에 타입이 검사되고, 동적 타입 언어에서는 실행되면서, 그것도 검사한다면 검사됩니다. 어느 쪽도 보편적으로 옳지 않으며, 정직한 틀은 보장과 유연성의 맞교환입니다. 정적 타이핑은 기계가 검사하는 계약, 신뢰할 수 있는 리팩터링, 사물이 무엇인지 아는 도구(자동 완성, 안전한 이름 바꾸기, 정의로 이동)를 줍니다. 동적 타이핑은 빠른 프로토타이핑, 간결한 코드, 스크립트와 탐색적 작업에 맞는 낮은 의례를 줍니다. 시스템이 크고, 오래 살고, 위험이 클수록 정적 쪽이 더 값을 합니다. 코드베이스 전체 리팩터링의 비용과 런타임 타입 오류의 비용이 둘 다 규모와 함께 커지기 때문입니다.
두 번째, 직교하는 축에 대해서도 정확히 하십시오. 강한 타이핑 대 약한 타이핑입니다. 강한 타입 언어는 호환되지 않는 타입을 조용히 강제 변환하기를 거부하고(숫자에 문자열을 더하면 오류), 약한 타입 언어는 조용히 변환해 "3" + 4가 의도하지 않은 무언가를 내는 놀라움을 낳습니다. 정적이면서 약할 수도, 동적이면서 강할 수도 있습니다. 언어를 평가할 때 두 질문을 따로 하십시오. 사람들이 “타입이 있다”고 말할 때 실제로 원하는 것은 흔히 “강하다”이기 때문입니다.
타입 추론으로 타입을 싸게 유지한다
정적 타이핑에 대한 흔한 반대는 모든 줄에 타입을 쓰는 소음입니다. 타입 추론이 그 비용의 대부분을 없앱니다. 컴파일러가 맥락에서 타입을 추론하므로 경계(함수 시그니처, 공개 인터페이스)에만 주석을 달고 내부는 추론에 맡깁니다. 현대 언어는 공격적으로 추론하여 동적 코드의 간결함 대부분과 함께 정적 검사의 안전을 줍니다. 독자가 계약으로 의존하는 부분, 즉 내보낸 함수와 공개 타입에는 주석을 달고 지역 변수는 추론에 맡기는 사내 규칙을 채택하십시오. 이는 시그니처를 정직하고 스스로 문서화하게 유지하면서 내부를 어지러움에서 면하게 하며, 2.1장의 가독성 목표로 이어집니다.
불법 상태를 표현할 수 없게 만든다
실용적 타입 설계에서 가장 강력한 아이디어는 잘못된 상태를 적어 둘 수 없도록 타입을 만드는 것입니다. 주문이 결제 없는 “초안”이거나 결제가 있는 “접수됨”이라면, 초안이 실수로 결제를 가질 수 있고 접수된 주문이 결제를 못 가질 수 있는 null 허용 필드의 하나의 구조체로 모델링하지 마십시오. 합 타입(태그된 유니온, 구별된 유니온, 베리언트라고도 함)으로 모델링하십시오. 각자 자기 데이터를 가진 고정된 모양 집합 중 정확히 하나인 값입니다. 이제 잘못된 조합은 존재하지 않고, 값을 처리하는 코드는 각 경우를 다루어야 하며 아니면 컴파일러가 불평합니다. 이는 런타임의 “절대 일어나서는 안 됨”을 컴파일 시점의 “일어날 수 없음”으로 바꾸며, 그것이 요점입니다.
같은 본능이 몇몇 일상 도구를 이끕니다. 고정된 상태 집합에는 마법 문자열 대신 열거형을 쓰십시오. 검증된 값을 별개의 타입으로 감싸십시오(맨 문자열이 아니라 EmailAddress). 그러면 “검증되지 않은 입력”과 “검증된 이메일”이 컴파일러가 구분하는 다른 타입이 됩니다. 이는 오류 처리(2.20장)의 경계 검증 규율의 타입 시스템식 표현입니다. 가장자리에서 한 번 검증하고, 보장을 담은 타입으로 변환하고, 내부가 그것을 신뢰하게 하십시오.
널 가능성과 제네릭을 진지하게 받아들인다
발명자가 “10억 달러짜리 실수”라 부른 널 포인터는 정적 타입 시스템이 거짓말하던 가장 흔한 방식이었습니다. 문자열로 타입이 정해진 값이 몰래 null일 수 있었고, 충돌로 알게 되었습니다. 현대 타입 시스템은 널 가능성을 명시적으로 만들어 이를 고칩니다. 값은 절대 null이 아닌 String이거나, 쓰기 전에 풀어야 하는 Option/Maybe/널 허용 타입이며, 컴파일러가 빈 경우를 처리하도록 강제합니다. 언어가 널 불허 타입이나 옵셔널 타입을 제공한다면 어디서나 쓰고, 맨 널 허용은 스멜로 다루십시오. 이는 프로덕션 충돌의 한 속 전체를 없앱니다.
제네릭, 곧 매개변수적 다형성은 타입 안전을 포기하지 않고 여러 타입에 걸쳐 동작하는 코드를 쓰게 해 줍니다. List<T>는 캐스팅하고 기도하는 타입 없는 것들의 리스트가 아니라, 컴파일 시점에 검사되는 어떤 특정 타입 T의 리스트입니다. 강한 타입을 유지하는 재사용 가능한 컨테이너, 함수, 추상화를 만들 때 제네릭에 손을 뻗으십시오. 합 타입, 널 불허 타입, 제네릭의 짝짓기가 현대 타입 시스템이 단지 원시 값에 태그를 다는 것이 아니라 실제 도메인 규칙을 표현하게 해 줍니다.
기존 동적 코드에 타입을 점진적으로 도입한다
타이핑의 이점을 얻으려고 동적 코드베이스를 다시 쓸 필요는 없습니다. 점진적 타이핑은 타입 있는 코드와 없는 코드를 공존시켜, 가장 값을 하는 곳에 타입을 점진적으로 더하게 해 줍니다. 이제 많은 생태계가 이를 직접 지원합니다. 별도 타입 검사기가 검사하는 Python의 타입 힌트, 동적 언어로 컴파일되는 타입 있는 상위 집합, 기존 런타임 위에 얹는 타입 주석입니다. 경계와 가장 중요한 모듈(돈 코드, 보안 코드, 데이터 모델)에서 시작하고, 검사기를 허용적 모드로 켜고, 시간이 지나며 조이십시오. 오래된 코드가 따라잡는 동안에도 새 코드는 타입이 있어야 한다는 규칙을 더하십시오. 몇 분기 안에 큰 타입 없는 코드베이스도 대부분의 변경이 타입 검사되는 지점에 이를 수 있고, 가장 중요한 부분이 먼저 덮입니다.
린터, 타입 검사기, 더 깊은 분석기를 함께 실행한다
타입 검사는 한 계층이며, 다른 것들을 더하십시오. 린트 도구는 타입 검사기가 무시하는 의심스러운 패턴을 잡습니다. 항상 참인 대입, 사용되지 않는 변수, switch의 폴스루, 닫히지 않는 자원입니다. 더 깊은 분석기는 프로그램의 동작을 추론합니다. 데이터 흐름 분석은 값이 코드를 통해 어떻게 움직이는지 추적하여 “이 변수가 대입되기 전에 쓰이는가” 또는 “이 파일 핸들이 오류 경로에서 누수될 수 있는가” 같은 질문에 답합니다. 이런 도구 중 다수는 추상 해석에 기반합니다. 정확한 숫자 대신 “양수”, “영”, “음수”처럼 가능한 값의 집합 위에서 프로그램을 추상적으로 실행해, 어떤 단일 실행도 돌리지 않고 모든 실행에 걸친 속성을 증명하는 기법입니다.
일부 분석기는 보안 도구 옆에 있습니다. 정적 애플리케이션 보안 테스트(SAST)는 인젝션, 안전하지 않은 역직렬화, 오염된 데이터가 위험한 싱크에 닿는 것 같은 취약점 패턴을 소스에서 스캔하며, 여기서 설명한 데이터 흐름 기법을 공유합니다. 이 계열의 일부로 다루고 애플리케이션 보안(4.2장)과 조율하십시오. 실용적인 권장은 계층화된 집합입니다. 스타일과 명백한 버그를 위한 빠른 린터, 계약을 위한 타입 검사기, 도메인에 중요한 속성을 위한 하나 이상의 더 깊은 분석기입니다. 규칙이 모두에게 같도록 버전 관리되는 파일에서 설정하십시오.
경고를 오류로 다루고 기준선을 래칫한다
빌드를 실패시키지 않는 경고는 무시될 경고입니다. 로그가 수백 개의 용인된 경고로 차면 아무도 읽지 않고, 중요한 하나는 소음 속에 숨습니다. 새 경고가 빌드를 깨뜨려 가장 쌀 때 고쳐지도록 경고를 오류로 다루는 정책을 채택하십시오. 기존 경고가 수천 개인 레거시 코드베이스에서는 그 스위치를 하룻밤에 뒤집을 수 없으므로 래칫을 쓰십시오. 현재 개수를 기준선으로 기록하고, 이를 늘리는 변경을 막고, 시간이 지나며 줄여 가십시오. 기준선은 내려가기만 할 수 있습니다. 이렇게 하면 거대한 선행 정리 없이 오늘 엄격한 규칙을 켜면서, 상황이 결코 나빠지지 않고 꾸준히 좋아지는 것을 보장합니다.
분석을 편집기와 CI에 연결하고 피드백을 빠르게 한다
정적 분석은 피드백이 즉각적일 때 가장 값을 합니다. 같은 검사를 언어 서버 프로토콜이나 그에 상응하는 것을 통해 편집기에서 실행하여, 개발자가 저장하기도 전에 입력하는 동안 오류를 보게 하십시오. 그다음 동일한 규칙 집합을 지속적 통합(CI)에서 실행해 통과하지 않으면 아무것도 병합되지 않게 하고, 8.1장의 파이프라인과 연결하십시오. 둘은 일치해야 합니다. 편집기가 느슨하고 CI가 엄격하거나 그 반대면 사람들은 둘 다 불신하게 됩니다. 분석을 모든 변경에서 실행할 수 있을 만큼 빠르게 유지하고, 결과를 캐시하고, 가능한 곳에서는 바뀐 것만 분석해서 검사기가 세금이 아니라 도움이 되게 하십시오. 편집기와 파이프라인이 같은 규칙을 같은 방식으로 시행하면, 표준은 사람들이 잊는 문서이기를 멈추고 환경의 속성이 됩니다.
형식 검증은 그럴 만한 코드에 남겨 둔다
스펙트럼의 먼 끝에 형식 검증이 있습니다. 프로그램이 테스트를 통과한다는 것만이 아니라 정밀한 명세를 만족함을 수학적으로 증명하는 것입니다. 기법은 모델 검사(시스템의 상태를 완전 탐색)에서 정리 증명과 의존 타입(전체 명세를 담을 만큼 표현력이 큰 타입)까지 이릅니다. 이것은 가능한 가장 깊은 보장이면서 만들기 가장 비싸므로, 결함이 파국적이거나 인증이 요구하는 곳에서만 자리를 얻습니다. 암호 라이브러리, 비행 제어 코드, 하이퍼바이저, 핵심 프로토콜입니다. 대부분의 소프트웨어에 옳은 투자는 강한 타입과 좋은 분석기이며, 비용의 일부로 이점의 대부분을 얻습니다. 형식 기법(2.12장에서 소개)이 존재하고 그 선이 어디인지 알아, 필요한 드문 구성 요소에 의도적으로 손을 뻗으십시오.
억제를 정직하게 유지한다
어떤 분석기도 완벽하지 않으며, 신뢰받는 도구와 무시되는 도구를 가르는 규율은 그 실수를 다루는 방식입니다. 모든 진지한 도구는 발견 사항의 억제를 허용합니다. 각 억제가 좁고(한 줄이나 한 발견 사항이며 파일이나 규칙 전체는 안 됨), 주석에 이유를 담고, 다른 코드처럼 리뷰에서 보이도록 요구하십시오. 파일 맨 위의 일괄 비활성화는 커버리지가 조용히 썩는 방식입니다. 억제를 주기적으로 감사하고 늘어나는 더미를 규칙이 잘못 보정되었거나 누군가 숨기는 실제 문제가 코드에 있다는 신호로 다루십시오. 정직한 억제는 도구의 신뢰성을 유지하고, 조용하고 광범위한 억제는 도구를 연극으로 만듭니다.
장단점
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| 정적 타이핑 | 기계가 검사하는 계약. 안전한 리팩터링. 풍부한 도구 | 선행 의례가 더 많음. 초기 프로토타이핑이 느림 |
| 동적 타이핑 | 쓰기 빠름. 유연함. 낮은 의례 | 타입 오류가 런타임에 드러남. 리팩터링이 위험 |
| 타입 추론 | 간결함과 안전. 주석 소음이 적음 | 과용하면 추론된 타입이 의도를 가릴 수 있음 |
| 점진적 타이핑 | 점진적 도입. 핵심 코드를 먼저 덮음 | 타입 없는 가장자리가 샘. 부분적 보장 |
| 린터와 데이터 흐름 분석 | 타입이 놓치는 버그를 잡음. 실행이 쌈 | 거짓 양성. 설정하지 않으면 소음 |
| 래칫이 있는 경고의 오류화 | 새 문제를 막음. 기준선은 개선만 됨 | 방해로 느껴질 수 있음. 억제 정책 필요 |
| 형식 검증 | 가장 강한 보장. 모든 입력에 대한 속성 증명 | 비싸고 전문적. 정당화되는 경우가 드묾 |
반복되는 긴장은 보장 대 마찰입니다. 더 엄격한 타이핑과 더 깊은 분석으로 한 칸 갈 때마다 불가능해지는 버그의 한 부류를 사고, 한 칸마다 의례, 도구 실행 시간, 개발자에게 몇 분을 쓰게 하는 가끔의 거짓 양성이 더해집니다. 이해관계와 수명으로 해결하십시오. 일회성 스크립트나 스파이크는 가볍고 빠른 동적 끝을 원합니다. 결제 원장, 권한 검사, 정부가 15년 운영할 시스템은 강한 타입, 계층화된 분석기, 경고의 오류화, 가장 위험한 핵심에는 아마 형식 증명까지 원합니다. 엄밀함을 틀렸을 때의 비용에 맞추고, 추론과 점진적 도입으로 마찰을 감당할 수 있게 하십시오.
팀과 논의할 질문
코드베이스의 어디에서 타입 시스템이 최근 여러 프로덕션 인시던트를 막았을 것이며, 우리는 그것을 압니까? 대부분의 팀은 증거가 자기 인시던트 이력에 있는데도 타이핑을 추상적으로 논쟁합니다. 최근 10~20개의 프로덕션 결함을 뽑아 분류하십시오. 값이 기대되던 곳의 null, 경계를 가로질러 넘어간 잘못된 모양, 처리되지 않은 경우, 표류한 문자열 타입 값이 몇 개입니까? 이것들이 정확히 타입 검사기와 린터가 공짜로 잡는 결함입니다. 인시던트의 큰 비중이 그 바구니에 있다면, 그것이 일어난 모듈에서 더 강한 타이핑을 위한 구체적이고 금액으로 환산된 근거가 있는 것입니다. 거의 없다면 버그는 다른 곳(로직, 동시성, 요구 사항)에 있고 더 무거운 타이핑이 가치가 가장 높은 수가 아닐 수 있습니다. 어느 쪽이든 의견을 데이터로 바꿉니다.
점진적 타이핑을 채택한다면 어디서 시작하며, “충분히 됨”은 무엇을 뜻합니까? 큰 동적 코드베이스 전반에 검사기를 켜는 것은 스위치를 넘기는 일이 아니라 프로그램이며, 순서가 성공할지 멈출지를 결정합니다. 어느 모듈이 가장 위험을 지니는지(돈, 인증, 핵심 데이터 모델) 그래서 먼저 타입이 필요한지, 어느 것이 안정적이고 위험이 낮아 지금은 타입 없이 둘 수 있는지 논의하십시오. 백로그를 깎는 동안 타입 없는 표면이 더 자라지 않도록 새 코드(첫날부터 타입 있음)의 규칙에 합의하십시오. 목표를 정의하십시오. 아마도 모든 공개 함수 시그니처에 타입, 모든 경계가 타입으로 검증, 핵심 패키지에서 엄격 모드로 도는 검사기입니다. 정의된 결승선이 없으면 점진적 타이핑은 영원하고 반쯤만 덮여, 양쪽의 최악이 됩니다.
정적 분석기가 틀렸을 때의 정책은 무엇이며, 그것이 도구를 신뢰할 수 있게 유지합니까? 모든 분석기는 거짓 양성을 내고, 그것을 다루는 방식이 도구가 계속 유용할지 좌절 속에 비활성화될지를 결정합니다. 구체적 사례를 이야기해 보십시오. 발견 사항이 진짜 거짓 양성일 때, 억제는 좁고, 이유가 주석으로 달리고, 리뷰에서 보입니까, 아니면 누군가 저장소 전체에서 규칙 전체를 끕니까? 현재 억제를 보십시오. 몇 개이고, 정당화가 있으며, 마지막으로 감사한 것은 언제입니까? 설명 없는 넓은 억제 더미는 커버리지가 조용히 비어 있다는 뜻입니다. 목표는 분석기의 신뢰성을 유지하는 공유되고 시행되는 규율로, 발견 사항이 반사적으로 묵살되지 않고 신뢰받고 조치되게 하는 것입니다.
어떤 언어와 분석기를 표준화하며, 스택이 팀마다 쪼개지는 동안 어떻게 하나의 규칙 집합을 유지합니까? 수백 명의 엔지니어가 여러 언어로 일할 때, 모든 팀이 자기 검사기, 자기 린트 규칙, 자기 엄격도 설정으로 표류하면 보장이 조용히 파괴됩니다. 한 저장소에서 시행되는 계약이 다음 저장소에서는 제안에 불과하기 때문입니다. 상충하는 끌림은 실제입니다. 중앙 표준화는 이동 가능한 엔지니어와 균일한 감사 증거를 주지만, 중앙에서 강요한 규칙 집합은 언어의 관용구와 싸우거나 자체 설정의 좋은 이유가 있던 팀을 늦출 수 있습니다. 운영 중인 언어, 각 팀이 쓰는 분석기와 버전, 규칙 집합의 차이를 가져와 표류를 가정이 아니라 눈에 보이게 하십시오. 기업이나 정부 환경에서는 답을 조달과 감사에 연결하십시오. 모든 저장소가 상속하는 버전 관리된 단일 설정이 감사자가 같은 검사가 모든 곳에서 돌았음을 확인하게 하고, 공급자가 자기 직원이 충족해야 하는 것보다 약한 규칙으로 코드를 출하하는 것을 막습니다.
분석은 얼마나 빠르며, 어느 시점에 사람들이 그것을 우회하기 시작합니까? 검사기는 모든 변경에서 실행될 때만 보장이며, 편집-빌드 루프를 고통스럽게 만드는 순간 엔지니어는 건너뛰고, 로컬에서 끄고, 빨간 채로 병합하고 나중에 고치겠다고 약속하는 법을 배웁니다. 긴장은 깊이 대 속도입니다. 더 깊은 데이터 흐름이나 보안 패스는 빠른 린터가 놓치는 버그를 찾지만, 전체 스위트가 20분 걸리면 사람들은 기다리기를 멈추고, 아무도 기다리지 않는 검사는 아무것도 보호하지 않습니다. 실제 숫자를 논의에 가져오십시오. 편집기 피드백 지연, 분석 단계의 CI 실제 소요 시간, 캐시 적중률, 검사를 건너뛰거나 재정의한 채 병합된 빈도, 실행 중 증분 대 전체의 비율입니다. 큰 조직이나 공공 조직에서는 컴퓨팅 비용과 처리량 비용을 더하십시오. 함대 규모에서 느린 필수 분석 단계는 예산 항목이자 모든 릴리스를 지연시키는 대기열이며, 정직한 해법은 대개 규칙을 조용히 완화하는 것이 아니라 증분 분석과 캐싱입니다.
감사자에게 실제로 제시할 수 있는 기계가 검사한 증거는 무엇이며, 핵심 불변식 중 어느 것을 덮습니까? 규제되고 위험이 큰 시스템에서 타이핑과 정적 분석의 요점은 그것이 막는 일상의 버그를 넘어, 결함의 부류 전체가 구조상 불가능하다는 입증 가능한 증명이며, 어떤 불변식이 어디서 시행되는지 보일 수 없다면 그 주장은 무가치합니다. 트레이드오프는 범위 대 비용입니다. 더 많이 증명하면(어디서나 널 불허, 모든 합법 상태의 합 타입, 핵심 계산의 형식 검증) 더 강한 증거를 사지만, 엄밀함이 한 단계 오를 때마다 위험이 낮은 코드에는 필요 없을 주석 노력, 전문가 시간, 빌드 복잡도가 듭니다. 안전이 핵심인 모듈의 지도와 각각이 현재 지닌 보장, 정당화가 있는 열린 억제의 목록, 핵심 규칙이 컴파일러가 아니라 관례로 시행되는 간극을 가져오십시오. 정부나 규제 기업에서는 이를 인증 증거로 구성하십시오. 감사자가 요구되는 속성을 기계가 검사한 타입이나 증명까지 추적하고 모든 예외를 문서화하는 억제 로그를 볼 수 있어야 하므로, 컴플라이언스는 사후의 수동 리뷰가 아니라 도구 체인이 생성하는 산출물에 의존합니다.
분야별 관점
스타트업. 속도가 이기므로 늦추지 않는 가장 싼 안전에 손을 뻗으십시오. 강한 타입 언어나 허용적 모드의 타입 검사기, 편집기의 빠른 린터를 쓰고, 돈과 인증 코드에 먼저 타입을 다십시오. 형식 검증과 깊은 데이터 흐름 스위트는 완전히 건너뛰십시오. 없는 시간이 듭니다. 일찍 원하는 보상은 1만 줄에서 신뢰할 수 있는 리팩터링이므로, 코드베이스가 길들이기에 너무 커지기 전에 검사기를 켜십시오.
소기업. 정적 분석 전문가가 없으니, 조정하고 돌봐야 하는 스위트보다 좋은 기본값이 내장된 언어와 도구 체인을 선호하십시오. 자체 플랫폼을 세우는 대신 IDE와 호스팅 CI에 내장된 분석을 사고, 계약자나 신입이 알아보도록 규칙 집합을 커뮤니티 표준에 가깝게 유지하십시오. 경고의 오류화와 작은 타입 있는 핵심을 제한된 예산으로 할 수 있는 지렛대 효과가 가장 큰 수로 다루십시오.
대기업. 일은 많은 팀에 걸친 거버넌스입니다. 모든 저장소가 상속하는 버전 관리된 하나의 설정, 편집기와 파이프라인의 동일한 규칙, 어느 팀의 커버리지도 조용히 떨어질 수 없도록 래칫된 기준선입니다. 분석기를 표준화하고, 타입 커버리지와 억제 개수를 포트폴리오 지표로 추적하고, 기계가 검사한 보장이 감사자가 의존할 만큼 균일하게 유지되도록 고정된 주기로 억제를 감사하십시오. 공유 설정을 소유하는 플랫폼 팀을 예산에 넣으십시오. 수천 명의 엔지니어에 걸친 일관성은 저절로 유지되지 않습니다.
정부. 조달, 투명성, 긴 수명이 지배합니다. 계약에서 공급자가 자기 직원과 같은 분석 규칙을 충족하고 설정과 억제 로그를 인도물로 넘기도록 요구하여, 벤더가 바뀌어도 보장이 살아남게 하십시오. 자격과 지급 로직에는 수동 보증보다 기계가 검사한 증거를 선호하고, 형식 검증은 실패하면 급여를 불법적으로 거부하게 될 계산에 남겨 두고, 시스템이 운영될 10년 이상 동안 감사를 위해 모든 억제를 문서화해 두십시오.
사례
스타트업. 여섯 명 규모의 스타트업이 속도를 위해 동적 언어로 제품을 만들고, 1만 줄에서 리팩터링이 프로덕션에서만 발견되는 런타임 타입 오류를 일으키기 시작할 때까지 잘 지냅니다. 그들은 점진적 타이핑을 채택합니다. 허용적 모드로 타입 검사기를 켜고, 핵심 도메인 모델과 결제 코드에 먼저 타입 힌트를 더하고, 모든 새 모듈은 완전히 타입을 달아야 한다는 규칙을 정합니다. 검사기와 린터를 동일한 설정으로 편집기와 CI에 연결하고, 새 경고는 오류로 다루면서 기존 것은 래칫으로 줄여 갑니다. 두 분기 안에 어긋난 모양으로 인한 충돌이 사라지고, 리팩터링이 더는 무섭지 않으며, 신입의 자동 완성이 모든 함수가 무엇을 돌려주는지 실제로 압니다. 투자는 엔지니어 몇 주가 들었고 고객에게 보이는 버그의 반복적 원천을 없앴습니다.
대기업. 한 글로벌 은행이 수천 명의 엔지니어에 걸쳐 정적 분석을 표준화합니다. 모든 저장소가 공유 설정을 상속합니다. 엄격 모드의 타입 검사기, 린터, 데이터 흐름 분석기, 보안 패턴을 위한 SAST 스캐너가 모두 편집기에서 돌고 파이프라인에서 시행되어 통과하지 않으면 아무것도 병합되지 않습니다. 도메인 타입이 돈을 움직이는 코드에서 불법 상태를 표현할 수 없게 합니다. 기장된 거래와 보류된 거래는 다른 타입이고, 통화는 타입이 있어 달러에 유로를 더할 수 없으며, 검증된 입력은 원시 입력과 다른 타입입니다. 경고는 오류이고 각 팀의 기준선은 내려가기만 할 수 있습니다. 억제에는 정당화가 필요하고 분기마다 감사됩니다. 보장이 기계가 검사하고 균일하므로, 감사자는 결함의 부류 전체가 구조상 불가능함을 볼 수 있고 엔지니어는 낯선 서비스를 자신 있게 오갑니다.
정부. 한 국가 세무 당국이 수년간 정확하고 설명 가능해야 하는 급여 계산 시스템을 현대화합니다. 핵심 자격 로직은 도메인 모델이 규칙을 담는 강한 타입 언어로 쓰입니다. 신청자의 상태는 모든 합법적 경우를 덮는 합 타입이고, 금액은 개수와 혼동될 수 없는 전용 타입이며, 누락될 수 있는 값이 맨 널 허용으로 남지 않습니다. 정적 분석은 CI에서 관문으로 돌고, 가장 안전이 핵심인 계산 모듈은 추가로 형식 기법으로 검사되어 핵심 불변식이 모든 입력에서 성립함을 증명하고 인증 요건을 충족합니다. 모든 억제는 감사를 위해 문서화됩니다. 원래 저자가 떠나면 후임자는 계약을 컴파일러가 시행하는 코드를 물려받아, 10년 뒤에도 안전하게 바꿀 수 있습니다.
비즈니스 사례: 동기, ROI, TCO
타이핑과 정적 분석의 수익은 결함에 대해 어디서 값을 치르는지의 이동입니다. 편집기에서 타입 검사기가 잡은 결함은 몇 초가 들고, 같은 결함이 프로덕션에서 잡히면 인시던트, 조사, 어쩌면 고객 피해와 규제 지적이 듭니다. 결함 경제학 연구는 버그가 작성에서 리뷰, 테스트, 프로덕션으로 단계를 살아남을 때마다 비용이 한 자릿수 배씩 오름을 일관되게 보여 줍니다. 정적 분석은 결함의 한 범주 전체를 가장 싼 단계로, 모든 빌드에서, 결함당 노동 없이 옮깁니다. 이는 고정되고 대체로 일회성인 설정 비용으로 무한한 예방된 결함의 흐름을 사는 것이며, 엔지니어링에서 가장 좋은 지렛대에 가깝습니다.
비용은 실재하지만 소소하고 앞에 몰려 있습니다. 도구를 고르고 설정하고, 주석에 약간의 의례를 치르고(추론이 완화), 레거시 코드에 점진적 타이핑을 도입하는 데 엔지니어 시간을 쓰고, 가끔의 거짓 양성을 받아들입니다. 이에 맞서 대안의 총소유비용을 따져 보십시오. 프로덕션에 닿는 모든 타입 모양의 버그, 아무것도 정확성을 보장하지 않아 피한 모든 위험한 리팩터링, 코드가 자기 계약을 문서화하지 않아 느린 모든 온보딩, 규제 환경에서는 기계가 검사한 증거 대신 수동 리뷰로 충족해야 하는 모든 감사입니다. 리더십을 설득하려면 그들이 이미 추적하는 지표에 연결하십시오. 변경 실패율, 결함 유출률, 평균 복구 시간, 예방 가능한 타입 및 널 오류에 기인하는 인시던트의 비율입니다. 사람들을 설득하는 그래프는 검사기가 잡았을지 여부로 분류한 자기 인시던트 이력입니다.
안티패턴과 함정
- 습관이 된 탈출구: 검사기를 잠재우려고
any,dynamic, 또는 타입 없는 등가물로 캐스팅해, 가장 필요했던 바로 그곳에서 보장을 지우는 것. - 모든 것이 문자열 타입: 상태를 실제 타입으로 모델링하는 대신 경계를 가로질러 맨 문자열과 타입 없는 맵을 넘겨 컴파일러가 도울 수 없게 하는 것.
- 기본이 널 허용: 언어가 널 불허와 옵셔널 타입을 제공하는데 값을 널 허용으로 두어 10억 달러짜리 실수를 보존하는 것.
- 결코 실패하지 않는 경고: 아무것도 빌드를 깨뜨리지 않아 중요한 하나가 보이지 않는, 수천 개의 용인된 경고.
- 편집기와 CI의 불일치: 로컬에서 느슨하고 파이프라인에서 엄격하거나 그 반대여서, 개발자가 둘 다 불신하고 병합이 사람들을 놀라게 하는 것.
- 일괄 억제: 정당화된 하나의 발견 사항 대신 규칙이나 파일 전체를 비활성화해 커버리지를 조용히 비우는 것.
- 분석 연극: 아무도 읽거나 조치하지 않는 발견 사항의 도구를 돌려, 보고서가 쌓이고 가치는 영이 되는 것.
- 전부 아니면 전무 타이핑: 한꺼번에 전부 타입을 달 수 없다고 시작을 거부해, 핵심 코드에 먼저 타입을 다는 큰 이득을 포기하는 것.
- 어디서나 검증: 강한 타입으로 충분했을 곳에 희소한 전문가 노력을 쓰며 평범한 코드에 형식 기법에 손을 뻗는 것.
성숙도 모델
- 1단계, 시작: 타이핑과 분석이 즉흥적이고 개발자마다 다릅니다. 동적 코드에는 검사기가 없거나, 정적 언어가 경고를 무시한 채 돌아갑니다. 타입 모양의 버그(널, 잘못된 모양, 처리되지 않은 경우)가 정기적으로 프로덕션에 닿고, 아무것도 정확성을 검증하지 않으므로 리팩터링이 두려워집니다.
- 2단계, 발전: 일부 프로젝트에서 린터와, 관련 있는 곳에서는 타입 검사기가 돌지만, 규칙은 팀마다 다르고, 경고는 빌드를 실패시키지 않으며, 탈출구와 넓은 억제가 흔합니다. 어느 정도의 이점은 실현되지만 커버리지는 일관되지 않고 도구에 대한 신뢰는 고르지 않습니다.
- 3단계, 표준화: 공유되고 버전 관리되는 설정이 조직 전체에서 동일한 규칙으로 편집기와 CI의 타입 검사와 린팅을 시행합니다. 경고는 래칫된 기준선이 있는 오류이고, 널 가능성과 합 타입으로 경계에서 불법 상태를 표현할 수 없게 하며, 모든 억제에는 문서화되고 리뷰 가능한 이유가 필요합니다.
- 4단계, 관리: 분석이 기준선에 대해 측정되고 통제됩니다. 핵심 모듈의 타입 커버리지, 경고 수, 거짓 양성률, 억제 수, 검사기가 잡았을 프로덕션 인시던트의 비중이 모두 명시적 목표에 대해 추적됩니다. 지표가 변경을 관문으로 통제합니다. 돈과 인증 코드의 커버리지는 떨어질 수 없고, 상승하는 거짓 양성률은 규칙 재보정을 촉발하며, 대시보드는 보장이 단지 설정된 것이 아니라 실제로 유지되고 있는지 보여 줍니다.
- 5단계, 오케스트레이션: 분석이 조직 전체에서 지속적으로 개선되고 통합됩니다. 점진적 타이핑이 핵심 모듈에 도달했고, 데이터 흐름과 보안 분석기가 일상적으로 돌고, 규칙은 언어와 위협이 진화함에 따라 적응하며, 형식 검증은 실패가 파국적일 소수의 구성 요소에 의도적으로 적용됩니다. 도구, 지표, 규칙 집합이 설계, 채용, 조달에 되먹임되어 조직 전체가 꾸준히 더 안전하게 변경할 수 있게 자랍니다.
논의를 위한 아이디어
- 최근 프로덕션 버그 중 어느 것을 타입 검사기나 린터가 잡았을 것이며, 전체에서 어느 정도의 비중입니까?
- 도메인 모델의 어디에서 합 타입이나 검증된 래퍼 타입이 런타임의 “절대 일어나서는 안 됨”을 컴파일 시점의 “일어날 수 없음”으로 바꿀 수 있습니까?
- 내일 경고를 오류로 바꾼다면 몇 개가 빌드를 깨뜨리며, 정리 십자군 없이 정책을 채택하게 해 줄 기준선과 래칫은 무엇입니까?
- 편집기와 파이프라인이 정확히 같은 규칙을 실행합니까, 그리고 둘이 어긋났을 때 개발자는 어떻게 알게 됩니까?
- 지금 코드베이스에 억제가 몇 개 있고, 몇 개가 정당화를 지니며, 마지막으로 감사한 것은 언제입니까?
- 시스템에 실패가 형식 검증을 정당화할 만큼 파국적인 구성 요소가 있습니까, 그리고 어떻게 알겠습니까?
핵심 요점
- 정적 타이핑과 분석은 결함의 한 부류 전체를 고치기 가장 싼 순간으로 밉니다. 코드를 쓰는 중에, 모든 빌드에서, 결함당 노동 없이.
- 사람이 기억해야 하는 관례보다 기계가 검사하는 보장을 선호하고, 불법 상태를 아예 표현할 수 없도록 의도를 타입에 담으십시오.
- 전부 아니면 전무일 필요가 없습니다. 점진적 타이핑으로 나머지가 따라잡는 동안 핵심 코드(돈, 인증, 데이터 모델)를 먼저 덮을 수 있습니다.
- 경고를 래칫된 기준선과 함께 오류로 다루고, 편집기와 CI에서 동일한 규칙을 실행하고, 억제를 좁고 정당화되고 감사되게 유지하십시오.
- 엄밀함을 이해관계에 맞추십시오. 대부분의 시스템에는 강한 타입과 계층화된 분석기를, 형식 검증은 실패가 파국적인 드문 구성 요소에 남겨 두십시오.
참고 문헌과 더 읽을거리
- Benjamin C. Pierce, Types and Programming Languages
- Simon Peyton Jones (ed.), The Implementation of Functional Programming Languages
- Flemming Nielson, Hanne Riis Nielson, and Chris Hankin, Principles of Program Analysis
- Patrick Cousot and Radhia Cousot, “Abstract Interpretation: A Unified Lattice Model for Static Analysis of Programs by Construction or Approximation of Fixpoints”
- Scott Wlaschin, Domain Modelling Made Functional
- Steve McConnell, Code Complete: A Practical Handbook of Software Construction
- Michael Barr and the MISRA Consortium, MISRA C: Guidelines for the Use of the C Language in Critical Systems
- Al Bessey et al., “A Few Billion Lines of Code Later: Using Static Analysis to Find Bugs in the Real World,” Communications of the ACM