Язык “Движения” Google, мультиоценивают оператор возврата альтернатива исключениям?

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

, по моему скромному мнению, добавляя 500 + строковые записи должны определенно использовать StringBuilder.

20
задан reechard 2 September 2011 в 01:03
поделиться

6 ответов

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

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

Почему «утверждения» считаются альтернативой?

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

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

То, для чего вы используете утверждения, на самом деле не то же самое, что вы используете исключения: когда языки программирования, такие как C ++ и Java, предоставляют исключения для «невозможного» ситуациях ( logic_error , ArrayOutOfBoundsException ), они непреднамеренно побуждают некоторых программистов думать, что их программы должны попытаться выйти из ситуаций, когда они действительно вышли из-под контроля. Иногда это уместно, но совет Java не перехватывать RuntimeExceptions существует не зря. Иногда бывает полезно поймать одного, поэтому они существуют.

14
ответ дан 30 November 2019 в 00:59
поделиться

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

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

class config {
   // throws key_not_found
   string get( string const & key );
   template <typename T> T get_as( string const & key ) {
      return boost::lexical_cast<T>( get(key) );
   }
};

Теперь проблема состоит в том, как поступить, если ключ не был найден. Если вы используете коды возврата (скажем, в ходу), проблема в том, что get_as должен обрабатывать код ошибки из get и действовать соответственно. Поскольку он на самом деле не знает, что делать, единственная разумная вещь - это вручную распространять ошибку в восходящем направлении:

class config2 {
   pair<string,bool> get( string const & key );
   template <typename T> pair<T,bool> get_as( string const & key ) {
      pair<string,bool> res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast<T>(res.first);
          } catch ( boost::bad_lexical_cast const & ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast<T>(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

Разработчик класса должен добавить дополнительный код для пересылки ошибок, и этот код смешивается с собственно логика проблемы. В C ++ это, вероятно, более обременительно, чем в языке, предназначенном для множественных назначений ( a, b = 4,5 ), но все же, если логика зависит от возможной ошибки (здесь вызывается lexical_cast следует выполнять только в том случае, если у нас есть реальная строка), тогда вам все равно придется кэшировать значения в переменные.

единственная разумная вещь - это распространение ошибки вручную вверх по течению:

class config2 {
   pair<string,bool> get( string const & key );
   template <typename T> pair<T,bool> get_as( string const & key ) {
      pair<string,bool> res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast<T>(res.first);
          } catch ( boost::bad_lexical_cast const & ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast<T>(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

Реализующий класс должен добавить дополнительный код для пересылки ошибок, и этот код смешивается с фактической логикой проблемы. В C ++ это, вероятно, более обременительно, чем в языке, предназначенном для множественных назначений ( a, b = 4,5 ), но все же, если логика зависит от возможной ошибки (здесь вызывается lexical_cast следует выполнять только в том случае, если у нас есть реальная строка), тогда вам все равно придется кэшировать значения в переменные.

единственная разумная вещь - это распространение ошибки вручную вверх по течению:

class config2 {
   pair<string,bool> get( string const & key );
   template <typename T> pair<T,bool> get_as( string const & key ) {
      pair<string,bool> res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast<T>(res.first);
          } catch ( boost::bad_lexical_cast const & ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast<T>(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

Разработчик класса должен добавить дополнительный код для пересылки ошибок, и этот код смешивается с фактической логикой проблемы. В C ++ это, вероятно, более обременительно, чем в языке, предназначенном для множественных назначений ( a, b = 4,5 ), но все же, если логика зависит от возможной ошибки (здесь вызывается lexical_cast следует выполнять только в том случае, если у нас есть реальная строка), тогда вам все равно придется кэшировать значения в переменные.

4
ответ дан 30 November 2019 в 00:59
поделиться

Это не Go, но в Lua многократный возврат является чрезвычайно распространенной идиомой для обработки исключений.

Если бы у вас была такая функция, как

function divide(top,bottom)
   if bottom == 0 then 
        error("cannot divide by zero")
   else
        return top/bottom
   end
end

, то когда bottom было 0, возникнет исключение и выполнение программы остановится, если вы не заключите функцию div в pcall (или защищенный вызов) .

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

Следующий (надуманный) фрагмент Lua показывает это при использовании:

local top, bottom = get_numbers_from_user()
local status, retval = pcall(divide, top, bottom)
if not status then
    show_message(retval)
else
    show_message(top .. " divided by " .. bottom .. " is " .. retval)
end

Конечно, вам не нужно использовать pcall , если функция, которую вы вызываете, уже возвращает в форме status, value_or_error .

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

3
ответ дан 30 November 2019 в 00:59
поделиться

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

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

Подумайте об альтернативном случае, когда исключительные коды возврата условно обрабатываются в несколько десятков вызовов методов. Как будут выглядеть следы стека с точки зрения номера строки, вызывающей нарушение?

2
ответ дан 30 November 2019 в 00:59
поделиться

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

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

Основное различие между обработкой ошибок посредством возвращаемых значений и исключений состоит в том, что исключения заставляют программиста иметь дело с необычными условиями. У вас никогда не может быть «тихой ошибки», если вы явно не поймаете исключение и ничего не сделаете в блоке catch. С другой стороны, вы получаете неявные точки возврата везде внутри функций, что может привести к другим типам ошибок. Это особенно распространено в C ++, где вы управляете памятью явно и вам нужно убедиться, что вы никогда не потеряете указатель на что-то, что вы разместили.

Пример опасной ситуации в C ++:

struct Foo {
    // If B's constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

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

Некоторые языки имеют как множественные возвращаемые значения, так и исключения (или аналогичные механизмы). Одним из примеров является Lua .

struct Foo {
    // If B's constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

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

Некоторые языки имеют как несколько возвращаемых значений, так и исключения (или аналогичные механизмы). Одним из примеров является Lua .

struct Foo {
    // If B's constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

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

Некоторые языки имеют как несколько возвращаемых значений, так и исключения (или аналогичные механизмы). Одним из примеров является Lua .

2
ответ дан 30 November 2019 в 00:59
поделиться

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

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// return value type
template <typename T> 
struct RV {
    int mStatus;
    T mValue;

    RV( int status, const T & rv ) 
        : mStatus( status ), mValue( rv ) {}
    int Status() const { return mStatus; }
    const T & Value() const {return mValue; }
};

// example of possible use
RV <string> ReadFirstLine( const string & fname ) {
    ifstream ifs( fname.c_str() );
    string line;
    if ( ! ifs ) {
        return RV <string>( -1, "" );
    }
    else if ( getline( ifs, line ) ) {
        return RV <string>( 0, line );
    }
    else {
        return RV <string>( -2, "" );
    }
}

// in use
int main() {
    RV <string> r = ReadFirstLine( "stuff.txt" );
    if ( r.Status() == 0 ) {
        cout << "Read: " << r.Value() << endl;
    }
    else {
        cout << "Error: " << r.Status() << endl;
    }
}
1
ответ дан 30 November 2019 в 00:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: