기본적으로 잘못됨

10539 단어 programminglanguages
일부 프로그래밍 언어는 리소스 정리를 위해 "지연"패턴을 사용합니다. 이것은 둘러싸는 블록(또는 함수)의 끝에서 실행할 정리 코드를 예약하는 defer 키워드와 같은 구성입니다. 이것은 Zig, Go 및 GCC C에서도 사용할 수 있습니다.
fn printFile(path: str) !void {
    var f = try open(path)
    defer f.close()
    for line in f {
        print(try line)
    }
    // File is closed here.
}

이는 몇 가지 주요 이점을 제공합니다.
  • 정리는 할당 바로 옆에 있습니다. 쉽게 발견하고 동기화할 수 있습니다.
  • 조기 반환 및 오류 사례의 경우에도 자동으로 정리가 수행됩니다.
  • 올바른 순서(키워드의 역순)로 정리가 자동으로 수행됩니다.

  • 이것은 수동으로 정리 코드를 작성하고 실행하는 것보다 크게 개선된 것입니다. 위의 각 항목은 이제 쉽게 피할 수 있는 일반적인 버그 소스입니다.

    그러나 defer에는 한 가지 큰 단점이 있습니다. 당신은 그것을 기억해야합니다.

    C++ 또는 Rust와 비교:
    fn printFile(path: &str) -> std::io::Result<()> {
        let f = std::fs::File::open(path)?;
        for line in std::io::BufReader::new(f).lines() {
            println!("{}", line?);
        }
        Ok(())
        // File is closed here.
    }
    

    물론 한 줄의 코드를 추가하는 것은 어렵지 않습니다. 하지만 잊을 수 있습니다. 어떤 유형이 close d, unlock ed 또는 다른 방식으로 폐기되어야 하는지 어떻게 알 수 있습니까? 자동 폐기( RAII 로 알려짐)는 프로그래머의 마음에서 벗어나 버그를 방지합니다. 정리해야 할 모든 것은 더 이상 사용하지 않을 때 정리됩니다.

    Java에도 동일한 문제가 있습니다. 많은 프로그래머는 가비지 컬렉터를 좋아합니다. 그러나 대부분의 가비지 수집기의 문제는 메모리만 정리한다는 것입니다! 잠금, 파일, 스레드 및 소켓과 같이 직접 처리해야 하는 다른 많은 유형의 리소스가 있습니다. 이들 중 일부는 종료자에 의해 처리될 수 있지만 실행되기까지 시간이 오래 걸릴 수 있기 때문에 잠금과 같은 일부 리소스에는 적합하지 않습니다.

    RAII와 같은 우수한 리소스 관리 솔루션은 프로그램에서 사용하는 모든 리소스를 처리할 수 있습니다. 오랫동안 Java에 대한 최상의 솔루션은 finally 블록이었습니다.
    FileInputStream inputStream = new FileInputStream("story.txt");
    try {
        // Read file here...
    } finally {
        inputStream.close();
    }
    

    이것은 defer보다 훨씬 더 많은 상용구이며 불쾌한 오른쪽 이동을 초래하지만 사실상 동일합니다. 그리고 가장 중요한 것은 어떤 유형을 닫아야 하는지 기억해야 한다는 것입니다.

    이것은 the try-with-resources statement 으로 Java 7에서 약간 향상되었습니다.
    try (FileInputStream inputStream = new FileInputStream("foo.txt")) {
        // Read file here...
    }
    

    그러나 여전히 정확성을 위해서는 수동 작업이 필요합니다. 그러나이 기능의 가장 좋은 부분은 java.lang.AutoCloseable interface 일 수 있습니다. 마지막으로 어떤 유형을 정리해야 하는지 알 수 있는 방법이 있습니다. 기계로 볼 수 있는 이 정보를 통해 효과적인 린트를 만들 수 있습니다. AutoClosable 유형을 얻은 경우 try-with-resources 문에서 사용하거나 신속하게 다른 사람에게 전달하고 부담을 다른 사람에게 전가하는 것이 좋습니다. 그러나 복잡한 사례는 안정적으로 감지하기 어려울 것입니다. 예를 들어 유형을 데이터 구조에 저장하고 제거한 후 정리하는 경우입니다. 단순히 여러 장소에 대한 참조를 전달하는 것만으로도 정리를 담당하는 사람이 모호해집니다.

    이야기의 사기는 버그를 피하는 가장 좋은 방법은 버그를 작성하기 어렵게 만드는 것입니다. 이상적으로는 올바른 코드보다 버그를 작성하기 어렵게 만듭니다. defer 및 try-with-resources는 "기본적으로 잘못된 것"이며 올바르게 되려면 프로그래머를 대신하여 명시적인 작업이 필요합니다. RAII는 프로그래머가 추가 작업을 수행하지 않아도 정확합니다.

    이 게시물은 defer 키워드에서 영감을 받았지만 그보다 훨씬 더 일반적인 문제입니다. --dont-be-stupid 플래그에 대한 Google의 문서를 기억합니다. 이들은 일반적으로 버그가 발견될 때 추가되지만 수정 사항에는 일부 클라이언트가 의존하는 동작 변경 사항이 포함되어 있거나 변경 사항을 신중하게 롤아웃해야 합니다. 몇 년 후 대부분의 서비스에는 서비스 구성에 모두 하드코딩된 이러한 플래그가 수십 개 있을 것입니다. 물론 때때로 서비스의 새 인스턴스가 설정되고 이 버그가 다시 나타납니다. 이전 버전과의 호환성 및 점진적 롤아웃은 좋지만 어느 시점에서 기본적으로 올바른 코드를 업데이트해야 합니다.

    좋은 웹페이지 즐겨찾기