Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Предлагаю концепцию Faultable type — модификацию типов по аналогии с nullable. Суть — в том, что этот тип может принимать особое значение если при выполнении программы что-то пошло не так: не найден нужный файл, произошло деление на 0 и т.д. Этот тип предназначен для работы с ошибками как можно ближе к месту их возникновения, в отличие от исключений, которые больше подходят для того, чтобы работать с ошибками централизованно. Чтобы не переключать 100500 раз раскладку, буду называть его «ошибаемым типом», как бы забавно это не звучало.
Объявление
Любую переменную или аргумент функции можно объявить ошибаемой.
faultable int number;
falutable string fileName;
faultable Customer user;
void markCustomer (faultable string name){
...
}
Возвращаемое значение функции становится ошибаемым по факту, если в какой-то из ветвей есть возврат ошибки либо ошибаемого значения.
Автоматический тип считается неошибаемым, если не указано обратное.
Присвоение ошибки
Основная особенность, ради которой вводится этот тип — возможность присваивать ему особое значение — значение ошибки с помощью специального ключевого слова. Значение ошибки может сопровождаться описанием или классом ошибки.
number = fault;
filename = fault("Невозможно создать файл");
return(fault(EOleException));
Присвоения обычному типу
В отличие от обнуляемых типов, ошибаемый тип может напрямую передавать значение в переменные неошибаемого типа и наоборот:
faultable int selNumber = 255;
...
int outNumber = selNumber;
Но при попытке присвоить неошибаемому значению ошибку, компилятор должен выйти из функции, возвращая ошибку:
faultable int selNumber = fault;
...
int outNumber = selNumber;//Здесь функция должна завершиться
Ошибка должна выбрасываться, если ошибка, возвращаемая функцией ничему не присваивается
int parseMyDate(string strDate)
{
...
return(fault);
}
...
parseMyDate("This is not a date");//Выброс
Доступ к любому полю, методу или свойству объекта или структуры, которой присвоена ошибка, ведёт себя так же, как чтение из ошибаемого поля, содержащего ошибку:
faultable Customer payer;
...
payer=fault("Customer not found");
...
faultable string name = customer.name;//Здесь не возникнет ошибки
В случае поддержки механизма ошибаемых типов вместе с механизмом исключений также можно добавить оператор, выбрасывающий в этом случае обычные исключения, которые в дальнейшем можно обрабатывать с помощью try...catch. Либо вовсе сделать такое поведение по умолчанию.
Присвоение без выброса ошибки
Чтобы при присвоении ошибаемого типа неошибаемому не возникло ошибки, достаточно использовать специальный оператор.
name=customer.name nofault;//Ошибка не будет выброшена, но нового значения переменной name присвоено не будет
Этому оператору можно передать значение, которое будет присвоено в случае ошибки
name=customer.name nofault "unknown";
А также перечень ошибок, для которых он сработает:
var file=openFile(path) nofault(EFileNotExists) createFile(path);
Ну и конечно можно просто проверить переменную на наличие в ней флага ошибки
if (!variable.isFault()){
outVar = variable;
}
else
{
...
}
Плюсы
Простота и лаконичность
Согласитесь, что amount = struct.getField("amount").toInteger() nofault 0;
выглядит короче, чем
try
{
amount = struct.getField("amount").toInteger();
}
catch
{
amount = 0;
}
Накладные расходы
Многие программисты не любят исключения за то, что они создают много накладных расходов. Это конечно зависит от конкретной реализации языков, но есть слишком много нюансов, которые мешают оптимизировать обработку исключений. Данный же способ обработки ошибок почти бесплатен.
Отладка
Ошибаемый тип позволяет самому выбирать место, в котором возврат ошибки будет приводить к остановке. Вам не придётся пробираться через каскады catch/throw, не придётся думать «как я сюда попал».
Многопоточность
Так как переменная с возвратом ошибки ничем не отличается от обычной переменной, следовательно можно забыть о множестве сопутствующих проблем.
Недостатки
Нельзя просто взять и добавить поддержку в существующий язык
Точнее, если просто добавить поддержку ошибаемых типов, ими невозможно будет полноценно пользоваться. Нужно, чтобы по-новому ошибку умели возвращать все операторы языка. Нужно, чтобы этим механизмом умела пользоваться стандартная библиотека языка. В противном случае им либо не будут пользоваться, либо оконечный код превратится в кашу, в которой тут обработка ошибок реализована на новом механизме, тут — по-старому, а тут — то так, то этак в зависимости от непонятно чего. Если уж добавлять такую возможность, то либо в новые языки, либо при крупном обновлении существующего.
Неудобно обрабатывать ошибки централизованно
Этот способ хорош, когда ошибку можно обработать в месте её появления. Когда же нужно ловить все ошибки подряд и записывать в лог — такой способ совершенно неудобен. Хотя, как написано в начале статьи, для этого он и не предназначен.