일부 프로그래밍 언어 사람들이 일관된 null 참조를 bashing하는 것을 이해하지 못합니다. 그들에 대해 왜 그렇게 나쁜가? 존재하지 않는 파일에 대한 읽기 액세스를 요청하면 예외 또는 null 참조를 얻는 것이 행복하지만 예외는 양호하지만 null 참조는 불량으로 간주됩니다. 이것에 대한 추론은 무엇입니까?
일부 프로그래밍 언어 사람들이 일관된 null 참조를 bashing하는 것을 이해하지 못합니다. 그들에 대해 왜 그렇게 나쁜가? 존재하지 않는 파일에 대한 읽기 액세스를 요청하면 예외 또는 null 참조를 얻는 것이 행복하지만 예외는 양호하지만 null 참조는 불량으로 간주됩니다. 이것에 대한 추론은 무엇입니까?
답변:
Null 참조는 적어도 내가 아는 사람이나 읽은 사람에 의한 예외보다 더 이상 "피할 수 없다". 나는 당신이 기존의 지혜를 오해하고 있다고 생각합니다.
나쁜 점은 null 참조 에 액세스 하거나 null 포인터를 역 참조하는 것입니다. 이것은 항상 버그를 나타 내기 때문에 나쁘다. 당신은 의도적으로 이런 일을하지 않을 것이며, 의도적 으로 행동한다면, 그것은 예상되는 행동과 버그가있는 행동을 구별하는 것을 불가능하게하기 때문에 더욱 악화됩니다.
이 정말 어떤 이유로 무효의 개념을 싫어 거기 특정 프린지 그룹은, 그러나 에드는 지적으로 , 당신이없는 경우 null
또는 nil
당신이 뭔가로 이어질 수있는 다른 뭔가로 대체해야합니다 충돌 (예 : 데이터 손상)보다 나쁩니다.
실제로 많은 프레임 워크가 두 개념을 모두 수용합니다. 예를 들어 .NET에서 자주 볼 수있는 패턴은 접두사 Try
(예 :)라는 한 쌍의 메소드 TryGetValue
입니다. 이 Try
경우 참조는 기본값 (일반적으로 null
) 으로 설정되고 다른 경우에는 예외가 발생합니다. 두 가지 접근 방식에는 아무런 문제가 없습니다. 둘 다 지원하는 환경에서 자주 사용됩니다.
그것은 실제로 의미론에 달려 있습니다. 경우 null
컬렉션을 검색하는 일반적인 경우와 같이 유효한 반환 값이며, 다음 리턴 null
. 이 경우 반면에, 하지 유효한 반환 값 - 예를 들어, 자신의 데이터베이스에서 온 기본 키를 사용하여 레코드를 찾고 - 후 반환하는 null
호출자가 아마 예상 할 수 없기 때문에 좋은 생각이 될 것이다 확인하지 않습니다.
사용할 의미론을 알아내는 것은 매우 간단합니다 . 함수의 결과가 정의되지 않은 것이 의미가 있습니까? 그렇다면 널 참조를 리턴 할 수 있습니다. 그렇지 않다면 예외를 던져라.
null
그래서 정말, 하스켈 여기에 꽤 무관은.
null
그렇게 오랫동안 잘 견디고있다가, 그렇지 않으면 말하는 사람들의 대부분은 불완전한 요구 사항 또는 최종 일관성 등의 제약 약간의 경험 디자인 실제 애플 리케이션과 학계 것으로 보인다.
가장 큰 차이점은 NULL을 처리하기 위해 코드를 남겨두면 나중에 관련되지 않은 오류 메시지와 함께 코드가 계속 충돌 할 수 있다는 것입니다. 예외와 마찬가지로 예외는 초기 실패 시점에서 예외가 발생합니다 (열기) 예제에서 읽을 파일).
널값은 프로그래밍 언어의 필수 부분이 아니며 일관된 버그 소스이기 때문입니다. 말했듯이 파일을 열면 오류가 발생하여 null 반환 값 또는 예외를 통해 다시 전달 될 수 있습니다. 널 (NULL) 값이 허용되지 않으면 실패를 통신하는 일관된 단일 방법이 있습니다.
또한 이것은 null에서 가장 일반적인 문제는 아닙니다. 대부분의 사람들은 반환 할 수있는 함수를 호출 한 후 null을 확인해야합니다. 문제는 프로그램 실행의 다양한 시점에서 변수를 null로 허용함으로써 자신의 디자인에서 훨씬 더 많이 발생합니다. null 값이 허용되지 않도록 코드를 디자인 할 수 있지만 언어 수준에서 null이 허용되지 않으면이 중 어느 것도 필요하지 않습니다.
그러나 실제로 변수가 초기화되었는지 여부를 나타내는 방법이 여전히 필요합니다. 그러면 프로그램이 충돌하지 않고 대신 잘못된 기본값을 계속 사용하는 일종의 버그가 생깁니다. 솔직히 어느 쪽이 더 좋은지 모르겠습니다. 내 돈을 위해 나는 종종 자주 충돌하는 것을 좋아합니다.
null
반환 값으로 얻을 때 무슨 일이 있었는지 알고 있습니다. 요청한 정보가 없습니다. 차이가 없습니다. 어느 쪽이든, 호출자는 실제로 특정 정보를 위해 해당 정보를 사용해야하는 경우 반환 값의 유효성을 검사해야합니다. null
, null 객체 또는 모나드를 유효성 검사하는지 여부 는 실질적인 차이가 없습니다.
처음에 null 참조 아이디어를 만든 Tony Hoare는이를 100 만 달러의 실수라고 합니다.
문제는 그 자체로 null 참조가 아니라 대부분의 (그렇지 않은) 유형 안전 언어에서 적절한 유형 검사가 부족하다는 것입니다.
이 언어의 지원이 부족하다는 것은 감지하기 전에 오랫동안 버그 "널 버그"가 프로그램에 숨어있을 수 있음을 의미합니다. 물론 버그의 특성이지만 "널 버그"는 이제 피할 수있는 것으로 알려져 있습니다.
이 문제는 특히 C 또는 C ++ (예 :)에서 발생하는 "하드"오류로 인해 발생합니다 (프로그램이 중단되고 즉시 복구되지 않음).
다른 언어에서는 항상 처리 방법에 대한 의문이 있습니다.
Java 또는 C #에서는 널 참조에서 메소드를 호출하려고 시도하면 예외가 발생하지만 괜찮을 수 있습니다. 따라서 대부분의 Java 또는 C # 프로그래머가 이에 익숙하며 왜 그렇게하고 싶은지 이해하지 못합니다 (C ++을 비웃음).
Haskell에서는 널 (null) 사례에 대해 명시 적으로 조치를 제공해야하므로 Haskell 프로그래머는 동료가 옳은 일을하기 때문에 동료를 칭찬합니다 (오른쪽?).
실제로 오래된 오류 코드 / 예외 토론이지만 이번에 는 오류 코드 대신 Sentinel Value 가 있습니다.
항상 가장 적합한 것은 항상 상황과 의미에 달려 있습니다.
null
하기 때문에 정말 악하다 . (적어도, 일부 언어에서는 다른 언어의 세부 표현이 단점이 있습니다.)
사람이 읽을 수있는 오류 메시지를 널 포인터에 첨부 할 수 없습니다.
그러나 로그 파일에 오류 메시지를 남길 수 있습니다.
포인터 인수 중 하나가 null로,이 계산에 허용되는 경우, 포인터 연산을 허용 일부 언어 / 환경에서는, 결과는 것입니다 무효 , 비 - 널 포인터. (*) 당신에게 더 많은 힘.
(*) 이것은 COM 프로그래밍 에서 많이 발생하는데 , 여기서 인터페이스 메소드를 호출하려고 시도하지만 인터페이스 포인터가 null 인 경우 거의 0이 아닌 거의 유효하지 않은 유효하지 않은 주소에 대한 호출이 발생합니다.
기술적으로나 개념적으로 오류를 알리기 위해 NULL (또는 숫자 0 또는 부울 false)을 반환합니다.
기술적으로, 반환 값을 정확하게 반환하는 시점에서 프로그래머가 반환 값을 즉시 확인해야합니다 . 20 개의 파일을 연속으로 열 때 NULL을 반환하여 오류 신호를 보내는 경우 소비 코드는 각 파일을 개별적으로 읽고 루프 및 유사한 구문에서 벗어나야합니다. 이것은 복잡한 코드를위한 완벽한 레시피입니다. 그러나 예외를 발생시켜 오류를 알리도록 선택하면 소비 코드는 예외를 즉시 처리하거나 함수 호출 전체 에서조차도 가능한 많은 수준으로 버블 링하도록 선택할 수 있습니다. 이것은 훨씬 깨끗한 코드를 만듭니다.
개념적으로 파일을 열 때 문제가 발생하면 값을 반환하는 것 (NULL조차도)이 잘못됩니다. 작업이 완료되지 않아 반환 할 내용이 없습니다. NULL을 반환하는 것은 "파일을 성공적으로 읽었으며 여기에 포함 된 내용은 없습니다"와 같은 개념입니다. 그것이 표현하려는 것이라면 (즉, NULL이 문제의 연산에 대한 실제 결과로 의미가있는 경우), 반드시 NULL을 반환하지만 오류를 알리려면 예외를 사용하십시오.
역사적으로 C와 같은 프로그래밍 언어에는 예외 처리 기능이 언어에 내장되어 있지 않기 때문에 오류 가보고되었습니다. 긴 점프를 사용하는 권장 방법은 약간 털이 많고 반 직관적입니다.
문제에 대한 유지 보수 측면도 있습니다. 예외를 제외하고는 실패를 처리하기 위해 추가 코드를 작성해야합니다. 그렇지 않으면 프로그램이 조기에 실패하게됩니다 (좋습니다). 오류를 알리기 위해 NULL을 반환하면 프로그램의 기본 동작은 오류를 무시하고 언어에 따라 손상된 데이터, segfaults, NullReferenceExceptions와 같은 다른 문제가 발생할 때까지 계속 진행하는 것입니다. 오류를 조기에 시끄럽게 알리려면 추가 코드를 작성하고 무엇을 추측해야합니다. 마감일이 촉박 할 때 제외되는 부분입니다.
이미 지적했듯이, 많은 언어는 널 포인터의 역 참조를 포착 가능한 예외로 변환하지 않습니다. 그렇게하는 것은 비교적 현대적인 트릭입니다. 널 포인터 문제가 처음 인식되었을 때 예외는 아직 발명되지 않았습니다.
널 포인터를 유효한 경우로 허용하는 경우에는 특별한 경우입니다. 많은 다른 장소에서 특수한 처리 논리가 필요합니다. 그것은 추가적인 복잡성입니다.
잠재적 인 널 포인터와 관련이 있는지 여부에 관계없이 예외 처리를 위해 예외 처리를 사용 하지 않는 경우 예외 처리를 다른 방법으로 처리해야합니다. 일반적으로 모든 함수 호출은 함수 호출이 부적절하게 호출되지 않도록하거나 함수가 종료 될 때 실패 사례를 감지하기 위해 예외적 인 경우를 점검해야합니다. 예외를 사용하여 피할 수있는 추가적인 복잡성입니다.
복잡성이 높을수록 더 많은 오류가 발생합니다.
데이터 구조에서 널 포인터를 사용하는 대신 (예 : 링크 된 목록의 시작 / 끝 표시) 센티넬 항목 사용이 있습니다. 이것들은 훨씬 적은 복잡성과 동일한 기능을 제공 할 수 있습니다. 그러나 복잡성을 관리하는 다른 방법이있을 수 있습니다. 한 가지 방법은 null 검사가 한 곳에서만 필요하도록 스마트 포인터 클래스에서 잠재적으로 null 포인터를 래핑하는 것입니다.
널 포인터가 감지되면 어떻게해야합니까? 예외 처리 방식으로 구축 할 수없는 경우 항상 예외를 처리하고 해당 특수 처리 방식을 호출자에게 효과적으로 위임 할 수 있습니다. 그리고 이것이 널 포인터를 역 참조 할 때 기본적으로 일부 언어가하는 일입니다.
C ++에만 해당되지만 C ++의 null 의미가 포인터 유형과 관련되어 있으므로 null 참조 가 없어졌습니다. 파일 열기 함수가 실패하고 널 포인터를 리턴하는 것이 상당히 합리적입니다. 실제로 fopen()
함수는 정확히 그렇게합니다.
언어에 따라 다릅니다.
예를 들어, Objective-C를 사용하면 문제없이 널 (NULL) 객체에 메시지를 보낼 수 있습니다. nil을 호출하면 nil도 반환되며 언어 기능으로 간주됩니다.
나는 당신이 그 행동에 의존하고 모든 복잡한 중첩 if(obj == null)
구조를 피할 수 있기 때문에 개인적으로 그것을 좋아합니다 .
예를 들어 :
if (myObject != nil && [myObject doSomething])
{
...
}
단축 할 수 있습니다 :
if ([myObject doSomething])
{
...
}
간단히 말해 코드를 더 읽기 쉽게 만듭니다.
널 (null) 참조는 일반적으로 프로그램 논리의 어떤 부분이 누락 되었기 때문에 발생합니다. 즉, 해당 코드 블록에 필요한 설정을 거치지 않고 코드 줄에 도달했습니다.
다른 한편으로, 당신이 무언가에 대한 예외를 던지면 그것은 프로그램의 정상적인 작동에서 특정 상황이 발생할 수 있음을 인식하고 처리되고 있음을 의미합니다.
널 참조는 종종 매우 유용합니다. 예를 들어, 링크 된 목록의 요소는 후임자가 있거나 후임자가 없을 수 있습니다. null
"후임자 없음"을 사용 하는 것은 완벽합니다. 또는 배우자가있을 수도 있고 없을 수도 있습니다. null
"배우자가없는 사람"에 사용 하는 것은 완벽하게 자연스럽고, "배우자가 없음"- Person.Spouse
회원이 언급 할 수 있는 특별한 가치를 갖는 것보다 훨씬 더 자연 스럽습니다 .
그러나 많은 값은 선택 사항 이 아닙니다 . 일반적인 OOP 프로그램에서는 null
초기화 후 참조의 절반 이상을 사용할 수 없거나 프로그램이 실패 한다고 말합니다 . 그렇지 않으면 코드가 if (x != null)
검사 로 수수께끼가되어야 합니다. 그렇다면 왜 모든 참조가 기본적으로 널 입력 가능 해야 합니까? 변수는 기본적으로 널 (null)이 허용되지 않아야하며 "oh,이 값 null
도 역시 가능합니다"라고 명시해야합니다 .
귀하의 질문은 혼란스럽게 표현됩니다. null 참조 예외 를 의미합니까 (실제로 de참조 null)? 이러한 유형의 예외를 원하지 않는 확실한 이유는 무엇이 잘못되었는지 또는 언제라도 프로그램의 어느 시점에서나 값이 null로 설정되었을 수 있다는 정보를 제공하지 않기 때문입니다. "존재하지 않는 파일에 대한 읽기 액세스를 요청하면 예외 또는 null 참조를 얻는 것이 매우 기쁩니다."라고 말하지만 원인을 알 수없는 무언가를 얻는 것이 완전히 행복해서는 안됩니다. . "널을 역 참조하려는 시도"라는 문자열에는 존재하지 않거나 존재하지 않는 파일에 대한 언급이 없습니다. 아마도 당신은 읽기 호출에서 반환 값으로 null을 얻는 것이 기쁘다는 것을 의미 할 것입니다. 그것은 상당히 다른 문제이지만, 여전히 읽기에 실패한 이유에 대한 정보는 제공하지 않습니다. 그것'