“TryParse / Parse como” padrão: qual é a melhor maneira de implementá-lo
-
06-07-2019 - |
Pergunta
Esta questão é um follow-up de Como indicam que um método foi vencida. O padrão de xxx () Tryxxx () é algo que pode ser muito útil em muitas bibliotecas. Eu estou querendo saber qual é a melhor maneira de oferecer ambas as implementações sem duplicar o meu código.
O que é melhor:
public int DoSomething(string a)
{
// might throw an exception
}
public bool TrySomething(string a, out result)
{
try
{
result = DoSomething(a)
return true;
}
catch (Exception)
{
return false;
}
ou
public int DoSomething(string a)
{
int result;
if (TrySomething(a, out result))
{
return result;
}
else
{
throw Exception(); // which exception?
}
}
public bool TrySomething(string a, out result)
{
//...
}
Eu instintivamente assumir que o primeiro exemplo é mais correto (você sabe exatamente qual exceção aconteceu), mas não conseguiu o try / catch ser muito caro? Existe uma maneira de capturar a exceção no segundo exemplo?
Solução
Fazendo TrySomething apenas pegar e engolir a exceção é uma péssima idéia. Metade do ponto do padrão TryXXX é evitar a batida de exceções desempenho.
Se você não precisa de muita informação na exceção, você poderia fazer o método DoSomething apenas chamar TrySomething e lançar uma exceção se ele falhar. Se você precisar de detalhes na exceção, você pode precisar de algo mais elaborado. Eu não ter atingido o tempo em que a maior parte do sucesso de exceções desempenho é - se é o lançamento, em vez da criação, você poderia escrever um método particular que tinha uma assinatura semelhante ao TrySomething, mas que retornou uma exceção ou nulo:
public int DoSomething(string input)
{
int ret;
Exception exception = DoSomethingImpl(input, out ret);
if (exception != null)
{
// Note that you'll lose stack trace accuracy here
throw exception;
}
return ret;
}
public bool TrySomething(string input, out int ret)
{
Exception exception = DoSomethingImpl(input, out ret);
return exception == null;
}
private Exception DoSomethingImpl(string input, out int ret)
{
ret = 0;
if (input != "bad")
{
ret = 5;
return null;
}
else
{
return new ArgumentException("Some details");
}
}
Tempo isso antes de se comprometer com ele embora!
Outras dicas
Eu costumo usar esse padrão. Depende de como o método interno é implementado como se ou não isso faz qualquer sentido. Se você tem que usar blocos catch condicionais ele pode ficar um pouco desagradável ...
public object DoSomething(object input){
return DoSomethingInternal(input, true);
}
public bool TryDoSomething(object input, out object result){
result = DoSomethingInternal(input, false);
return result != null;
}
private object DoSomethingInternal(object input, bool throwOnError){
/* do your work here; only throw if you cannot proceed and throwOnError is true */
}
O primeiro exemplo é correto se você está indo só para capturar a exceção e não fazer nada, mas falsa retorno com ele.
Você pode mudar TrySomething para olhar como abaixo.
public bool TrySomething(string a, out result, bool throwException)
{
try
{
// Whatever
}
catch
{
if(throwException)
{
throw;
}
else
{
return false;
}
}
}
public bool TrySomething(string a, out result)
{
return TrySomething(a, out result, false);
}
Assim DoSomething seria parecido
public int DoSomething(string a)
{
int result;
// This will throw the execption or
// change to false to not, or don't use the overloaded one.
TrySomething(a, out result, true)
return result;
}
Se você não queria TrySomething com throwException exposta ao público, você pode torná-lo um membro privado.
Exceções poderia ficar caro e você poderia fazer alguma RegEx verificando na corda para impedir alguém de ser jogado. Depende do que você está tentando fazer.
Assumindo que este é C #, eu diria que o segundo exemplo
public bool TrySomething(string a, out result)
{
try
{
result = DoSomething(a)
return true;
}
catch (Exception)
{
return false;
}
}
Ele imita o construído em int.TryParse(string s, out int result)
, e na minha opinião o seu melhor para ficar consistente com a linguagem / ambiente.