2009-03-13 8 views
7

Я постоянно слышу, как люди говорят о том, как ненулевые ссылочные типы решат так много ошибок и облегчают программирование. Даже создатель null называет его своим billion dollar mistake, а Spec# вводит типы, не допускающие нулевых значений, для борьбы с этой проблемой.Об обсуждениях, не связанных с нулевыми типами

EDIT: Игнорировать мой комментарий о SpeC#. Я неправильно понял, как это работает.

EDIT 2: я должен говорить с теми людьми, я действительно надеялся, для кого-то спорить с :-)


Так что я бы предположил, будучи в меньшинстве, что я ошибаюсь, но я не могу понять, почему в этой дискуссии есть какие-то заслуги. Я вижу нуль как инструмент поиска ошибок. Учитывайте следующее:

class Class { ... } 

void main() { 
    Class c = nullptr; 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

BAM! Нарушение доступа. Кто-то забыл инициализировать c.


Теперь рассмотрим это:

class Class { ... } 

void main() { 
    Class c = new Class(); // set to new Class() by default 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

возгласы. Петля тихо пропущена. Это может занять некоторое время, чтобы отследить проблему.

Если ваш класс пуст, код в любом случае будет терпеть неудачу. Почему бы вам не сказать вам систему (хотя и немного грубо), вместо того, чтобы самим понять это?

+0

Приятно видеть, что другие наслаждаются пустым, я все еще в школе, поэтому я просто предполагаю, что я чего-то не хватает. –

+1

Есть более принципиальные способы обработки «нет значения». NULL исключает примитивные типы, такие как int. Лучше, чтобы система типов представляла отсутствие значения последовательно для всех типов, а не только для ссылок. См. Типы Haskell «Maybe» и ML/OCaml/F #, чтобы посмотреть, как это должно быть сделано. – MichaelGG

+0

Возможный дубликат [Лучшее объяснение для языков без нуля] (http://stackoverflow.com/questions/3989264/best-explanation-for-languages-without-null) – nawfal

ответ

-2

Мне нравится использование NULL. Прошло некоторое время с тех пор, как я работал на C++, но мне было очень легко найти мои проблемы относительно возвращаемых значений и ссылок и исправить их.

+0

Пропущает точку. –

2

Я признаю, что я не очень много читал о SpeC#, но я понял, что NonNullable по существу является атрибутом, который вы добавляете к параметру, а не обязательно к объявлению переменной; Превратите ваш пример во что-то вроде:

class Class { ... } 

void DoSomething(Class c) 
{ 
    if (c == null) return; 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

void main() { 
    Class c = nullptr; 
    // ... ... ... code ... 
    DoSomething(c); 
} 

С SpeC#, вы маркировка DoSomething сказать «параметр с не может быть пустым». Это похоже на хорошую функцию для меня, поскольку это означает, что мне не нужна первая строка в методе DoSomething() (который легко забыть и совершенно бессмыслен для контекста DoSomething()).

6

Я не понимаю ваш пример. Если ваш «= новый класс()» является просто заполнителем вместо того, чтобы не иметь нулевого значения, то это (на мой взгляд), очевидно, ошибка. Если это не так, то реальная ошибка заключается в том, что «...» не установил ее содержимое правильно, что в обоих случаях одинаково.

Исключение, показывающее, что вы забыли инициализировать c, скажет вам, в какой момент он не инициализирован, но не там, где он должен был быть инициализирован. Аналогично, пропущенный цикл (неявно) сообщает вам, где ему нужно иметь ненулевой .count, но не то, что должно было быть сделано или где. Я не вижу никого более легкого для программиста.

Я не думаю, что точка «no nulls» состоит в том, чтобы просто выполнять текстовую поиск и замену и превращать их в пустые экземпляры. Это бесполезно.Дело в том, чтобы структурировать ваш код, чтобы ваши переменные никогда не находились в состоянии, когда они указывают на бесполезные/неправильные значения, одним из которых является NULL.

+0

Так что, если язык реализует типы с нулевым значением, что делает оператор типа «Class c;» установить c в? (Я полностью согласен с вашими комментариями кстати) – zildjohn01

+2

Моя первая мысль: ее нельзя допускать. Логически не имеет смысла говорить «сделать слот, который должен хранить Foo (но оставить его пустым)».Для этого, вероятно, требуется все-это-выражение (например, Lisp или Ruby). Я не знаю, как это работает, если вам нужно выполнить последовательные инструкции для выполнения назначения. – Ken

+1

Оглядываясь назад: думаю, SQL. Если у вас есть столбец «NOT NULL» и попробуйте вставить запись, не указав для него ненулевое значение, она просто вызывает ошибку. – Ken

0

Как я вижу, есть две области, где используется null.

Первое - отсутствие значения. Например, логическое значение может быть истинным или ложным, или пользователь еще не выбрал параметр, следовательно, null. Это полезно и хорошо, но, возможно, было реализовано неправильно изначально, и теперь есть попытка формализовать это использование. (Должно ли существовать второе логическое значение для сохранения состояния set/unset или null как часть логики с тремя состояниями?)

Второй - в смысле нулевого указателя. Это чаще всего проблема с программной ошибкой, т.е. исключение. Это не намеренное состояние, есть программная ошибка. Это должно быть под предлогом формальных исключений, реализованных на современных языках. То есть, NullException захватывается через блок try/catch.

Итак, какой из них вас интересует?

+0

# 2. – zildjohn01

2

Идея непустых типов заключается в том, чтобы позволить компилятору, а не вашему клиенту находить ошибки. Предположим, вы добавили на свой язык два спецификатора типа @nullable (может быть null) и @nonnull (никогда не null) (я использую синтаксис аннотации Java).

Когда вы определяете функцию, вы аннотируете ее аргументы. Например, следующий код будет компилироваться

int f(@nullable Foo foo) { 
    if (foo == null) 
    return 0; 
    return foo.size(); 
}

Даже если Foo может быть пустым при входе, поток управления гарантирует, что, когда вы звоните foo.size(), Foo является ненулевым.

Но если вы удалите чек для null, вы получите ошибку времени компиляции.

Ниже будет компилировать тоже, потому что Foo является ненулевым на входе:

int g(@nonnull Foo foo) { 
    return foo.size(); // OK 
}

Однако, вы не будете в состоянии назвать г с обнуляемым указателем:

@nullable Foo foo; 
g(foo); // compiler error!

Компилятор делает анализ потока для каждой функции, поэтому он может обнаруживать, когда @nullable становится @nonnull (например, внутри оператора if, который проверяет значение null). Он также примет достоверное определение @nonnull при условии, что он будет немедленно инициализирован.

@nonnull Foo foo = new Foo();

Там намного больше по этой теме в my blog.

0

В настоящее время я работаю над этой темой на C#. .NET имеет Nullable для типов значений, но обратная функция не существует для ссылочных типов.

Я создал NotNullable для ссылочных типов и перенес проблему с if (не более проверок на null) в домен типа данных. Это заставляет приложение генерировать исключения во время выполнения, а не во время компиляции.

12

Его немного странно, что ответ с пометкой «ответ» в этой теме фактически выдвигает на первый план проблему с нулем в первую очередь, а именно:

Я также обнаружил, что большинство моих NULL ошибки указателя вращаются вокруг функции от забывания, чтобы проверить возврат функций string.h, где NULL используется в качестве индикатора.

Было бы неплохо, если бы компилятор мог поймать такие ошибки во время компиляции, а не во время выполнения?

Если вы использовали ML-подобный язык (SML, OCaml, SML и F # в некоторой степени) или Haskell, ссылочные типы не имеют значения NULL. Вместо этого вы представляете «нулевое» значение, обертывая его типом параметра. Таким образом, вы фактически меняете возвращаемый тип функции, если он может вернуть значение null в качестве юридического значения. Итак, допустим, что я хотел, чтобы вытащить пользователя из базы данных:

let findUser username = 
    let recordset = executeQuery("select * from users where username = @username") 
    if recordset.getCount() > 0 then 
     let user = initUser(recordset) 
     Some(user) 
    else 
     None 

Поиск пользователя имеет тип val findUser : string -> user option, поэтому тип возвращаемого значения функции на самом деле говорит вам, что он может возвращать нулевое значение. Употреблять код, вам нужно обрабатывать как некоторые и None случаев:

match findUser "Juliet Thunderwitch" with 
| Some x -> print_endline "Juliet exists in database" 
| None -> print_endline "Juliet not in database" 

Если вы не обрабатываете оба случая, код не будет даже скомпилировать. Таким образом, система типов гарантирует, что вы никогда не получите исключение с помощью NULL-ссылки, и оно гарантирует, что вы всегда обрабатываете значения null. И если функция возвращает user, ее гарантируется как фактический экземпляр объекта. Awesomeness.

Теперь мы видим проблему в образце кода OP в:

class Class { ... } 

void main() { 
    Class c = new Class(); // set to new Class() by default 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

инициализирован и неинициализированные объекты имеют один и тот же тип данных, вы не можете сказать, разница между ними. Иногда полезно использовать null object pattern, но приведенный выше код демонстрирует, что компилятор не имеет возможности определить правильность использования ваших типов.

0

Ненужные типы имеют больше смысла для меня, когда мы имеем дело с объектами домена. Когда вы сопоставляете таблицы базы данных с объектами, и у вас есть столбцы с нулевым значением. Скажем, у вас есть таблица с именем User и она имеет столбец userid varchar (20), который не имеет значения NULL;

Это было бы так удобно иметь класс User с строковым полем UserId, который не имеет значения NULL. Вы отключите некоторые ошибки во время компиляции.