два типа возвращаемых функцией
От: 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'ов просто не может быть" — достигается только железной дисциплиной и прочими драконовскими мерами.

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