Введение

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

Пример

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

Практические последствия

Ранние программисты обрабатывали потенциально исключительные случаи, такие как деление, используя конвенцию, требующую, чтобы призывная рутина проверяла входные данные перед вызовом функции деления. Это имело две проблемы: во-первых, это сильно обременяло весь код, который выполнял деление (очень распространенная операция); во-вторых, это нарушало принципы "Не повторяй себя" и инкапсулирования, первый из которых предполагал устранение дублированного кода, а второй предполагал, что связанный с данными код должен содержаться в одном месте (в этом примере деления проверка ввода была сделана отдельно). Для вычисления, более сложного, чем деление, вызвателю может быть трудно распознать недействительный вход; в некоторых случаях определение действительности входа может быть столь же дорогостоящим, как выполнение всего вычисления. Целевая функция также может быть изменена и тогда ожидает различных предварительных условий, чем вызывающий; такая модификация потребует изменений в каждом месте, где вызвана функция.

Решение

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

Глобальная переменная для статуса возвращения

Подобно аргументу "out", глобальная переменная может хранить, какая ошибка произошла (или просто произошла ли ошибка). Например, если происходит ошибка, и она сигнализируется (обычно, как выше, незаконным значением, таким как -1), переменная errno Unix устанавливается, чтобы указать, какое значение произошло. Использование глобального имеет свои обычные недостатки: безопасность потоков становится проблемой (современные операционные системы используют безопасную версию errno), и если используется только один глобальный тип ошибки, его тип должен быть достаточно широким, чтобы содержать всю интересную информацию обо всех возможных ошибках в системе.

Исключения

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

Ручно созданные гибридные типы

В C, когда это возможно, обычный подход заключается в использовании типа данных, намеренно более широкого, чем строго необходимо для функции. Например, стандартная функция getchar определяется с возвратом типа int и возвращает значение в диапазоне [0, 255] (диапазон неподписанного char) при успешном выполнении или значение EOF (реализация определена, но вне диапазона неподписанного char) в конце ввода или ошибку считывания.

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

В языках с указателями или ссылками одним из решений является возвращение указателя к значению, а не самого значения. Этот показатель возвращения может быть затем установлен на нуль, чтобы указать ошибку. Обычно он подходит для функций, которые в любом случае возвращают указатель. Это имеет преимущество в производительности по сравнению с обработкой исключений в стиле ООП, с недостатком, что небрежные программисты могут не проверить возвращаемое значение, что приводит к сбою при использовании недействительного указателя. Является ли указатель нулевым или нет, это еще один пример проблемы предиката; null может быть флагом, указывающим на неудачу, или значением указателя, возвращенным успешно. Общая модель в среде UNIX устанавливает отдельную переменную для указания причины ошибки. Примером этого является функция fopen стандартной библиотеки C.

Неявные гибридные типы

В динамически типизированных языках, таких как PHP и Lisp, обычный подход заключается в возврате false, none или null, когда вызов функции не удается. Это работает, возвращая тип, отличный от обычного типа возврата (таким образом, расширяя тип). Это динамически типизированный эквивалент возвращения нулевого указателя. Например, числовая функция обычно возвращает число (int или float), и хотя ноль может быть действительным ответом, false - нет. Аналогичным образом, функция, которая обычно возвращает строку, может иногда возвращать пустую строку в качестве действительного ответа, но возвращать ложь при сбое. Этот процесс жонглирования типа требует осторожности при тестировании возвращаемого значения: например, в PHP используйте === (т.е. равно и того же типа), а не просто == (т.е. равно, после автоматического преобразования типа). Он работает только тогда, когда исходная функция не предназначена для возвращения булевого значения, и все еще требует, чтобы информация об ошибке передавалась другими средствами.

Явно гибридные типы

В Haskell и других функциональных языках программирования, это обычное использование типа данных, который является точно таким же большим, как это должно быть, чтобы выразить любой возможный результат. Например, можно написать функцию деления, которая возвращает тип Maybe Real, и функцию getchar, возвращающую Either String Char. Первый - это тип опции, который имеет только одно значение ошибки, Ничто. Второй случай - это маркированный союз: результатом является либо строка с описательным сообщением об ошибке, либо успешно прочитаный символ. Система вывода типов Хаскелла помогает обеспечить, чтобы вызватели справлялись с возможными ошибками. Поскольку условия ошибки становятся явными в типе функции, взгляд на ее подпись сразу же говорит программисту, как обращаться с ошибками. Кроме того, маркированные союзы и типы опций образуют монады, когда наделены соответствующими функциями: это может быть использовано для поддержания кода в порядке путем автоматического распространения необработанных условий ошибки.