Por que ter um método que retorna um bool / int e tem o objeto real como parâmetro de saída?

12

Vejo o seguinte padrão de código em todo o lugar na base de código da minha empresa (aplicativo .NET 3.5):

bool Foo(int barID, out Baz bazObject) { 
    try { 
            // do stuff
            bazObject = someResponseObject;

            return true;
    }
    catch (Exception ex) { 
        // log error
        return false;
    }
}

// calling code
BazObject baz = new BazObject();
fooObject.Foo(barID, out baz);

if (baz != null) { 
    // do stuff with baz
}

Estou tentando Fooentender por que você faria isso, em vez de o método simplesmente pegar o ID e retornar um Bazobjeto, em vez de retornar um valor que não é usado e o objeto real ser um parâmetro ref ou output.

Há algum benefício oculto desse estilo de codificação que estou perdendo?

Wayne Molina
fonte
No seu exemplo, bazser nulle o boolser retornado nãofalse são equivalentes. nunca é , portanto, a menos que seja atualizado antes do lançamento , quando retornado nunca será . Ele iria ajudar tremendamente se a especificação para estavam disponíveis. De fato, esse talvez seja o problema mais sério exibido por esse código. new BazObject()nullbazObjectExceptionFoofalsebaznullFoo
21414 Steve
Minha memória está nebulosa, como foi há muito tempo, mas acho que eu tinha estragado o exemplo e estava verificando se "baz" era falso, não se era nulo. Em qualquer caso, o padrão parecia realmente arcaico para mim, como foi de VB6 e o desenvolvedor nunca se preocupou em melhorar o seu código (que ele não fez)
Wayne Molina

Respostas:

11

Você costuma usar esse padrão para escrever código como este:

if (Foo(barId, out bazObject))
{
  //DoStuff with bazobject
}

é usado no CLR for TryGetValue na classe de dicionário, por exemplo. Evita redundância, mas os parâmetros out e ref sempre pareciam um pouco confusos para mim

Homde
fonte
1
+1, esta é a razão pela qual, embora eu tendem para retornar um objeto com tanto o boolean e o objeto, em vez de devolvê-los tanto separadamente
PDR
Indicar isso como a resposta, uma vez que explica o motivo, embora o consenso pareça estar bastante desatualizado, exceto em circunstâncias específicas.
Wayne Molina
Esta resposta justifica o uso desse padrão. Mas se for TODO O SEU código, provavelmente é uma prática ruim como o lado de Sean.
Codism
11

Este é um código antigo de estilo C, antes de haver exceções. O valor de retorno indicava se o método foi bem-sucedido ou não e, se fosse, o parâmetro seria preenchido com o resultado.

No .net, temos exceções para esse fim. Não deve haver razão para seguir esse padrão.

[edit] Obviamente, há implicações de desempenho no tratamento de exceções. Talvez isso tenha algo a ver com isso. No entanto, nesse trecho de código já existe uma exceção. Seria mais limpo deixá-lo subir a pilha até ficar preso em um local mais apropriado.

Sean Edwards
fonte
Não. Não posso concordar com isso. Nunca use uma exceção para uma condição esperada. Se você estiver analisando uma string para um int, por exemplo, é sempre possível que alguém passe uma string não analisável. Você não deve lançar uma exceção nesse caso
pdr
Não foi o que eu disse. Se você ler o código que o OP postou, ocorre explicitamente um caso de exceção, mas o método o captura e retorna false. Esta pergunta está relacionada ao tratamento de erros, às condições não esperadas.
Sean Edwards
Sim, parece ser usado principalmente em métodos que carregam dados do banco de dados, por exemplo, if (baz.Select())mas, na maioria das vezes, o valor de retorno é jogado fora e o valor é verificado em relação a propriedades nulas ou alguma.
Wayne Molina
@ Sean, veja o que você está dizendo, eu apenas me oponho à frase "No .NET, temos exceções para esse fim". Exceções servem a um propósito completamente diferente para mim
PDR
1
Ok, deixe-me esclarecer. É verdade que você não deve apenas adicionar um booleano a todos os métodos para explicar erros de codificação em argumentos inválidos. Mas onde a entrada para um método vem de um usuário, e não de um desenvolvedor, você deve tentar manipular a entrada sem gerar uma exceção, caso entenda errado.
pdr 13/05
2

Dado o trecho de código, parece completamente inútil. Para mim, o padrão de código inicial sugere que nulo para BazObject seria um caso aceitável e o retorno bool é uma medida de determinar um caso de falha com segurança. Se o seguinte código fosse:

// calling code
BazObject baz = new BazObject();
bool result = fooObject.Foo(barID, out baz);

if (result) { 
    // do stuff with baz
    // where baz may be 
    // null without a 
    // thrown exception
}

Isso faria mais sentido para mim fazer dessa maneira. Talvez este seja um método que alguém antes de você estava usando para garantir que o baz estava sendo passado por referência sem entender como os parâmetros do objeto realmente funcionam em C #.

Joel Etherton
fonte
Eu acho que foi escrito por um membro atual da equipe. Talvez isso é algo que deve se preocupar com O_O
Wayne Molina
@Wayne M: Menos de uma preocupação do que uma potencial oportunidade de treinamento :)
Joel Etherton
1

Às vezes, esse padrão é útil quando você, o responsável pela chamada, não deve se importar se o tipo retornado é um tipo de referência ou valor. Se você estiver chamando esse método para recuperar um tipo de valor, esse tipo de valor precisará de um valor inválido estabelecido (por exemplo, double.NaN) ou você precisará de alguma outra maneira de determinar o sucesso.

Kevin Hsu
fonte
Isso faz sentido. Parece um pouco estranho, mas isso soa como uma razão válida.
Wayne Molina
0

A idéia é retornar um valor que indique se o processo foi bem-sucedido, permitindo códigos como este:

Baz b;
if (fooObject.foo(id, out b)) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Se o objeto não puder ser nulo (o que parece correto no seu exemplo), é melhor fazer o seguinte:

Baz b = fooObject.foo(id);
if (b != null) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Se o objeto puder ser nulo, as exceções são o caminho a seguir:

try {
   Baz b = fooObject.foo(id);
}
catch (BazException e) {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

É um padrão comum usado em C, onde não há exceções. Permite que a função retorne uma condição de erro. Exceções são geralmente uma solução muito mais limpa.

Michael K
fonte
Especialmente porque o código já está lançando uma exceção.
David Thornley