Pergunta

Parece-me alternativas do Google para exceções são

  • GO: multi-valor de retorno "retorno val, err";
  • GO, C ++: cheques nulo (regresso antecipado)
  • GO, C ++: "manipular o erro maldito" (meu termo)
  • C ++: Assert (expressão)

  • GO: Adiar / pânico / recuperar são a linguagem funcionalidades adicionadas após essa pergunta foi feita

É multi-valor de retorno bastante útil para atuar como uma alternativa? Por que "afirma" consideradas alternativas? O Google acha que O.K. se um programa pára se ocorrer um erro que não é tratada corretamente?

eficaz GO: retorno múltipla valores

Uma das características incomuns de ir é que as funções e métodos podem retornar vários valores. Isto pode ser usado para melhorar um par de idiomas desajeitados em programas C:. Na-banda retornos de erro (como -1 para EOF) e modificando um argumento

Em C, um erro de gravação é sinalizado por um contagem negativa com o código de erro secretado afastado em um local volátil. Em Go, Write pode retornar uma contagem e um de erro: “Sim, você escreveu alguns bytes, mas não todos eles, porque você encheu o dispositivo". A assinatura do * File.Write no pacote do SO é:

func (file *File) Write(b []byte) (n int, err Error)

e, como diz a documentação, ele devolve o número de bytes escrito e um erro não-zero quando n! = len (b). Este é um estilo comum; Veja o seção sobre manipulação de erro para mais exemplos.

eficaz GO: parâmetros de resultado nomeados

O retorno ou de resultado "parâmetros" de um Go função pode ser dada nomes e utilizados como variáveis ??regulares, assim como os parâmetros de entrada. Quando chamado, eles são inicializados para zero valores para os seus tipos quando o função começa; se a função executa uma instrução de retorno sem argumentos, os valores atuais do parâmetros de resultados são utilizados como o valores devolvidos.

Os nomes não são obrigatórios, mas eles pode tornar o código mais curto e mais claro: eles são documentação. Se o nome do resultados de nextInt torna-se óbvio que retornou int é qual.

func nextInt(b []byte, pos int) (value, nextPos int) {

Como os resultados nomeados são inicializados e amarrado a um retorno sem adornos, eles podem simplificar o bem como esclarecer. Aqui está uma versão de io.ReadFull que os usa assim:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
  for len(buf) > 0 && err == nil {
    var nr int;
    nr, err = r.Read(buf);
    n += nr;
    buf = buf[nr:len(buf)];
  }
  return;
}

Por que Go não tem exceções ?

As exceções são uma história similar. Uma série de projetos para exceções foram propostos, mas cada um adiciona complexidade significativa para a linguagem e tempo de execução. Pela sua própria natureza, as exceções abrangem funções e talvez até goroutines; eles têm amplo implicações. Há também preocupação com o efeito que teria sobre as bibliotecas. Eles são, por definição, excepcional ainda experiência com outros idiomas que os suportam mostrar que têm profundo efeito sobre a especificação de biblioteca e interface. Seria bom para encontrar um design que lhes permite ser verdadeiramente excepcional sem incentivar erros comuns para se transformar em fluxo de controle especial que requer todo programador para compensar.

Como os genéricos, exceções continuam a ser uma questão em aberto.

Guia de Estilo ++ C Google: exceções

Decisão:

Em seu rosto, os benefícios de ucantar exceções superam os custos, especialmente em novos projetos. Contudo, para o código existente, a introdução de exceções tem implicações em todos os o código dependente. Se exceções podem ser propagado além de um novo projeto, também se torna problemático para integrar o novo projeto para existente sem exceção código. porque a maioria código C ++ existente no Google não é preparado para lidar com exceções, é comparativamente difícil adoptar novo código que gera exceções.

Uma vez que o código existente do Google é não exceção tolerantes, os custos de usando exceções são um pouco maior que os custos em um novo projeto. O processo de conversão seria lenta e propenso erro de. Nós não acreditamos que alternativas disponíveis para exceções, como os códigos de erro e afirmações, introduzir uma significativa fardo.

O nosso conselho contra o uso de exceções é Não por base filosófica ou razões morais, mas as práticas. Porque nós gostaríamos de usar o nosso projetos open-source do Google e é difícil fazê-lo se os projetos usar exceções, precisamos desaconselham exceções no Google projetos open-source também. Coisas provavelmente seria diferente se tivéssemos para fazer tudo de novo do zero.

GO: Adiar, pânico e recuperar

declarações Adiar permitir-nos a pensar sobre o fechamento de cada arquivo logo após abri-lo, garantindo que, independentemente do número de declarações de retorno na função, os arquivos serão fechados.

O comportamento das demonstrações Adiar é simples e previsível. Existem três regras simples:

1. argumentos de uma função diferido são avaliados quando a declaração Adiar é avaliada.

Neste exemplo, a expressão "i" é avaliado quando a chamada println é adiada. A chamada diferido irá imprimir "0" após o retorno da função.

    func a() {
         i := 0
         defer fmt.Println(i)
         i++
         return    
    }

2. chamadas de função diferidos são executadas em ordem Last In First Out após a função circundantes retornos Esta função imprime "3210":.

     func b() {
        for i := 0; i < 4; i++ {
            defer fmt.Print(i)
        }   
     }

3. funções diferidas podem ler e atribuir aos valores de retorno chamados da função retornar.

Neste exemplo, um incrementos de função diferido o valor de retorno i após a função circundantes retornos. Assim, esta função retorna 2:

    func c() (i int) {
        defer func() { i++ }()
        return 1 
    }

Isto é conveniente para modificar o valor de retorno de erro de uma função; vamos ver um exemplo disso em breve.

O pânico é um built-in função que interrompe o fluxo normal de controle e começa a entrar em pânico. Quando a função F chama pânico, execução de F pára, quaisquer funções diferidos em F são executados normalmente, e então F retorna ao seu chamador. Para o interlocutor, F, em seguida, se comporta como uma chamada a entrar em pânico. O processo continua até a pilha até que todas as funções na goroutine atual retornaram, altura em que o programa trava. Pânicos pode ser iniciada pela invocação de pânico diretamente. Eles também podem ser causados ??por erros de execução, tais como out-of-limites acessos matriz.

Recuperar é uma função interna do que retoma o controle de um goroutine em pânico. Recuperar só é útil dentro diferido funções. Durante a execução normal, uma chamada para recuperar retornará nil e não têm outro efeito. Se o goroutine atual está em pânico, uma chamada para recuperar irá capturar o valor dado a entrar em pânico e retomar a execução normal .

Aqui está um programa de exemplo que demonstra a mecânica de pânico e adiar:

<snip>

Para um exemplo real de pânico e recuperar, ver o pacote json da biblioteca padrão Go. ele decodados de des-JSON codificada com um conjunto de funções recursivas. Quando JSON malformada for encontrado, o analisador chama de pânico é para relaxar a pilha para a chamada de função de nível superior, que se recupera do pânico e retorna um valor de erro apropriada (ver o 'erro' e funções 'desempacotar' em decode.go) . Há um exemplo semelhante desta técnica na rotina de compilação do pacote de expressão regular. A convenção nas bibliotecas Go é que, mesmo quando um usos pacote pânico internamente, sua API externo ainda apresenta valores de retorno de erro explícito.

Outros usos de adiar (além do exemplo File.Close () dada anteriormente) incluem a liberação de um mutex:

mu.Lock()  
defer mu.Unlock
Foi útil?

Solução

Várias devoluções não são exclusivos de Go, e eles não são um substituto para exceções. Em C (ou C ++) termos, que são um substituto concisa e de fácil utilização para o regresso de uma estrutura (objecto) contendo vários valores.

Eles fornecem um meio conveniente de erros indicando, se isso é tudo que você quer dizer.

alternativas Porque são "afirma" considerados?

Afirma são inicialmente para depuração. Eles parar o programa em situações em que está em um estado "impossível", que o projeto diz que não deve acontecer, mas que tem de qualquer maneira. Retornando um erro é improvável que ajuda muito. A base de código, obviamente, não funciona ainda, assim como na terra pode-lo com sucesso recuperar? Por que você ainda quer que, quando há um erro que precisa de atenção?

Usando afirma no código de produção é um pouco de uma questão diferente - obviamente, há desempenho e tamanho do código preocupações, de modo a abordagem usual é para removê-los uma vez que sua análise de código e testes de tê-lo convencido de que as situações "impossíveis" são realmente impossível. Mas, se você estiver executando o código a este nível de paranóia, que é própria de auditoria, então você provavelmente está também paranóico que, se você deixá-lo continuar a correr em um estado "impossível", então ele pode fazer algo perigosamente quebrado: corrompendo dados valiosos, invadindo a alocação de pilha e talvez criar vulnerabilidades de segurança. Então, novamente, você só quer encerrar o mais depressa possível.

O material que você usar afirma para realmente não é o mesmo que o material que você usar exceções para: quando linguagens de programação como C ++ e Java prever excepções para situações "impossíveis" (logic_error, ArrayOutOfBoundsException), eles involuntariamente incentivar alguns programadores para pensar que seus programas deve tentar recuperar de situações em que realmente eles estão fora de controle. Às vezes isso é apropriado, mas o conselho Java não RuntimeExceptions captura está lá por um bom motivo. Muito ocasionalmente, é uma boa idéia para pegar um, e é por isso que eles existem. Quase sempre não é uma boa idéia para pegá-los, o que significa que equivaleria a interromper o programa (ou pelo menos o fio) de qualquer maneira.

Outras dicas

Você deve ler um par de artigos sobre exceções para perceber que os valores de retorno não são exceções. Não no C 'in-band' forma ou de qualquer outra forma.

Sem entrar em uma discussão profunda, as exceções são feitos para serem jogados em que a condição de erro é encontrado e capturado em que a condição de erro pode ser significativamente tratada. valores de retorno só são processados ??na primeira função até a pilha de hierarquia, que poderiam ou não como processar o problema. Um exemplo simples seria um arquivo de configuração que pode recuperar valores como strings, e também suporta o processamento em declarações de retorno digitados:

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) );
   }
};

Agora, o problema é como você lida se a chave não foi encontrado. Se você usar códigos de retorno (digo no go-way) o problema é que get_as deve lidar com o código de erro de get e agir em conformidade. Como ele realmente não sabe o que fazer, a única coisa sensata é manualmente propagar o montante de erro:

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
      }
   }
}

O implementador da classe deve adicionar código extra de transmitir os erros, e que código é misturado com a lógica real do problema. Em C ++ esta é provavelmente mais onerosa do que em uma linguagem projetada para várias atribuições (a,b=4,5), mas ainda, se a lógica depende do possível erro (aqui chamando lexical_cast só deve ser realizada se temos uma seqüência real), então você terá que valores de cache em variáveis ??de qualquer maneira.

Não é Go, mas na Lua, o retorno múltipla é uma expressão extremamente comum para tratamento de exceções.

Se você tivesse uma função como

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

Então, quando bottom foi de 0, uma exceção seria levantado e execução do programa vai suspender, a menos que envolveu a função divide em uma pcall (ou chamada protegida) .

pcall sempre retorna dois valores:. O resultado primeiro é um revelador boolean se a função retornado com êxito, e o segundo resultado é o valor de retorno ou a mensagem de erro

As seguintes (inventados) Lua fragmento mostra esta em uso:

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

Claro, você não tem que usar pcall, se a função que você está chamando já retorna na forma de status, value_or_error.

retorno múltipla tem sido bom o suficiente para Lua por vários anos, por isso, enquanto isso não garantir que é bom o suficiente para Go, é favorável à ideia.

Sim, valores de retorno de erro são bons, mas não captam o verdadeiro significado de manipulação de exceção ... que é a capacidade e gestão de casos excepcionais em que não se normalmente a intenção.

O Java (ie) O projeto considera Exceções IMO para ser fluxo de trabalho válido cenários e eles têm um ponto sobre a complexidade das interfaces e bibliotecas que têm que declarar e versão estes exceção lançada, mas, infelizmente Exceções servir um papel importante no domino da pilha.

Pense no caso alternativo, onde os códigos de retorno excepcionais são condicionalmente tratadas de várias chamadas dúzia de métodos de profundidade. O que empilhar traços semelhante em termos de onde o número da linha ofensiva é?

Esta questão é um pouco complicado para responder objetivamente e opiniões sobre exceções podem diferir bastante.

Mas se eu fosse para especular, eu acho que as exceções principal razão não está incluído no Go é porque ele complica o compilador e pode levar a implicações não triviais ao escrever bibliotecas. Exceções é difícil de acertar, e eles priorizadas recebendo algo de trabalho.

A principal diferença entre o tratamento de erros através de valores de retorno e exceções é que as exceções forças do programador para lidar com condições incomuns. Você nunca pode ter um "erro silenciosa" a menos que você pegar explicitamente uma exceção e não fazer nada no bloco catch. Por outro lado, você ganha pontos de retorno implícito em todos os lugares dentro de funções que pode levar a outros tipos de bugs. Isto é especialmente prevalente com C ++ onde você gerenciar a memória explícita e necessidade de certificar-se de que você nunca perde um ponteiro para algo que você tenha alocado.

Exemplo de situação perigosa em 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;
};

Vários valores de retorno faz com que seja mais fácil de implementar o tratamento de erros com base valor de retorno, sem ter que depender de fora argumentos para funções, mas não muda nada fundamentalmente.

Algumas línguas têm ambos vários valores de retorno e exceções (ou mecanismos similares). Um exemplo é Lua .

Aqui está um exemplo de como vários valores de retorno pode funcionar em C ++. Eu não iria escrever este código eu mesmo, mas eu não acho que isso é totalmente fora de questão de usar essa abordagem.

#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;
    }
}

Se você precisar a forma como C ++ de fazer um "nulo" boost uso objeto :: opcional . Você testá-lo como um boolean e se avalia verdade, então você desreferenciava-lo para um T válido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top