два типа возвращаемых функцией
От: TheOldMan  
Дата: 26.04.11 14:21
Оценка:
Всем привет!

(язык программирования -- PHP)

Наткнулся вот на метод с именем getTopBarResponse(). Метод может возвратить объект типа TopBarResponse (наследуется от AbstractResponse) или же null.

Я считаю правильным, сделать так, что функция будет отдавать именно то, что ожидаем от нее, именно то, что обещает ёё имя: TopBarResponse. И поэтому считаю правильным возвращать вместо null, экземпляр класса NullTopBarResponse (или NullResponse), который наследуется от TopBarResponse (AbstractResponse). NullTopBarResponse — реализируем по принципу Null Object Pattern.

Иначе, у нас получится, что метод может возвращать данные сразу двух типов (NB: в PHP, null -- отдельный тип данных). Что ведет к тому, что объект получается менее целостным, а в ходе использования нужно еще делать дополнительные проверки.

И хочу спросить (удостовериться): правильно я рассуждаю или нет? Хорошо ли активно пользоваться тем же принципом, который лежит в основе null object pattern'a, и по пути минимизировать получение каких-либо null'ов?
суть в простоте, а простота в сути
Re: два типа возвращаемых функцией
От: LeonidV Ниоткуда http://vygovskiy.com
Дата: 26.04.11 14:33
Оценка:
Здравствуйте, TheOldMan, Вы писали:

TOM>Хорошо ли активно пользоваться тем же принципом, который лежит в основе null object pattern'a, и по пути минимизировать получение каких-либо null'ов?

Ну, я также стараюсь делать.
http://jvmmemory.com — простой способ настройки JVM
Re: два типа возвращаемых функцией
От: Lloyd Россия  
Дата: 26.04.11 14:43
Оценка: +1
Здравствуйте, TheOldMan, Вы писали:

TOM>Иначе, у нас получится, что метод может возвращать данные сразу двух типов (NB: в PHP, null -- отдельный тип данных). Что ведет к тому, что объект получается менее целостным,


Что вы имеете в виду под "целостностью"? И какой именно объект?

TOM>а в ходе использования нужно еще делать дополнительные проверки.


Главное, чтобы вы потом не стали везде пихать проверки на тип.

TOM>И хочу спросить (удостовериться): правильно я рассуждаю или нет? Хорошо ли активно пользоваться тем же принципом, который лежит в основе null object pattern'a, и по пути минимизировать получение каких-либо null'ов?


Зависит от способа использования возвращаемого значения.
Re[2]: два типа возвращаемых функцией
От: TheOldMan  
Дата: 26.04.11 15:06
Оценка:
Здравствуйте, Lloyd, Вы писали:

L>Что вы имеете в виду под "целостностью"? И какой именно объект?


Впервую очередь метод получается менее целостным, менее строгим по отношению к тому, что он может возвратить. А уж затем, как следствие, объект.

Целостность представляю, как уровень конкретности реализации некоторой идеи. Чем конкретнее реализация (например, меньшее количество вариантов входных и выходных данных и/или типов), тем плотнее целостность (реализация идеи получается менее размытой).

L>Главное, чтобы вы потом не стали везде пихать проверки на тип.


В том числе проверки на null?

L>Зависит от способа использования возвращаемого значения.


Укажите, пожалуйста, в каких случаях хорошо возвращать null, а в каких лучше избежать?
суть в простоте, а простота в сути
Re[3]: два типа возвращаемых функцией
От: Lloyd Россия  
Дата: 26.04.11 15:14
Оценка:
Здравствуйте, TheOldMan, Вы писали:

L>>Главное, чтобы вы потом не стали везде пихать проверки на тип.


TOM>В том числе проверки на null?


Не понял, что вы хотели спросить.

L>>Зависит от способа использования возвращаемого значения.


TOM>Укажите, пожалуйста, в каких случаях хорошо возвращать null, а в каких лучше избежать?


Первый пример, который приходит в голову — если метод ищет данные в базе и использует возвращаемый null чтобы показать, что данные не найдены. Если код-потребитель специфическим образом обрабатывает это возвращаемое значение, то NullObject не дает абсолютно ничего.
Re[4]: два типа возвращаемых функцией
От: TheOldMan  
Дата: 26.04.11 15:25
Оценка:
Здравствуйте, Lloyd, Вы писали:

L>Здравствуйте, TheOldMan, Вы писали:


L>>>Главное, чтобы вы потом не стали везде пихать проверки на тип.


TOM>>В том числе проверки на null?


L>Не понял, что вы хотели спросить.


Правильно будет, если дополню ваш ответ: "Главное, чтобы вы потом не стали везде пихать проверки на тип и/или null. "?
суть в простоте, а простота в сути
Re[5]: два типа возвращаемых функцией
От: Lloyd Россия  
Дата: 26.04.11 15:33
Оценка: 4 (1)
Здравствуйте, TheOldMan, Вы писали:

L>>Не понял, что вы хотели спросить.


TOM>Правильно будет, если дополню ваш ответ:


Вы его не дополнили, а свели к какой-то нелепице.

TOM>"Главное, чтобы вы потом не стали везде пихать проверки на тип и/или null. "?


Зачем нужны проверки на Null при использовании NullObject-а?
Re[6]: два типа возвращаемых функцией
От: TheOldMan  
Дата: 26.04.11 15:40
Оценка:
Здравствуйте, Lloyd, Вы писали:

TOM>>Правильно будет, если дополню ваш ответ:


L>Вы его не дополнили, а свели к какой-то нелепице.


TOM>>"Главное, чтобы вы потом не стали везде пихать проверки на тип и/или null. "?


L>Зачем нужны проверки на Null при использовании NullObject-а?


Сначала ответ ваш неправильно понял. Теперь всё ок.

Спасибо!
суть в простоте, а простота в сути
Re: два типа возвращаемых функцией
От: Undying Россия  
Дата: 26.04.11 16:47
Оценка:
Здравствуйте, TheOldMan, Вы писали:

TOM>Я считаю правильным, сделать так, что функция будет отдавать именно то, что ожидаем от нее, именно то, что обещает ёё имя: TopBarResponse. И поэтому считаю правильным возвращать вместо null, экземпляр класса NullTopBarResponse (или NullResponse), который наследуется от TopBarResponse (AbstractResponse). NullTopBarResponse — реализируем по принципу Null Object Pattern.


А что дает Null Object Pattern? Все равно же, как правило, нужно проверять вернули тебе реальный объект или заглушку.
Re[2]: два типа возвращаемых функцией
От: Ziaw Россия  
Дата: 26.04.11 17:39
Оценка:
Здравствуйте, Undying, Вы писали:

U>А что дает Null Object Pattern? Все равно же, как правило, нужно проверять вернули тебе реальный объект или заглушку.


Паттерн как раз для тех сценариев где не надо проверять. Например для строк как NullObject хорошо подходит String.Empty, применяется очень регулярно. Очень часто возврат пустой строки, предпочтительнее возварта null. Для коллекций — пустая коллекция.

В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.
Re[3]: два типа возвращаемых функцией
От: NotGonnaGetUs  
Дата: 26.04.11 19:06
Оценка: 90 (4) +2
Здравствуйте, Ziaw, Вы писали:

Z>Паттерн как раз для тех сценариев где не надо проверять. Например для строк как NullObject хорошо подходит String.Empty, применяется очень регулярно. Очень часто возврат пустой строки, предпочтительнее возварта null. Для коллекций — пустая коллекция.


Z>В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.



А в реальности область применения ещё уже, чем кажется.

"+": в ряде случаев можно не писать проверку на null.
"-": в куче случаев обычная проверка на null превращается в монстров
( o instanceof NullBlaBla или o == BlaBla.NULL или BlaBla.isNull() или ... — и в каждом конкретном случае нужно залезть по глубже, чтобы узнать как правильно...)
"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).
"-": ошибки программиста становится труднее выявлять (н-р, NPE явно рапортует проблему в точке возникновения, NullBlaBla проскочивший на удачу пару-тройку методов может вдруг оказаться в бд или улететь в космическое пространство по сети. Искать концы будет трудно...)
"-": код из разряда "где проверку писать не надо" должен быть гарантированно вызван из кода, который не передаст туда null — а это опять ручная работа по анализу стеков вызовов (отчасти проблему решают всякие инструменты на базе аннотаций, но только от части...).
"--": все выше перечисленные минусы можно умножить на два, т.к. команда разработчиков это не один человек с железной дисциплиной, а много и совсем без последней.

И тем не менее, да, есть случаи, где использование этого паттерна упрощает код (н-р, в кишках описывающих состояние какого-нибудь объекта).
Однако, мне не кажется, что это повод безустали пропагандировать NullObject.
Re[4]: два типа возвращаемых функцией
От: LeonidV Ниоткуда http://vygovskiy.com
Дата: 27.04.11 04:25
Оценка:
Здравствуйте, NotGonnaGetUs, Вы писали:

NGG>Здравствуйте, Ziaw, Вы писали:


Z>>Паттерн как раз для тех сценариев где не надо проверять. Например для строк как NullObject хорошо подходит String.Empty, применяется очень регулярно. Очень часто возврат пустой строки, предпочтительнее возварта null. Для коллекций — пустая коллекция.


Z>>В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.



NGG>А в реальности область применения ещё уже, чем кажется.


NGG>"+": в ряде случаев можно не писать проверку на null.

Суть в том, чтобы null'ов или вообще не было в программе, или они были изолированы в DAL (там null возвращается из БД).

NGG>"-": в куче случаев обычная проверка на null превращается в монстров

NGG> ( o instanceof NullBlaBla или o == BlaBla.NULL или BlaBla.isNull() или ... — и в каждом конкретном случае нужно залезть по глубже, чтобы узнать как правильно...)
Если все правильно сделать, то проверять на null или nullobject не потребуется никогда. У нас в коде таких проверок практически нет.

NGG>"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).

Приведите, пожалуйста, пример.

Собственно, все остальные минусы выходят из предпосылки возможного null в программе. Если считается, что null'ов просто не может быть, то и минусов не будет.
http://jvmmemory.com — простой способ настройки JVM
Re[4]: два типа возвращаемых функцией
От: Ziaw Россия  
Дата: 27.04.11 04:42
Оценка: 1 (1)
Здравствуйте, NotGonnaGetUs, Вы писали:

Z>>Паттерн как раз для тех сценариев где не надо проверять. Например для строк как NullObject хорошо подходит String.Empty, применяется очень регулярно. Очень часто возврат пустой строки, предпочтительнее возварта null. Для коллекций — пустая коллекция.


Z>>В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.


NGG>А в реальности область применения ещё уже, чем кажется.


NGG>"+": в ряде случаев можно не писать проверку на null.


NGG>"-": в куче случаев обычная проверка на null превращается в монстров

NGG> ( o instanceof NullBlaBla или o == BlaBla.NULL или BlaBla.isNull() или ... — и в каждом конкретном случае нужно залезть по глубже, чтобы узнать как правильно...)

Это решается одним соглашением. Не надо каждого для каждого конкретного случая придумывать свою реализацию. А обычных проверок на null должно остаться очень мало.

NGG>"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).


Отсюда у вас и проблемы. NullObject должен вести себя как любой другой объект этого типа, это и есть LSP.

NGG>"-": ошибки программиста становится труднее выявлять (н-р, NPE явно рапортует проблему в точке возникновения, NullBlaBla проскочивший на удачу пару-тройку методов может вдруг оказаться в бд или улететь в космическое пространство по сети. Искать концы будет трудно...)


Если он вдруг(!) оказывается в БД, значит скорее всего ему там и место.

NGG>"-": код из разряда "где проверку писать не надо" должен быть гарантированно вызван из кода, который не передаст туда null — а это опять ручная работа по анализу стеков вызовов (отчасти проблему решают всякие инструменты на базе аннотаций, но только от части...).


А не надо использовать null вообще как значение для этого типа. Возникнуть он может в очень узком классе мест, их легко локализовать.

NGG>Однако, мне не кажется, что это повод безустали пропагандировать NullObject.


Зачем его пропагандировать? Это просто один из паттернов. И как любой паттерн применяется там, где в нем есть необходимость, какая тут может быть пропаганда?
Re[5]: два типа возвращаемых функцией
От: Miroff Россия  
Дата: 27.04.11 04:55
Оценка:
Здравствуйте, LeonidV, Вы писали:

NGG>>"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).

LV>Приведите, пожалуйста, пример.

public Dog findMyDog() {
    return Dog.NULL_DOG;
}

Dog rex = findMyDog();

rex.bark() //<--


Вопрос, что должен делать метод bark у NullDog? Лаять он не может, потому что на самом деле это не собака. Соответственно, LSP нарушен.
Re[6]: два типа возвращаемых функцией
От: Sorc17 Россия  
Дата: 27.04.11 08:59
Оценка:
Здравствуйте, Miroff, Вы писали:

M>
M>public Dog findMyDog() {
M>    return Dog.NULL_DOG;
M>}

M>Dog rex = findMyDog();

M>rex.bark() //<--
M>


M>Вопрос, что должен делать метод bark у NullDog?


Ничего / Вывести сообщение в лог что пустая собака лаять не умеет. По-моему не плохо.
Для нас [Thompson, Rob Pike, Robert Griesemer] это было просто исследование. Мы собрались вместе и решили, что ненавидим C++ [смех].
Re[3]: два типа возвращаемых функцией
От: Undying Россия  
Дата: 27.04.11 10:24
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Паттерн как раз для тех сценариев где не надо проверять. Например для строк как NullObject хорошо подходит String.Empty, применяется очень регулярно. Очень часто возврат пустой строки, предпочтительнее возварта null. Для коллекций — пустая коллекция.


А кроме коллекций (string это по сути тоже коллекция) можно примеры привести, где выполняется следующее?

Z>В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.
Re[7]: два типа возвращаемых функцией
От: blackhearted Украина  
Дата: 27.04.11 10:27
Оценка:
Здравствуйте, Sorc17, Вы писали:

S>Здравствуйте, Miroff, Вы писали:


M>>
M>>public Dog findMyDog() {
M>>    return Dog.NULL_DOG;
M>>}

M>>Dog rex = findMyDog();

M>>rex.bark() //<--
M>>


M>>Вопрос, что должен делать метод bark у NullDog?


S>Ничего / Вывести сообщение в лог что пустая собака лаять не умеет. По-моему не плохо.


А может исключение? А вдруг где-то пропустить следующие шаги?
Вы предлагаете перенести логику обработки в NullObj ?
Re[5]: два типа возвращаемых функцией
От: NotGonnaGetUs  
Дата: 27.04.11 12:27
Оценка: 10 (2) +1
Здравствуйте, Ziaw, Вы писали:


NGG>>"-": в куче случаев обычная проверка на null превращается в монстров

NGG>> ( o instanceof NullBlaBla или o == BlaBla.NULL или BlaBla.isNull() или ... — и в каждом конкретном случае нужно залезть по глубже, чтобы узнать как правильно...)

Z>Это решается одним соглашением. Не надо каждого для каждого конкретного случая придумывать свою реализацию. А обычных проверок на null должно остаться очень мало.


1) См. пункт про дисциплину. В реальности соглашениям следуют только в том случае, если они действительно упрощают жизнь.

2) Из перечисленных вариантов нельзя выбрать один "правильный".
— BlaBla.NULL не дружит с наследованием от BlaBla, зато довольно эффективен с точки зрения производительности.
— o instanceof NullBlaBla — решает (ли ?) проблему с наследованием, но медленный и трудно набираемый (да, программисты ленивые)
— BlaBla.isNull() — избавляет от необходимости писать сложные конструкции проверки, краток, но... костыль он и есть костыль.

Положа руку на сердце: если возникает необходимость узнать является объект нулевым или нет, идиома o != null/ o == null
явно в выйгрыше: нет проблем с наследованием, не нужно ничего изучать (эту идиому знает КАЖДЫЙ), что написано то и делается.

Естественно возникает вопрос: а есть ли нужда в таких проверках?

Достаточно посмотреть на языки, где понятия null просто нет. И сразу увидим, что там используется идиома MayBe (или Option), если функция/метод может вернуть, а может и не вернуть значение.
Заметьте — type (MayBe a) != type (a) и
система типов ФОРСИРУЕТ необходимость проверки было ли возвращено реальное значение или нет.
И если какая-то ветка не была обработана будет исключение времени выполнения. Т.е. тот же NPE по сути.

Следовательно мечта ВООБЩЕ не иметь в коде проверок на особые значения — довольна наивна.



NGG>>"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).


Z>Отсюда у вас и проблемы. NullObject должен вести себя как любой другой объект этого типа, это и есть LSP.


У меня отсюда никаких проблем

"Должен" вести, хорошо. Обычное наследование — тоже, но почему-то практика показывает несколько иную картину.

Подвох номер один в том, что null может иметь разную смысловую нагрузку:
— данных нет
— данные не известны

Значит обработка этих случаев в общем случае должна быть различной.
Обычно вариант обработки выводится из контекста и факта получения null.

Как вы этого добъётесь, если у вас вместо null всегда NullBlaBla и делать проверку на null вы принципиально не желаете?
(кстати, а NullBlaBla.equals(NullBlaBla) должен возвращать тру или фалсе? — рекомендую обратиться к дейту и его мыслям относительно null-значениях в базах данных).

Н-р,
// fragment 1
   BlaBla b = ...
   if (b == null) 
   {
      ... show user dialog ...
   }
   else 
   {
       b.foo();
   }

// fragment 2
   BlaBla b = ...
   if (b == null) 
   {
      ... do nothing, it's ok in this context ...
   }
   else 
   {
      b.foo();
   }


Что должно делать NullBlaBla.foo(), что бы удовлетворить всех желающих?
Придётся ввести контекст (fragment1/fragment2) в качестве параметра в foo(), т.е. поменять интерфейс BlaBla...
Беее... и так для каждой проверки на null в коде.
... Или вынести зависимость от контекста в блоки "b = ...", но тогда у нас появится NullBlaBla1 и NullBlaBla2.
Столько мороки и не очевидностей в коде из-за желания не делать проверку на null — стоит ли оно того?


Подвох номер два:
// fragment 1
  Order o = ...
  if (o == null || o.isBuy()) {
     doBuy();
  } else {
     doSell();
  }

// fragment 2
  Order o = ...
  if (o == null || !o.isBuy()) {
     doSell();
  } else {
     doBuy();
  }

Э... NullOrder.isBuy() должен возвращать true или false, чтобы оба фрагмента продолжили корректно работать?
Суть в том, что значение isBuy() не определено для NullOrder, а поэтому никакое дефолтное значение не будет правильным в общем случае.


Z>Если он вдруг(!) оказывается в БД, значит скорее всего ему там и место.


Если бы программисты вдруг(!) не допускали ошибок

Z>А не надо использовать null вообще как значение для этого типа. Возникнуть он может в очень узком классе мест, их легко локализовать.


Ну, если держать под рукой розги и тратить рабочий день на ревью кода... Может быть и получится.

Z>Зачем его пропагандировать? Это просто один из паттернов. И как любой паттерн применяется там, где в нем есть необходимость, какая тут может быть пропаганда?


Вот и я думаю, зачем? Иногда полезен, чаще — нет.

В тоже время в этом обсуждении звучат такие лозунги:
"Суть в том, чтобы null'ов или вообще не было в программе, или они были изолированы в DAL (там null возвращается из БД)."
Re[8]: два типа возвращаемых функцией
От: Sorc17 Россия  
Дата: 27.04.11 12:31
Оценка:
Здравствуйте, blackhearted, Вы писали:

M>>>Вопрос, что должен делать метод bark у NullDog?


S>>Ничего / Вывести сообщение в лог что пустая собака лаять не умеет. По-моему не плохо.


B>А может исключение? А вдруг где-то пропустить следующие шаги?

B>Вы предлагаете перенести логику обработки в NullObj?

Я сроду не реализовывал NullObjекты, так что не знаю как там правильно надо делать. Вроде ничего плохого, если будет как-то так

class NullDog ... {
    @override
    void bark() {
        throw new DogException("Собаки не существует.");
    }
}
Для нас [Thompson, Rob Pike, Robert Griesemer] это было просто исследование. Мы собрались вместе и решили, что ненавидим C++ [смех].
Re[5]: два типа возвращаемых функцией
От: NotGonnaGetUs  
Дата: 27.04.11 12:48
Оценка:
Здравствуйте, LeonidV, Вы писали:

LV>Если все правильно сделать, то проверять на null или nullobject не потребуется никогда. У нас в коде таких проверок практически нет.


Таки делаете что-то не правильно?


NGG>>"-": в большинстве случаев NullBlaBla не будет удовлетворять LSP (если даже "обычное наследование" следует ему через раз ) => требуется тщательный контроль потоками данных в приложение (причём ручной).

LV>Приведите, пожалуйста, пример.

Смотри тут http://rsdn.ru/forum/design/4251145.1.aspx
Автор: NotGonnaGetUs
Дата: 27.04.11


LV>Собственно, все остальные минусы выходят из предпосылки возможного null в программе. Если считается, что null'ов просто не может быть, то и минусов не будет.


Предпосылка "возможного null в программе" — это факт в языках, где существует понятие "null" и существуют стандартные библиотеки, где им пользуются.

"считается, что null'ов просто не может быть" — достигается только железной дисциплиной и прочими драконовскими мерами.

Очевидно, моя предпосылка ближе к реальности
Re[6]: два типа возвращаемых функцией
От: LeonidV Ниоткуда http://vygovskiy.com
Дата: 27.04.11 13:31
Оценка:
Здравствуйте, Miroff, Вы писали:

M>Здравствуйте, LeonidV, Вы писали:


M>Вопрос, что должен делать метод bark у NullDog? Лаять он не может, потому что на самом деле это не собака. Соответственно, LSP нарушен.

Почему не собака? NullDog это собака, которая лает "ничем", то есть молча. Брость исключение, ясное дело, нельзя — тут сразу нарушается контракт и смысл NullObject теряется.
http://jvmmemory.com — простой способ настройки JVM
Re[6]: два типа возвращаемых функцией
От: LeonidV Ниоткуда http://vygovskiy.com
Дата: 27.04.11 13:51
Оценка:
Здравствуйте, NotGonnaGetUs, Вы писали:

NGG>1) См. пункт про дисциплину. В реальности соглашениям следуют только в том случае, если они действительно упрощают жизнь.

Возможность не задумываться, вернется тебе null или нет, придет к тебе null или нет действительно упрощает жизнь.

NGG>Следовательно мечта ВООБЩЕ не иметь в коде проверок на особые значения — довольна наивна.

Есть такое. Но проверк можно делать через map.contains(key) : Boolean. Понятно, если contains делает запрос к БД, так лучше не делать и проще обработать null.

NGG>Подвох номер один в том, что null может иметь разную смысловую нагрузку:

NGG>- данных нет
NGG>- данные не известны

Я так понимаю, тут различие в том что пользователь при заполнение анкеты в графе "судимости:
— ставить нет // сохраняем false
— ничего не ставит //сохраняем true.

Тогда можно из БД считать null, а в модель поместить признак "судимостиУказаны: Boolean". Сам список судимостей будет, соответственно пустым. Если нужно просто вывести все анкеты — все работает отлично. Если, например, нужно получить статистику — запрашиваем признак. А если без nullobject, то проверку на null надо будет делать и при выводе.

Вопросы, а что делать с nullobject для всей анекты нужно рассматривать отдельно, мне контекст использования не придумать.


NGG>Положа руку на сердце: если возникает необходимость узнать является объект нулевым или нет, идиома o != null/ o == null

NGG>явно в выйгрыше: нет проблем с наследованием, не нужно ничего изучать (эту идиому знает КАЖДЫЙ), что написано то и делается.
Проверка на null не показывает: нет данных или данные не известны.
http://jvmmemory.com — простой способ настройки JVM
Re[6]: два типа возвращаемых функцией
От: LeonidV Ниоткуда http://vygovskiy.com
Дата: 27.04.11 13:52
Оценка:
Здравствуйте, NotGonnaGetUs, Вы писали:

NGG>1) См. пункт про дисциплину. В реальности соглашениям следуют только в том случае, если они действительно упрощают жизнь.

Возможность не задумываться, вернется тебе null или нет, придет к тебе null или нет действительно упрощает жизнь.

NGG>Следовательно мечта ВООБЩЕ не иметь в коде проверок на особые значения — довольна наивна.

Есть такое. Но проверк можно делать через map.contains(key) : Boolean. Понятно, если contains делает запрос к БД, так лучше не делать и проще обработать null.

NGG>Подвох номер один в том, что null может иметь разную смысловую нагрузку:

NGG>- данных нет
NGG>- данные не известны

Я так понимаю, тут различие в том что пользователь при заполнение анкеты в графе "судимости:
— ставить нет // сохраняем false
— ничего не ставит //сохраняем null.

Тогда можно из БД считать null, а в модель поместить признак "судимостиУказаны: Boolean". Сам список судимостей будет, соответственно пустым. Если нужно просто вывести все анкеты — все работает отлично. Если, например, нужно получить статистику — запрашиваем признак. А если без nullobject, то проверку на null надо будет делать и при выводе.

Вопросы, а что делать с nullobject для всей анекты нужно рассматривать отдельно, мне контекст использования не придумать.


NGG>Положа руку на сердце: если возникает необходимость узнать является объект нулевым или нет, идиома o != null/ o == null

NGG>явно в выйгрыше: нет проблем с наследованием, не нужно ничего изучать (эту идиому знает КАЖДЫЙ), что написано то и делается.
Проверка на null не показывает: нет данных или данные не известны.
http://jvmmemory.com — простой способ настройки JVM
Re[6]: два типа возвращаемых функцией
От: Ziaw Россия  
Дата: 27.04.11 19:46
Оценка:
Здравствуйте, NotGonnaGetUs, Вы писали:

NGG>>>"-": в куче случаев обычная проверка на null превращается в монстров

NGG>>> ( o instanceof NullBlaBla или o == BlaBla.NULL или BlaBla.isNull() или ... — и в каждом конкретном случае нужно залезть по глубже, чтобы узнать как правильно...)

Z>>Это решается одним соглашением. Не надо каждого для каждого конкретного случая придумывать свою реализацию. А обычных проверок на null должно остаться очень мало.


NGG>1) См. пункт про дисциплину. В реальности соглашениям следуют только в том случае, если они действительно упрощают жизнь.


ОМГ, этож какая дисциплина должна быть, если команда не может следовать единму стилю в паттерне null object . Может стоит запретить все сложнее if и goto?

NGG>2) Из перечисленных вариантов нельзя выбрать один "правильный".

NGG> — BlaBla.NULL не дружит с наследованием от BlaBla, зато довольно эффективен с точки зрения производительности.
NGG> — o instanceof NullBlaBla — решает (ли ?) проблему с наследованием, но медленный и трудно набираемый (да, программисты ленивые)
NGG> — BlaBla.isNull() — избавляет от необходимости писать сложные конструкции проверки, краток, но... костыль он и есть костыль.

Либо третий вариант, либо первый, не пойму почему первый не дружит.

NGG>Положа руку на сердце: если возникает необходимость узнать является объект нулевым или нет, идиома o != null/ o == null

NGG>явно в выйгрыше: нет проблем с наследованием, не нужно ничего изучать (эту идиому знает КАЖДЫЙ), что написано то и делается.

Согласен.

NGG>Естественно возникает вопрос: а есть ли нужда в таких проверках?


NGG>Достаточно посмотреть на языки, где понятия null просто нет. И сразу увидим, что там используется идиома MayBe (или Option), если функция/метод может вернуть, а может и не вернуть значение.

NGG>Заметьте — type (MayBe a) != type (a) и
NGG>система типов ФОРСИРУЕТ необходимость проверки было ли возвращено реальное значение или нет.
NGG>И если какая-то ветка не была обработана будет исключение времени выполнения. Т.е. тот же NPE по сути.

Именно, поэтому там где этого нет приходится придумывать костыли вроде NO.

NGG>Следовательно мечта ВООБЩЕ не иметь в коде проверок на особые значения — довольна наивна.


Блин =) Еще раз озвучу свой поинт. Этот паттерн подходит в тех случаях, где он подходит. Там где нужны проверки — он не подходит. Простейшие случаи применения я уже описал — возвращаем пустую строку либо пустой массив вместо null.

NGG>Подвох номер один в том, что null может иметь разную смысловую нагрузку:

NGG>- данных нет
NGG>- данные не известны

NGG>Значит обработка этих случаев в общем случае должна быть различной.

NGG>Обычно вариант обработки выводится из контекста и факта получения null.

Обычно в таких случаях нафик не нужен null object. Нужна сигнализация, что данных нет (если это доставание из базы по ключу, обычно подходит банальный экзепшн).

NGG>Как вы этого добъётесь, если у вас вместо null всегда NullBlaBla и делать проверку на null вы принципиально не желаете?

NGG>(кстати, а NullBlaBla.equals(NullBlaBla) должен возвращать тру или фалсе? — рекомендую обратиться к дейту и его мыслям относительно null-значениях в базах данных).



NGG>Н-р,

NGG>
NGG>// fragment 1
NGG>   BlaBla b = ...
NGG>   if (b == null) 
NGG>   {
NGG>      ... show user dialog ...
NGG>   }
NGG>   else 
NGG>   {
NGG>       b.foo();
NGG>   }

NGG>// fragment 2
NGG>   BlaBla b = ...
NGG>   if (b == null) 
NGG>   {
NGG>      ... do nothing, it's ok in this context ...
NGG>   }
NGG>   else 
NGG>   {
NGG>      b.foo();
NGG>   }
NGG>


NGG>Что должно делать NullBlaBla.foo(), что бы удовлетворить всех желающих?

NGG>Придётся ввести контекст (fragment1/fragment2) в качестве параметра в foo(), т.е. поменять интерфейс BlaBla...

Хотел сразу поругаться на код, но лучше просто попрошу раскрыть многоточия в инициализации. Причем так, чтобы было понятно, что и для чего берется.

NGG>Беее... и так для каждой проверки на null в коде.

NGG>... Или вынести зависимость от контекста в блоки "b = ...", но тогда у нас появится NullBlaBla1 и NullBlaBla2.
NGG>Столько мороки и не очевидностей в коде из-за желания не делать проверку на null — стоит ли оно того?

Я не очень понимаю с чем идет спор. Я не противник этих проверок (в языках без option type). Просто есть места, где их можно элегантно обойти с помощью NO. Не вижу ни одного повода не использовать этот паттерн там где он уместен.

NGG>Подвох номер два:

NGG>
NGG>// fragment 1
NGG>  Order o = ...
NGG>  if (o == null || o.isBuy()) {
NGG>     doBuy();
NGG>  } else {
NGG>     doSell();
NGG>  }

NGG>// fragment 2
NGG>  Order o = ...
NGG>  if (o == null || !o.isBuy()) {
NGG>     doSell();
NGG>  } else {
NGG>     doBuy();
NGG>  }
NGG>

NGG>Э... NullOrder.isBuy() должен возвращать true или false, чтобы оба фрагмента продолжили корректно работать?
NGG>Суть в том, что значение isBuy() не определено для NullOrder, а поэтому никакое дефолтное значение не будет правильным в общем случае.

Снова использование null в качестве сигнализации спец-состояния вместо возврата значения. Мне этот подход не нравится, но иногда он удобнее всего остального.

Z>>А не надо использовать null вообще как значение для этого типа. Возникнуть он может в очень узком классе мест, их легко локализовать.


NGG>Ну, если держать под рукой розги и тратить рабочий день на ревью кода... Может быть и получится.


Мы сейчас про розги или про то, как сделать код лучше? Это разные понятия.

Z>>Зачем его пропагандировать? Это просто один из паттернов. И как любой паттерн применяется там, где в нем есть необходимость, какая тут может быть пропаганда?


NGG>Вот и я думаю, зачем? Иногда полезен, чаще — нет.


NGG>В тоже время в этом обсуждении звучат такие лозунги:

NGG>"Суть в том, чтобы null'ов или вообще не было в программе, или они были изолированы в DAL (там null возвращается из БД)."

Это другая крайность, но если люди могут ее достичь, я могу только аплодировать.
Re[4]: два типа возвращаемых функцией
От: Ziaw Россия  
Дата: 27.04.11 19:58
Оценка:
Здравствуйте, Undying, Вы писали:

U>А кроме коллекций (string это по сути тоже коллекция) можно примеры привести, где выполняется следующее?


Z>>В том то и фишка паттерна, что проверять не надо, код и так ведет себя с ними так, как требуется.


То есть пользу для коллекций ты признаешь?

Я не буду трогать объекты предметной области (хотя Фаулер как раз для них рекомендует). Просто на каждый частный случай можно придумать сценарий, где НО покажет себя не очень. Несмотря на это, я считаю, что этих частных случаев неожиданно возникает на порядки меньше чем потенциальных NRE от применения null в качестве многоликого спец-кода возврата (ничего не нашел, пользователь не знает чего хочет). Единственный правильный нулл — поле в базе не заполнено.

В качестве примеров, кроме коллекций, могу предложить объект конфигурации (если не найден возвращается дефолтный). Логгер который ничего никуда на самом деле не пишет. Функция которая ничего не делает (реально часто использую).
Re[7]: два типа возвращаемых функцией
От: NotGonnaGetUs  
Дата: 28.04.11 08:18
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>ОМГ, этож какая дисциплина должна быть, если команда не может следовать единму стилю в паттерне null object . Может стоит запретить все сложнее if и goto?


Goto тоже нужно запретить! Только для этого тоже нужна строгая дисциплина

NGG>> — BlaBla.NULL не дружит с наследованием от BlaBla, зато довольно эффективен с точки зрения производительности.

Z>Либо третий вариант, либо первый, не пойму почему первый не дружит.

BlaBla = List.

Чем должно быть List.NULL?
Не изменяемым списком нулевой длинны? А если список изменяемый (ArrayList,LinkedList)? Пользовательский код расчитан на возможность модификации списка, а не тут то было. И т.п.


NGG>>Естественно возникает вопрос: а есть ли нужда в таких проверках?


NGG>>Достаточно посмотреть на языки, где понятия null просто нет. И сразу увидим, что там используется идиома MayBe (или Option), если функция/метод может вернуть, а может и не вернуть значение.

NGG>>Заметьте — type (MayBe a) != type (a) и
NGG>>система типов ФОРСИРУЕТ необходимость проверки было ли возвращено реальное значение или нет.
NGG>>И если какая-то ветка не была обработана будет исключение времени выполнения. Т.е. тот же NPE по сути.

Z>Именно, поэтому там где этого нет приходится придумывать костыли вроде NO.


Так эти костыли не заменяют MayBe, т.к. type NO == type O и следовательно нет никакого форсирования проверки результата/входного параметра. В тоже время от null никуда не денешься. Как минимум ошибка программиста всплывёт в том месте, где она случилась, а не через 10 уровней по стеку.

NGG>>Следовательно мечта ВООБЩЕ не иметь в коде проверок на особые значения — довольна наивна.


Z>Блин =) Еще раз озвучу свой поинт. Этот паттерн подходит в тех случаях, где он подходит. Там где нужны проверки — он не подходит. Простейшие случаи применения я уже описал — возвращаем пустую строку либо пустой массив вместо null.


Во-во

А я ещё раз озвучу мой поинт:

"А в реальности область применения ещё уже, чем кажется."

Упрощая при помощи NO частые случаи проверки на null, приходится расширять спецификацию класса (специальным значением null) и значит при работе в коде с этим классом нужно всегда задавать себе вопрос: а будет ли этот код корректно работать с таким инстансом.

Н-р, в классе Double есть спец. значения NaN/Inf. Остаётся только благодарить бога, большая часть кода с ними не вызывается

Z>Обычно в таких случаях нафик не нужен null object. Нужна сигнализация, что данных нет (если это доставание из базы по ключу, обычно подходит банальный экзепшн).


Труъ. И очень многих других


NGG>>Что должно делать NullBlaBla.foo(), что бы удовлетворить всех желающих?

NGG>>Придётся ввести контекст (fragment1/fragment2) в качестве параметра в foo(), т.е. поменять интерфейс BlaBla...

Z>Хотел сразу поругаться на код, но лучше просто попрошу раскрыть многоточия в инициализации. Причем так, чтобы было понятно, что и для чего берется.


Этот код не нужно ругать, он просто служит для иллюстрации того, что способ обработки null в разных контекстах может различаться, а поэтому установка разумного поведения методов NullObject'ов в _общем_ случае затруднена.

Другое дело private поле в классе, которое не выходит за пределы этого класса, и значит конткест его использования легко поддаётся исследованию.
В этом случае, можно попытаться упростить код используя вместо null особый инстанс класса.
Даже если этот инстанс будет нарушать LSP самым жестоким образом ничего страшного не случится (в теории).


Z>Я не очень понимаю с чем идет спор. Я не противник этих проверок (в языках без option type). Просто есть места, где их можно элегантно обойти с помощью NO. Не вижу ни одного повода не использовать этот паттерн там где он уместен.


Пример из жизни.

Был класс с массивом внутри, который то и дело проверялся на null.
Массив был в основном только на чтение, поэтому замена массива на пустой массив (вместо null) вполне была разумна.
Разработчик (из лучших побуждений) это сделал, но в куске кода, который изменял значение этого поля проверку на null не поставил (а все проверки на null при чтении, естественно, убрал).

В итоге NO призванный избавить от NPE, только послужил его причиной — парадокс
+ времени на ругань было потрачено больше, чем потроебовалось бы на написание 256 проверок на null.

NGG>>Ну, если держать под рукой розги и тратить рабочий день на ревью кода... Может быть и получится.


Z>Мы сейчас про розги или про то, как сделать код лучше? Это разные понятия.


У "лучше" тоже много значений. Если "лучше" предполагает наличие разработчиков уровня "выше среднего", то это "лучше" уже под вопросом.


NGG>>В тоже время в этом обсуждении звучат такие лозунги:

NGG>>"Суть в том, чтобы null'ов или вообще не было в программе, или они были изолированы в DAL (там null возвращается из БД)."

Z>Это другая крайность, но если люди могут ее достичь, я могу только аплодировать.


Главное, что бы затраты ресурсов на достижение этой цели не оказались слишком высоки.
Re[5]: два типа возвращаемых функцией
От: Undying Россия  
Дата: 28.04.11 11:33
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>То есть пользу для коллекций ты признаешь?


Да. Но топикстартер, насколько я понял, говорил об использовании NullablePattern'а в любых случаях.

NullablePattern хорош, когда не приводит к появлению новых сущностей в виде дополнительных особых значений. Например, любые операции над пустой строкой возвращают значения, которые можно получить и работая с непустой строкой, поэтому пустая строка это удачная замена null'а. В твоем примере с логгером, у логгера скорей всего вообще нет видимого состояния/результата, поэтому опять же NullablePattern может быть успешно применен.

Если же новая сущность появляется, например, у объекта есть некий идентификатор, который у NullableObject требуется выставлять в какое-то особое значение, то NullablePattern резко ухудшит качество кода. Т.к. на это специальное значение все равно придется делать проверки (пусть не везде), а такая проверка неочевидна, нестандартна и не контролируется рантаймом в отличии от проверки на банальный null.

Z>Я не буду трогать объекты предметной области (хотя Фаулер как раз для них рекомендует).


Как правило попытка использовать NullablePattern для бизнес-объектов приводит к появлению особых значений, поэтому Фаулер дает вредные советы.
Re[9]: два типа возвращаемых функцией
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.05.11 15:12
Оценка:
Здравствуйте, Sorc17, Вы писали:

S>
S>class NullDog ... {
S>    @override
S>    void bark() {
S>        throw new DogException("Собаки не существует.");
S>    }
S>}
S>

И чем это принципиально отличается от null? На всякий случай напомню, что null.bark() всегда выкинет нам NRE — полный аналог сообщения "собаки не существует". Вот только он делает это бесплатно — не нужно реализовывать этот bark вручную.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: два типа возвращаемых функцией
От: -VaS- Россия vaskir.blogspot.com
Дата: 16.05.11 17:47
Оценка:
TOM>И хочу спросить (удостовериться): правильно я рассуждаю или нет? Хорошо ли активно пользоваться тем же принципом, который лежит в основе null object pattern'a, и по пути минимизировать получение каких-либо null'ов?

А еще можно сокращать количество query-методов — проблема возвращения null исчезает сама собой.
Re[2]: два типа возвращаемых функцией
От: TheOldMan  
Дата: 17.05.11 00:24
Оценка:
Здравствуйте, -VaS-, Вы писали:

VS>А еще можно сокращать количество query-методов — проблема возвращения null исчезает сама собой.


Query-методы — это методы, которые возвращают какой-либо результат? Просто передаем методам объекты и методы модифицируют их?
суть в простоте, а простота в сути
Re[3]: два типа возвращаемых функцией
От: -VaS- Россия vaskir.blogspot.com
Дата: 19.05.11 08:44
Оценка:
TOM>Query-методы — это методы, которые возвращают какой-либо результат?

Да.

TOM>Просто передаем методам объекты и методы модифицируют их?


Можно и так, только главное не скатиться к фактической эмуляции query (типа void GetOrders(IList<Order> result))

Главное — не вытягивать из объекта данные (ask), а говорить ему (tell), что нужно сделать. Конечно, это в основном применимо только для бизнес-логики.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.