나는 왜 null이 바람직하지 않은지에 대한 간결한 요약은 무의미한 상태는 표현할 수 없다는 것 입니다.
문을 모델링한다고 가정 해 봅시다. 개방, 종료, 잠금 해제 및 종료 및 잠금의 세 가지 상태 중 하나 일 수 있습니다. 이제 라인을 따라 모델링 할 수 있습니다.
class Door
private bool isShut
private bool isLocked
세 가지 상태를이 두 부울 변수에 매핑하는 방법이 명확합니다. 그러나 이로 인해 바람직하지 않은 네 번째 상태를 사용할 수 있습니다 isShut==false && isLocked==true
. 표현으로 선택한 유형이이 상태를 허용하므로 클래스가이 상태에 도달하지 않도록 (정확하게 불변을 명시 적으로 코딩하여) 정신적 인 노력을 기울여야합니다. 반대로, 대수 데이터 형식 또는 확인 된 열거 형 언어를 사용하는 경우 정의 할 수 있습니다.
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
그런 다음 정의 할 수 있습니다
class Door
private DoorState state
더 이상 걱정할 필요가 없습니다. 타입 시스템은 인스턴스가 존재할 수있는 상태가 3 가지에 불과하다는 것을 보장합니다 class Door
. 이것은 컴파일시 전체 클래스의 에러를 명시 적으로 배제하는 타입 시스템의 장점입니다.
문제 null
는 모든 참조 유형이 일반적으로 원치 않는 공간에서이 추가 상태를 얻는다는 것입니다. string
변수는 임의의 문자열이 될 수 있거나,이 미친 추가 될 수 있습니다 null
내 문제 영역에 매핑되지 않는 값입니다. Triangle
목적은 세 가지가 Point
자신이 가지고들, X
그리고 Y
값을하지만, 불행히도 Point
의 또는 Triangle
자체 수도 내가에서 일하고 있어요 그래프 도메인에 의미가이 미친 널 (null) 값을합니다. 등
존재하지 않는 값을 모델링하려는 경우 명시 적으로 선택해야합니다. 내가 사람들을 모델링하려는 방식으로 모든 사람 Person
이 a FirstName
와 a LastName
을 가지고 있지만 일부 사람들 만 MiddleName
s를 가지고 있다면
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
여기서 string
널이 아닌 유형으로 가정합니다. 그런 다음 NullReferenceException
누군가의 이름의 길이를 계산할 때 까다로운 불변이 없으며 예기치 않은 일이 없습니다 . 타입 시스템은 코드 MiddleName
의 가능성을 설명 하는 계정을 다루는 코드를 보장하는 None
반면, 코드를 다루는 코드 FirstName
는 값이 있다고 가정 할 수 있습니다.
예를 들어 위의 유형을 사용하여이 바보 같은 함수를 작성할 수 있습니다.
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
걱정할 필요가 없습니다. 대조적으로, 문자열과 같은 유형에 대한 nullable 참조가있는 언어에서는
class Person
private string FirstName
private string MiddleName
private string LastName
당신은 같은 물건을 작성 결국
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
들어오는 Person 객체에 null이 아닌 모든 것이 변하지 않으면 폭발하거나
let TotalNumCharsInPersonsName(p:Person) =
(if p.FirstName=null then 0 else p.FirstName.Length)
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ (if p.LastName=null then 0 else p.LastName.Length)
아니면
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
p
첫 번째 / 마지막이 있지만 중간이 null 일 수 있다고 가정 하거나 다른 유형의 예외를 던지는 검사 또는 누가 무엇을 알고 있는지 확인하십시오. 당신이 원하지 않거나 필요로하지 않는이 어리석은 표현 가능한 값이 있기 때문에이 미친 구현 선택과 생각할 것들.
널은 일반적으로 불필요한 복잡성을 추가합니다. 복잡성은 모든 소프트웨어의 적이므로 합리적 일 때마다 복잡성을 줄이려고 노력해야합니다.
(주에도 다음의 간단한 예를 더 복잡가 잘있다. A는해도 FirstName
할 수 없다 null
, A가 string
나타낼 수 ""
도 아마 우리가 모델로하고자하는 사람의 이름이 아닙니다 (빈 문자열). 이와 같이,도 비와 함께 nullable 문자열 인 경우에도 "무의미한 값을 나타내는"경우 일 수 있습니다. 다시, 런타임시 변하지 않는 코드와 조건부 코드를 통해 또는 유형 시스템 (예 : NonEmptyString
유형) 을 사용하여이 문제를 해결하도록 선택할 수 있습니다 . 후자는 ( "좋은"유형의 일반적인 작업의 집합을 통해 종종 "폐쇄"이며, 예를 들면 아마 경솔한 인 NonEmptyString
이상 닫혀 있지 않습니다.SubString(0,0)
)이지만 디자인 공간에서 더 많은 포인트를 보여줍니다. 하루가 끝날 때, 어떤 주어진 유형의 시스템에서는 제거하기에 매우 좋은 복잡성과 제거하기가 본질적으로 더 어려운 다른 복잡성이 있습니다. 이 주제의 핵심은 거의 모든 유형 시스템에서 "기본적으로 널 입력 가능 참조"에서 "기본적으로 널 입력 불가능 참조"로 변경되는 것은 거의 항상 간단한 변경으로, 유형 시스템이 복잡성과의 싸움에서 훨씬 더 우수하다는 것입니다. 특정 유형의 오류와 무의미한 상태를 배제합니다. 너무 많은 언어 가이 오류를 반복해서 반복한다는 것은 정말 미친 일입니다.)