본문 바로가기
카테고리 없음

kotlin) Meta에서 Java의 null 안전성 개선

by 얼굴값하는사람 2024. 1. 20.
반응형

https://engineering.fb.com/2022/11/22/developer-tools/meta-java-nullsafe/

 
  • Meta에서 Java 코드의 NPE(NullPointerException) 오류를 감지하는데 사용되는 Nullsafe라는 새로운 정적 분석 도구를 개발했습니다.
  • 레거시 코드와의 상호 운용성과 점진적인 배포 모델은 Nullsafe의 광범위한 채택의 핵심이었으며 수백만 줄의 코드 베이스에서 null이 안전하지 않은 언어의 Context에서 일부 Nullsafe 속성을 복구할 수 있었습니다.
  • Nullsafe는 전체 NPE 오류 수를 크게 줄이고 개발자의 생산성을 향상시키는 데 도움이 되었습니다. 이는 실제 문제를 대규모로 해결하는 데 있어 정적 분석의 가치를 보여줍니다.

Null 역참조는 Java에서 일반적인 유형의 프로그래밍 오류입니다. Android에서는 NPE(NullPointerException) 오류가 Google Play 앱 충돌의 가장 큰 원인 입니다 . Java는 널성 불변성을 표현하고 확인하는 도구를 제공하지 않으므로 개발자는 코드의 안정성을 향상시키기 위해 테스트 및 동적 분석에 의존해야 합니다. 이러한 기술은 필수적이지만 신호 전달 시간 및 적용 범위 측면에서 자체적인 한계가 있습니다.

2019년에 우리는 앱 내에서 이러한 문제를 해결하고 정적 분석을 통해 Java 코드의 null 안전성을 크게 향상시키는 것을 목표로 0NPE 라는 프로젝트를 시작했습니다 .

2년에 걸쳐 우리는 Java에서 NPE 오류를 감지하기 위한 정적 분석기인 Nullsafe를 개발하고 이를 핵심 개발자 워크플로에 통합했으며 대규모 코드 변환을 실행하여 수백만 줄의 Java 코드를 Nullsafe와 호환되도록 만들었습니다.

그림 1: 시간 경과에 따른 널 안전 코드 비율(대략)

Meta의 가장 큰 Android 앱 중 하나인 Instagram을 예로 들면 , 우리는 코드 변환 18개월 동안 프로덕션 NPE 충돌이 27% 감소한 것을 관찰했습니다. 더욱이 NPE는 더 이상 알파 및 베타 채널 모두에서 충돌의 주요 원인이 아니며, 이는 향상된 개발자 경험과 개발 속도를 직접적으로 반영합니다.

null  문제

널 포인터는 프로그램에 버그를 일으키는 것으로 악명 높습니다. 아래와 같은 작은 코드 조각에서도 여러 가지 방법으로 문제가 발생할 수 있습니다.

목록 1 : 버그가 있는 getParentName 메소드

Path getParentName(Path path) {
  return path.getParent().getFileName();
}
  1. getParent()는 null을 생성 하고 getParentName(…) 에서 로컬로 NullPointerException을 발생시킬 수 있습니다 .
  2. getFileName()은 null을 반환할 수 있으며 이는 더 전파되어 다른 곳에서 충돌을 일으킬 수 있습니다.

전자는 발견하고 디버깅하기가 상대적으로 쉽지만 후자는 특히 코드베이스가 성장하고 발전함에 따라 어려울 수 있습니다. 

값의 nullness를 파악하고 잠재적인 문제를 발견하는 것은 위와 같은 간단한 예제에서는 쉽지만, 수백만 줄의 코드 규모에서는 매우 어렵습니다. 그런 다음 하루에 수천 개의 코드 변경을 추가하면 단일 변경으로 인해 다른 구성 요소에서 NullPointerException이 발생하지 않는지 수동으로 확인할 수 없습니다 . 결과적으로 사용자는 충돌로 고통받고 애플리케이션 개발자는 값의 nullness를 추적하는 데 과도한 정신 에너지를 소모해야 합니다.

그러나 문제는 Null 값 자체가 아니라 API에 명시적인 Null 정보가 부족하고 코드가 Null을 적절하게 처리하는지 확인하는 도구가 부족하다는 것입니다.

자바와 nullness

이러한 문제에 대응하여 Java 8에서는 java.util.Optional<T> 클래스를 도입했습니다. 그러나 성능에 미치는 영향과 레거시 API 호환성 문제로 인해 Optional은 null 허용 참조에 대한 범용 대체 수단으로 사용될 수 없습니다.

동시에 주석은 언어 확장 지점으로 성공적으로 사용되었습니다. 특히 @Nullable  @NotNull 과 같은 주석을 일반 널 입력 가능 참조 유형에 추가하는 것은 Optional 의 단점을 피하면서 명시적인 널을 사용하여 Java 유형을 확장할 수 있는 실행 가능한 방법입니다 . 그러나 이 접근 방식에는 외부 검사기가 필요합니다.

목록 1 의 주석이 달린 코드 버전은 다음과 같습니다.

목록 2 : 정확하고 주석이 달린 getParentName 메소드

// (2)                          (1)
@Nullable Path getParentName(Path path) {
  Path parent = path.getParent(); // (3)
  return parent != null ? parent.getFileName() : null;
            // (4)
}

null 안전하지만 주석이 없는 버전과 비교하여 이 코드는 반환 유형에 단일 주석을 추가합니다. 여기에는 주목할 만한 몇 가지 사항이 있습니다.

  1. 주석이 없는 유형은 nullable이 아닌 것으로 간주됩니다 . 이 규칙은 주석 부담을 크게 줄여주지만 자사 코드에만 적용됩니다.
  2. 메서드가 null 을 반환할 수 있으므로 반환 유형은 @Nullable 로 표시됩니다 .
  3. 지역 변수 parent에는 주석이 달리지 않습니다. 그 이유는 null이 정적 분석 검사기에서 추론되어야 하기 때문입니다. 이렇게 하면 주석 부담이 더욱 줄어듭니다.
  4. null 값을 확인하면 해당 유형이 해당 분기에서 null을 허용하지 않도록 구체화됩니다. 이를 흐름 감지 유형 지정이라고 하며 관용적으로 코드를 작성하고 실제로 필요한 경우에만 nullness를 처리할 수 있습니다.

null에 대해 주석이 달린 코드는 null 안전성을 정적으로 검사할 수 있습니다. 분석기는 회귀로부터 코드베이스를 보호하고 개발자가 자신감을 갖고 더 빠르게 움직일 수 있도록 해줍니다.

Kotlin과 nullness

Kotlin 은 Java와 상호 운용되도록 설계된 최신 프로그래밍 언어입니다. Kotlin에서는 유형에서 null이 명시적이며, 컴파일러는 코드가 null을 올바르게 처리하는지 확인하여 개발자에게 즉각적인 피드백을 제공합니다. 

우리는 이러한 장점을 인식하고 있으며 실제로 Meta에서는 Kotlin을 많이 사용합니다 . 하지만 우리는 하룻밤 사이에 Kotlin으로 이동할 수 없거나 때로는 이동해서는 안 되는 비즈니스에 중요한 Java 코드가 많다는 사실도 알고 있습니다. 

Java와 Kotlin이라는 두 언어가 공존해야 합니다. 즉, Java에 대한 널 안전 솔루션이 여전히 필요하다는 의미입니다.

Static analysis for nullness checking at scale - 대규모 Nullness 검사를 위한 정적 분석

Meta가 Infer , Hack  Flow 와 같은 다른 정적 분석 도구를 성공적으로 구축 하고 이를 실제 코드 기반에 적용함으로써 우리는 다음과 같은 Java용 널성 검사기를 구축할 수 있다는 확신을 갖게 되었습니다. 

  1. 인체공학적: 코드의 제어 흐름을 이해하고 개발자가 코드를 준수하기 위해 뒤로 구부릴 필요가 없으며 주석 부담을 최소화합니다. 
  2. 확장성: 수백 줄의 코드에서 수백만 줄까지 확장할 수 있습니다.
  3. Kotlin과 호환: 원활한 상호 운용성을 제공합니다.

돌이켜보면 정적 분석 검사기 자체를 구현하는 것은 아마도 쉬운 부분이었을 것입니다. 이 검사기를 개발 인프라와 통합하고, 개발자 커뮤니티와 협력하고, 수백만 줄의 프로덕션 Java 코드를 null-safe로 만드는 데 실질적인 노력이 들어갔습니다.

우리는 Infer의 일부로 Java용 Nullness 검사기의 첫 번째 버전을 구현했으며 이는 훌륭한 기반 역할을 했습니다. 나중에 우리는 컴파일러 기반 인프라로 전환했습니다. 컴파일러와의 긴밀한 통합을 통해 분석의 정확성을 높이고 개발 도구와의 통합을 간소화할 수 있었습니다. 

이 분석기의 두 번째 버전은 Nullsafe라고 하며 아래에서 이에 대해 다루겠습니다.

Null-checking under the hood - 내부적으로 Null 검사하기

Java 컴파일러 API는 JSR-199를 통해 도입되었습니다 . 이 API를 사용하면 컴파일된 프로그램의 컴파일러 내부 표현에 액세스할 수 있으며 컴파일 프로세스의 여러 단계에서 사용자 정의 기능을 추가할 수 있습니다. 우리는 이 API를 사용하여 Nullsafe 분석을 실행한 다음 Nullness 오류를 수집하고 보고하는 추가 패스를 통해 Java의 유형 검사를 확장합니다.

분석에 사용되는 두 가지 주요 데이터 구조는 AST(추상 구문 트리)와 CFG(제어 흐름 그래프)입니다. 예제는 목록 3과 그림 2 및 3을 참조하세요.

  • AST는 구두점과 같은 불필요한 세부 정보 없이 소스 코드의 구문 구조를 나타냅니다. 유형 및 주석 정보와 함께 컴파일러 API를 통해 프로그램의 AST를 얻습니다.
  • CFG는 코드 조각의 흐름도입니다. 즉, 제어 흐름의 변경을 나타내는 화살표로 연결된 명령 블록입니다. 우리는 Dataflow 라이브러리를 사용하여 특정 AST에 대한 CFG를 빌드하고 있습니다.

분석 자체는 두 단계로 나뉩니다.

  1. 유형 추론 단계는 다양한 코드 조각의 nullness를 파악하고 다음과 같은 질문에 답하는 역할을 합니다.
    • 이 메서드 호출이 프로그램 지점 X에서 null을 반환할 수 있습니까 ?
    • 이 변수는 프로그램 지점 Y에서 null이 될 수 있습니까 ?
  2. 유형 검사 단계에서는 코드가 null 허용 값을 역참조하거나 예상하지 못한 곳에 null 허용 인수를 전달하는 등 안전하지 않은 작업을 수행하지 않는지 확인하는 작업을 담당합니다.

목록 3 : getOrDefault 메소드 예시

String getOrDefault(@Nullable String str, String defaultValue) {
  if (str == null) { return defaultValue; }
  return str;
}
그림 2: 목록 3의 코드에 대한 CFG
그림 3: 목록 3의 코드에 대한 AST

Type-inference phase

Nullsafe는 코드의 CFG를 기반으로 유형 추론을 수행합니다. 추론의 결과는 다양한 프로그램 지점에서 표현식을 Null 확장 유형으로 매핑하는 것입니다.

상태 = 표현 x 프로그램 포인트 → nullness – 확장형

추론 엔진은 CFG를 탐색하고 분석 규칙에 따라 모든 명령을 실행합니다 . Listing 3 의 프로그램의 경우 다음과 같습니다.

  1. <entry> 지점 에서 매핑으로 시작합니다 
    • {str  @Nullable 문자열, defaultValue  문자열} .
  2. str == null 비교를 실행하면 제어 흐름이 분할되고 두 개의 매핑이 생성됩니다.
    • THEN: { str  @Nullable 문자열, defaultValue  문자열 } .
    • ELSE: { str  문자열 , defaultValue  문자열 } .
  3. 제어 흐름이 결합되면 추론 엔진은 두 가지 모두의 상태를 과도하게 근사하는 매핑을 생성해야 합니다. 한 분기에 @Nullable String이 있고 다른 분기에 String이 있는 경우 과도하게 근사된 유형은 @Nullable String이 됩니다 .
그림 4: 분석 결과가 포함된 CFG

추론에 CFG를 사용하는 주요 이점은 분석을 흐름에 민감하게 만들 수 있다는 것입니다. 이는 이와 같은 분석이 실제로 유용하려면 매우 중요합니다.

위의 예는 제어 흐름에 따라 값의 nullness가 정제되는 매우 일반적인 경우를 보여줍니다. 실제 코딩 패턴을 수용하기 위해 Nullsafe는 SAT 해결을 사용하는 계약 및 복잡한 불변성부터 프로시저 간 객체 초기화 분석에 이르기까지 고급 기능을 지원합니다. 그러나 이러한 기능에 대한 논의는 이 게시물의 범위를 벗어납니다.

Type-checking phase

Nullsafe는 프로그램의 AST를 기반으로 유형 검사를 수행합니다. AST를 순회함으로써 소스 코드에 지정된 정보와 추론 단계의 결과를 비교할 수 있습니다.

목록 3의 예에서는 반환 str 노드를 방문할 때 str 표현식 의 유추된 유형(String) 을 가져오고 이 유형이 String 으로 선언된 메서드의 반환 유형과 호환되는지 확인합니다 .

그림 5: AST 순회 중 유형 확인.

객체 역참조에 해당하는 AST 노드를 보면 추론된 수신기 유형이 null 을 제외하는지 확인합니다 . 암시적 언박싱도 비슷한 방식으로 처리됩니다. 메소드 호출 노드의 경우 추론된 인수 유형이 메소드의 선언 유형과 호환되는지 확인합니다. 등등.

전반적으로 유형 검사 단계는 유형 추론 단계보다 훨씬 간단합니다. 여기서 중요한 측면 중 하나는 오류 렌더링입니다. 여기서 유형 추적, 코드 출처 및 잠재적인 빠른 수정과 같은 컨텍스트를 사용하여 유형 오류를 보강해야 합니다.

Challenges in supporting generics - 제네릭 지원의 과제

위에 제공된 nullness 분석의 예에서는 소위 루트 nullness 또는 값 자체의 nullness만 다루었습니다. 제네릭은 언어에 완전히 새로운 차원의 표현성을 추가하며, 마찬가지로 nullness 분석을 확장하여 일반 및 매개변수화된 클래스를 지원함으로써 API의 표현성과 정밀도를 더욱 향상시킬 수 있습니다.

제네릭을 지원하는 것은 분명히 좋은 일입니다. 그러나 추가적인 표현에는 비용이 따릅니다. 특히 유형 추론은 훨씬 더 복잡해집니다.

매개변수화된 클래스 Map<K, List<Pair<V1, V2>>> 를 고려해보세요 . 제네릭이 아닌 nullness 검사기 의 경우 추론할 수 있는 루트 nullness만 있습니다.

// NON-GENERIC CASE
   ␣ Map<K, List<Pair<V1, V2>>
// ^
// \--- Only the root nullness needs to be inferred

일반적인 경우 에는 이미 복잡한 흐름에 민감한 분석 외에 채우기 위해 훨씬 더 많은 공백이 필요합니다.

// GENERIC CASE
   ␣ Map<␣ K, ␣ List<␣ Pair<␣ V1, ␣ V2>>
// ^     ^    ^      ^      ^      ^
// \-----|----|------|------|------|--- All these need to be inferred

이것이 전부는 아닙니다. 분석에서 유추하는 일반 유형은 가짜 오류를 방지하기 위해 Java 자체가 유추한 유형의 형태를 밀접하게 따라야 합니다. 예를 들어 다음 코드 조각을 고려해 보세요.

interface Animal {}
class Cat implements Animal {}
class Dog implements Animal {}

void targetType(@Nullable Cat catMaybe) {
  List<@Nullable Animal> animalsMaybe = List.of(catMaybe);
}

List.<T>of(T…)는 일반적인 방법이며 별도로 List.of(catMaybe) 유형을 List<@Nullable Cat> 으로 추론할 수 있습니다 . 이는 Java의 제네릭이 불변이기 때문에 문제가 될 수 있습니다. 즉, List<Animal>은 List<Cat> 과 호환되지 않으며 할당 시 오류가 발생한다는 의미입니다.

이 코드 유형을 확인하는 이유는 Java 컴파일러가 할당 대상의 유형을 알고 이 정보를 사용하여 할당 컨텍스트(또는 문제에 대한 메서드 인수)에서 유형 추론 엔진이 작동하는 방식을 조정하기 때문입니다. 이 기능을 대상 타이핑 이라고 하며 , 제네릭 작업의 인체공학적 측면을 향상시키기는 하지만 이전에 설명한 일종의 순방향 CFG 기반 분석과 잘 작동하지 않으며 처리에 특별한 주의가 필요했습니다.

위의 사항 외에도 Java 컴파일러 자체에는 Nullsafe 및 유형 주석과 함께 작동하는 기타 정적 분석 도구에서 다양한 해결 방법이 필요한 버그(예: this )가 있습니다.

이러한 과제에도 불구하고 우리는 제네릭 지원에 상당한 가치가 있다고 생각합니다 . 특히:

  • 인체공학적 개선 . 제네릭에 대한 지원이 없으면 개발자는 컬렉션 및 기능 인터페이스에서 스트림에 이르기까지 null을 인식하는 방식으로 특정 API를 정의하고 사용할 수 없습니다. 그들은 신뢰성에 해를 끼치고 나쁜 습관을 강화하는 nullness 검사기를 우회해야 합니다. 우리는 널 안전 제네릭이 부족하여 취약한 코드와 버그가 발생하는 코드베이스의 여러 위치를 발견했습니다 .
  • 더욱 안전한 Kotlin 상호 운용성 . Meta는 Kotlin을 많이 사용하며 제네릭을 지원하는 널성 분석을 통해 두 언어 간의 격차를 줄이고 이기종 코드베이스에서 상호 운용성 및 개발 경험 의 안전성을 크게 향상시킵니다 .

Dealing with legacy and third-party code - 레거시 및 타사 코드 처리

개념적으로 Nullsafe가 수행하는 정적 분석은 Null 안전이 안전하지 않은 언어에 Null 안전을 개선하려는 시도로 Java에 새로운 의미론적 규칙 세트를 추가합니다. 이상적인 시나리오는 모든 코드가 이러한 규칙을 따르는 것입니다. 이 경우 분석기에서 발생하는 진단은 관련성이 있고 실행 가능합니다. 현실은 새로운 규칙에 대해 아무것도 모르는 널 안전 코드가 많고, 널 안전하지 않은 코드가 훨씬 더 많다는 것입니다. 이러한 레거시 코드 또는 레거시 구성 요소를 호출하는 최신 코드에 대해 분석을 실행하면 너무 많은 노이즈가 발생하여 마찰이 추가되고 분석기의 가치가 훼손됩니다.

Nullsafe에서 이 문제를 처리하기 위해 코드를 세 가지 계층으로 분리합니다.

  • 계층 1: Nullsafe 규격 코드. 여기에는 @Nullsafe 로 표시되고 오류가 없는 것으로 확인된 자사 코드가 포함됩니다 . 여기에는 알려진 양호한 주석이 달린 타사 코드 또는 Nullness 모델을 추가한 타사 코드도 포함됩니다.
  • 계층 2: Nullsafe를 준수하지 않는 자사 코드. 이는 명시적인 nullness 추적을 염두에 두지 않고 작성된 내부 코드입니다. 이 코드는 Nullsafe에 의해 낙관적으로 검사됩니다.
  • 계층 3: 검증되지 않은 타사 코드. 이는 Nullsafe가 전혀 알지 못하는 타사 코드입니다. 이러한 코드를 사용할 때 용도가 비관적으로 검사되고 개발자는 적절한 Nullness 모델을 추가하도록 촉구됩니다.

이 계층화된 시스템의 중요한 측면은 Nullsafe가 Tier Y 코드 를 호출하는 Tier X 코드를 형식 확인할 때 Tier Y 의 규칙을 사용한다는 것입니다. 특히:

  1. Tier 1에서 Tier 2로의 통화는 낙관적으로 확인됩니다.
  2. Tier 1에서 Tier 3까지의 통화는 비관적으로 확인되며,
  3. Tier 2에서 Tier 1로의 통화는 Tier 1 구성 요소의 nullness에 따라 확인됩니다.

여기서 주목할 만한 두 가지 사항은 다음과 같습니다.

  1. A 지점에 따르면 Tier 1 코드에는 안전하지 않은 종속성이 있거나 안전하지 않게 사용되는 안전한 종속성이 있을 수 있습니다. 이러한 불건전함은 코드 베이스에서 Nullsafe의 출시 및 채택을 간소화하고 점진적으로 진행하기 위해 지불해야 했던 대가입니다. 우리는 다른 접근 방식을 시도했지만 추가 마찰로 인해 확장하기가 극도로 어려워졌습니다. 좋은 소식은 더 많은 Tier 2 코드가 Tier 1 코드로 마이그레이션됨에 따라 이 점에 대한 우려가 줄어든다는 것입니다.
  2. 타사 코드(B 지점)를 비관적으로 처리하면 Nullness Checker 채택에 추가 마찰이 발생합니다. 그러나 우리의 경험에 따르면 비용은 그리 높지 않았으며 Tier 1 및 Tier 3 코드 상호 운용성의 안전성은 실제로 향상되었습니다.
그림 6: 널 안전 규칙의 3개 계층.

Deployment, automation, and adoption - 배포, 자동화 및 채택

널니스 검사기만으로는 실질적인 영향을 미치기에 충분하지 않습니다. 검사기의 효과는 이 검사기를 준수하는 코드의 양에 비례합니다. 따라서 마이그레이션 전략, 개발자 채택 및 회귀 방지가 주요 관심사가 됩니다.

우리는 이니셔티브의 성공에 필수적인 세 가지 주요 사항을 발견했습니다.

  1. 빠른 수정은 매우 도움이 됩니다. 코드베이스는 사소한 null 안전 위반으로 가득 차 있습니다. 오류를 확인하는 것뿐만 아니라 빠른 수정을 제공하기 위한 정적 분석을 교육하면 많은 기반을 다룰 수 있으며 개발자에게 의미 있는 수정 작업을 수행할 수 있는 공간을 제공할 수 있습니다.
  2. 개발자 채택이 핵심입니다. 이는 검사기와 관련 도구가 기본 개발 도구(빌드 도구, IDE, CLI 및 CI)와 잘 통합되어야 함을 의미합니다. 그러나 더 중요한 것은 애플리케이션과 정적 분석 개발자 사이에 작동 피드백 루프가 있어야 한다는 것입니다.
  3. 추진력을 유지하려면 데이터와 지표가 중요합니다. 현재 위치, 진행 상황, 차선책을 알면 마이그레이션을 촉진하는 데 큰 도움이 됩니다.

Longer-term reliability impact - 장기적인 안정성 영향

한 가지 예를 들어, Instagram Android 앱에 대한 18개월 간의 안정성 데이터를 살펴보겠습니다.

  • Nullsafe를 준수하는 앱 코드의 비율이 3%에서 90%로 증가했습니다.
  • 모든 릴리스 채널에서 NPE( NullPointerException ) 오류 의 상대적인 양이 크게 감소했습니다 (그림 7 참조). 특히 생산 부문에서는 NPE 규모가 27% 감소했다.

이 데이터는 다른 유형의 충돌에 대해 검증되었으며 앱의 안정성과 null 안전성이 실제로 향상되었음을 보여줍니다. 

동시에 개별 제품 팀은 Nullsafe에서 보고한 nullness 오류를 해결한 후 NPE 충돌의 양이 크게 감소했다고 보고했습니다. 

프로덕션 NPE 감소는 팀마다 다르며 35%에서 80%까지 개선되었습니다 .

결과에서 특히 흥미로운 점 중 하나는 알파 채널에서 NPE가 급격히 감소했다는 것입니다 . 이는 Nullness Checker를 사용하고 의존함으로써 얻을 수 있는 개발자 생산성 향상을 직접적으로 반영합니다.

우리의 북극성 목표이자 이상적인 시나리오는 NPE를 완전히 제거하는 것입니다. 그러나 실제 신뢰성은 복잡하며 역할을 수행하는 더 많은 요소가 있습니다.

  • 실제로 상위 NPE 충돌의 상당 부분을 담당하는 null 안전하지 않은 코드가 여전히 있습니다. 그러나 이제 우리는 표적화된 null-safety 개선이 중요하고 지속적인 영향을 미칠 수 있는 위치에 있습니다.
  • 충돌의 양은 안정성 향상을 측정하는 가장 좋은 지표가 아닙니다. 프로덕션에 발생한 버그 하나가 매우 뜨거워지고 혼자서 결과를 왜곡할 수 있기 때문입니다. 더 나은 지표는 릴리스당 새로운 고유 충돌 수로, n 배의 개선 효과를 볼 수 있습니다.
  • 모든 NPE 충돌이 앱 코드의 버그로 인해 발생하는 것은 아닙니다. 클라이언트와 서버 간의 불일치는 다른 수단을 통해 해결해야 하는 생산 문제의 또 다른 주요 원인입니다.
  • 정적 분석 자체에는 특정 버그가 프로덕션에 빠져들게 하는 제한 사항과 불건전한 가정이 있습니다.

이는 코드의 안전성을 향상시키기 위해 수백 명의 엔지니어가 Nullsafe를 사용하는 종합적인 효과 일 뿐만 아니라 다른 안정성 이니셔티브 의 효과라는 점을 기억하는 것이 중요합니다 . 따라서 이러한 개선이 Nullsafe의 사용에만 있다고 볼 수는 없습니다. 그러나 지난 몇 년간의 보고서와 자체 관찰을 바탕으로 Nullsafe가 NPE 관련 충돌을 줄이는 데 중요한 역할을 했다고 확신합니다.

그림 7: 릴리스 채널별 NPE 충돌 비율.

메타를 넘어

위에서 설명한 문제는 Meta에만 국한된 문제가 아닙니다. 예상치 못한 null 역참조로 인해 여러 회사에서 수많은 문제가 발생했습니다 . C#과 같은 언어는 유형 시스템에서 명시적인 nullness를 갖도록 발전한 반면, Kotlin과 같은 다른 언어는 처음부터 이를 갖고 있었습니다. 

Java의 경우 JSR-305를 시작으로 nullness를 추가하려는 시도가 여러 번 있었지만 널리 성공한 것은 하나도 없었습니다. 현재 CheckerFramework, SpotBugs, ErrorProne, NullAway 등 nullness를 확인할 수 있는 훌륭한 Java용 정적 분석 도구가 많이 있습니다. 특히 Uber는 NullAway 검사기를 사용하여 Android 코드베이스를 null-safe로 만드는 동일한 길을 걸었습니다. 그러나 결국 모든 체커는 서로 다르고 미묘하게 호환되지 않는 방식으로 Nullness 분석을 수행합니다. 정확한 의미를 지닌 표준 주석이 부족하여 업계 전반에서 Java에 대한 정적 분석의 사용이 제한되었습니다.

이 문제는 JSpecify 작업 그룹이 해결하려는 목표와 정확히 같습니다. JSpecify는 2019년에 시작되었으며 Google, JetBrains, Uber, Oracle 등과 같은 회사를 대표하는 개인 간의 협업입니다. Meta는 2019년 말부터 JSpecify의 일부가 되었습니다.

nullness에 대한 표준은 아직 확정되지 않았지만 사양 자체와 도구에 대한 많은 진전이 있었으며 곧 더 흥미로운 발표가 있을 예정입니다. JSpecify에 대한 참여는 Meta에서 Java의 nullness와 자체 코드 베이스 발전에 대해 생각하는 방식에도 영향을 미쳤습니다.

 
 
 
 
원본글
반응형