Ошибка дизайна


Именно так и никак иначе: null в C# — однозначно ошибочное решение, бездумно скопированное из более ранних языков.


  1. Самое страшное: в качестве значения любого ссылочного типа может использоваться универсальный предатель — null, на которого никак не среагирует компилятор. Зато во время исполнения легко получить нож в спину — NullReferenceException. Обрабатывать это исключение бесполезно: оно означает безусловную ошибку в коде.
  2. Перец на рану: сбой (NRE при попытке разыменования) может находится очень далеко от дефекта (использование null там, где ждут полноценный объект).
  3. Упитанный пушной зверек: null неизлечим — никакие будущие нововведения в платформе и языке не избавят нас от прокаженного унаследованного кода, который физически невозможно перестать использовать.

Этот ящик Пандоры был открыт еще при создании языка ALGOL W великим Хоаром, который позднее назвал собственную идею ошибкой на миллиард долларов.


Лучшая историческая альтернатива


Разумеется, она была, причем очевидная по современным меркам


  1. Унифицированный Nullable для значимых и ссылочных типов.
  2. Разыменование Nullable только через специальные операторы (тернарный — ?:, Элвиса — ?., coalesce — ??), предусматривающие обязательную обработку обоих вариантов (наличие или отсутствие объекта) без выбрасывания исключений.
  3. Примеры:
    object o = new object(); // ссылочный тип - корректная инициализация
    object o = null; // ссылочный тип - ошибка компиляции, так как null недопустим
    object? n = new object; // nullable тип - корректная инициализация
    object? n = null; // nullable тип - корректная инициализация
    object o = n; // ссылочный тип - ошибка компиляции, типы object и object? несовместимы
    object o = n ?? new object(); // разыменование с fallback значением (coalesce), дополнительное значение будет вычислено только если n != null
    Type t = n ? value.GetType() : typeof(object); // специальный тернарный оператор - value означает значение n, если оно не null
    Type? t = n ? value.GetType(); // бинарная форма оператора ? - возвращает null, если первый операнд null, иначе вычисляет второй операнд и возвращает его, завернутого в nullable
  4. В этом случае NRE отсутствует по определению: возможность присвоить или передать null определяется типом значения, конвертация с выбросом исключения отсутствует.

Самое трагичное, что все это не было откровением и даже новинкой уже к моменту проектирования первой версии языка. Увы, тогда матерых функциональщиков в команде Хейлсберга не было.


Лекарства для текущей реальности


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


  1. Явные проверки на null в операторе if. Очень прямолинейный способ с массой серьезных недостатков.


    1. Гигантская масса шумового кода, единственное назначение которого — выбросить исключение поближе к месту предательства.
    2. Основной сценарий, загроможденный проверками, читается плохо
    3. Требуемую проверку легко пропустить или полениться написать
    4. Проверки можно добавлять отнюдь не везде (например, это нельзя сделать для автосвойств)
    5. Проверки не бесплатны во время выполнения.

  2. Атрибут NotNull. Немного упрощает использование явных проверок


    1. Позволяет использовать статический анализ
    2. Поддерживается R#
    3. Требует добавления изрядного количества скорее вредного, чем бесполезного кода: в львиной доле вариантов использования null недопустим, а значит атрибут придется добавлять буквально везде.

  3. Паттерн проектирования Null object. Очень хороший способ, но с ограниченной сферой применения.


    1. Позволяет не использовать проверок на null там, где существует эквивалент нуля в виде объекта: пустой IEnumerable, пустой массив, пустая строка, ордер с нулевой суммой и т.п. Самое впечатляющее применение — автоматическая реализация интерфейсов в мок-библиотеках.
    2. Бесполезен в остальных ситуация: как только вам потребовалось отличать в коде нулевой объект от остальных — вы имеете эквивалент null вместо null object, что является уже двойным предательством: неполноценный объект, который даже NRE не выбрасывает.

  4. Конвенция о возврате живых объектов по умолчанию. Очень просто и эффективно.


    1. Любой метод или свойство, для которых явно не заявлена возможность возвращать null, должны всегда предоставлять полноценный объект. Для поддержания достаточно выработки хорошей привычки, например, посредством ревью кода.


    2. Разработчики сторонних библиотек ничего про ваше соглашение не знают
    3. Нарушения соглашения выявить непросто.

  5. Конвенция о стандартных способах явно указать что свойство или метод может вернуть null: например, префикс Try или суффикс OrDefault в имени метода. Органичное дополнение к возврату полноценных объектов по умолчанию. Достоинства и недостатки те же.


  6. Атрибут CanBeNull. Добрый антипод-близнец атрибута NotNull.


    1. Поддерживается R#
    2. Позволяет помечать явно опасные места, вместо массовой разметки по площадям как NotNull
    3. Неудобен в случае когда null возвращается часто.

  7. Операторы C# (тернарный, Элвиса, coalesce)


    1. Позволяют элегантно и лаконично организовать проверку и обработку null значений без потери прозрачности основного сценария обработки.
    2. Практически не упрощают выброс ArgumentException при передаче null в качестве значения NotNull параметра.
    3. Покрывают лишь некоторую часть вариантов использования.
    4. Остальные недостатки те же, что и у проверок в лоб.

  8. Тип Optional. Позволяет явно поддержать отсутствие объекта.


    1. Можно полностью исключить NRE
    2. Можно гарантировать наличие обработки обоих основных вариантов на этапе компиляции.
    3. Против легаси этот вариант немного помогает, вернее, помогает немного.
    4. Во время исполнения помимо дополнительных инструкций добавляется еще и memory traffic

  9. Монада Maybe. LINQ для удобной обработки случаев как наличия, так и отсутствия объекта.


    1. Сочетает элегантность кода с полнотой покрытия вариантов использования.
    2. В сочетании с типом Optional дает кумулятивный эффект.
    3. Отладка затруднена, так как с точки зрения отладчика вся цепочка вызовов является одной строкой.
    4. Легаси по-прежнему остается ахиллесовой пятой.

  10. Программирование по контракту.


    1. В теории почти идеал, на практике все гораздо печальнее.
    2. Библиотека Code Contracts скорее мертва, чем жива.
    3. Очень сильное замедление сборки, вплоть до невозможности использовать в цикле редактирование-компиляция-отладка.

  11. Пакет Fody/NullGuard. Автоматические проверки на null на стероидах.


    1. Проверяется все: передача параметров, запись, чтение и возврат значений, даже автосвойства.
    2. Никакого оверхеда в исходном коде
    3. Никаких случайных пропусков проверок
    4. Поддержка атрибута AllowNull — с одной стороны это очень хорошо, а с другой — аналогичный атрибут у решарпера другой.
    5. С библиотеками, агрессивно использующими null, требуется довольно много ручной работы по добавлению атрибутов AllowNull
    6. Поддержка отключения проверки для отдельных классов и целых сборок
    7. Используется вплетение кода после компиляции, но время сборки растет умеренно.
    8. Сами проверки работают только во время выполнения.
    9. Гарантируется выброс исключения максимально близко к дефекту (возврату null туда, где ожидается реальный объект).
    10. Тотальность проверок помогает даже при работе с легаси, позволяя как можно быстрее обнаружить, пометить и обезвредить даже null, полученный из чужого кода.
    11. Если отсутствие объекта допустимо — NullGuard сможет помочь только при попытках передать его куда не следует.
    12. Вычистив дефекты в тестовой версии, можно собрать промышленную из тех же исходников с отключенными проверками, получив нулевую стоимость во время выполнения при гарантии сохранения всей прочей логики.

  12. Ссылочные типы без возможности присвоения null (если добавят в одну из будущих версий C#)


    1. Проверки во время компиляции.
    2. Можно полностью ликвидировать NRE в новом коде.
    3. В реальности не реализовано, надеюсь, что только пока
    4. Единообразия со значимыми типами не будет.
    5. Легаси достанет и здесь.


Итоги


Буду краток — все выводы в таблице:


Настоятельная рекомендация Антипаттерн На ваш вкус и потребности
4, 5, 7, 11, 12 (когда и если будет реализовано) 1, 2 3, 6, 8, 9, 10

На предвосхищение ООП через 20 лет не претендую, но дополнениям и критике буду очень рад.


Обновление


добавил примеры кода к утопической альтернативе.

Поделиться с друзьями
-->

Комментарии (287)


  1. Reeze
    12.09.2016 04:21

    Можно перейти на F# и… начать лепить вместо null везде Unchecked.defaultof<'T>


    1. johnnymmc
      12.09.2016 23:42

      Что, на сколько я понимаю, не избавляет от теоретической возможности появления null где-нибудь и от необходимости проверки значений на null. При программировании на Scala тоже раздражает существование null-ей при том, что есть Nothing.


      1. Sirikid
        13.09.2016 05:06

        Насколько я понял в F# это сделано как в Kotlin, т. е. чистый F# null-safe


        1. Reeze
          14.09.2016 03:28

          Да, на нём можно писать в функциональном стиле, а Unchecked добавлен для совместимости с C#.

          Интересно, что оператор isNull добавили только в версии 4.0 языка.


        1. johnnymmc
          15.09.2016 13:43

          В смысле «чистый F#»? Без внешних, написанных на C# .Net библиотек? А какой от него практический толк тогда? Спортивная информатика? Или там весь основной функционал (включая функции работы с внешними web-сервисам, файловой системой, графикой и прочим) продублирован в null-safe манере?


          1. Sirikid
            15.09.2016 18:11

            В прямом. Да. Не знаю. Может быть. Может только стандартная библиотека, опять же, в Kotlin сделали так: https://habrahabr.ru/post/309462/#comment_9801496, как сделали в F# я не знаю.


  1. S_A
    12.09.2016 05:06
    +4

    Бедный этот null. Добавьте в статью опрос, как часто вы имеете NullReferenceException? Лично вот я — не чаще раза в неделю, а то и реже, использую только ?:,?.. и ??.. Я конечно, стараюсь null не возвращать и не передавать, но это на уровне смысла методов скорее, чем претензия к языку.

    Проблема не в том что разыменование бросает исключение. Есть и другие исключения, тоже летят. Выход за границы массива например. А представьте, нету null. Тогда все как в анекдоте про буратино и яблоки. Добавлять метод IsValid? Те же проверки же. По-моему суть вопроса не в наличии маркера (отсутствия чего-либо) null, а в том что языки программирования и компиляторы различают объекты от ссылок. Переменная на то и переменная, что она может быть изменена.


    1. Sirikid
      12.09.2016 06:26
      +3

      Если нет null обычно есть что-то другое, например Maybe, гораздо хуже когда null есть, а операторов из п.7 нет.


      1. S_A
        12.09.2016 08:20
        +3

        Ну и получаем (с Maybe) тот же if null, только обернутый.

        Моя мысль скорее в том, что языки из 90-х (и их наследники) оперируют в императивном стиле, и, следовательно, есть переменные. C# не исключение, и ждать от него обязательств что у переменной всегда должен быть объект… зачем? Там выше про F# сказали. Давно уже хотел присмотреться к нему как следует, но всё времени нет. Теперь его приоритет увеличил. Может следующий проект на нём буду :) Если Xamarin F#-ready (а это вроде так).


        1. Bonart
          12.09.2016 10:13
          +3

          С Maybe у вас есть возможность требовать обязательной обработки как наличия, так и отсуствия значения, не оставляя места для ошибок в коде, ведущих к NRE.


        1. Sirikid
          12.09.2016 10:19

          if null тоже разный бывает, pattern matching (в Haskell, ML, Rust, Scala) мне кажется более изящным и интуитивным решением. Maybe без nullable типов может и должен быть, а вот наоборот гораздо сложнее.


          1. 0xd34df00d
            15.09.2016 09:30

            Монадические операции еще изящнее, ИМХО (от do до sequence).


        1. qw1
          12.09.2016 16:31
          +1

          языки из 90-х (и их наследники) оперируют в императивном стиле, и, следовательно, есть переменные
          Скорее, тут корни идут от указателей и ручного управления памятью.


          1. S_A
            13.09.2016 04:56

            Не идут. Присвоение к null не обязывает объект быть удаленным.


            1. Pakos
              13.09.2016 11:03
              +1

              Но после удаления хорошо бы присвоить null, если переменная ещё в области видимости.


            1. qw1
              13.09.2016 14:32

              Дело не в частностях. А в том, что мозги разработчиков были воспитаны на unmanaged языках, и объектную ссылку никто не воспринимал иначе, как указатель, со всеми вытекающими последствиями.

              В школе их учили Паскалю, или Аде кодом типа

              var
                p: ^TMyObject;
              begin
                New(p);
                p.DoSomething;
                Dispose(p);
              end.
              в воздухе ещё не витали концепции монад, optional-ов и т.п.


              1. S_A
                13.09.2016 15:55

                1. Задним числом все умные.
                2. Откуда инфа, кто что как воспринимал? Я вот таких обобщений не делаю.

                В этом ответвлении я уже всё сказал, и совсем в другую сторону. Язык и то, во что он компилируется далеко не только предысторией привыкших к чему-то программистов связаны. И те или иные решения в нём приняты не потому что так у всех, или все привыкли, но еще и потому что бывают и коммерческие ограничения (время, бюджет), и технические (что мог тогда .NET и что сейчас), и много всего разного. А по-вашему весь контекст ситуации — это программисты на Паскале.


    1. Bonart
      12.09.2016 10:10
      +5

      NRE раз в неделю — это ровно на один раз в неделю больше чем нужно. NRE в продакшене вообще недопустимо.
      Проблема именно в том, что разыменование любого ссылочного типа может бросить исключение.


      1. Если null в этом месте не нужен, то гораздо лучше поддерживать non-nullable типы с помощью компилятора и не иметь NRE.
      2. Если null в этом месте возможен по бизнес-логике, то гораздо лучше требовать обработку обоих случаев (наличие и отсутсвие значения) на этапе компиляции и не иметь NRE.


      1. S_A
        12.09.2016 10:57
        +1

        NRE в продакшене… Думаете я посреди ночи проснусь в холодному поту, если приснится? В разработке крупной платежной системы лет 10 назад работал, на С++ писал — и нормально с этим жили, читали дампы, а специальные демоны рестартовали сервисы. У конечного пользователя все это неприятно, но и в Java, и в Swift есть тот самый null.

        Да и вообще причем тут NRE. Объясните, почему другие исключения не удостаиваются чести иметь столько внимания и языки, в которых они не случаются? К чему такое избирательство? Все это в случае конкретного языка и конкретно одного исключения больше напоминает упражнения для ума.

        По поводу compile-time проверок. Еще раз. Переменная (ссылка) может иметь любое значение, это значит что выполняющее (программу) устройство зарезервировало под него кусок места в памяти. Вот этот кусок в памяти может указывать на что угодно, на умерший, невалидный объект, или валидный. Отказ от null в языке означал бы по факту отказ от ссылок, и превратил бы язык в фактически интерпретируемый, потому что в компайл-тайме объекты не создаются.


        1. S_A
          12.09.2016 14:01
          +1

          Раскрою немного мысль. Все ухищрения по поводу оборачивания языковых конструкций — начиная со smart_ptr из С++, это создание собственного интепретатора в языке и написание кода на нём. Дело не в оверхеде, дело в том, что если не нравится язык — лучше его сменить, чем и без того обрастаемые костылями старые языки латать. Благо сейчас много чего быстро работает.

          А что касается NRE, я их тоже не люблю, просто это вообще не самая большая проблема в моей практике.


          1. asdf87
            12.09.2016 16:41
            +3

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

            Not null reference type планировалось добавить в C#7, но потом отложили эту фичу до лучших времен (лучшие времена ожидаются с С#8, но там опять же… кто знает что будет). Вот народ и негодует, что средство для практически автоматического улучшения качества программ и упрощения разработки еще прийдется черт знает сколько ждать.


        1. areht
          12.09.2016 18:18
          +4

          > Объясните, почему другие исключения не удостаиваются чести иметь столько внимания и языки, в которых они не случаютс

          Потому, что NRE — это всегда ошибка разработчика, а какое-нибудь UnauthorizedAccessException — нет.


          1. S_A
            13.09.2016 04:46

            А какое-нибудь index out of range, или division by zero, или stack overflow? Тред не читай@на коммент отвечай?
            Любой язык будет допускать ошибки разработчика. Даже русский.

            Мне лично самому не нравится, когда в рантайме летят исключения, касающиеся языка. Очень раздражает. Это с того момента, как я с плюсов пересел на Java/C#. Хотя это намного, намного лучше, чем core dump, gdb и stack frames.

            Какой смысл предъявлять к дизайну языка претензии через 10 лет после его создания, он для своего времени и так больше чем мог сделал. Я больше по этому поводу озадачен.


            1. areht
              13.09.2016 12:44

              > index out of range

              Не знаю о чём вы, я использую foreach

              > division by zero

              Нет

              > stack overflow

              Придумаете как победить на уровне языка — дайте знать.

              > Какой смысл предъявлять к дизайну языка претензии через 10 лет после его создания

              Почему 10 лет? Язык называется «C# 6.0», в названии, кстати, и ответ на «какой смысл» заложен.


              1. iqiaqqivik
                13.09.2016 14:10

                Научи?те избегать division by zero без дополнительной проверки операнда на нуль, пожалуйста.


                1. areht
                  13.09.2016 14:53

                  А 2 инта складывать без OverflowException научить?

                  оборачиваете в try и выдаёте пользователю, что некорректные данные.


                  1. iqiaqqivik
                    13.09.2016 16:10
                    +2

                    Спасибо, поржал.

                    А NRE в `try` в шарпе запрещено оборачивать? Разница-то в чем?


                    1. areht
                      13.09.2016 17:18

                      Я же написал,NRE это ошибка разработчика. Пользователю бесполезно о ней ошибку выбрасывать. А данные в знаменателе поправить обычно может.


                    1. areht
                      13.09.2016 18:39

                      Вообще, NRE оборачивать не то, что запрещено, но довольно бессмысленно. Т.е. обернув вызов чистой функции какого-то расчёта и получив division by zero я уверен, что ошибка изолирована и приложение дальше работает корректно, а получив NRE — нет, и дальше продолжать выполнение некорректно.


                      1. qw1
                        13.09.2016 19:08

                        Не вижу никакой разницы между NRE и DBZ в случае чистой функции.
                        Какая разница, какой-то метод ошибочно вернул 0 в переменную «кол-во процессоров в системе» и дальше упало при делении, или вернул null в переменную CPUManager.


                        1. areht
                          13.09.2016 19:52

                          Разница в том, откуда приходят данные. Циферки, обычно, приходят извне (их там миллионы, косяки всегда есть). А null — это где-то присвоить забыли в своём коде.

                          Одна ошибка на миллион пользовательских данных — это нормально, нужно быть готовым обрабатывать.
                          Вернуть 0 в переменную «кол-во процессоров в системе» — это экзотика.
                          Миллион CPUManager, один из которых null — это надо талант иметь, скорее у нас память битая.
                          Все(один) CPUManager null(WTF, а это вообще кто-то тестил?) — зачем так жить?

                          Можно придумать какие-то исключения, но, в целом, DBZ — не кодерская ошибка, но «бытовая». А NRE — точно какая-то хрень, которая сама не починиться.


                      1. qadmium
                        13.09.2016 19:30

                        если бы получили DBZ из функции, значит функция написана неверено, какой смысл что-то изолировать, если у вас программа написана неверно? хоть какой-то смысл это имеет на границе плагина или чего-то такого что можно безболезненно прибить и оно при этом нам не подконтрольно


                        1. areht
                          13.09.2016 19:59

                          0) возврат DBZ — часть естественного контракта функции. Функции деления, например.
                          1) в программе кнопок больше одной, одна может не работать. Главное — побочных эффектов не настругать.
                          2) Функция может работать в рамках контракта, который мы где-то нарушили. Самое банальное — плохо проверили пользовательский ввод. Нехорошо, но не смертельно — введут ещё раз.


                          1. qadmium
                            14.09.2016 11:40

                            возврат DBZ — часть естественного контракта функции. Функции деления, например.


                            нет

                            в программе кнопок больше одной, одна может не работать. Главное — побочных эффектов не настругать.


                            и что? не работает — перепиши

                            Функция может работать в рамках контракта, который мы где-то нарушили. Самое банальное — плохо проверили пользовательский ввод. Нехорошо, но не смертельно — введут ещё раз.


                            контракт нарушен -> написано неверено -> смысла работать дальше нет


                            1. areht
                              14.09.2016 14:41

                              > нет

                              Это факт, а не вопрос

                              > и что? не работает — перепиши

                              В следующую итерацию


                  1. qadmium
                    13.09.2016 19:25
                    +1

                    если у вас ожидается что инты будут переполняться, то, очевидно, использовать механизм исключений для этого глубоко неправильно. Впрочем как и ловля любого SystemException без rethrow


                    1. areht
                      13.09.2016 20:04

                      Как бы вы написали минимально простой калькулятор, в котором пользователь формулу вводит? Ну, без ловли ArithmeticException.


                      1. qadmium
                        14.09.2016 11:42

                        валидировал бы ввод и отселаживал overflow/underflow без эксепций, удивительно просто, да?


                        1. areht
                          14.09.2016 13:57

                          А как? Получить ArithmeticException способов больше одного: сложение, умножение, деление, логарифм — всё проверками обложить?

                          SystemException нельзя ловить как таковой, а OverflowException — можно


                          1. qadmium
                            14.09.2016 15:27

                            А как? Получить ArithmeticException способов больше одного: сложение, умножение, деление, логарифм — всё проверками обложить?


                            ну если делаем лабораторку в универе, то можно и не обкладывать )) а какие еще есть варианты? input всегда нужно валидировать

                            SystemException нельзя ловить как таковой, а OverflowException — можно


                            я попробую раскрыть свою мысль: если мы говорим, например, про деление то контракт у деления должен выгялдить вот так:

                            Contract.Require(divisor != 0);

                            если мы говорим про overflow то у функции сложения контракт будет вглядеть вот так:

                            Contract.Require(int.MaxValue — arg1 > arg2 && int.MaxValue — arg2 > arg1);

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

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

                            а вообще за всю мою практику из наследников SystemException мне приходилось ловить и проглатывать только TaskCanceledException, все что было связано с арифметикой — это всегда была моя ошибка


                            1. areht
                              14.09.2016 16:04

                              > input всегда нужно валидировать

                              Если формула отпарсилась в дерево — инпут годный. Но от переполнения при умножении это не спасёт.

                              > Contract.Require(int.MaxValue — arg1 > arg2 && int.MaxValue — arg2 > arg1);

                              А для возведения в степень? А во сколько раз дольше ваша проверка будет считаться (да и писаться, с тестами), чем само вычисление?

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


                              1. qadmium
                                14.09.2016 16:43

                                >Если формула отпарсилась в дерево — инпут годный. Но от переполнения при умножении это не спасёт.

                                а инпут к формуле?

                                >А во сколько раз дольше ваша проверка будет считаться (да и писаться, с тестами), чем само вычисление?

                                один раз — при комипяции. вы вообще слышали про контракты?


                                1. areht
                                  14.09.2016 17:04

                                  Как вы пользовательский ввод при компиляции проверите?


                                  1. qadmium
                                    14.09.2016 17:53

                                    воувоу, я все понял. вам нужны лекции «бертран мейер» «основы объектно-ориентированного программирования». там вы узнаете почему контракты и ввод не соотносятся чуть менее чем никак, и почему контракты должны проверяться во время компиляции


                                    1. areht
                                      14.09.2016 19:13

                                      Я спрашивал про пользователя вводящего формулу, это вы контракты притащили.

                                      Ввод хоть тушкой, хоть чучелком в рантайме проверять придёться на соответствие «int.MaxValue — arg1 > arg2 && int.MaxValue — arg2 > arg1»


                                      1. qadmium
                                        15.09.2016 10:13

                                        конечно, и это правильно


                                        1. areht
                                          15.09.2016 15:09
                                          +1

                                          Конечно. Но долго, дорого, и часто не понятно зачем именно нужно.


                              1. qadmium
                                14.09.2016 16:49

                                >Программирование ради программирования, выбрасывание которого — необходимость, а не компромисс.

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


                                1. areht
                                  14.09.2016 17:31

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

                                  Инструмент должен соответствовать задаче, хоть null, хоть goto.


                                  1. qadmium
                                    14.09.2016 17:51

                                    ну нет, всегда должен быть недостижимый идеал


                                    1. areht
                                      14.09.2016 19:07

                                      https://www.ted.com/talks/malcolm_gladwell_on_spaghetti_sauce


                              1. qadmium
                                14.09.2016 16:58

                                >А для возведения в степень?

                                ЗЫ. Math.Pow не бросает эксепций, также как и деление у double ))


                                1. areht
                                  14.09.2016 17:43

                                  Значит у нас будет не double. Не спрашивайте, заказчик попросил.


                                  1. qadmium
                                    14.09.2016 17:51

                                    тогда не получится в отрицательную степень возвести, тоже выглядит как контракт функции ))


              1. S_A
                13.09.2016 16:07
                -2

                По логике противников NRE stack overflow легко победить запретив рекурсию и так далее.


                1. areht
                  13.09.2016 17:24
                  -1

                  Нельзя запретить рекурсию, не запертив функции.


                  1. S_A
                    14.09.2016 04:26

                    И?


                    1. areht
                      14.09.2016 04:52

                      и «легко победить» — бред, не имеющий отношение к противникам NRE


                      1. S_A
                        14.09.2016 05:10

                        Вы уже утомили хуже горькой редьки отказом понимать весь смысл написанного (открою тайну: бывают языки и без функций). Своими доморощенными классификациями и наколенными соображениями опустили уровень дискуссии на дно.


                        1. areht
                          14.09.2016 05:37

                          Вы уже утомили хуже горькой редьки отказом понимать весь смысл написанного. Бывает и brainfck, только на нём никто не пишет.


                  1. playermet
                    14.09.2016 12:49

                    Не думаю что это хорошая идея, но все же, что мешает запретить в языке рекурсию не запрещая функции?


                    1. Azoh
                      14.09.2016 13:49

                      Можно не запрещать. Достаточно отслеживать циклические вызовы компилятором (чтобы неявную рекурсию убрать) и запретить вызов по указателю (чтобы не проверять в рантайме). Если с первым еще можно смириться (любой рекурсивный алгоритм достаточно тривиально превращается в нерекурсивный), то второе уже серьезно ограничивает область применения такого языка.


                      1. areht
                        14.09.2016 14:36

                        > Достаточно отслеживать циклические вызовы компилятором

                        На какую глубину? На сколько такой компилятор будет тормозить?


                        1. Azoh
                          14.09.2016 15:56

                          Можно прикинуть (очень грубо). Пускай N — количество функций. Наибольшее количество уникальных вызовов (в смысле откуда и что) будет достигаться, если каждая функция будет вызывать каждую. Таким образом для проверки нам потребуется проверить не более N^2 вариантов. Соответственно сложность проверки ограничена O(N^2), что не так уж и много.


                          К тому же, фехтование верхом на стульях требует времени: https://xkcd.com/303/


                          1. qw1
                            14.09.2016 16:03

                            Соответственно сложность проверки ограничена O(N^2), что не так уж и много.

                            Для типичного проекта в 100.000 функций — много.
                            Тем более, надо отсекать варианты непрямых вызовов (A > B > C > D > A), а это построение матрицы достижимости на ориентированном графе, что с использованием лучших известных алгоритмов имеет сложность O(N3)


                            1. Azoh
                              14.09.2016 16:17

                              Нам достаточно проверить ориентированный граф вызовов на ацикличность. Граф без петель (вызов функцией самой себя тривиально проверяется). Сложность этого алгоритма — O(M), где M — количеством ребер, которое ограничено количеством ребер в полном графе, т.е. N*(N-1). Т.е. общая сложность — O(N^2). Вот задача поиска ВСЕХ таких циклов уже другое дело.


                              1. areht
                                14.09.2016 16:32

                                > Вот задача поиска ВСЕХ таких циклов уже другое дело.

                                Хотелось бы в ошибках компиляции весь список, конечно.

                                И, кстати, не забудьте про неявные вызовы через .net fw, я затейник.

                                А вообще, вы почему-то думаете, что граф вызовов статический и известен при компиляции. Это немножко не так. По крайней мере я не представляю как иначе.


                                1. Azoh
                                  14.09.2016 17:39

                                  Я в изначальном комментарии сказал, что вызовов по указателю нет. Любой непрямой вызов попадает под это условие.


                                  1. areht
                                    14.09.2016 17:48

                                    Это в рамках языка. Из внешнего мира то нас могут вызывать?


                                    1. Azoh
                                      15.09.2016 01:28

                                      А это все имеет смысл только в рамках языка.


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


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


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


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


                                      … Либо не соглашается и делает что хочет, но ответственность лежит полностью на внешнем мир


                                      1. areht
                                        15.09.2016 02:00

                                        > ответственность лежит полностью на внешнем мир

                                        Если я использую внешний мир так, что возникает рекурсия — это не вина внешнего мира. И не вина компилятора.

                                        Пока есть стек — его можно переполнить.


                            1. playermet
                              14.09.2016 16:26

                              > Для типичного проекта в 100.000 функций — много.
                              В типичном проекте не будет глубины в 100.000 вложенностей.

                              > надо отсекать варианты непрямых вызовов
                              Для этого и достаточно O(N^2).


                      1. playermet
                        14.09.2016 16:17

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


        1. s-kozlov
          13.09.2016 12:36

          Переменная (ссылка) может иметь любое значение, это значит что выполняющее (программу) устройство зарезервировало под него кусок места в памяти. Вот этот кусок в памяти может указывать на что угодно, на умерший, невалидный объект, или валидный.


          Для программиста ссылка — это ни одним местом не адрес в памяти. Ссылка — это «пульт» объекта с «кнопками» в виде доступных полей, методов, свойств и т.д. Какая к черту память? Адреса в памяти — это сишные указатели. Даже в С++ мухи отделены от котлет.

          А нулевая ссылка — это такой пульт, который выглядит как нормальный, но при нажатии кнопки вместо переключения на футбол на месте тебя и твоего дома остаются дымящиеся руины и надгробная плита с надписью «телевизор не найден».


          1. S_A
            13.09.2016 16:05

            Вы свой язык когда-нибудь пробовали писать? Я писал. Он был в 2004 году, без null, и был фактически SQL с классами. На нём было написано штук пять среднего размера программ для работы с данными (всякого рода поиски по различным сущностям). Он был интерпретируемым.

            Картинку из Ералаша про «батарейки забыл» лень искать. Если бы я захотел его транслировать в машинный код, в нём был бы null. В противном случае бинарник содержал бы в себе исходник и интерпретатор.


            1. s-kozlov
              13.09.2016 19:56

              Если бы я захотел его транслировать в машинный код, в нём был бы null. В противном случае бинарник содержал бы в себе исходник и интерпретатор.


              В огороде бузина, а в Киеве дядька.


              1. S_A
                14.09.2016 04:26

                Программистам на диване читают лекции в другом месте.


          1. iqiaqqivik
            13.09.2016 16:15

            Вот не нужно драматизировать, пожалуйста. Нулевая ссылка — это банальный бутафорский пульт, при нажатии кнопки на котором ничего не происходит. Правда, если запустить им в окно, окно разобьется, но бытует мнение, что пульт в этом не виноват.


            1. asdf87
              13.09.2016 17:04

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


            1. qw1
              13.09.2016 17:25

              SEGFAULT — это «ничего не происходит» ))


              1. asdf87
                13.09.2016 17:42

                Похоже у товарища iqiaqqivik свои особенные null'ы после которых ничего не происходит ))


              1. iqiaqqivik
                13.09.2016 19:22

                Ох. А есть ли какая-нибудь принципиальная разница в случае гипотетической транзакции (whatever it means), упали мы в корку, или просто элегантно упали в мессаджбокс «ошибка 512».

                Я вообще адепт идеологии «падать лучше как можно раньше»: эрланг, ОТП, супервизоры, вот это все.


                1. s-kozlov
                  13.09.2016 20:11

                  Есть принципиальная разница между ошибками времени компиляции и выполнения. В языке, где любая ссылка может быть null, компилятор не сможет гарантировать, что в рантайме не будет NRE. Если делать без null, можно разрулить опциональность с помощью соответствующего типа (Option). Никаких NRE не будет в принципе.


                  1. iqiaqqivik
                    14.09.2016 13:06

                    Можно разрулить. Не будет NRE. Я это все понимаю. Я не понимаю, почему это вдруг всегда лучше.

                    Ну, кроме аргумента «это же очевидно».


                    1. Bonart
                      14.09.2016 14:17

                      Аргумент "вместо ошибки периода выполнения непонятно где и когда получаем ошибку периода компиляции по месту дефекта" вы тоже не увидели?


                      1. iqiaqqivik
                        14.09.2016 18:04

                        Это не аргумент. Во-первых, «где» в рантайме ничуть не хуже, даже наведенный, если есть стектрейс. «Когда» — да, это весомо. Но цену за строгую типизацию приходится платить тоже немаленькую.

                        Вместо того, чтобы, грубо говоря, вызвать цепочку `a ? b ? c ? d ? e`, словить NRE, по стеку определить, кто виноват и выплюнуть: «не могу этого сделать, „c“ не определен», мне придется заморачиваться наличием у всех акторов сигнатуры, отличающей Empty, или заворачивать это все в Either. Иногда это оправдано. Иногда — нет.

                        И когда это оправдано, мне никто не помешает быть готовым ко всему. А когда нет — меня очень выручает null. Еще раз: ваш аргумент блестяще доказывает несомненную пользу подхода в первом случае. Зачем при этом вымарывать очень удобный иногда null из языка — убейте, не пойму.


                        1. amironov
                          15.09.2016 07:43

                          Желания убрать null нет, есть желание, чтобы язык поддерживал not nullable ссылки, а для nullable ссылок выдавал ошибку на возможные места возникновения NRE.


                        1. s-kozlov
                          15.09.2016 15:27
                          +2

                          словить NRE


                          Жесть какая.

                          Нафиг вам вообще типы, заморачиваться ими еще. Просто везде принимайте и возвращайте object, в рантайме «словите» ошибку и разберетесь.


                          1. iqiaqqivik
                            16.09.2016 15:46

                            Я именно так и делаю, спасибо.


        1. 0xd34df00d
          15.09.2016 09:38

          Хаскель интерпретируемый?


    1. PsyHaSTe
      15.09.2016 15:38

      Я конечно, стараюсь null не возвращать и не передавать, но это на уровне смысла методов скорее, чем претензия к языку.

      Почему бы не починить проблему на уровне языка, которую можно починить на уровне языка?

      Лично я был бы рад не писать повсеместно [NotNull], а использовать корректный тип. null не является валидным значением типа. Это отсутствие значения. И то, что объекты по-умолчанию нулы это явная недоработка.

      Почему просто так скастовать default(int?) в int нельзя, а (null) Person в Person можно? Почему в первом случае мы явно выделяем отдельный тип, а во втором это одно и то же? Ведь как все помним, вся разница между структурами и классами — в семантике копирования ссылки против копирования значения.



  1. NeoCode
    12.09.2016 08:50

    Интересно, а можно ли было ссылочные типы объединить с optional? То есть, иными словами можно ли на уровне языка любой ссылочный тип рассматривать как optional у которого null — значение none?
    Или может быть можно сформулировать иначе: возможно ли (опять на уровне языка) сделать optional таким, чтобы любой ссылочный тип рассматривался как optional, но при этом была возможность явно указать что данный ссылочный тип не может иметь значения null (атрибут NotNull?)


    1. Bonart
      12.09.2016 10:14

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


  1. pengyou
    12.09.2016 08:54
    +7

    Хайп вокруг заменителей null напоминает страшный социальный эксперимент, в котором 9 подставных человек называют белое черным, чтобы смутить десятого. Потому что, я бы ещё понял null-aware работу с обычным указателем, без которой компилятор вас изобьет. Но попытка убедить людей в том, что какой-то особый монадический контейнер с точно такой же повсеместной проверкой на IfPresent (или как-то иначе) засоряющий клиентский код это намного лучше — это просто какой-то артхаусный фильм про 'мир сошёл с ума'. Что там, кстати, с NULL в sql, не надумали отказаться? Пусть реальный мир подвинется, в ИТ не может не быть значения, пусть даже это значение НетЗначения.


    1. NeoCode
      12.09.2016 09:30
      +3

      Да тут все проще. Сам null — вполне нормальное явление. Но некоторые типы нуллабельны по умолчанию, некоторые — ненуллабельны. И получается так, что это свойство типа неявное, оно как-бы спрятано внутри самого типа. То есть когда вы имеете дело с типом T, вы заранее не знаете, может ли он быть null, или нет.
      А если бы например при разработке языка ввели правило, что T — это всегда ненуллабельный тип, а скажем T? — всегда нуллабельный, вероятно ясности было бы больше.


      1. Bonart
        12.09.2016 10:16
        +8

        Дело в том, что все ошибки, связанные с null, можно и нужно обнаруживать на этапе компиляции.


        1. general
          12.09.2016 10:55
          -1

          Это не так.Вот вам контрпример:
          1) Вы пишите библиотеку
          2) Я спустя год пишу код, который ее использует

          Как вы на этапе компиляции библиотеки отследите все случаи, когда использующий ее код передает в нее null?


          1. Bonart
            12.09.2016 11:20
            +6

            Это "не так" в C# и многих других языках как раз из-за ошибки дизайна.
            В Haskell NRE невозможен по построению.
            Тип A требует значение типа A. Тип Maybe A позволяет не иметь значения, но требует обрабатывать и его наличие (Just a), и отсутствие (Nothing).


            1. pengyou
              12.09.2016 12:27
              -4

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

              Конечно, ошибка дизайна тоже имеется, но она не в null заключается, а в отсутствии понятия «предусловия метода» в подавляющем числе систем модульности в любой экосистеме, хоть в джаве, хоть в дотнете (про .so вообще молчу, позорище из 70-х).


              1. Bonart
                12.09.2016 23:44

                Переменная типа Object требует значение типа Object или null
                Из-за ошибки дизайна.

                Без нее переменная типа Object требовала бы значения типа Object, а null допускала бы переменная типа Object? (точно также как и для значимых типов)


              1. Sirikid
                13.09.2016 05:13

                Типы аргументов это тоже предусловия и сейчас они не могут выразить non-null без костылей вроде R# и спецатрибутов.


            1. nickolaym
              12.09.2016 16:25
              +1

              В хаскелле — очень даже возможен, только компилятор будет ругаться (или не будет, если его немножко запутать) на неисчерпывающий перебор.

              foo (Just x) = "foo"
              -- foo Nothing не добавили
              
              bar x = case x of (Just y) -> "bar"
              --              | otherwise не добавили
              
              
              f = foo Nothing
              b = bar Nothing
              


              вылетит исключение Non-exhaustive patterns in case


              1. Bonart
                12.09.2016 16:38

                Насколько я помню, компилятор обломает в обоих случаях и до ошибок периода исполнения дело не дойдет.


                1. nickolaym
                  12.09.2016 17:45
                  +1

                  Не обломает. http://ideone.com/jN7nAB

                  foo (Just t) = "just"
                  --foo Nothing    = "nothing"
                  
                  main = do
                    putStrLn "begin..."
                    putStrLn (foo Nothing)
                    putStrLn "end."
                  


                  1. johhy13
                    13.09.2016 15:36

                    А это что? prog: prog.hs:1:1-21: Non-exhaustive patterns in function foo


                    1. nickolaym
                      14.09.2016 12:50

                      А это — stderr исполнения программы, куда был направлен вывод обработчика исключений!
                      Сперва в stdout попало «begin...», а затем, не доходя до «end.», прилетела ошибка, которая вывалилась в stderr (в соседний блок на странице).

                      Была бы ошибка компиляции, там ideone совсем по-другому написало бы, — в ещё одном блоке.


              1. 0xd34df00d
                15.09.2016 09:42

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

                Впрочем, это общая проблема анализа на bottom, null тут частный случай.


          1. Seekeer
            12.09.2016 11:25

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


          1. qadmium
            12.09.2016 12:18

            Как вы на этапе компиляции библиотеки отследите все случаи, когда использующий ее код передает в нее null?


            code contracts


          1. Mingun
            12.09.2016 19:42

            Как вы на этапе компиляции библиотеки отследите все случаи, когда использующий ее код передает в нее null?

            В том-то и прелесть not-null типов, что это делать будите вы, когда захотите передать в библиотеку null тип, а не автор библиотеки. А коли передается без проверок на наличие объекта — то ССЗБ.


      1. iqiaqqivik
        12.09.2016 11:14
        -2

        Если воспринимать `null` так, как его воспринимают в реальном мире, а именно — как отсутствие чего либо, пустой слот — то все типы нуллабельны, вообще-то.


        1. playermet
          12.09.2016 22:58
          +3

          Как null не воспринимай, в int по умолчанию его присвоить нельзя.


          1. spinmozg
            13.09.2016 11:32

            нельзя, потому что язык не позволяет.
            а если смотреть шире (что и предлагает делать iqiaqqivik), то вполне себе можно


            1. Bonart
              13.09.2016 11:33
              +1

              а если смотреть шире (что и предлагает делать iqiaqqivik), то вполне себе можно

              И создать себе геморрой еще и со значимыми типами.


              1. spinmozg
                13.09.2016 11:46

                про какой геморрой вы говорите? NULL — это значение. У него логическая нагрузка другая, это да, но это значение. Третье состояние, если использовать терминологию схемотехники. Ведь никто не возражает против третьего состояния в отношении микросхем, никто не возражает, что у типа данных Variant есть состояние unassigned. Потому что это нормально — отсутствие известного значения. Это не бесплатно, да. Но это нормально


                1. playermet
                  13.09.2016 16:20
                  +1

                  про какой геморрой вы говорите?
                  Например про необходимость каждому полю и переменной значимых типов где-то хранить флаг отвечающий за наличие значения. Итого выравнивание в структурах/классах слетает, памяти жрется больше, кеш-миссы всегда чаще.


              1. iqiaqqivik
                13.09.2016 12:26
                -1

                Примерно в этом месте обычно уже начинается проповедь про то, что строгая типизация — это хорошо.

                В некоторых (далеко не во всех, впрочем) случаях — это даже правда.

                Реальный же мир устроен по другому. Я только на это указал.


                1. areht
                  13.09.2016 14:31
                  +1

                  Реальный мир устроен так, что функция работает только с определённым набором значений (не пересекающийся с типами в общем случае). В них null может входить, или не входить. Предикаты вы можете проверять на компиляции или вручную. Но вы не должны выполнять функцию при невыполнении её предикатов.

                  А входит ли «null» или «68543» в конкретный тип (подмножество значений) — это на совести конкретного типа. Тип — это не элемент «реального мира».


                1. qw1
                  13.09.2016 14:38

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

                  Если под null подразумевать «мы не знаем», то это уже не конструкция реального мира, а наша абстрация поверх неё.


                  1. areht
                    13.09.2016 15:00

                    > всегда есть (опуская

                    Это замечательно.

                    null часто подразумевает не «не знаю», а «не применим». Какого цвета электрон? Какой запах у песни?


                    1. qw1
                      13.09.2016 15:07

                      В реальном мире нет этого значения, значит, под него вообще не требуется переменная.
                      Если у вас есть переменная, хранящая «запах песни» = null, это плохой дизайн )))


                      1. areht
                        13.09.2016 15:09

                        В реальном мире нет переменных, и типов(каждый объект уникален). Есть характеристики.


                        1. playermet
                          13.09.2016 16:24
                          +1

                          В реальном мире нет способа (функции), который померяет у музыки запах и вернет null. Несовместимость интерфейсов.


                          1. areht
                            13.09.2016 17:39

                            Вы вообще что называете функцией в реальном мире?


                            1. playermet
                              14.09.2016 12:44

                              Ничего не называю. Функцию я написал как ближайший аналог в ЯП.


                    1. qw1
                      13.09.2016 15:13

                      Под «всегда есть» я имел ввиду, что null-значений не бывает.
                      Если у объекта есть температура, она не-null


                      1. areht
                        13.09.2016 17:46

                        Это как то противоречит тому, что я сказал?


                        1. qw1
                          13.09.2016 17:54

                          Да. В хорошем дизайне не должно быть ситуации, в которой в переменную «цвет электрона» кладём значение «не применим». эта информация должна быть на мета-уровне, на уровне схемы данных, а не как значение «null»


                          1. areht
                            13.09.2016 18:30

                            Что значит «в хорошем дизайне»? Мы о реальном мире? Не нравится цвет электрона — скажите размер ноги безногого.


                            1. qw1
                              13.09.2016 19:10

                              О программе, моделирующей реальный мир. О плохом дизайне модели, если модель легко может отобразить невозможную ситуацию.


                              1. areht
                                13.09.2016 22:14

                                Так и что там с моделью безногого? Это невозможная ситуация?


                                1. playermet
                                  14.09.2016 12:47

                                  В хорошей модели безногого нет метода, возвращающего отсутствующую ногу, чтобы что-то там у нее измерять.


                                  1. areht
                                    14.09.2016 14:02

                                    В реальном мире объекты уникальны, невозможно составить модель каждого без-чего-то-там.
                                    Делают одну модель человека.


                                    1. playermet
                                      14.09.2016 16:35

                                      > В реальном мире объекты уникальны, невозможно составить модель каждого без-чего-то-там
                                      Но вы ведь называете это моделью безногого. Зачем модели безногого возможности иметь ноги? Может заодно и возможность наличия колес ему дать, на случай кибернетизации?


                                      1. areht
                                        14.09.2016 16:39

                                        > Но вы ведь называете это моделью безногого.

                                        *модель, способную описать безногого


                                        1. playermet
                                          14.09.2016 17:00

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

                                          Так можно уйти в философию и дойти до концептов уровня any UniversalType.getProperty(string propertyName).

                                          Но вообще, можно и с опциональной ногой:
                                          bool human.applyIfLegPresent(toolCallback);


                                          1. areht
                                            14.09.2016 17:19

                                            > А зачем навязывать модель, которая возможно хранит ноги, если нам нужен именно безногий?

                                            Не нужен, у нас равноправие и толерантность. Безногих в обувной магазин пускают (так и вижу, тыкаешь в раздел «обувь», а всплывает плашка «а у вас ноги есть?).

                                            > Так можно уйти в философию и дойти до концептов уровня any UniversalType.getProperty(string propertyName).

                                            dynamic — это практика. А в JS — суровая реальность.

                                            > bool human.applyIfLegPresent(toolCallback);

                                            Это про интерфейс, а не модель. Ну а внутри то переменная есть?

                                            И дальше что? IfKneePresent, ifMiddleFingerPresent? Сразу же написал — »невозможно составить модель каждого без-чего-то-там"


                                            1. playermet
                                              14.09.2016 17:33

                                              > Безногих в обувной магазин пускают (так и вижу, тыкаешь в раздел «обувь», а всплывает плашка «а у вас ноги есть?)
                                              Пускают, но никакого null при доступе к размеру отсутствующих ног они не получают, потому что и доступа этого нет.

                                              > Ну а внутри то переменная есть?
                                              Нет. У безногого этот метод вернет false и ничего не сделает.

                                              > И дальше что? IfKneePresent
                                              А чем это отличается от «knee = getKnee(); if (knee != null) {… };», кроме отсутствия null?
                                              Да, у человека очень много свойств, и никакая модель не поможет их убрать, если не абстрагироваться.

                                              > невозможно составить модель каждого
                                              Можно составить модель каждого, кто предусмотрен к обработке в системе. В любой момент времени мы либо знаем тип объекта (и знаем наверняка, что у него можно мерять), либо не знаем (а там у нас возможно щупальценогий крылоклюв). Если интерфейс совпадает — меряем. Если нет — мы бессильны.


                            1. qw1
                              14.09.2016 15:15

                              скажите размер ноги безногого
                              Какое приложение делаем? По подбору обуви? Не должно оно запускаться у безногого. Если это часть более общего приложения по подбору гардероба, модуль обуви должен быть недоступен для безногого.


                              1. areht
                                14.09.2016 15:21

                                Регистрационную форму соцсети фетишистов.


                                1. qw1
                                  14.09.2016 15:36

                                  Здесь одного null-а недостаточно.
                                  Сегодня требуют вариант «я безногий инвалид», а завтра захотят «я параноик, поэтому не скажу».
                                  Тут в любом случае Option, с вариантами, и если вариант — числовой, доступен метод получения значения.


                                  1. areht
                                    14.09.2016 16:25

                                    Не надо придумывать сложностей на ровном месте. В социльной сети фетишистов параноики не регистрируются.

                                    Если вы под «Option» и «доступен метод» подразумеваете, что при обращении к недоступному методу я получу исключение — так работает null. Хотя, конечно, и у него есть фатальный недостаток.


                  1. iqiaqqivik
                    13.09.2016 16:31
                    -1

                    > вместо null должна быть другая конструкция, более определённая, чем универсальный null

                    Да почему? Почему вы считаете, что ваша менее универсальная конструкция лучше? В некоторых случаях — лучше, а в некоторых — нет. В конце концов, не нравится — не пользуйся. Но объявлять это ошибкой дизайна как минимум смешно. Мода на монады пройдет так же, как прошла в свое время мода на ООП. Empty в подавляющем большинстве случаев — овердизайн, который только заслоняет суть.


                    1. qw1
                      13.09.2016 17:01
                      +2

                      Потому что null-object «без запаха» имеет тип, и вместо него нельзя подставить «без цвета».


                      1. iqiaqqivik
                        13.09.2016 19:36
                        -1

                        Почему это нельзя? При наличии в системе null очень даже можно. И сразу же из коробки можно получить список всех элементов «без одного признака». Что, мягко говоря, поторебует километров ненужного кода при строгой типизации.

                        Шах и мат.


                        1. qw1
                          13.09.2016 20:46

                          Я, вообще-то, про паттерн «null object».


                    1. asdf87
                      13.09.2016 17:26
                      +4

                      А может все-таки все наоборот и not-null reference types + optional более явно бы показывало суть, чем текущий дизайн reference types? Если опустить варианты оптимизации производительности через null, как особое значение, то я не вижу не одного случая где бы null'ы делали бы код читабельнее или безопаснее. Можете такой случай привести?


                      1. iqiaqqivik
                        13.09.2016 19:31

                        Легко.

                        (Сразу оговорюсь, что такое «делать код безопаснее» я не понимаю, в моем мире безопасным код делает разработчик, поэтому далее — про читабельность.)

                        Анкета. 500 вопросов. Ответы бывают самые разные: галочка, радиокнопочка, текст, картинка. Нужна (помимо прочего) функция, получающая среднее количество ответов в анкете. Пойдет? Или желаете чего-нибудь поизощреннее, с промежуточной генерацией кода?


                        1. areht
                          13.09.2016 20:18

                          У вас у анкеты один инстанс подразумевается? Разряженный массив объектов ответов (разных типов и с null вперемешку) и доступ по индексу с последующим кастингом по метаданным анкеты? Хочу читабельностью вдохновиться.

                          А в псевдокоде можно? А то не понятно что вам мешает null object пересчитать, вместо null'ов.


                        1. qw1
                          13.09.2016 20:50

                          Наследовать надо от BaseAnswer типы CheckBoxAnswer, TextAnswer, ImageAnswer. И заодно туда же — NoAnswer.


                          1. areht
                            13.09.2016 22:24

                            А что именно вы хотите от BaseAnswer в NoAnswer отнаследовать?


                            1. qw1
                              14.09.2016 15:12

                              Да, верно. По-хорошему, NoAnswer не нужен. Должна быть коллекция заполненных ответов, map: QuestionId -> BaseAnswer.


                        1. asdf87
                          14.09.2016 01:43

                          «безопаснее» и удобнее в моем понимании — это перекладывать на компилятор всю рутинную работу, которую компилятор может со 100% точностью сделать гораздо эффективнее человека. Например: проверка ошибок (не давать изменять readonly-поле), представление компилятором более безопасного и читаемого синтаксиса (ссылки, с их как бы автоматическим разыменованием по сравнению с указатеями), генерация кода по определенному паттерну(типа генерация конечного автомата по async..await-синтаксису) и т. п. Это все из той же оперы. Компьютер практически не делает ошибок(если у него есть строгий корректный алгоритм) по сравнению с человеком в такой рутинной работе. Следовательно, преимущества на лицо: гораздо быстрее получаем корректный результат. Просто нужно иметь возможность это удобно выразить в языке.

                          П. С. iqiaqqivik, мне как и areht'у хотелось бы пример с пояснением, что вы считаете удобством. Только желательно с упором в «безопасность» т. е. простоту поддержки этого кода другими членами команды.


                1. s-kozlov
                  13.09.2016 20:28

                  В «реальном мире» в случае хорошего дизайна опциональность нужна относительно редко, но в Java/C# она навязана всем, всегда и везде. Вместо редких случаев небольшого оверхеда (и то не факт) нас имеют мы имеем кучу шаблонного кода, заключающегося в проверках на null 95 % аргументов функций.


    1. dotneter
      12.09.2016 10:07
      +4

      var r;
      var foo = getFoo();
      var bar = getBar();
      if(foo != null && bar != null){
        r = calc(foo, bar);
      }

      r = do
        foo <- getFoo
        bar <- getBar
        return $ calc foo bar

      Где тут проверка на IfPresent?


      1. pengyou
        12.09.2016 10:31
        -2

        Вместе с null выплеснули всё остальное императивное программирование? Лихо.


        1. s-kozlov
          13.09.2016 20:30
          +3

          Вы так говорите, как будто это что-то плохое.


      1. 0xd34df00d
        15.09.2016 17:12

        liftM2 calc getFoo getBar, пожалуйста.

        И в императивном коде вам надо проверять foo перед получением bar.


    1. amironov
      12.09.2016 13:11
      +1

      Основная тема статьи: не как отказаться от null, а как избежать ошибок разыменования и сделать это как можно раньше.


      Что там, кстати, с NULL в sql, не надумали отказаться?

      В sql с null-значения проблемы другого рода: можно получить проблемы с производительностью (например, oracle не индексируют null-значения), можно легко сделать логическую ошибку в предикате запроса (троичная логика не всегда очевидна). И отказ от null — одно из возможных решений.


    1. minamoto
      12.09.2016 14:12
      +1

      В SQL с null куча своего геммороя.

      Так, например, Oracle считает, что пустая строка и null — это одно и то же.


      1. amironov
        13.09.2016 11:42

        Да, писать банальное условие str = '' в oracle противопоказано, можно долго отлаживаться. Кстати, из-за этой "фичи" строковый тип называется varchar2, а varchar не рекомендован к использованию.


        1. Igelko
          13.09.2016 13:00

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


  1. CarambaPirat
    12.09.2016 09:58
    +1

    Мне одному кажется что проблема излишне раздута в статье?

    Все (по крайней мере должны) проверяют входные параметры в функцию и выходные из функции. Работа кода, который делает что-то с пустым объектом обычно бесполезна. Что толку, если вы получили пустой объект? Логика работы программы всё равно нарушена.
    Да и возврат не null, а пустого объекта тоже имеет место быть.


    1. Bonart
      13.09.2016 00:53
      +1

      Все (по крайней мере должны) проверяют входные параметры в функцию и выходные из функции.

      Да, восемь строк проверки на три строки полезного кода смотрятся просто, чисто и элегантно.
      Проверки выходных параметров и возвращаемых значений в коде не припоминаю за все время работы.
      Посмотрите решение 11 — оно справится с проверками лучше программиста-человека.


    1. s-kozlov
      13.09.2016 20:33
      +3

      Все (по крайней мере должны) проверяют входные параметры в функцию и выходные из функции.


      А должен проверять компилятор. В этом, черт возьми, вся соль.


  1. Sinatr
    12.09.2016 10:49
    -1

    Статья не понравилась. Интрига есть (я «почти» согласился, что null — это «однозначно ошибочное решение, бездумно скопированное из более ранних языков»), но в «исторической альтернативе» все как-то, извините, «сдулось» в один маленький абзац.

    Нет примеров, видно, что автор знает больше, но рассказывать явно не собирается (ссылки — хорошо, но примеры никто не отменял, кстате, некоторые из ссылк на ru-ru msdn, некоторые на en-us). Сухие перечисления без примеров — бесполезны, никто не будет «заучивать» их наизусть, без понимания почему так.

    Почему ?. называют Элвисом (кстате, правильно использовать в качестве термина словосочетание «null-coalescing», а не глалог «coalesce)?

    Почему if(something != null) вдруг „антипаттерн“? „Единственное назначение которого — выбросить исключение поближе к месту предательства“ — никто не выбрасывает NullReferenceException и никто не отменял валидацию параметров метода (InvalidArgumentException) или ошибочных сценариев (InvalidOperationException). Или имелось в виду что-то другое?


    1. Sirikid
      12.09.2016 11:17
      +1

      > Почему?.. называют Элвисом
      Если присмотреться можно увидеть кок и два глаза


    1. tyomitch
      12.09.2016 11:28
      -4

      Элвисом называют ?:

      ?. называет Элвисом только Bonart. Не знаю, почему у него Элвис одноглазый.


      1. Bonart
        12.09.2016 11:40

        По ссылке ниже тоже только я? Да еще за нескольких участников дискуссии?
        http://stackoverflow.com/questions/27493541/null-conditional-operator-and-string-interpolation-in-c-sharp-6


        1. tyomitch
          12.09.2016 11:56

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


          1. Bonart
            12.09.2016 12:22

            Комментарии от Nick Orlando и Randall Deetz тоже были от меня?
            И Bill Wagner по ссылке ниже тоже я?
            http://www.informit.com/articles/article.aspx?p=2421572
            И гугл при поиске "elvis operator c#" почему-то находит null-conditional operator раньше тернарного.
            И это все я один?


            1. tyomitch
              12.09.2016 12:27
              -1

              Элвис — это не тернарный ?:, а как раз-таки бинарный.


              1. Bonart
                12.09.2016 13:07

                Похоже, что только tyomitch знает про бинарный ?: в C#


                1. tyomitch
                  12.09.2016 13:24
                  -3

                  Нету его в С#. О том и речь.
                  В некоторых других языках — есть.


        1. playermet
          12.09.2016 14:25
          +1

          По ссылке ниже оператором Элвиса в шарпе называют '??'. При этом указывают, что официально он называется «null-conditional operator», ибо на смайл Элвиса он уже не похож.


          1. Bonart
            12.09.2016 14:50
            +1

            ?? официально называется null-coalescing operator. null-conditional operator — это как раз официальное название для ?.


            1. playermet
              12.09.2016 15:38
              +1

              Вы это не мне, а процитированному мной человеку напишите. Суть в том, что функциональную роль оператора Элвиса в шарпе выполняет как раз '??'.

              > null-conditional operator — это как раз официальное название для?..
              Быть точнее, null-conditional member access operator.


      1. nickolaym
        12.09.2016 17:57

        Где же элвис одноглазый? Привыкли все, что смайлики повёрнуты, как совёнок-брат-дебил. А тут чуб наверху, два глаза внизу:
        ?..


        1. tyomitch
          12.09.2016 18:21
          -1

          Ну значит, два разных оператора — это «горизонтальный Элвис» и «вертикальный Элвис».
          Если верить вики, в Groovy есть они оба.


    1. fogone
      12.09.2016 12:01
      +3

      Элвисом называют ?: — тут хорошо виден чуп и глаза, если смотреть на ?. — то и правда неясно — здесь, видимо, «король» нам подмигивает.


      1. fogone
        12.09.2016 12:26
        +4

        Я посмотрел повнимательнее и теперь и во втором варианте тоже вижу Элвиса! Кажется, я прозрел!1 Привычность восприятия играет с нами шутку. (хинт: точка от вопросительного знака — первый глаз). Только насколько я знаю такой элвис обычно называют safe call и у него немного другой смысл, чем у того элвиса, что повернут как классические смайлы.


    1. qadmium
      12.09.2016 12:15
      +3

      Почему if(something != null) вдруг „антипаттерн“? Единственное назначение которого — выбросить исключение поближе к месту предательства“ — никто не выбрасывает NullReferenceException и никто не отменял валидацию параметров метода (InvalidArgumentException) или ошибочных сценариев (InvalidOperationException).


      потому что в свете последних за 20 лет веяний в ООП эта задача должна быть переложена на контракты


    1. Bonart
      13.09.2016 00:34

      в «исторической альтернативе» все как-то, извините, «сдулось» в один маленький абзац.

      А там и не требуется сильно больше: источником серьезной проблемы стала экономия буквально на спичках. Добавил примеры кода к альтернативе.


      Сухие перечисления без примеров — бесполезны, никто не будет «заучивать» их наизусть, без понимания почему так.

      В этом случае каждое решение грозит превратиться в отдельную статью, что повысит трудоемкость для меня и ухудшит усвояемость для читателей.
      Я не стремился подробно осветить каждый нюанс каждого частного случая. Моей задачей было дать обзор типовых проверенных решений и высокоуровневые критерии для их выбора. Для тех, кто хочет узнать больше о конкретном варианте — в тексте есть ссылки.


  1. Diaskhan
    12.09.2016 11:03
    +1

    Null и вправду великий и предателский, вопрос в другом, что делать если есть таблица 59 миллионов записей,

    А вот вы говорите что поле не должно быть null. Получается так что для того чтобы добавить поле субд должно будет ставить этому поле значение по умолчанию? В таком случае справится ли СУБД?

    Больше все го в действительно напрягает что нужно все время делать COALESCE на проверках в СУБД.
    Но с другой стороны для поля бит мы должны будем инициализировать либо 0 или 1, для поля интегер 0, для пустой строки мы должны будем проинициализировать \0 (нулевым символом). То есть null обусловлен тем что многие субд немогут просто так взять и выкинуть NULL, Потому что при ALTER новый столбец при не NULL порождает дорогостоящую операцию для инициализации 59 миллионов UPDATE.

    А самая тормозну тая операция в СУБД это как раз таки UPDATE!
    NULL необходим в СУБД. По крайне мере в RDBMS. И условность это больше техническая нежели концептуальная.


    1. Pakos
      12.09.2016 12:03

      В БД нужно ставить два поля — bit/bool есть значение (результат измерения) и само измерение(int, например), ибо не всегда понятно — 0 это отсутствие измерения или значение измерения 0.


      1. Diaskhan
        12.09.2016 12:15
        +1

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

        И по большей части зависит от того как это реализуется в конкретной СУБД.


        1. Pakos
          12.09.2016 12:29

          Может, не очень удачно выразился — я как раз против излишнего добавления поля (ставить два поля вместо одного — плохо). NULL, конечно, тоже хранится где-то (в виде маски свободных/занятых полей в записи, например), но по крайней мере код объявления, вставки, обновления не растёт от использования NULL, а при втором поле растёт.


    1. Bonart
      12.09.2016 20:11
      +1

      Проблема не в том, есть null или нет, а в том что


      1. null допускают все типы полей в БД
      2. Операции с null ведут к ошибкам при исполнении а не при компиляции запроса.

      Если бы значения integer null и integer not null были бы


      1. разных типов,
      2. не совместимых между собой без явного coalesce
      3. с доступом к большинству операций только для integer not null,

      то с null и в SQL было бы намного меньше проблем.


  1. Steed
    12.09.2016 11:25
    +1

    В C++ есть довольно простой способ не писать часть лишних проверок — передача интерфейсов в функцию по ссылке. Тогда внутри самой функции и вызываемых из нее проверки на NULL не нужны (синтаксически), а вызывающему коду приходится явно разыменовать указатель, что для любого программиста красная тряпка и повод железно убедиться, что разыменовываешь не null:


    void StartVehicle( Vehicle& object )
    {
      object.CloseDoors();
      RunEngine(object); // void RunEngine( Vehicle& object );
    }
    void CallerCode()
    {
      Vehicle* object = FindObjectSomewhere();
      if(object) // Проверка только в момент фактического появления неопределенности
      {
        StartVehicle(*object);
      }
    }
    

    Недостатки:


    • Если нужно хранить объект, то это не всегда возможно сделать в ссылке (например, при отложенной инициализации)
    • Не для всех привычно использовать перегруженные интерфейсы через ссылку.
    • Это C++.

    Пожалуй, в описанном сценарии (передача параметров в функцию) это аналог вашего варианта 2 (NotNull). По-моему, вы его несколько недооценили. Конечно, для полностью нового кода писать везде NotNull (или &) — некоторая морока, но зато на рабочей кодобазе можно потихоньку очищать код, добавляя NotNull снизу вверх, пока указатели с возможно нулевым значением не останутся только на инфраструктурном уровне в местах, где это объективно необходимо.


    1. Symphel
      12.09.2016 12:26

      Есть еще один недостаток — в ваш метод rvalue ссылку вы не передадите, нужно или использовать универсальные ссылки (если это шаблонный код), либо делать ссылку константной, либо дублировать код для rvalue ссылок при необходимости.


      1. Steed
        12.09.2016 13:03

        Rvalue ссылки имеют смысл, когда объекты передаются по значению (в C# это была бы структура). Это иная парадигма, и там нет проблемы нулевых указателей. Как и при передаче интерфейсов по указателю нет проблемы rvalue ссылок, т.к. указатели тривиально копируются.
        Кстати, парадигма передачи объектов по значению, если она реализуется последовательно, — тоже, пожалуй, один из ответов на проблему null.


        Вопрос константности в данном случае продиктован требованиями функции. Для не изменяющей объект функции ссылка, конечно, будет константной.


  1. qadmium
    12.09.2016 11:25
    +1

    грамотный разбор, добавить нечего

    На предвосхищение ООП через 20 лет не претендую


    это пять ))


  1. stalsoft
    12.09.2016 12:02

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

    Например, разработчик получит ссылочное значение, забудет проверить его на HasValue, и начнет с ним работать как с «правильным» объектом (получать значения свойств, вызывать методы), то такое в итоге приведет к некорректному поведению приложения.
    В случае же с null, даже если разработчик не выполнит проверку, то при первом же обращении к объекту он получит ошибку, которая прекратит выполнение метода/приложения. Это мне кажется защита понадежнее


    1. fogone
      12.09.2016 12:16
      +2

      В императивном подходе избавляться от null нет никакого смысла. Статья написана так густо, что «мессаджа» за ней и не видно. У null-а проблема скорее не в его наличии, а в том, что знание о том может быть значение null-ом или нет содержится в головах, которые пишут код, в хорошем случае в документации и в идеальном в виде аннотации к параметру. Но хотелось бы, чтобы компилятор сам предостерегал нас от проблем с такими значениями. Потому и появляются всякие Optional в таких языках как java, чтобы обозначить, что возвращаемого значения может и не быть и потом что-то с этим удобно сделать.


      1. stalsoft
        12.09.2016 12:33
        -1

        Я себе взял за привычку всегда проверять возвращаемое методом значение на null (ссылочные типы), если конечно сам метод не генерирует исключение в случае ошибки. Ничего плохого в таком подходе пока что не находил.

        Про возложения этой обязанности на компилятор — допустим такая фича появиться, но что в итоге она должна делать? Запрещать компилировать без проверки? Мне кажется это неправильно. Или показывать warning о том, что проверки на null нет? В итоге такое предупреждение так же может быть успешно проигнорировано разработчиком как и сама проверка на null


        1. fogone
          12.09.2016 13:30

          Что делать — это философский вопрос. Это решают создатели языка. В котлине, например, получая из метода значение, тип которого помечен, что там не может быть null, просто нет никакого смысла его проверять на null. А если всё-таки тип возвращенного значения nullable, то ты просто не сможешь вызвать у него метод, пока не проверишь, что это не null. Поначалу кажется сложным, по факту привыкаешь и потом очень без такого страдаешь в java.


        1. s-kozlov
          13.09.2016 20:38

          Ничего плохого в таком подходе пока что не находил.


          Куча шаблонного кода?


          1. stalsoft
            14.09.2016 09:07

            А Вы считаете если в c# добавить фичу как в котлине, о которой писалось выше, то это избавит от шаблонных проверок? Можно узнать каким образом?


            1. Bonart
              14.09.2016 09:43
              +1

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


              1. stalsoft
                14.09.2016 10:09

                Я все равно не вижу никакой разницы. Например, есть у нас метод MyMethod, который возвращает объект ссылочного типа, то есть он может быть равен null. Каким образом Вы собираетесь избежать шаблонных, как Вы выразились проверок? Проверку нужно будет написать в любом случае.

                P.S. каждый .net разработчик должен понимать разницу между ссылочными и значимыми типами, а особенно то, что значимый тип нету никакого смысла сравнивать с null, о чем Visual Srudio так же подсказывает


                1. Bonart
                  14.09.2016 10:25

                  В котлине есть ссылочные типы, не допускающие null. Им проверки на null не нужны по построению.


                  1. stalsoft
                    14.09.2016 10:28

                    Ну то есть Ваш комментарий выше «Куча шаблонного кода?» не касается .net и c#? Тогда какой смысл писать комментарий о котлине в статье о c# .net, и говорить что это полезно, но только для котлина?


                    1. Bonart
                      14.09.2016 11:05
                      +1

                      Вы спрашивали, избавит ли в C# от проверок добавка фичи из котлина и каким образом.
                      Да, от части проверок избавит — ссылочные типы без null соответсвуют решению 12 из статьи и позволяют не проверять их значения на null по построению.
                      Смысл обсуждать котлин в теме про C# простой:


                      1. Ссылочные типы без null всегда обсуждаются при разработке новых версий C#.
                      2. Котлин свободен от NRE, кроме случаев взаимодействия с внешним не-котлин кодом.
                      3. У JVM и Java проблема с null в точности та же что и в .NET и C#
                      4. У C# в сравнении с котлином есть дополнительное обременение в виде совместимости с предыдущими версиями языка.


                      1. stalsoft
                        14.09.2016 11:24
                        -2

                        По поводу пункта №12 — если такие типы и добавят в c#, то я думаю добавят также и соответствующее предупреждение, например точно такое же как и при сравнивании значимого типа с null.

                        Хотя я не совсем понимаю какой смысл ссылочного типа, который не может быть null. Как в конечном итоге понять, содержит ли этот тип все нужные значения или нет? Добавлять свойство HasValue? И зачем создавать «пустой» объект в куче, это ведь дополнительная работа для того же даже сборщика мусора.


                        1. amironov
                          15.09.2016 08:43
                          +2

                          Смысл в том, что по ссылке всегда объект, и с ней можно работать не опасаясь NRE. Если ссылка позволяет null, то компилятор должен выдать ошибку при обращении к объекту, если нет явной проверки на null. Пустой объект не создается, так как корректность использования ссылок проверяется на этапе компиляции.


                          1. stalsoft
                            15.09.2016 09:41
                            -3

                            Ваши слова немного противоречат друг другу.
                            1. "Смысл в том, что по ссылке всегда объект" — исходя из Ваших же слов, именно тут и будет создан пустой объект. Например, метод FindPerson вместо null (всеми почему-то ненавистного) вернет «пустой» объект, потому что null вернуть он не может. Соответственно я и предположил, что этот объект будет занимать место в куче, и обрабатываться GC. Или я ошибаюсь?
                            2. "Если ссылка позволяет null" — это никак не избавляет разработчика от вышеописанных «шаблонных» проверок
                            3. "Пустой объект не создается" — получается что создается (см. пункт 1)

                            Лично для меня в такой логике ничего полезного нет, проверки на null остались, плюс добавились абсолютно бесполезные «пустые» объекты. Вам же все равно после получения такого объекта придется проверять, содержит ли он значения или нет. А это ни чем не отличается от проверки на null. То есть, теперь «шаблонных» проверок будет два типа: проверка на null, проверка на HasValue

                            P.S. может быть мы друг друга неправильно поняли, но переменную ссылочного типа, которая равна null, то есть не указывает на конкретный объект в куче я не считаю «пустым» объектом.


                            1. Bonart
                              15.09.2016 11:04
                              +2

                              Вы можете попробовать читать именно то что написано.


                              "Смысл в том, что по ссылке всегда объект" — исходя из Ваших же слов, именно тут и будет создан пустой объект.

                              Это не чьи-то слова — это ваши собственные идеи.


                              1. Совсем не факт что будет использован паттерн null object — у него есть существенные ограничения.
                              2. Когда null object все-таки применяется — нейтральный объект обычно создается статически и переиспользуется многократно.

                              Например, метод FindPerson вместо null (всеми почему-то ненавистного) вернет «пустой» объект, потому что null вернуть он не может.

                              1. Вы статью вообще прочитали? Недостатки реализации null в C# расписаны еще до ката.
                              2. С чего вы взяли что FindPerson должен использовать именно null object? Исключение при неудачном поиске бросить не судьба?
                              3. Позволяющих null ссылок заведомо меньше, чем их общее число, а на практике от силы процентов 5. Кроме того, при наличии ссылок без поддержки null проверку для каждого случая появления null достаточно сделать лишь однажды.

                              Лично для меня в такой логике ничего полезного нет, проверки на null остались, плюс добавились абсолютно бесполезные «пустые» объекты.

                              Вы сами предложили такую логику, не надо приписывать ее кому-то еще.


                            1. amironov
                              15.09.2016 13:43

                              1. Чем пустой объект отличается от null? Если фунция должна вернуть объект, она должна вернуть реальный объект или, если не может этого сделать, бросить исключение:


                                Person! p =  FindPerson(); // Получаем либо объект, либо исключение PersonNotFound
                                Log(p.ToString()); // Работаем с объектом, не опасаясь NRE

                                Если функция может вернуть null, то перед использование результата необходима проверка:


                                Person p =  FindPersonOrDefault();
                                Log(p.ToString()); // должна быть ошибка компиляции, т.к. нет гарантии, что p != null
                                if (p != null) {
                                    Log(p.ToString()); // Работаем с объектом, не опасаясь NRE
                                }

                              2. Не избавляет, а наоборот, вынуждает писать проверки по месту использования ссылки.


                              3. Зачем нужен "пустой" объект и что с ним дальше делать? Проще не создавать.

                              P.S. Мы же 12-й пункт обсуждаем?


                              1. stalsoft
                                15.09.2016 15:16
                                -1

                                Bonart, про не судьбу бросить исключение — в своем комментарии выше я писал об этом. Так что видимо это Вы что то не читали. И кстати, бросать исключения — это не 100% подход на все случаи жизни, все зависит от ситуации. Если метод описан, и указано, что он может возвращать null, то в этом нет ничего плохого.

                                amironov, по поводу Вашего пункта 2 — выше Bonart написал, что код необходимо избавлять от «кучи шаблонного кода», но с таким подходом шаблонный код никуда не девается.


                                1. PsyHaSTe
                                  15.09.2016 15:59

                                  Прям как на MSDN про винапи: вторым параметров всегда должно идти TRUE, а десятый должен всегда быть NULL. Мы это задокументировали, отлично, раз в доке написано, значит поведение корректное и система хороша.

                                  по поводу Вашего пункта 2 — выше Bonart написал, что код необходимо избавлять от «кучи шаблонного кода», но с таким подходом шаблонный код никуда не девается.
                                  Много проверок пишете для проверки int на NULL? Ведь для типов, не содержащих NULL «шаблонный код никуда деться не может».

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

                                  Не знаю как у вас в 90х, а в 2016 принято кидать эксепшн, а не возвращать код ошибки (в данном случае null это флаг, что нинашла).


                                  1. stalsoft
                                    15.09.2016 16:51

                                    Во первых — я говорил про возвращаемое методом значение, а не про 2-ой и 10-ый аргументы метода.
                                    Во вторых — исходя из Ваших комментариев, любой метод, который возвращает объект ссылочного типа должен выбрасывать исключение если что то не так. Так зачем тогда вообще проверки на null, если этот null никогда не вернется из метода, а вызывающий код просто поймает исключение?


                                    1. PsyHaSTe
                                      15.09.2016 18:14

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

                                      И вот эти лишние проверки, которых вполне не было бы если б нулл вообще не мог прийти занимают по статистике 20% кода (источник не приведу — уже не помню где видел).


                                      1. stalsoft
                                        15.09.2016 20:16

                                        Когда я создаю новый метод, то всегда пишу к нему xml комментарии, где описываю сам метод, его аргументы и возвращаемое значение. И если бы мы с вами работали вместе, то у вас не возникло бы ни единого вопроса (это конечно только если вы читаете описание методов, которые вызываете).

                                        А по поводу возвращать null или бросать исключения — однозначного ответа нет, все как я писал выше зависит от ситуации, даже в вашем 2016-ом. Но как я читал, если факт отсутствие или существование значения является нормальным для логики приложения, то вполне нормально возвращать null.


                                        1. PsyHaSTe
                                          15.09.2016 21:34
                                          +3

                                          Нормально. Только null не должен быть валидным значением типа. Например int не должен быть null, для возможности вернуть такое значение его явно нужно обернуть в int?.. Что и нужно было изначально сделать для референсных типов.

                                          Когда я создаю новый метод, то всегда пишу к нему xml комментарии, где описываю сам метод, его аргументы и возвращаемое значение. И если бы мы с вами работали вместе, то у вас не возникло бы ни единого вопроса (это конечно только если вы читаете описание методов, которые вызываете).

                                          Ну то есть так и есть, то что должен делать компилятор вы перекладываете на человека. «Не прочитал XML комментарий — получи эксепшн, так тебе и надо». Почему бы проверкой не заняться компилятору? Ведь если я не прочитал комментарий и не сделал проверку — то я не прав? Почему нет ОБЯЗАТЕЛЬСТВА сделать проверку? Это ведь легко сделать. Например с тем же int? у меня просто нет другого способа передать его в другой метод, который требует число, кроме как вначале проверить на HasValue (ну или свалиться при попытке получить значение).

                                          Но конечно же лучше отказаться от всего этого, ведь можно написать xml-комментарий. А еще можно всегда везде возвращать dynamic, а в XML комментарии писать, что за тип на самом деле пришел. Вуух, какая гибкость! А кто не прочитал, тот сам себе злобный буратино.


                                          1. stalsoft
                                            15.09.2016 23:04
                                            -1

                                            «Референсные типы» для того и reference types, что бы не содержать значение, а только на него указывать. А пока объект ссылочного типа не указывает на значение в куче, то он равен null, по моему это более чем логично.

                                            А если вы не прочитали описание метода и в итоге получили NRE (или любое другое непредсказуемое поведение) — неправы именно вы!

                                            Ведь если я не прочитал комментарий и не сделал проверку

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

                                            Кстати, если вам так действительно не хватает данной «фичи», то предложите ее реализовать (или проголосуйте за реализацию, если такая уже есть) для разработчиков самого языка. А то пока вы будете придумывать очередной пример с dynamic, то «зарелизят» следующую версию.


                                            1. PsyHaSTe
                                              16.09.2016 00:16
                                              +1

                                              «Референсные типы» для того и reference types, что бы не содержать значение, а только на него указывать.
                                              Верно, это означает исключительно семантику оператора «присвоить». Соответственно
                                              А пока объект ссылочного типа не указывает на значение в куче, то он равен null, по моему это более чем логично.
                                              нифига не так. Ссылка всегда должна указывать на существующий объект. Собственно в том же котлине это и сделано, афайк.
                                              Это как бы вы выстрелили себе в ногу, и жаловались на то, что револьвер сделан неправильно, ведь это мелочи, что инструкцию вы не прочитали та и вообще использовали его неправильно.

                                              Нет, это у вас чуть что не так — пользователь виноват.

                                              Вы можете 100 раз ему сказать, что он должен был прочитать документацию, посидеть с бубном и т.п., но если программа сделала не то, что ожидал пользователь — то виноват разработчик. Можете потом сидеть доказывать ему в спину, что он должен был прочитать 200-страничный мануал, а человек просто уйдет к конкурентам, у которых перед тем как дропнуть все данные выводится окошко с подтверждением, а не подход «сам виноват».
                                              Кстати, если вам так действительно не хватает данной «фичи», то предложите ее реализовать (или проголосуйте за реализацию, если такая уже есть) для разработчиков самого языка. А то пока вы будете придумывать очередной пример с dynamic, то «зарелизят» следующую версию.
                                              Вы за меня не переживайте, такая фича уже заявлена, и я заявку давно заапрувил.

                                              Ну и вы не ответили на вопрос, чем плохо переложить на компилятор проверку этих XML-комментариев?


                                          1. qw1
                                            16.09.2016 02:15

                                            Например int не должен быть null, для возможности вернуть такое значение его явно нужно обернуть в int?.. Что и нужно было изначально сделать для референсных типов
                                            Я бы хотел использовать такой язык. Но пока не понимаю, как будет выглядеть default-значение. Например, если Document — не-null тип, то чему равен default(Document)?

                                            Например, default(bool)==false, а default(int)==0.
                                            Этим значением заполняются поля классов и структур, сразу после их создания.

                                            Или not-null ссылки допустимы только в локальных переменных и в возвращаемых из функций значениях?


                                            1. Sirikid
                                              16.09.2016 03:20

                                              Обычно просто обязывают инициализировать переменную перед использованием, иначе compile error.


                                              1. qw1
                                                16.09.2016 16:04

                                                Думаю, если попытаться надёжно покрыть покрыть все сценарии, получим сложную спецификацию, в стиле c++ с его конструкторами копирования и прочими плюшками.

                                                Самый простой случай — как должен работать Array.Resize?


                                  1. 0xd34df00d
                                    16.09.2016 01:45

                                    У меня в 2016 принято Maybe или Either, а не нарушать систему типов императивными экзепшонами.


                                1. amironov
                                  16.09.2016 06:34
                                  +1

                                  amironov, по поводу Вашего пункта 2 — выше Bonart написал, что код необходимо избавлять от «кучи шаблонного кода», но с таким подходом шаблонный код никуда не девается.

                                  Еще раз: когда гарантия not null есть — избавляет от проверок, когда нет — обязывет сделать проверку.


                                  1. stalsoft
                                    16.09.2016 09:15
                                    -1

                                    amironov, про ссылочные типы NULL и NOT NULL — я понял. Я не понял каким образом разработчики будут вынуждены использовать NOT NULL. Например, найдется человек, который не читает ни xml описания, ни документации (по словам PsyHaSTe это нормально, так как все само должно работать и исправлять ошибки), и вернет по старинке NULL тип вместо NOT NULL. В таком случае компилятор заставит писать «шаблонный код» if == null, от которого в начале это дискуссии пытались уйти.

                                    А Вы или PsyHaSTe, например, создадите метод MeMythod, который должен возвращать сущность из БД. При чем возвращаемое значение пометите как NOT NULL. По логике PsyHaSTe, если метод ничего не найдет, он бросит exception, а если найдет — вернет сущность. И никаких проверок if == null не нужно. Все бы ничего, но вот только точно такого поведения можно добиться и без добавления третьего типа, то есть просто выбрасывать исключение в методе.

                                    PsyHaSTe,

                                    доказывать ему в спину

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

                                    Ну и вы не ответили на вопрос, чем плохо переложить на компилятор проверку этих XML-комментариев?

                                    Переложить конечно хорошо, но повторюсь, каким образом вы собираетесь избавлять от «шаблонного» кода if == null? Ведь дискуссия началась именно с этого.


                                    1. amironov
                                      16.09.2016 10:46
                                      +2

                                      В таком случае компилятор заставит писать «шаблонный код» if == null, от которого в начале это дискуссии пытались уйти.

                                      Цель — избавиться от NRE: чтобы были подсказки от компилятора, когда эти проверки обязательны, а когда нет.


                                      Каким образом вы собираетесь избавлять от «шаблонного» кода if == null?

                                      void ProcessPerson(Person! person)
                                      {
                                              // Не надо писать
                                              // if (person == null)
                                              //     throw new ArgumentNullException("person");
                                              Log(p.ToString());
                                      }
                                      
                                      ProcessPerson(new Person()); // ok
                                      ProcessPerson(null); // ошибка компиляции


                                      1. stalsoft
                                        16.09.2016 10:51

                                        amironov, шаблонного кода может и станет меньше, но полностью от него вы не избавитесь. От if == null вы уходите, только если ссылочный тип не может быть null, а если может — вы снова напишите шаблонную проверку.


                                        1. Bonart
                                          16.09.2016 11:44
                                          +1

                                          От if == null вы уходите, только если ссылочный тип не может быть null

                                          Совершенно верно. Заодно компилятор даст по рукам, тому кто попытается null туда запихнуть.


                                          а если может — вы снова напишите шаблонную проверку.

                                          Совершенно верно. Только после первой же успешной проверки у меня будет значение ссылочного типа, не допускающего null и его, в отличие от нынешних, не надо будет проверять по всей глубине стека вызовов. Заодно компилятор даст по рукам тому, кто рискнет пропустить обязательную проверку.


                                          Итоги:


                                          1. Полностью исключены проверки с выбросом InvalidArgumentException
                                          2. А там где null — не ошибка, наличие проверки проконтролирует компилятор.
                                          3. NRE исключено по построению
                                          4. PROFIT


                                        1. PsyHaSTe
                                          16.09.2016 17:39

                                          Никто не говорил, что компилятор решит все проблемы. Смысл в том, чтобы на представленном ниже рисунке оставить только элементы в зеленых областях, потому что красные это либо мусорные проверки (которые не нужны), либо как раз место для NullReference
                                          image


                                          1. PsyHaSTe
                                            16.09.2016 17:44

                                            Я не понял каким образом разработчики будут вынуждены использовать NOT NULL. Например, найдется человек, который не читает ни xml описания, ни документации (по словам PsyHaSTe это нормально, так как все само должно работать и исправлять ошибки), и вернет по старинке NULL тип вместо NOT NULL. В таком случае компилятор заставит писать «шаблонный код» if == null, от которого в начале это дискуссии пытались уйти.

                                            stalsoft не передергивайте. Я говорил про то, что не надо заставлять читать документацию, когда это не является обязательным. А то так действительно дойдем до венгерской нотации, когда все туда-сюда кидается одним и тем же образом, а программист должен сопоставив кучу фактов правильно угадать, что же это такое.


                                            1. stalsoft
                                              16.09.2016 18:42

                                              PsyHaSTe, а я сначала про документацию и не говорил, я говорил об xml комментарии, который находится в коде. И я действительно не понимаю, как можно вызывать «чужой» метод даже не прочитав одну строку о том, что он делает.


                                              1. PsyHaSTe
                                                16.09.2016 20:02

                                                Ясно, вы видимо либо один всегда на проекте работаете, либо у вас идеальные разработчики, которые не могут устать/забыть/просмотреть, которые никогда не допускают багов (ведь если прочитал документацию и XML-комментарий, то правильно использовал, а система состоящая только из рабочих компонент, которые правильно связаны друг с другом не может вываливаться с ошбками), и всегда с первого раза пишут корректный код, который идет сразу в прод.


                                                1. stalsoft
                                                  16.09.2016 21:35

                                                  PsyHaSTe, я действительно удивлен, что вы меня не понимаете как разработчик. Если есть метод со следующей сигнатурой int DoSomeWork(string name) — вы что его будете вызывать передавая туда параметры наугад?


                                                  1. PsyHaSTe
                                                    16.09.2016 23:17

                                                    А вы проверяете возвращаемое значение этого метода на null?


                                                    1. stalsoft
                                                      17.09.2016 07:31

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


    1. s-kozlov
      13.09.2016 20:40

      Дергать HasValue вообще не обязательно. Option — монада, у нее должны быть удобные методы типа flatMap и filter.


  1. vyatsek
    12.09.2016 12:04
    +2

    А почему нельзя просто не использовать null там где не надо? как вариант защиты — использовать контракт. А там где может быть null обрабатывать его корректно.


    1. PsyHaSTe
      16.09.2016 23:17

      Это предложено в п.12


  1. fogone
    12.09.2016 12:10

    В котлине достаточно неплохо решены некоторые проблемы null-ов. Тип переменной или поля задается сразу с указанием может ли в нем null храниться или нет. Это позволяет компилятору избавить нас от лишних проверок в тех местах, где null-а быть не может и требуя их там, где он может появиться. Думаю, аннотируя хотя бы параметры методов аннотациями, которые добавляют эту информацию, можно частично решить на этапе компиляции эту проблему и в C#.


    1. Bonart
      13.09.2016 00:36

      А что будет при тесном взаимодействии с любым Java-фреймворком?


      1. Sirikid
        13.09.2016 05:23

        Kotlin поддерживает стандартные аннотации (JSR 305), если аннотации нет добавляется проверка на null при использовании объекта полученного из Java-кода если она не была сделана заранее.


      1. fogone
        13.09.2016 10:20
        +1

        после долгих мытарств, они пришли к такому подходу — если есть информация о нулабельности (вроде аннотаций), то она используется, а если нет, то отдается на откуп разработчика определить тип и считается, что в этом месте безопасность не гарантируется.


  1. force
    12.09.2016 12:54

    Ну вот мне, кажется, что гораздо хуже чем null, в дизайне C#, это void. Из-за этого сейчас активно дублируется всё API по работе с функциями (Action и Func, Task и Task<>). Вот это, раздражает гораздо больше.


    1. Azoh
      12.09.2016 14:23

      Учитывая, что для обработки ошибок используются исключения и что в императивной парадигме не всякий код возвращает значение, то альтернатив особо нет. Можно было бы сделать специальный класс, который бы обозначал отсутствие возвращаемого значения (назовем его Void), который бы использовался во всех API в качестве возвращаемого значения. И получилось бы примерно так: Action превращается в Func, Action<...> в Func<..., Void>, Task в Task и так далее. Что-то особого улучшения не ощущается.


      1. force
        12.09.2016 14:29

        Ну, вообще void — это тип. System.Void, и вы можете сделать typeof(void), больше, правда ничего не можете сделать с ним. Т.е. в кишках тип номинально уже есть. Какой-нить Invoke, имеющий возвращаемый тип object, на void возвращает null (с горя). Т.е. уже встроенная библиотека .NET борется с этим типом.
        На мой взгляд, Func<void> всё-таки лучше чем грустный Action. Хотелось бы, чтобы его можно было использовать более гибко.


        1. asdf87
          12.09.2016 16:43

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


  1. Viacheslav01
    12.09.2016 13:39
    +1

    «Хитроумный идальго Дон Кихот Ламанчский» на современный лад


  1. potan
    12.09.2016 15:27

    Чем отличаются 8 и 9?
    Option и Maybe — просто разные названия одного и того же (первое во всех языках, кроме Haskell, второе — в остальных).


    1. nickolaym
      12.09.2016 18:04
      +1

      Option (или optional) — это тип контейнера, а Maybe — это монада.
      Так повелось, что в хаскелле монада уникально идентифицируется типом контейнера. То есть Maybe t — это монада Maybe, а Either () t — это уже нет, хотя логика там будет точно такая же.


    1. Bonart
      12.09.2016 23:55

      Maybe — это вовсе не обязательно конкретный тип, это всего лишь монада оперирующая возможно отсутствующим значением. Монаду Maybe в C# можно определить как над обычным ссылочным типом так и над специальным (Optional).
      Optional — тип, который допускает хранение значения или явное указание на его отсутствие, при этом не приводится к типу значения и требует явно обработать оба варианта использования, что позволяет обойтись без NRE by design.


  1. Jef239
    13.09.2016 03:51

    > Обрабатывать это исключение бесполезно: оно означает безусловную ошибку в коде.

    Автор считает, что ошибки в коде не надо обрабатывать? Видимо автор очень далеко от тех областей, где из-за ошибок в коде могут погибнуть люди. Как минимум в надежном коде надо:

    1) Изолировать отказ. То есть отказ в одной функциональности программы не должен прекращать ее исполнение, а лишь уменьшать её функциональность.

    2) Вывести диагностику в лог отказов.

    3) Пересоздать объекты, задействованные в отказавшей функциональности.

    4) Перезапустить подсистемы, задействованные в отказавшей функциональности.

    5) Использовать резервный сервер.

    Пункты 1 и 2 — выгодны, они упрощают сопровождение. Пункты 3, 5, 5 — достаточно затратны и нужны лишь для программ высокой надежности.


    1. Bonart
      14.09.2016 09:48

      Вы, возможно, спутали разные виды отказов. От NRE ваши способы 1-3 не помогают никак, 4-5 чуть лучше, но тоже ничего не гарантируют. Этим ошибки в коде кардинально отличается от сбоев в окружении, для обхода которых и придуманы ваши пять пунктов.


      1. Jef239
        14.09.2016 15:59

        Опыт 25 лет библиотеки VCL и 10 лет работы 365*24 моего приложения показывает, что помогает. Не во всех типах программ, но во многих — помогает.

        С диагностикой понятно — она всегда полезна. Анализ логов позволяет выделить часто встречающиеся проблемы и понять приоритет при исправлении. Да и сам поиск ошибок становится проще.

        Изоляция полезна всегда, когда у приложения много функций. Как пример — интерактивные приложения вроде Word. Получаем NRE при тройном клике мышкой (выделение фразы). Если NRE не изолирован — приложение падает. Если изолирован — пользователь выделит нужный кусок стрелками или движением мыши с нажатой клавишей.

        Я уточню, что речь идет об отлаженных приложениях. То есть NRE при выделении фразы — будет не на любой фразе, а на фразе со сложными знаками препинания, например с непарными кавычками. Тем самым мы сводим отказ от «программа вылетает» до «иногда не работает, но легко обходится». То есть понижаем важность исправления на 2 ступени.

        Именно так работает любое GUI приложение, основанное на VCL. Базовая изоляция внесена в основную структуру VCL.

        Ещё один тип отказов, где изоляция помогает — это многопоточные приложения. Повторюсь — что приложение отлажено и ошибки в нем редки Если мы раз в месяц поймали NRE, то при огромная вероятность, что при повторе операции его не будет. NRE могло возникнуть из-за гонки тредов или редкой последовательности событий.

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

        Насчет окружения — вы правы. NRE в отлаженной программе — следствие редких событий в окружении. В действиях пользователя, в последовательности исполнения, в значениях текущих данных…

        Ещё пример такой программы — обработка текущих измерений. Ну скажем поиск кота на данных с видеокамеры. Отказ на одном кадре — не означает, что будут отказы на следующих кадрах. При хорошей изоляции — не будет, отказ затронет лишь один кадр со специфичными помехами.

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


        1. Bonart
          14.09.2016 21:55

          С диагностикой понятно — она всегда полезна. Анализ логов позволяет выделить часто встречающиеся проблемы и понять приоритет при исправлении. Да и сам поиск ошибок становится проще.

          Логирование исключений в диагностических целях не требует перехвата и обработки именно NRE. Обычно делается хук на выброс исключения и логируется все подряд, за редким исключением. Для ошибок в коде разве что приоритет ставится FATAL.


          Изоляция полезна всегда, когда у приложения много функций.

          "Изоляция" при NRE — это попытка делать вид, что "все хорошо, прекрасная маркиза", при том, что о реальном состоянии системы вам известно только то, что воспроизвелась ошибка в коде.
          Типовая реакция от VCL на этот случай годится только для тех приложений, ответственность за исправное функционирование которых невелика.
          Опять-таки, это делается хуком c отловом всех необработанных исключений без специальной реакции конкретно на NRE.
          Во всем остальном коде пытаться перехватить и обработать NRE значит стрелять себе же в ногу.


          Я уточню, что речь идет об отлаженных приложениях. То есть NRE при выделении фразы — будет не на любой фразе, а на фразе со сложными знаками препинания, например с непарными кавычками. Тем самым мы сводим отказ от «программа вылетает» до «иногда не работает, но легко обходится». То есть понижаем важность исправления на 2 ступени.
          Такая реакция — сладкая ложь для пользователя. Если ошибка и впрямь привязана к окружению и пользовательскому вводу, то перезапуск даст шанс их обойти, сохранив корректность работы. А вот продолжение работы с гарантированно поломанными инвариантами в надежде на "изоляцию" означает ненулевую вероятность действительно крупных неприятностей: от порчи важных данных и заведомо неудачных автоматических сделок на бирже до инъекции смертельной дозы обезболивающего.

          Если вы не в курсе, самые опасные сбои в промышленной эксплуатации из-за ошибок в коде как раз редкие и плавающие. Частые и легко воспроизводимые чаще всего не доходят до боевой площадки, а если доходят, то легко исправляются.


          У программ нет такого показателя как надежность. Есть корректность — отсутствие ошибок кодирования, есть отказоустойчивость — способность восстанавливаться после сбоев. Описанная вами методика при NRE дает иллюзию корректности с помощью иллюзии отказоустойчивости, по факту не обеспечивая ни того, ни другого.


          В программе с высокой ценой сбоев, исключений, означающих ошибку в коде, не должно быть вообще. Для NRE этого добиться просто, для дедлоков — сложнее, но цель должна быть только такой. У меня был опыт доведения проекта, выдававшего по десятку страниц разных исключений вроде AV и т.п., до вылизанного состояния.


          1. Igelko
            14.09.2016 23:12
            +1

            Тем не менее let it crash подход с локализацией любого падения и перезапуском упавшего куска супервизором с дальнейшим пересозданием состояния начисто из доверенного персистентного источника вполне прижился в эрланге.
            Впрочем в эрланге это прокатывает лишь благодаря куче других хитрых особенностей рантайма — повальной иммутабельности, взаимодействию через обмен сообщениями итд.
            Так что при определённой гигиене кодирования данная техника действительно позволяет писать отказоустойчивый код. Корректный — нет.


            1. Bonart
              14.09.2016 23:49

              Так в эрланге и изоляция настоящая, и источник реально доверенный, и отказоустойчивость неиллюзорная.


              1. Igelko
                15.09.2016 08:09

                всё так. нужно использовать сразу весь комплекс техник оттуда, чтоб получить реальный рост отказоустойчивости. иначе первый же залетевший race condition разрушит цивилизацию.


                1. Jef239
                  15.09.2016 11:03

                  я вас удивлю — у нас гонки были, ничего не рушилось. Программа сама убивала одну (или обе) нити, участвующие в гонке и запускала их заново. Для этого нужен всего лишь механизм транзакций. Как пример — SQL живет себе на транзакциях и ему этого хватает.


                  1. Bonart
                    15.09.2016 11:14

                    "Всего лишь механизм транзакций" — это уже из серии "90% любой крупной программы на C++ представляет собой медленную и глючную реализацию подмножества спецификации Common Lisp" (по памяти).
                    Если нужны качества, которые может дать только безопасный рантайм — логично использовать готовый и проверенный безопасный рантайм вместо велосипедов. А для некоторых тем вроде криптографии это вообще категорический императив.


                    1. Jef239
                      15.09.2016 11:24

                      Ну мы по нужным нам характеристикам обогнали Industrial SQL Server.
                      1) Количество записей — до миллиона переключений в секунду. И это не только в пике, мы на этой скорости и работать могли.
                      2) Формат хранения — упакованный (сжатие в 6 раз).
                      3) Поиск редких событий по всей базе за секунды.

                      Все это было на 300 Мгц компе (пень II)

                      Но, самой собой, это все очень не универсально.

                      Ну а не нравятся ошибки рантайма — пишите свой собственный. В Delphi ошибок в рантайме хватает, в других языках — в общем-то тоже. Исходить нужно из того, что ошибки были, есть и будут и в компиляторе, и в библиотеках и в вашем собственном коде. А не надеяться, что за 10 лет вы найдете ВСЕ ошибки.


                  1. Igelko
                    15.09.2016 11:35

                    Транзакционный доступ — это как раз про отсутствие гонок =)
                    Транзакции — это абстракция немножко выше уровнем, чем параллельный доступ к кускам памяти. Нужна ещё реализация, которая их обеспечит.
                    Дедлоки при транзакциях возможны, и за этим проследят супервизоры и отстрелят зависшие.


                    Гонка — это когда происходят чтения или записи кусков, которые в этот же момент пишут другие, т.е. в реализации транзакций будет баг (блокировку не захватили или барьеры не выставили, или версию забыли поднять в mvcc, а уже пишем в shared-кусок), к примеру.
                    Они чреваты тем, что вместо целостной структуры будет полная галиматья, где полструктуры обновлены одним потоком, а вторые полструктуры — другим. Это, конечно, можно вычислять какой-то функцией, которая в рантайме будет проверять инварианты, но откуда она возьмёт данные, на которые надо откатиться, и как она найдет виноватых?


                    У вас была какая-то готовая и надежная реализация транзакций, или пришлось писать самим и вылавливать затейливые баги?


                    1. Jef239
                      15.09.2016 12:03

                      У нас все намного проще было и колхозней. Гонки — отдельно, транзакции — отдельно.

                      Схема такая. 12 контролеров, в каждом кольцевой буфер событий минут на 20. По читающему треду на контроллер. Потом из читателей данные идут на первый синхронизатор. Он выдает общую очередь событий + срез на последнйи момент времени. это все идет на диагностику и виртуальный контроллер. Потом — второй синхронизатор вместе с результатами диагностики и виртконтроллера. И уже сформированный потом — на тред, ведающий записью.

                      А уже запись — базировалась на атомарных операциях Windows, включая запись на диск в обход дискового кэша. И транзакционно — работала только запись. То есть прочли с диска одной операцией чтения, добавили данные, записали данные одной операцией в.tmp, потом переименовали старое в .bak, а .tmp в основной файл. Операции переименования в NTFS -атомарны, даже если будет перезагрузка компа во время операции — все равно будет или старое или новое состояние, но не промежуточный вариант.

                      Так что фактически — мы паразитировали на транзакционности NTFS. Зато — мы были на 100% уверены, что на диск пишутся корректные данные. И могли перезапускать любые нити без потерь данных. Максимум убытка — перезапрос данных заново с контроллера.

                      А гонки… гонки могли быть в синхронизаторах, в диагностике, в вирт.контролере, но в треде записи на диск — их не было.

                      Само собой, что очереди передачи данных были хорошо отлажены. Основа — TThreadQueue в нашей собственной реализации.


                      1. Igelko
                        15.09.2016 14:31

                        т.е. в треде записи на диск стоял верификатор данных, который анализировал данные на бред и если что-то не так, то сбрасывал рекурсивно все поломавшиеся кеши между слоями и перезапрашивал данные?


                        1. Jef239
                          15.09.2016 15:38

                          Ну не совсем так… Треды друг друга контролировали. Основной контроль — на синхронизаторах, которые сливали несколько потоков в один. Им как раз видно было, откуда данные идут, откуда не идут, какие длины очередей и так далее. Но если синхронизаторы сбоили — то второй синхронизатор перезапускал первый, а тред записи — второй синхронизатор. То есть второй синзронизатор в качестве последнего бастиона. И операция переменования изTMP как окончание транзакции.

                          А при перезапуске — любой тред перезапрашивал предыдущие дать данные, начиная грубо говоря с контрольной точки.

                          Ну и очень много контролей для проверки инвариантов. один ассерт — на 10-20 строк кода (если вне много раз исполняемого цикла).

                          А между слоями — не кэши, там очереди. Причем очереди — рассчитанные на доступ из разных тредов.

                          Там всего 130 тысяч строк кода, это и GUI-клиент, и OPC-сервер, и собственно основной сервер. Так что наворочено защит было много. :-)


                        1. Jef239
                          15.09.2016 15:53

                          Не в качестве рекламы (фирмы уже нет), но вот описание http://www.sysauto.ru/index.php?pageid=508
                          В 2001 году, оказывается внедрение было. Окончание опытной эксплуатации — 2002ой год. И второе внедрение — в 2005ом.

                          Ну и в 2017ом хотят модернизацию на другой тип контроллера.

                          Собственно уникальность там в том, что вроде мы единственные, кто записывает данные с привязкой к номеру скана, то есть синхронно, а все остальные — асинхронно.

                          В 2005ом я смотрел, что на АНГА. За 3 года — ни минуты простоя по вине службы автоматики. То есть и у нас все хорошо, и заводчане с помощью нашей системы отладили программы управления станом в контролерах. А там — 8 тысяч входов и 2 тысячи выходов.


            1. Jef239
              15.09.2016 06:17
              +1

              Корректность тоже сильно увеличивается. Дело в том, что такая техника позволяет раньше перейти к опытной эксплуатации. Стадии, когда имитаторы не выявляют ошибок, мы достигли рано. Но кривая обнаружения ошибок показывала, что на самом деле их ещё порядочно. А комплексная отладка на оборудовании — 2 часа в месяц, только на выходе и планово-предупредительного ремонта. Так что поставили на опытную эксплуатацию и уже во время неё, по логам обнаруживали и исправляли ошибки.

              Таким образом, эта техника позволяет за то же календарное время обнаружить больше ошибок. Ну и исправить их.


              1. Bonart
                15.09.2016 11:20

                Ну в опытной эксплуатации на фоне промышленной все наоборот — надо "ломать и крушить", давая нагрузки и условия заведомо жестче боевых.


                1. Jef239
                  15.09.2016 11:37
                  +1

                  А это уже коммерция не позволяет. Стоимость продукции за сутки-была порядка миллиона долларов. Стоимость всей разработки — НАМНОГО меньше. С одной стороны, 10 остановок предотвратили — мы себя окупили. С другой стороны, одну остановку на нас повесили — фирма разорилась. Это ещё было после кризиса 98ого года когда доллар в 6 раз скакнул.

                  Так что на отладку — 2 часа в месяц. На выходе из планово-предупредительного ремонта были 2 часа планового брака. Но там и без нас хватало желающих потестироваться.

                  Зато с помощью нашего софта умудрились уменьшить время выхода рулона стали с часа до 40 минут. То есть вместо миллиона долларов — полтора. Выигрыш — продукции на полмиллиона в сутки.

                  А нагрузки мы давали на имитаторах. Работали на скоростях в 100 раз больше реальной пиковой нагрузки.

                  На самом деле, заказчики просто прикололись. Пик — 10 тысяч записей одновременно. Мы достигли 100 тысяч. А заказчики в шутку попросили миллион. Ну напряглись и сделали им миллион. Потом оказалось, что они просто пошутили. Но — запас карман не тянет.


                  1. Bonart
                    16.09.2016 01:14

                    98 год?
                    Да, все то, про что я думал как о лучших альтернативах, еще не существовало, даже файберы надо руками писать.
                    Дикий капитализм — цена разработки системы меньше чем недельная прибыль от ее эксплуатации, офигеть.


                    1. Jef239
                      16.09.2016 04:10
                      +1

                      Начало разработки — весна 1999ого. Планировалась окупаемость за год, то есть предотвращение олной остановки стана в месяц. Реально — вышло за сутки, время оцинковки рулона стали с помощью нашей системы уменьшили с часа до 40 минут. Мы писали события с привязкой к сканам и это позволило заводским инженерам убрать все узкие места, мешающие прохождению стальной ленты на высокой скорости.

                      Капитализм там до сих пор такой же. За переделку системы под новый контролер мы с трудом доторговались до цены полутора рулонов стали. :-)

                      Поразило другое. Ежемесячный трехдневный планово-предупредительный ремонт. Вся инженерная команда трое суток не спит. И вот после 3х суток напряженной работы инженер-автоматчик подходит к начальнику:
                      — Стан поехал, все в норме, МОЖНО я пойду спать?

                      И отсутствие рабочих. Ну есть уборщицы, есть крановщики, но у всех остальных — минимум незаконченное высшее. И пустота в цехах. На 3 стана ночью — человек 15 персонала на 2 километра длины цеха.

                      И весь город, в 2001 году переведенный на банковские карточки. Даже в ночных ларьках спрашивали «у вас денюшка али карточка». Питер и Москва до этого уровня до сих пор не дошли.

                      И дыра в потолке, дождь падает почти на контроллеры, сам контролеры прикрыты наклонным листом стали.
                      — Что вы делаете, когда ломаются контроллеры OMRON?
                      — Они не ломаются.

                      Вода льется ручьем в 40 сантиметрах от контролеров, а они — не ломаются.
                      В бытовках за месяц выпадает миллиметр металлической окалины, а они — не ломаются.

                      И Ethernet II — мегабит на толстом коаксиале для связи с контроллером. Надежная техника — техника на очень изученной элементной базе.

                      Файберы — это легковесные thread? Они там не нужны, обычных нитей хватало. Это же не веб-сервер, где операция создания-удаления треда частая.Клиенты подключаются-отключается редко, обычных нитей хватало. Из экзотики — я широко использовал виндовые PIPE, сейчас все бы сделал на TCP/IP. Но тогда PIPE казался более универсальным вариантом.


          1. Jef239
            15.09.2016 06:05
            +2

            Согласен, что обработка — более-менее одинакова для большинства типов исключений, ну кроме исключений по вине неверного ввода пользователя.

            Вы знаете, можно при каждом укусе осы и каждом порезе пальца в реанимацию ложиться. От укуса осы умерла жена знакомого (анафилактический шок), про смерти от столбняка немало написано в литературе. Только в большинстве случаев (99.99%) все ограничивается ерундой.

            Инварианты проверяются НАПРЯМУЮ и довольно регулярно. Для каждого объекта, функциональности, подсистемы, всего приложения. Шансов пропустить разрушения инвариантов — меньше, чем шансов умереть от укуса осы. А если нашли разрушение — все по той же схеме — пересоздать (перезапустить) разрушенный уровень. То есть функциональность, подсистему, приложение. Если 3 раза не помогло — перезапускаем уровень выше. Аналогично — при зависании, оно тоже контролируется. Причем зависание одной подсистемы (нити) контролирует несколько других нитей.

            Само собой, что приложение должно позволять безопасно пересоздавать его части. То есть иметь контрольные точки, с которых можно перезапуститься. Ну как пример: при записи на диск проверяем соблюдение всех инвариантов. А при перезапуске подсистемы архивирования — заново считываем и исходные данные с датчиков и состояние архива после последней записи.

            На самом деле реальная шкала опасности исключений совсем другая.

            1) Опаснее всего NOT EHOUGTH MEMORY, то есть нехватка памяти. В большинстве случаев она означает, что структура кучи разрушена безвозвратно.То есть по хорошему при любой нехватке памяти надо проверять кучу. Причем не системными процедурами (они могут зависнуть), а своими собственными с контролем времени исполнения. Вторая причина — фатальная фрагментация кучи. Третья — ошибка в алгоритме, например разрастание очередей из-за неверных приоритетов. И самое редкое — ну действительно не хватило виртуальной памяти. То есть может так и бывает, но в реальности — не видел.

            2) Следующее по опасности — FPE. То есть корень из отрицательного числа, деление на ноль и так далее. Говорит о реальном разрушении инвариантов. Грубо говоря, вся накопленная матрица — летит в мусорную корзину. Причем если матрица копилась часы — без возможности восстановления, если секунды — есть шансы хранить исходные данные и перезапустить вычисление, но это маловероятно, что поможет.

            3) Далее у нас Access Violation. Тот, что не через нулевой указатель. Это часто означает, что или нам потерли память или мы пытаемся затереть чужую память (а скорее всего уже что-то потерли). Часто бывает следствием оставшегося указателя на уже удаленный объект. Ну или двойного выполнения деструктора. Пережить можно, но только с восстановлением с диска. Или из областей памяти, защищенных контрольной суммой.

            4) Stack Overflow. Обычно возникает при исключениях на обработке исключений. Иногда означает разрушение структуры кучи. Если алгоритм не рекурсивный по своей природе — скорее всего восстановление не поможет. Рекомендация — если Stack Overflow удалось поймать — проверить структуру кучи. Но чаще это гибель приложения и диагностика уровня ОС.

            Это вот действительно опасная четверка. А NRE — это ошибка легкая. Дело в том, что память при NRE не портится. Даже, если NRE на запись — все равно память не испорчена. В большинстве случаев NRE — это пропущенная проверка на NULL в деструкторе или в записи в лог о редкой
            ошибке. Вы правильно писали в https://habrahabr.ru/post/72959/ что в деструкторе надо использовать FreeAndNil. Только забыли упомянуть, что он появился лишь в Delphi 5, и в VCL далеко не всегда используется.Так что получить NRE в деструкторе формы после исключения во время создания формы — не так сложно.

            Теоретически бывают ситуации, когда NRE — следствие порчи памяти, но я такого не припомню. Ошибки типа +-1 (неверный подсчет числа оборотов цикла) бывало, что приводили к NRE. А порча памяти — сама по себе зверь редкий, да и проявляется не так. Скорее уж указатель на уже удаленный объект так проявится.

            А вообще все зависит от того КТО и в каком стиле пишет. Ели вам не лень — приведите ситуации, когда NRE означает что-то серьезное и необнаруживаемое проверкой инвариантов.

            Теперь о мелочах.

            > Во всем остальном коде пытаться перехватить и обработать NRE значит стрелять себе же в ногу.
            НЕВЕРНО. Изоляция должна быть многоуровневой. Try Except Raise EAbort. При этом роль ближайшего except — прежде всего диагностика как можно ближе к точке ошибки. Ресурсы, как правило, отдаются в finally. Перехвата только на одном уровне — просто мало для нахождения ошибок по логу.

            > продолжение работы с гарантированно поломанными инвариантами в надежде на «изоляцию» означает ненулевую вероятность действительно крупных неприятностей:
            Сломанные инварианты далеко не всегда ведут к NRE. Так что, если у вас что-то серьезно — проверяйте инварианты напрямую. И тогда NRE — означает именно NRE и ничего более. Ну а если инварианты регулярно не проверяются — значит у вас та самая «сладкая ложь».

            > Описанная вами методика при NRE дает иллюзию корректности с помощью иллюзии отказоустойчивости, по факту не обеспечивая ни того, ни другого.
            По ФАКТУ — 10 лет 365*24 без сбоев (на обоих станах). 130 тысяч строк кода, примерно 2-4 ошибки на 1000 строк кода осталось (это по динамике обнаружения ошибок). Комплексная отладка на стане не проводилась вообще — там непрерывное производство, время на отладку — 2 часа в месяц. Отладка на имитаторах закончилась после года без багрепортов от персонала и месяца отсутствия сбоев в логах. Ориентировочное ожидаемое количество сбоев в логах — несколько в год.

            > В программе с высокой ценой сбоев, исключений, означающих ошибку в коде, не должно быть вообще.
            Это маркетинговые сказки. Или некомпетентность. Невозможно отловить все ошибки — последние из них будут проявляться с частотой 1 раз в тысячу лет и не отлавливаться на тестах. Это лишь САМООБМАН, что можно найти все ошибки в коде. Написать надежное приложение — можно. Написать безошибочное приложение большого размера — просто нельзя.

            > Для NRE этого добиться просто, для дедлоков — сложнее, но цель должна быть только такой.
            Дедлоск — следствие ошибок в архитектуре. У нас их не было, от слова совсем. А вот NRE вполне могли остаться в частях кода, используемых реже, чем раз в год. Повторюсь, типичный NRE — это пропуск If NOT Assigned при обработке редкой исключительной ситуации. Он вторичен и не важен.

            А цель — надежность, а не корректность. То есть ошибок должно быть мало, а восстановление — работать уверенно. Перекос в любую сторону — ухудшает характеристики программы.

            > У меня был опыт доведения проекта, выдававшего по десятку страниц разных исключений вроде AV и т.п., до вылизанного состояния.
            И что такое для вас «вылизанное состояние»? Сколько сбоев за 10 лет на миллион строк кода? Можете дать свои цифры? Я вот не постеснялся дать свои.


            1. Bonart
              16.09.2016 01:07

              Сколько сбоев за 10 лет на миллион строк кода?

              У меня нет такой статистики, к сожалению — я уже давно не работаю в той компании. Да и 10 лет тому проекту нет и сейчас. Условия тоже иные — требования по надежности менее жесткие, а вот унаследованного технического долга был вагон и маленькая тележка. Тем не менее от исключений, которые сами по себе означали ошибку в коде, мы избавились.


              Это маркетинговые сказки. Или некомпетентность. Невозможно отловить все ошибки

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


              Следующее по опасности — FPE.

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


              Только забыли упомянуть, что он появился лишь в Delphi 5, и в VCL далеко не всегда используется.Так что получить NRE в деструкторе формы после исключения во время создания формы — не так сложно.

              Коммерческий опыт у меня начался с Delphi 7. Для младших версий FreeAndNil несложно написать самому. От формы (а в идеале от GUI вообще) исправность системы ИМХО зависеть не должна.


              PS: Большая статья о вашей системе станет украшением любого тематического сайта.


              1. Jef239
                16.09.2016 05:22
                +1

                > Тем не менее от исключений, которые сами по себе означали ошибку в коде, мы избавились.
                Мы тоже избавились, но не могу гарантировать, что стопроцентно. Срабатывает редкий Assert, ну скажем раз в 5 лет. И при выводе в лог — забыли проставить проверку, что все объекты существуют. И получаем вторичный NRE. Вот такое — могло и остаться.

                Мы старались избавится от всех ошибок, но прикидывали количество неизвестных ошибок по падению частоты их обнаружения и количеству уже исправленных ошибок. Формула, кажется, была у Майерса — http://publ.lib.ru/ARCHIVES/M/MAYERS_Glenford_Dj/_Mayers_G.Dj..html в книге «Надежность программного обеспечения»

                FPE — это из несколько другого проекта, связанного с GPS. Моя текущая работа. Там формируется матрица на основе многих измерений. И FPE — означает, что матрицу можно выкидывать, а накопление вести заново. Ну и из математики получается, что FPE — следствие ошибок в коде, а не шума в измерениях.

                Мы начали с Delphi 4, потом перешли на Delphi 5, потом адаптировали под Delphi 7, но он оказался хуже — больше ошибок в VCL. Нарвавших на ошибку в функции ожидания окончания завершения треда, зависящую от скорости процессора — я решил, что на Delphi 5 надежней оставаться.

                А лучшая книга по написанию компонентов для VCL — это Рей Конопка, описывающий вообще Delphi 1. Кстати, визуальные компоненты AVT (хитрые гриды с фильтрацией и сортировкой по колонкам) у нас писал 17 летний Дмитрий Жемеров (@yole), ныне большая шишка в JetBrains, и один из авторов языка Котлин. :-)

                > От формы (а в идеале от GUI вообще) исправность системы ИМХО зависеть не должна.
                От формы не зависит сохранность данных. Но смсыл системы был именно в GUI.

                Ночь. Два дежурных автоматчика дрыхнут. Стан встал, сигнал по громкой связи. И далее — счет на минуты и секунды. НАДО:
                1) Найти причину аварии. Например — отказавшая лампочка на линейке фотодатчиков контроля положения полосы.
                2) Модернизировать программу в контроллере для обхода аварии.

                Не нашел, не успел — простой запишут на службу автоматики. 3 часа простоя в месяц — 100% премии снимается со ВСЕЙ службы. Не с работника, а со всех.

                Потом, днем — разбор, анализ, подготовка планов на ППР (ежемесячный планово-предупредительный ремонт). Но пока стан стоит — главное время. Быстро найти причину и ликвидировать её. Быстро — пока ещё весь рулон стали не улетел в брак.

                Так что работоспособность GUI — очень важный фактор. Поэтому в GUI любая операция могла делаться многими способами. Отказал один — не смертельно, просто лишние 5 секунд потрачено.

                После внедрения системы — за 3 года ни минуты простоя по вине службы автоматики. Да, датчики иногда отказывают. Но анализ любой аварии и её обход — идет быстро. Собственно в этом и состоит смысл системы — при аварии стана быстро и в 99% случаев автоматически понять, что её вызвало. Это вот та часть, которой я горжусь — автоматический анализ ladder-диаграмм. Причем ещё и многотактный и по нескольким нетворкам. В качестве хвастовства — в единственном случае, когда заводчане не согласились с результатами автоматического анализа, в итоге выяснилось, что автор программы контроллера не разобрался, как работает его код в сложной многотактовой ситуации.

                > PS: Большая статья о вашей системе станет украшением любого тематического сайта
                СПАСИБО. Я пока минусы получаю, когда пытаюсь объяснить, что можно изолировать ошибки, а assert — не надо отключать в боевом коде. Статья о системе — http://www.sysauto.ru/index.php?pageid=508


          1. Jef239
            15.09.2016 06:24

            Ещё одна хитрость. Почти каждая ошибка исправляется 4 раза
            1) Проверяется, что после ошибки система восстанавливается. если нет — исправляется.
            2) Обеспечивается раннее обнаружение ошибок данного класса. То есть на уровне проверок, а не реальной ошибки в коде.
            3) Ошибка исправляется.
            4) Ведет поиск аналогичных ошибок по всему коду.


          1. Jef239
            15.09.2016 11:16
            +1

            На самом деле у вас неправильная модель NRE в голове. Вы считаете, что NRE — это неверная структура данных. На самом деле, наоборот — NRE — это корректная (но упрощенная) структура данных, а вот алгоритм не понимает некоторые редкие упрощенные случаи.

            В такой трактовке понятно, почему NRE не означает нарушение инвариантов?


  1. aspcartman
    13.09.2016 10:14
    -1

    В Obj-C
    ```
    (null).anyMethod == 0/null
    ```
    и никаких nullReferenceException.

    Это было чуть ли не идеальное решение.

    > Обрабатывать это исключение бесполезно: оно означает безусловную ошибку в коде.

    Что? Нет! Ну как, конечно если язык падает при доступе к такой переменной и заставляет ее всячески обворачивать то да, ошибка. А так — нет. И никаких лишних движений.


    1. qadmium
      13.09.2016 10:44

      >Что? Нет! Ну как, конечно если язык падает при доступе к такой переменной и заставляет ее всячески обворачивать то да, ошибка. А так — нет. И никаких лишних движений.

      Framework Design Guidelines

      DO NOT catch System.Exception or System.SystemException in framework code, unless you intend to rethrow.


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


      1. aspcartman
        13.09.2016 11:09

        Ну да, и мне интересен ответ на вопрос «а зачем так сделали?». Вон Swift сейчас с этими оптционалами городит. И зачем оно? А если меня устраивает null повсюду? А если в выражении `var firstCarIsRed = (containers.cars.firstObject.color == red)` меня не волнует, что containers == null / cars.count = 0? А если в 90% кода ситуация аналогичная? Моя мысль в том, что писать код гораздо проще несколько с другой стороны: проще требовать не-нулл в тех местах, где это важно, чем повсеместно проверять на null везде, чтобы оно не упало.


        1. qadmium
          13.09.2016 11:23

          тогда, очевидно, должен быть способ задать поведение по умолчанию. А оно может быть различным для каждого случая, и тогда начинаем использовать?.. и ??, либо различным для каждого типа, тогда нам нужен или Null Object или типизированный null(еще вопрос где описать поведение такого null). Все это очень сложно, гораздо проще не иметь дела с null вообще, и тогда, возможно, мы увидим какой-то сахар для этого в языке


        1. Bonart
          13.09.2016 11:29
          +1

          Когда есть выбор между


          1. "писать код проще" и
          2. "задешево превратить целый класс ошибок периода выполнения в ошибки периода компиляции"

          результат немного предсказуем.


          1. Viacheslav01
            15.09.2016 13:30

            "(null).anyMethod == 0/null"

            Период компиляции говорите? На мой взгляд это полная потеря контроля над работой программы.


  1. areht
    13.09.2016 14:36

    > Бесполезен в остальных ситуация: как только вам потребовалось отличать в коде нулевой объект от остальных — вы имеете эквивалент null вместо null object, что является уже двойным предательством: неполноценный объект, который даже NRE не выбрасывает.

    Смысл null object в том, что бы не проверять, что это null. Зачем вы предлагаете его неполноценным делать, а потом проверять?

    > Поддержка атрибута AllowNull — с одной стороны это очень хорошо, а с другой — аналогичный атрибут у решарпера другой.

    Решарпер настраивается, на сколько я помню.

    > С библиотеками, агрессивно использующими null, требуется довольно много ручной работы по добавлению атрибутов AllowNull
    > Поддержка отключения проверки для отдельных классов и целых сборок

    Очевидно, довольно много ручной работы не требуется, если вынести в отдельную сборку


  1. aspcartman
    13.09.2016 16:11

    Кстати, волею судеб вынужден работать с C#. Вопрос к тем, кому скучно и он долистал до дна коментариев:
    Как в С# симулировать поведение obj-c:
    ```
    Class1 a = obj1.method1().method2()
    ```
    В а должен оказаться null, даже если obj1 == null или obj1.method1() == null? Это какое-то издевательство бегать и ловить эти null'ы. Эквивалентный код:

    ```
    Class1 a = null;
    if (obj1 != null && obj1.method1() != null) { a = obj1.method1() }
    ```


    1. Bonart
      13.09.2016 16:18

      Как в С# симулировать поведение obj-c

      Элементарно:


      var a = obj1?.method1()?.method2()
      


    1. Sirikid
      13.09.2016 16:19

      Я буду обновлять комментарии


  1. Nipheris
    13.09.2016 17:15
    +1

    Надо же, такая холиварная статья, а никто не вспомнил про фичу TypeScript 2. Возможно, пора набраться смелости и пойти на аналогичный шаг и в C# — добавить в компилятор режим, в котором будет использоваться более строгое подмножество языка. Не могу сказать, насколько это выполнимо в принципе, но было бы весьма заманчиво.

    Как говорил Хоар про null-ы у указателей — ошибка на миллиард.


    1. asdf87
      13.09.2016 17:38
      +2

      Судя по комментариям, существует отдельный класс людей, которых эта ошибка вполне устраивает и даже нравится


      1. Bonart
        13.09.2016 18:02
        +1

        Да, не ту часть проблемы я обозначил как пушного зверька


      1. Viacheslav01
        15.09.2016 13:37

        Как я люблю эти разговоры про отдельный класс людей…