널 (null)이 나쁜 경우 현대 언어가 왜 그것을 구현합니까? [닫은]


82

Java 또는 C #과 같은 언어 디자이너는 null 참조의 존재와 관련된 문제를 알고 있다고 확신합니다 ( null 참조가 실제로 나쁜 것입니까? 참조 ). 또한 옵션 유형을 구현하는 것은 null 참조보다 훨씬 복잡하지 않습니다.

그들은 왜 어쨌든 포함하기로 결정 했습니까? 나는 null 참조가 부족하면 언어 작성자와 사용자 모두가 더 나은 품질의 코드 (특히 더 나은 라이브러리 디자인)를 장려하거나 강요 할 것이라고 확신합니다.

그것은 단순히 보수주의 때문일까요? "다른 언어들도 그것을 가지고 있어야합니다."


99
널은 위대하다. 나는 그것을 좋아하고 매일 사용합니다.
Pieter B

17
@PieterB 그러나 대부분의 참조에 사용합니까, 아니면 대부분의 참조가 null이 아니기를 원하십니까? 논증은 널 입력 가능 데이터가 없어야한다는 것이 아니라 명시 적이며 옵트 인이어야한다는 것입니다.

11
@PieterB 그러나 대다수가 널 입력 가능하지 않아야 할 때 기본값 대신 예외를 허용하지 않는 것이 합리적이지 않습니까? 일반적으로 옵션 유형의 디자인은 부재 및 포장 풀기를 명시 적으로 검사하는 것이지만, 널 입력 가능 옵트 인 옵트 인에 대해 잘 알려진 Java / C # / ... 의미를 가질 수도 있습니다 (널링 가능하지 않은 것처럼 사용) null 인 경우). 적어도 일부 버그를 방지하고 null 검사 누락에 대해 불평하는 정적 분석을 훨씬 더 실용적으로 만듭니다.

20
WTF는 너희들과 함께? 소프트웨어로 할 수 있고 잘못 될 수있는 모든 것 중에서 null을 역 참조하려고 시도해도 전혀 문제가 없습니다. 항상 AV / segfault를 생성하므로 수정됩니다. 이 문제에 대해 너무 많은 버그 부족이 있습니까? 그렇다면 충분한 여유가 있으며 null 참조 / 포인터에 문제가 없습니다.
마틴 제임스

13
@MartinJames "항상 AV / segfault를 생성하므로 수정됩니다"-아니요, 아니요.
detly

답변:


97

면책 조항 : 나는 개인적으로 언어 디자이너를 모르기 때문에 내가 당신에게주는 대답은 투기 적입니다.

Tony Hoare 자신 으로부터 :

나는 그것을 10 억 달러의 실수라고 부릅니다. 그것은 1965 년에 null 참조의 발명이었다. 당시 나는 객체 지향 언어 (ALGOL W)로 참조 할 수있는 최초의 포괄적 인 타입 시스템을 설계하고 있었다. 필자의 목표는 컴파일러가 자동으로 검사를 수행하여 모든 참조 사용이 절대적으로 안전하도록하는 것이 었습니다. 그러나 구현하기가 쉽기 때문에 null 참조를 넣는 유혹에 저항 할 수 없었습니다. 이로 인해 수많은 오류, 취약성 및 시스템 충돌이 발생하여 지난 40 년간 수십억 달러의 고통과 피해를 초래했을 수 있습니다.

강조합니다.

당연히 그것은 그에게 나쁜 생각처럼 보이지 않았습니다. 그것은이 같은 이유로 일부 영속 된 가능성이 높습니다 - 그것은 퀵의 튜링 상을 수상한 발명가에 좋은 생각처럼 보였다 경우, 많은 사람들이 있다는 놀라운 일이 아니다 여전히 그것이 악 이유를 이해하지 않습니다. 또한 마케팅 및 학습 곡선의 이유로 새로운 언어가 이전 언어와 유사하기 때문에 부분적으로 가능합니다. 지목 사항:

"우리는 C ++ 프로그래머를 뒤 따랐다. 많은 사람들을 Lisp로 반쯤 끌었다." -Guy Steele, Java 사양의 공동 저자

(출처 : http://www.paulgraham.com/icad.html )

물론 C에는 null이 있으므로 C ++에는 null이 있으며 C의 역사적 영향에 관여 할 필요가 없습니다. C #은 Microsoft의 Java 구현 인 J ++로 대체되었으며 Windows 개발에서 선택한 언어로 C ++로 대체되었으므로 어느 쪽에서 나 얻을 수있었습니다.

편집 여기 Hoare가 고려할만한 또 다른 인용문이 있습니다.

전체적으로 프로그래밍 언어는 예전보다 훨씬 더 복잡합니다. 객체 지향, 상속 및 기타 기능은 일관성 있고 과학적으로 잘 기초 된 학문 또는 정확성 이론의 관점에서 실제로 생각되지 않습니다. . 내가 평생 과학자로 추구해 온 원래의 가정은 올바른 프로그래밍 언어 디자인 (사용자를 위해 함정을 설정하지 않는 프로그래밍 언어 디자인)에 수렴하는 수단으로 정확성 기준을 사용한다는 것입니다. 프로그램의 다른 구성 요소는 해당 사양의 다른 구성 요소와 명확하게 일치하므로 구성에 대해 추론 할 수 있습니다. [...] 컴파일러를 포함한 도구는 올바른 프로그램을 작성한다는 의미에 대한 이론을 기반으로해야합니다. -2002 년 7 월 17 일, 영국 캠브리지, 필립 L. 프란 아의 구술 역사 인터뷰; 미네소타 대학교 찰스 배비지 연구소. [ http://www.cbi.umn.edu/oh/display.phtml?id=343]

다시 한 번 강조한다. Sun / Oracle 및 Microsoft는 회사이며 모든 회사의 결론은 돈입니다. 그들로 인한 이익 null이 단점보다 더 클 수도 있고, 또는 문제를 완전히 고려하기에는 마감 기한이 너무 짧을 수도 있습니다. 마감일로 인해 발생했을 수있는 다른 언어 실수의 예 :

Cloneable이 고장났다는 것은 부끄러운 일이지만 발생합니다. 원래 Java API는 마감 시한에 맞춰 매우 빠르게 마감되어 시장 마감 시간을 맞이했습니다. 원래 Java 팀은 ​​놀라운 작업을 수행했지만 모든 API가 완벽하지는 않습니다. Cloneable은 약점이며 사람들은 그 한계를 알고 있어야한다고 생각합니다. 조쉬 블로흐

(출처 : http://www.artima.com/intv/bloch13.html )


32
친애하는 downvoter : 어떻게 대답을 향상시킬 수 있습니까?
Doval

6
당신은 실제로 그 질문에 대답하지 않았습니다. 당신은 일부 사후 의견에 대한 인용과 "비용"에 대한 추가적인 손짓 만 제공했습니다. (null이 10 억 달러의 실수 인 경우 MS와 Java가 그 부채를 줄임으로써 저축하지 말아야합니까?)
DougM

29
@DougM 지난 50 년 동안 모든 언어 디자이너를 불러내 어 왜 null자신의 언어로 구현했는지 물어 보 시겠습니까? 이 질문에 대한 답변은 언어 디자이너가 제공하지 않는 한 추론 적입니다. Eric Lippert 외에이 사이트가 빈번한 지 잘 모르겠습니다. 마지막 부분은 여러 가지 이유로 붉은 청어입니다. MS 및 Java API 위에 작성된 타사 코드의 양은 API 자체의 코드 양보다 훨씬 큽니다. 따라서 고객이 원하는 경우 고객 null에게 제공합니다 null. 또한 그들이 수락했다고 null해서 돈을 지불 한다고 가정 합니다.
Doval

3
당신이 줄 수있는 유일한 대답이 투기 적이라면, 첫 단락에 그 답을 명확하게 기술하십시오. (당신은 당신의 대답을 향상시킬 수있는 방법을 물었고 나는 대답했다. 어떤 괄호는 무시할 수있는 해설 일 뿐이다. 괄호는 영어로 된 것이다.)
DougM

7
이 대답은 합리적입니다. 내 고려 사항을 더 추가했습니다. 나는이 유의 ICloneable유사 .NET에서 고장; 불행히도 이것은 Java의 단점을 제 시간에 배우지 못한 곳입니다.
Eric Lippert

121

Java 또는 C #과 같은 언어의 디자이너가 null 참조의 존재와 관련된 문제를 알고 있다고 확신합니다.

물론이야.

또한 옵션 유형을 구현하는 것은 null 참조보다 훨씬 복잡하지 않습니다.

나는달라고 간청한다! C # 2에서 Null을 허용하는 값 ​​형식으로 들어가는 디자인 고려 사항은 복잡하고 논쟁의 여지가 많았습니다. 그들은 언어와 런타임의 디자인 팀을 수개월 간의 토론, 프로토 타입 구현 등으로 가져 갔으며 실제로 nullable boxing의 의미는 C # 2.0 배송에 매우 가깝게 변경되었으며 이는 매우 논쟁의 여지가 있습니다.

그들은 왜 어쨌든 포함하기로 결정 했습니까?

모든 디자인은 미묘하고 심하게 양립 할 수없는 많은 목표 중에서 선택하는 과정입니다. 고려할 몇 가지 요소에 대한 간단한 스케치 만 제공 할 수 있습니다.

  • 언어 기능의 직교성은 일반적으로 좋은 것으로 간주됩니다. C #에는 nullable 값 형식, nullable 값 형식 및 nullable 참조 형식이 있습니다. 널 입력 불가능 참조 유형이 존재하지 않으므로 유형 시스템이 직교하지 않습니다.

  • 기존 C, C ++ 및 Java 사용자에게 친숙 함이 중요합니다.

  • COM과의 쉬운 상호 운용성이 중요합니다.

  • 다른 모든 .NET 언어와의 쉬운 상호 운용성이 중요합니다.

  • 데이터베이스와의 쉬운 상호 운용성이 중요합니다.

  • 의미론의 일관성이 중요합니다. 우리가 TheKingOfFrance를 null과 동일하게 언급한다면 그것은 항상 "지금은 프랑스의 왕이 없다"는 것을 의미합니까, 또는 "프랑스의 왕이 분명히 있습니다. 또는 "프랑스에서 왕이 있다는 개념은 무의미하므로 질문하지 않아도됩니다!" Null은 C #에서 이러한 모든 것을 의미 할 수 있으며 이러한 모든 개념이 유용합니다.

  • 성능 비용이 중요합니다.

  • 정적 분석에 적합한 것이 중요합니다.

  • 형식 시스템의 일관성이 중요합니다. 우리는 할 수 항상 nullable이 아닌 참조가 없다는 것을 알고 결코 아래에 있는 유효하지 않은 것으로 관찰 상황? null이 아닌 참조 유형의 필드를 가진 객체의 생성자에서는 어떻습니까? 참조를 작성 해야하는 코드에서 예외가 발생 하여 객체가 완성되는 해당 객체의 마무리 장치 어떻습니까? 보증에 관한 거짓말 시스템은 위험합니다.

  • 그리고 시맨틱의 일관성은 어떻습니까? Null 값은 사용될 때 전파되지만 Null 참조는 사용될 때 예외를 발생시킵니다. 일관성이 없습니다. 그 불일치가 어떤 이점으로 정당화 되었습니까?

  • 다른 기능을 중단하지 않고 기능을 구현할 수 있습니까? 이 기능이 배제 할 수있는 다른 기능은 무엇입니까?

  • 당신은 당신이 원하는 군대가 아닌, 당신이 가진 군대와 전쟁을합니다. C # 1.0에는 제네릭이 없으므로 Maybe<T>대안으로 말하는 것은 시작이 아닌 완전한 방법입니다. 런타임 팀이 null 참조를 제거하기 위해 제네릭을 추가하는 동안 .NET이 2 년 동안 미끄러 졌어 야합니까?

  • 타입 시스템의 일관성은 어떻습니까? Nullable<T>모든 유형의 값에 대해 말할 수 있습니다. 아니, 그건 거짓말입니다. 말할 수 없습니다 Nullable<Nullable<T>>. 할 수 있을까요? 그렇다면 원하는 의미론은 무엇입니까? 이 기능에 대해서만 전체 유형 시스템에 특별한 경우를 두는 것이 가치가 있습니까?

등등. 이러한 결정은 복잡합니다.


12
모든 것을 제외하고 +1, 특히 제네릭을 가져옵니다. 제네릭이 존재하지 않은 Java와 C #의 역사에는 오랜 시간이 걸렸다는 것을 잊기 쉽습니다.
Doval

2
어쩌면 바보 같은 질문 (단지 IT 학부생)이지만 구문 유형 (CLR에 대해 아무것도 알지 못함)에서 옵션 유형을 구현 할 수 없었습니다. 암호? 옵션 유형은 런타임에 검사가 필요하지 않다고 생각합니다.
mrpyo

2
@mrpyo : 물론 구현 가능한 선택입니다. 다른 디자인 선택은 사라지지 않으며 구현 선택에는 많은 장단점이 있습니다.
Eric Lippert

1
@mrpyo "가치가있는"점검을 강요하는 것은 좋은 생각이 아니라고 생각합니다. 이론적으로 그것은 매우 좋은 생각이지만 실제로는 IMO는 컴파일러를 만족시키기 위해 모든 종류의 빈 검사를 가져옵니다 catches. 아마도 잘못된 상태에서 작업을 계속하는 대신 시스템을 폭파시키는 것이 좋습니다.
NothingsImossible

2
@voo : 널이 불가능한 참조 유형의 배열은 여러 가지 이유로 어렵습니다. 가능한 솔루션이 많이 있으며 모두 다른 운영에 비용을 부과합니다. Supercat의 제안은 요소를 할당하기 전에 합법적으로 읽을 수 있는지 여부를 추적하여 비용을 부과하는 것입니다. 배열이 표시되기 전에 각 요소에서 이니셜 라이저를 실행하여 다른 비용 세트를 부과하는 것입니다. 그래서 여기에 문지가 없습니다 : 상관없이, 누군가에 대한 효율적이지 불평 것입니다 하나는 선택이 기술의 어느 자신의 애완 동물 시나리오. 이것은 기능에 대한 심각한 요점입니다.
Eric Lippert

28

Null은 가치 부족을 나타내는 매우 유효한 목적을 제공합니다.

나는 내가 무질서한 학대와 모든 두통과 그들이 자유로이 사용될 때 발생할 수있는 고통에 대해 내가 아는 가장 성실한 사람이라고 말할 것입니다.

내 개인적인 입장은 사람들이 필요하고 적절하다고 정당화 할 수있을 때만 null을 사용할 수 있다는 것입니다.

널을 정당화하는 예제 :

사망 날짜는 일반적으로 널 입력 가능 필드입니다. 사망일에는 3 가지 가능한 상황이 있습니다. 사람이 사망하고 날짜를 알거나, 사람이 사망하고 날짜를 알 수 없거나, 사람이 죽지 않았으므로 사망 날짜가 존재하지 않습니다.

사망 날짜도 DateTime 필드이며 "알 수 없음"또는 "빈"값이 없습니다. 기본 날짜는 사용 된 언어에 따라 달라지는 새로운 날짜 시간을 만들 때 나타나는 기본 날짜를 갖지만 기술적으로 실제로 그 시간에 사람이 죽어“빈 값”으로 표시 될 가능성이 있습니다 기본 날짜를 사용하십시오.

데이터는 상황을 올바르게 나타내야합니다.

사망자가 사망 한 것으로 알려져 있습니다 (1984 년 3 월 9 일)

간단한 '1984 년 3 월 9 일'

사람이 사망 한 날짜를 알 수 없음

가장 좋은 것은 무엇입니까? Null , '0/0/0000'또는 '01 / 01 / 1869 '(또는 기본값은 무엇입니까?)

사망하지 않은 사람 사망일은 해당되지 않습니다

가장 좋은 것은 무엇입니까? Null , '0/0/0000'또는 '01 / 01 / 1869 '(또는 기본값은 무엇입니까?)

따라서 각각의 가치를 생각해 봅시다 ...

  • Null 은주의를 기울여야합니다. 예를 들어 null이 아닌지 확인하지 않고 실수로 조작하려고하면 예외가 발생하지만 실제 상황을 가장 잘 나타냅니다. 죽음의 날짜는 존재하지 않습니다 ... 아무것도 아닙니다 ... null입니다 ...
  • 0/0/0000 , 일부 언어에서는 가능하며 날짜가없는 적절한 표현 일 수도 있습니다. 불행히도 일부 언어 및 유효성 검사는이를 유효하지 않은 날짜 시간으로 거부하므로 많은 경우에 실패하지 않습니다.
  • 1/1/1869 (또는 기본 날짜 시간 값이 무엇이든) 여기서 문제는 다루기가 까다로워집니다. 사망일이없는 모든 기록을 걸러 내려면 어떻게됩니까?를 제외하고는 가치 가치가없는 것으로 사용할 수 있습니까? 나는 그 날짜에 실제로 죽은 사람들을 쉽게 걸러내어 데이터 무결성 문제를 일으킬 수 있습니다.

사실은 때때로 당신입니다 마십시오 아무것도 표현하지 있는지 때때로 변수 유형가 잘 작동하지만, 종종 변수 유형 아무것도 표현하지 할 수 있어야합니다.

사과가 없으면 사과가 0 개인데 사과가 몇 개인 지 모른다면 어떻게해야합니까?

반드시 null은 남용되고 잠재적으로 위험하지만 때때로 필요합니다. 값을 제공 할 때까지 값이없고 무언가를 나타내야하기 때문에 많은 경우에 기본값입니다. (없는)


37
Null serves a very valid purpose of representing a lack of value.Option또는의 Maybe유형은 타입 시스템을 우회하지 않고이 매우 유효 용도로 사용됩니다.
Doval

34
어느 누구도 가치가 결여되어서는 안된다고 주장하는 사람은 없으며, 누락 될 수있는 가치는 잠재적으로 누락 된 모든 가치가 아니라 명시 적 으로 표시 되어야한다고 주장하고 있습니다 .

2
RualStorge가 SQL과 관련하여 이야기하고있는 것 같습니다. 모든 열을로 표시 해야하는 캠프가 있기 때문 NOT NULL입니다. 내 질문은 RDBMS와 관련이 없지만 ...
mrpyo

5
"값 없음"과 "알 수없는 값"을 구별하기위한 +1
David

2
사람의 상태를 분리하는 것이 더 합리적이지 않습니까? 즉이 Person유형은이 state유형의 필드 State차별의 조합이다, AliveDead(dateOfDeath : Date).
jon-hanson

10

나는 "다른 언어들도 그것을 가지고 있고, 우리도 그것을 가져야 만한다 ..."그런 식으로 Joneses를 따라 잡는 것처럼 가지 않을 것이다. 새로운 언어의 주요 특징은 다른 언어로 된 기존 라이브러리와 상호 작용할 수 있다는 것입니다 (C 읽기). C에는 null 포인터가 있으므로 상호 운용성 계층에는 반드시 null 개념이 필요합니다 (또는 사용할 때 폭발하는 다른 "존재하지 않음").

언어 디자이너가 옵션 유형 을 사용하도록 선택 하고 널 이 될 수있는 모든 곳 에서 널 경로를 처리하도록 할 수 있습니다. 그리고 그것은 거의 확실히 버그를 줄입니다.

그러나이 상호 운용성 계층에 옵션 유형을 사용하는 경우 (특히 도입시기 및 대상 대상으로 인해 Java 및 C #의 경우) 채택을 어지럽히 지 않으면 피해를 입었을 수 있습니다. 옵션 유형은 90 년대 중반에서 후반의 C ++ 프로그래머들로부터 지옥을 성가 시게하거나 또는 null을 만나면 상호 운용성 계층에서 예외가 발생하여 90 년대 중반에서 후반까지의 C ++ 프로그래머들로부터 지옥을 성가 시게합니다. ..


3
첫 번째 단락은 이해가되지 않습니다. Java에는 제안한 형태의 C interop이 없습니다 (JNI가 있지만 참조와 관련된 모든 것에 대해 이미 수십 개의 후프를 뛰어 넘고 실제로는 거의 사용되지 않습니다). 다른 "현대"언어와 동일합니다.

@delnan-죄송합니다 .C #에 더 익숙합니다 .C #에는 이런 종류의 interop이 있습니다. 오히려 많은 기본 Java 라이브러리가 맨 아래에서도 JNI를 사용한다고 가정했습니다.
Telastyn

6
널을 허용하는 것에 대한 좋은 주장을하지만, 장려 하지 않고도 널을 허용 할 수 있습니다 . 스칼라는 좋은 예입니다. null을 사용하는 Java API와 완벽하게 상호 운용 할 수 있지만 Scala 내에서 사용 하기 위해 랩핑하는 것이 좋습니다 . 실제로 사람들이의 이점을 보는 데 시간이 오래 걸리지 않습니다 . Optionval x = Option(possiblyNullReference)Option
Karl Bielefeldt

1
옵션 유형은 (정적으로 검증 된) 패턴 일치와 함께 사용되며 C #에는 불행히도 없습니다. F #은 훌륭하지만 훌륭합니다.
Steven Evers

1
@SteveEvers 개인 생성자, 봉인 된 내부 클래스 및 Match대리자를 인수로 사용 하는 메서드가 있는 추상 기본 클래스를 사용하여 위조 할 수 있습니다 . 그런 다음 람다 식을 Match(명명 된 인수를 사용하기위한 보너스 포인트)에 전달 Match하고 올바른 것을 호출합니다.
Doval

7

우선, 우리는 무의 개념 이 필요 하다는 것에 모두 동의 할 수 있다고 생각 합니다. 정보 의 부재 를 대변해야하는 상황이 있습니다 .

null참조 (및 포인터)를 허용 하는 것은이 개념의 한 가지 구현 일뿐이며 C, Java, Python, Ruby, PHP, JavaScript 등의 문제가 있음을 알고 있지만 가장 인기가 있습니다. 모두 비슷한 것을 사용합니다 null.

왜 ? 글쎄, 대안은 무엇입니까?

Haskell과 같은 기능적 언어에서는 Option또는 Maybe유형이 있습니다. 그러나 그것들은 다음에 기반합니다 :

  • 파라 메트릭 유형
  • 대수 데이터 유형

이제 원래 C, Java, Python, Ruby 또는 PHP가 이러한 기능 중 하나를 지원 했습니까? 아니요. Java의 결함이있는 제네릭은 최근 에 언어 역사에있어 다른 사람들이 전혀 구현하지 않은 것 같습니다.

거기 있어요 null파라 메트릭 대수 데이터 형식이 더 어렵습니다. 사람들은 가장 간단한 대안으로 갔다.


"널 (null)이 쉽고 파라 메트릭 대수 데이터 유형이 더 어렵다"+1 그러나 저는 이것이 파라 메트릭 타이핑과 ADT가 더 어려워지는 문제는 아니라고 생각합니다. 필요에 따라 인식되지 않는 것입니다. 반면에 Java가 객체 시스템없이 배송 된 경우에는 실패했을 것입니다. OOP는 "보이지 않는"기능이었습니다.이 기능이 없으면 아무도 관심이 없었습니다.
Doval

@Doval : Java에는 OOP가 필요할 수도 있지만 C에는 필요하지 않았습니다. 그러나 Java가 단순하다는 목표는 사실입니다. 불행히도 사람들은 간단한 언어가 간단한 프로그램으로 이어진다 고 가정하는 것 같습니다. 그들은 매우 유용 할 수 있습니다.
Matthieu M.

1
@MatthieuM .: 실제 시스템은 복잡합니다. 복잡성이 모델링되는 실제 시스템과 일치하는 잘 설계된 언어를 사용하면 간단한 코드를 사용하여 복잡한 시스템을 모델링 할 수 있습니다. 언어를 지나치게 단순화하려는 시도는 단순히 언어를 사용하는 프로그래머에게 복잡성을 가중시킵니다.
supercat

@ supercat : 나는 더 이상 동의 할 수 없었다. 또는 아인슈타인은 다음과 같이 설명합니다. "모든 것을 가능한 한 단순하게 만드십시오."
Matthieu M.

@MatthieuM .: 아인슈타인은 여러면에서 현명했습니다. "모든 것이 객체이고, 참조가 저장 될 수있는"것으로 가정하는 언어 Object는 실제 응용 프로그램이 공유 불가능한 가변 객체와 공유 불가능한 불변 객체 (모두 값처럼 동작해야 함)와 공유 가능 및 공유 불가능이 필요하다는 것을 인식하지 못한다 엔티티. Object모든 것에 단일 유형을 사용 한다고해서 그러한 구분이 필요하지는 않습니다. 그것들을 올바르게 사용하기 어렵게 만듭니다.
supercat

5

Null / nil / none 자체는 악이 아닙니다.

Tony Hoare가 자신의 이름을 불렀던 유명한 연설 "Billion dollar Mistake"를 본다면 어떤 변수가 널 (null)을 보유 할 수있게하는 것이 큰 실수 인지에 대해 이야기합니다 . 옵션을 사용하는 대안 은 실제로 널 참조를 제거 하지 않습니다 . 대신 널을 보유 할 수있는 변수와 그렇지 않은 변수를 지정할 수 있습니다.

사실, 적절한 예외 처리를 구현하는 최신 언어를 사용하면 null 역 참조 오류는 다른 예외와 다르지 않습니다. 찾아서 수정합니다. null 참조에 대한 일부 대안 (예 : Null Object 패턴)은 오류를 숨기고 나중에 훨씬 나중에 자동으로 실패합니다. 제 생각에는 빨리 실패하는 것이 훨씬 좋습니다 .

그렇다면 왜 언어가 옵션을 구현하지 못하는가? 사실, C ++은 가장 널리 사용되는 언어로 할당 할 수없는 객체 변수를 정의 할 수 있습니다 NULL. 토니 호 아어 (Tony Hoare)는 그의 연설에서 언급 된 "널 문제"에 대한 해결책이다. 다음으로 가장 많이 사용되는 유형 언어 인 Java가없는 이유는 무엇입니까? 왜 일반적으로 유형 시스템에 결함이 많은지 묻습니다 . 언어가 체계적으로이 실수를한다고 말할 수는 없다고 생각합니다. 어떤 사람들은 그렇지 않습니다.


1
구현 관점에서 Java의 가장 큰 장점 중 하나이지만 언어 관점에서 볼 때의 약점은 기본이 아닌 유형 인 Promiscuous Object Reference가 하나뿐이라는 것입니다. 이것은 런타임을 엄청나게 단순화하여 매우 가벼운 JVM 구현을 가능하게합니다. 그러나이 디자인은 모든 유형이 기본값을 가져야한다는 것을 의미하며, 무차별 객체 참조의 경우 가능한 유일한 기본값은 null입니다.
supercat

글쎄, 하나의 루트가 아닌 기본 유형입니다. 언어 관점에서 이것이 왜 약점입니까? 이 사실이 왜 모든 유형에 기본값이 있어야하는지 (또는 반대로 여러 루트 유형이 유형에 기본값을 갖지 못하게하는 이유) 또는 왜 약점인지 이해하지 못합니다.
BT

필드 나 배열 요소가 가질 수있는 다른 비 원시적 유형은 무엇입니까? 단점은 일부 참조가 ID를 캡슐화하는 데 사용되고 일부는 식별 된 객체에 포함 된 값을 캡슐화하는 데 사용된다는 것입니다. 식별을 캡슐화하는 데 사용되는 참조 유형 변수 null의 경우 유일한 기본값입니다. 그러나 값을 캡슐화하는 데 사용되는 참조는 형식이 적절한 기본 인스턴스를 가지거나 구성 할 수있는 경우 적절한 기본 동작을 가질 수 있습니다. 참고 문헌이 어떻게 행동해야하는지에 대한 많은 측면은 가치를 캡슐화하는지의 여부와 방법에 달려 있지만, ...
supercat

... 자바 타입 시스템은이를 표현할 방법이 없습니다. 경우 foo에 대한 유일한 참조 보유 int[]포함하는 {1,2,3}코드가 원하는 foo에 대한 참조를 개최 int[]함유 {2,2,3}, 가장 빠른 방법은이 증가하는 것입니다 달성하기 위해 foo[0]. 코드가 메소드에 foohold를 알리고 자하는 {1,2,3}경우, 다른 메소드는 배열 foo을 수정하거나 수정하려는 지점 이상으로 참조를 유지하지 않습니다. 이를 달성하는 가장 빠른 방법은 배열에 대한 참조를 전달하는 것입니다. Java에 "일시적인 읽기 전용 참조"유형이있는 경우 ...
supercat

... 어레이는 임시 참조로 안전하게 전달 될 수 있으며 값을 유지하려는 방법은 배열을 복사해야한다는 것을 알게됩니다. 이러한 유형이없는 경우 배열의 내용을 안전하게 노출하는 유일한 방법은 배열의 사본을 만들거나 해당 목적을 위해 만든 객체에 배열하는 것입니다.
supercat

4

프로그래밍 언어는 일반적으로 기술적으로 정확하기보다는 실질적으로 유용하도록 설계 되었기 때문입니다. 사실 null상태는 데이터가 잘못되었거나 누락되었거나 아직 결정되지 않은 상태로 인해 일반적으로 발생합니다. 기술적으로 우수한 솔루션은 단순히 널 (null) 상태를 허용하고 프로그래머가 실수를한다는 사실을 잊는 것보다 다루기 힘들다.

예를 들어 파일과 함께 작동하는 간단한 스크립트를 작성하려면 다음과 같이 의사 코드를 작성할 수 있습니다.

file = openfile("joebloggs.txt")

for line in file
{
  print(line)
}

joebloggs.txt가 없으면 단순히 실패합니다. 문제는 아마도 괜찮은 간단한 스크립트와 더 복잡한 코드의 많은 상황에서 그것이 존재하고 실패가 발생하지 않아서 시간 낭비를 확인하도록 강요한다는 것입니다. 더 안전한 대안은 잠재적 인 실패 상태를 올바르게 처리하도록함으로써 안전을 달성하지만 종종 그렇게하고 싶지는 않습니다.


13
그리고 여기서 당신은 null에 정확히 무엇이 잘못되었는지에 대한 예를 들었습니다. 올바르게 구현 된 "openfile"함수는 발생한 파일에 대한 정확한 설명과 함께 실행을 중지시키는 예외 (파일 누락)를 발생시켜야합니다. 대신 null을 반환하면 더 전달되어 (to for line in file) 의미없는 null 참조 예외가 발생합니다. 이는 간단한 프로그램에는 적합하지만 훨씬 더 복잡한 시스템에서 실제 디버깅 문제를 발생시킵니다. 널이 존재하지 않으면 "openfile"디자이너는이 실수를 할 수 없습니다.
mrpyo

2
"프로그램 언어는 일반적으로 기술적으로 정확하지 않고 실질적으로 유용하도록 설계되었으므로"+1
Martin Ba

2
내가 아는 모든 옵션 유형을 사용하면 하나의 짧은 추가 메소드 호출로 실패시 null을 수행 할 수 있습니다 (예 :) let file = something(...).unwrap(). POV에 따라 오류나 널 (null)이 발생할 수없는 간결한 어설 션을 처리하지 않는 쉬운 방법입니다. 낭비되는 시간은 최소화되며 다른 곳에서 시간을 절약 할 수 있습니다. 무언가 가 null 일 있는지 여부를 알 필요가 없기 때문 입니다. 다른 장점은 (추가로 호출 할 가치가있을 수 있음) 오류 사례 를 명시 적으로 무시 한다는 것입니다 . 그것이 실패했을 때 무엇이 ​​잘못되었는지와 수정이 필요한 곳은 거의 의심의 여지가 없습니다.

4
@mrpyo 모든 언어가 예외 및 / 또는 예외 처리 (일명 시도 / 캐치)를 지원하는 것은 아닙니다. 예외도 남용 될 수 있습니다. "흐름 제어와 같은 예외"는 일반적인 안티 패턴입니다. 파일이 존재하지 않는이 시나리오는 AFAIK가 해당 안티 패턴에서 가장 자주 인용되는 예입니다. 한 가지 나쁜 습관을 다른 것으로 바꾸는 것 같습니다.
David

8
@mrpyo if file exists { open file }는 경쟁 조건으로 고통 받고 있습니다. 파일을 열면 성공할 수있는 확실한 방법은 파일을 열어 보는 것입니다.

4

NULL(또는 nil, 또는 Nil, 또는 null, 또는 Nothing선호하는 언어로 호출되는 것) 포인터의 명확하고 실용적인 용도가 있습니다.

예외 시스템 (예 : C)이없는 언어의 경우 포인터를 리턴해야 할 때 널 포인터를 오류 표시로 사용할 수 있습니다. 예를 들면 다음과 같습니다.

char *buf = malloc(20);
if (!buf)
{
    perror("memory allocation failed");
    exit(1);
}

여기에서 NULL반환 된 malloc(3)값은 실패 표시 자로 사용됩니다.

메소드 / 함수 인수에 사용될 때 인수의 기본값 사용을 표시하거나 출력 인수를 무시할 수 있습니다. 아래 예.

예외 메커니즘이있는 언어의 경우에도 예외 처리가 비싸면 (예 : Objective-C) 소프트 포인터 (즉, 복구 가능한 오류)의 표시로 널 포인터를 사용할 수 있습니다.

NSError *err = nil;
NSString *content = [NSString stringWithContentsOfURL:sourceFile
                                         usedEncoding:NULL // This output is ignored
                                                error:&err];
if (!content) // If the object is null, we have a soft error to recover from
{
    fprintf(stderr, "error: %s\n", [[err localizedDescription] UTF8String]);
    if (!error) // Check if the parent method ignored the error argument
        *error = err;
    return nil; // Go back to parent layer, with another soft error.
}

여기서 소프트 오류는 프로그램이 잡히지 않으면 충돌을 일으키지 않습니다. 이것은 소프트 오류가 중단되지 않기 때문에 Java와 같은 미친 try-catch를 제거하고 프로그램 흐름을보다 잘 제어 할 수 있습니다 (그리고 남아있는 몇 가지 하드 예외는 일반적으로 복구 할 수 없으며 잡히지 않습니다)


5
문제는 절대 포함하지 않아야하는 변수를 구분할 수있는 방법이 없다는 것 null입니다. 예를 들어 Java에서 5 개의 값을 포함하는 새로운 유형을 원한다면 열거 형을 사용할 수 있지만 얻을 수있는 것은 6 개의 값을 보유 할 수있는 유형입니다 (5는 + 5 원했습니다 null). 타입 시스템의 결함입니다.
Doval

@Doval 상황에 따라 NULL에 의미를 지정하거나 기본값이있는 경우 기본값과 동의어로 취급하거나 NULL (처음에는 절대 나타나지 않아야 함)을 소프트 오류의 마커로 사용하십시오. (즉, 오류는 있지만 최소한 아직 충돌하지는 않음)
Maxthon Chan

1
@MaxtonChan Null은 유형의 값에 데이터가없는 경우에만 의미를 할당 할 수 있습니다 (예 : 열거 형 값). 값이 더 복잡한 것이면 (예 : 구조체) null해당 유형에 적합한 의미를 할당 할 수 없습니다. null구조체 또는 목록으로 사용할 방법이 없습니다 . 또한 null오류 신호 로 사용하는 데 따른 문제점 은 널을 리턴하거나 널을 승인 할 수있는 대상을 알 수 없다는 것입니다. 프로그램의 모든 변수는 매번 사용하기 전에 모든 단일 변수 null를 확인하는 것이 극히 세심한 경우 가 아니라면 가능합니다 null.
Doval

1
@Doval : 불변의 참조 유형을 null사용 가능한 기본값으로 간주하는 데있어 고유 한 어려움은 없습니다 (예 : 기본값 string이 이전 공통 오브젝트 모델 에서 와 같이 빈 문자열로 작동 함). 비가 상 구성원을 호출 할 call때보다는 언어를 사용 하는 것이 필요 callvirt했을 것 입니다.
supercat

@supercat 좋은 지적이지만 이제 불변과 불변이 아닌 유형을 구별하기위한 지원을 추가 할 필요가 없습니까? 언어에 추가하는 것이 얼마나 사소한 지 잘 모르겠습니다.
Doval

4

두 가지 관련이 있지만 약간 다른 문제가 있습니다.

  1. null전혀 존재 해야합니까 ? 아니면 항상 Maybe<T>null이 유용한 곳에 사용해야 합니까?
  2. 모든 참조가 널 입력 가능해야합니까? 그렇지 않은 경우 어떤 것이 기본값이어야합니까?

    널 입력 가능 참조 유형을 명시 적으로 선언 string?하거나 이와 유사 하게 선언 null하면 프로그래머가 사용하는 것과 크게 다르지 않고 대부분의 문제점 원인을 피할 수 있습니다.

최소한 모든 참고 문헌이 무효가 될 수는 없다는 점에 동의합니다. 그러나 null을 피하는 것이 복잡성이없는 것은 아닙니다.

.NET default<T>은 관리 코드로 먼저 액세스하기 전에 모든 필드를 초기화합니다 . 즉, 참조 유형의 경우 필요 null하거나 동등한 항목이 있으며 코드를 실행하지 않고도 값 유형을 0 으로 초기화 할 수 있습니다 . 이 두 가지 모두 심각한 단점이 있지만 default초기화 의 단순성이 그 단점보다 더 클 수 있습니다.

  • 예를 들어 필드this포인터를 관리 코드에 노출시키기 전에 필드 초기화를 요구하여이 문제를 해결할 수 있습니다 . Spec #은 C #에 비해 생성자 체인과 다른 구문을 사용하여이 경로를 사용했습니다.

  • 들어 정적 필드 당신은 단순히 숨길 수 없기 때문에 필드 이니셜 라이저에서 실행할 수 있습니다 코드의 종류에 대한 강력한 제한 포즈를 제외하고는이를 보장 어렵 this포인터.

  • 참조 유형의 배열을 초기화하는 방법? List<T>길이보다 용량이 큰 어레이가 지원 하는 것을 고려하십시오 . 나머지 요소는 가질 필요가 일부 값입니다.

또 다른 문제는 마치 아무것도 찾지 못하는 것처럼 bool TryGetValue<T>(key, out T value)리턴 하는 메소드를 허용 하지 않는다는 것입니다. 이 경우 out 매개 변수가 처음에 나쁜 디자인이라고 주장하기 쉽지만이 방법은 차별적 인 노동 조합 또는 어쩌면 대신 반환해야 합니다.default(T)value

이러한 모든 문제를 해결할 수는 있지만 "널을 금지하고 모든 것이 잘되는 것"만큼 쉽지는 않습니다.


List<T>그것을 필요로하기 때문에, 이럴 가장 좋은 예입니다 중 하나마다 그 T배킹 스토어에있는 모든 항목이 될 것을, 기본값이 Maybe<T>추가 "isValid"필드, 경우에도 TA는 Maybe<U>, 또는에 대한 코드하는 List<T>행동하라 다르게 따라 T자체가 널 입력 가능 유형 인지 여부 T[]요소를 기본값으로 초기화 하는 것이 그러한 선택 중에서 가장 악의적이라고 생각하지만 물론 요소가 기본값 을 가져야 한다는 것을 의미합니다 .
supercat

녹은 지점 1을 따르며 전혀 null이 없습니다. 실론은 기본적으로 null이 아닌 포인트 2를 따릅니다. 널이 될 수있는 참조는 참조 또는 널을 포함하는 공용체 유형으로 명시 적으로 선언되지만 널은 절대 참조의 값이 될 수 없습니다. 결과적으로 언어는 완전히 안전하며 의미 적으로 불가능하기 때문에 NullPointerException이 없습니다.
Jim Balter

2

가장 유용한 프로그래밍 언어는 프로그램을 실행하기 전에 읽기 및 쓰기 순서를 정적으로 결정하는 것이 불가능할 수 있도록 데이터 항목을 임의의 순서로 쓰고 읽을 수 있도록합니다. 실제로 코드가 유용한 데이터를 읽기 전에 모든 슬롯에 저장하지만 증명하기 어려운 경우가 많이 있습니다. 따라서 코드가 이론적으로 유용한 값으로 아직 작성되지 않은 것을 읽으려고 이론적으로 가능한 프로그램을 실행해야하는 경우가 종종 있습니다. 코드가 합법적이든 아니든 코드의 시도를 막는 일반적인 방법은 없습니다. 유일한 질문은 그것이 일어날 때 어떤 일이 발생해야 하는가입니다.

언어와 시스템에 따라 접근 방식이 다릅니다.

  • 한 가지 방법은 기록되지 않은 것을 읽으려고하면 즉시 오류가 발생한다는 것입니다.

  • 두 번째 방법은 저장된 값이 의미 적으로 유용 할 수있는 방법이 없더라도 코드를 읽을 수 있기 전에 모든 위치에 값을 제공하도록 코드를 요구하는 것입니다.

  • 세 번째 방법은 단순히 문제를 무시하고 "자연스럽게"일어날 수있는 모든 일이 발생하도록하는 것입니다.

  • 네 번째 방법은 모든 유형이 기본값을 가져야하며, 다른 것으로 기록되지 않은 슬롯은 해당 값을 기본값으로 사용하는 것입니다.

접근법 # 4는 접근법 # 3보다 훨씬 안전하며 일반적으로 접근법 # 1 및 # 2보다 저렴합니다. 그런 다음 참조 유형에 대한 기본값이 무엇인지에 대한 질문을 남깁니다. 불변의 참조 유형의 경우, 대부분의 경우 기본 인스턴스를 정의하고 해당 유형의 변수에 대한 기본값이 해당 인스턴스에 대한 참조 여야한다고 말하는 것이 좋습니다. 그러나 변경 가능한 참조 유형의 경우 그다지 도움이되지 않습니다. 작성하기 전에 변경 가능한 참조 유형을 사용하려고 시도하는 경우 일반적으로 사용 시도 시점에서 트랩을 제외하고는 안전한 조치가 없습니다.

의미 적으로 말하자면, 하나 customers의 유형 의 배열 을 가지고 있고 아무것도 저장하지 않고 Customer[20]시도 하면 실행이 트랩되어야합니다. 코드 를 읽을 때까지 기다리지 않고 즉시 읽으려고 시도하면 즉시 트랩해야 한다고 주장 할 수 있지만 슬롯을 읽고 값을 보유하지 않는 것을 발견 한 다음 사용하는 것이 유용한 경우가 충분합니다. 읽기 시도 자체가 실패하는 것은 종종 큰 문제가 될 수 있습니다.Customer[4].GiveMoney(23)Customer[4]Customer[4]GiveMoney

일부 언어에서는 특정 변수에 null이 포함되지 않도록 지정할 수 있으며 null을 저장하려고하면 즉시 트랩이 트리거됩니다. 유용한 기능입니다. 그러나 일반적으로 프로그래머가 참조 배열을 생성 할 수있게하는 모든 언어는 null 배열 요소의 가능성을 허용하거나 의미가없는 데이터로 배열 요소를 초기화해야합니다.


하지 않은 것입니다 Maybe/ Option당신이 참조 값이없는 경우 이후 유형, # 2 문제를 해결할 아직 미래의 일을하지만 것입니다, 당신은 단지 저장할 수 있습니다 NothingA의 Maybe <Ref type>?
Doval

@Doval : 아니요, 적어도 null 참조를 다시 도입하지 않으면 문제가 해결되지 않습니다. "아무것도"유형의 멤버처럼 행동해야합니까? 그렇다면 어느 것입니까? 아니면 예외를 던져야합니까? 어떤 경우에, 단순히 null올바르게 / 지각 적으로 사용하는 것 보다 어떻게 더 나은가 ?
cHao

@Doval : 백업 유형은 List<T>a T[]또는 a 여야 Maybe<T>합니까? 의 백업 유형은 List<Maybe<T>>어떻습니까?
supercat

나는의 백업 유형 방법을 잘 모르겠어요 @supercat Maybe차종가 감지 List이후 Maybe단일 값을 보유하고 있습니다. 당신은 의미 했습니까 Maybe<T>[]?
Doval

@cHao는 Nothing유일한 유형의 값을 할당 할 수 있습니다 Maybe, 그래서 아주 할당처럼 아니다 null. Maybe<T>그리고 T두 가지 종류가 있습니다.
Doval
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.