Is it recommended to manipulate rules with "Try-catch"?

Asked

Viewed 557 times

5

We can manipulate errors in various ways, but the one that will use less code and time of the programmer is the try, present in almost all object-oriented languages. But, when should be used try, language-independent?

Consider the C# code below:

string SenhaNumerica = Console.ReadLine(); // obtém a senha que o usuário digitar
Autenticador x = new Autenticador();
// Suponha que a classe Autenticador só irá aceitar a senha se ela seguir
// estas regras:
//  - Deve ser numérica
//  - Deve ter mais de cinco caracteres
//  - Não deve começar com número abaixo de 4
//  - Deve ter algum caractere especial
// Caso não siga essas regras, é atirado uma exceção no runtime.

As described in the above commentary, the class Autenticador will throw an exception if the password does not follow the compliant ones.

That was the beginning of the code. Now comes the practice, which of these below would be the correct practice to manipulate this password?

// PRÁTICA DE CONDIÇÕES
// - "Deve ser numérica"
int tmp_variavel;
if(int.TryParse(SenhaNumerica, out tmp_variavel) == false) {
    Console.WriteLine("A senha não é numérica");
    return;
}
// - "Deve ter mais de cinco caracteres"
if(SenhaNumerica.Length <= 5) {
    Console.WriteLine("Senha não numérica.");
    return;
}
// - "Não deve começar com número abaixo de 4"
if(Convert.ToInt32(SenhaNumerica.Substring(0, 1)) < 4) {
    Console.WriteLine("Não pode começar com número abaixo de 4.");
    return;
}
// Deve ter algum caractere especial
if(SenhaNumerica.Any(ch => !Char.IsLetterOrDigit(ch) == false) {
    Console.WriteLine("Não foi encontrado nenhum caractere especial");
    return;
}
// Enfim, com certeza que todas regras foram obedecidas, passa
// a senha para o Autenticador
Autenticador.Senha = SenhaNumerica;
// OK!

But, all this huge code above can be shortened with a simple block:

try {
    Autenticador.Senha = SenhaNumerica;
} catch (Exception ex) {
    Console.WriteLine("Senha não segue regras.");
    return;
}

I don’t understand when to use the try and why to use it. Since there are several ways to bypass it, it is still recommended to use the block try instead of creating conditions?

To tag "C#" was placed to color the code.

1 answer

5


Exception has nothing to do with object-oriented language, these concepts are completely orthogonal. There is OO spoken language that has no exception and non-OO language that has exception.

There are schools, ie a lot of people who have an opinion, on how to use. Each language tends to recommend one more thing or another.

More modern languages tend to avoid the use of exceptions. It is true that they have not become mainstream, but it is more for market reasons. From the engineering point of view it is already considered a mistake to have an exception in the language, mainly the way they are implemented.

C# began adopting the Java posture and abusing exceptions, soon understood that there were several cases where it didn’t make sense to have exceptions and started creating better mechanisms. I have a response that speaks as the use of exceptions is usually wrong in C# in most cases.

Note that the origin of C# exceptions has even to do with C++ and in this language it is common for teams to prohibit the use of exceptions.

One of the reasons not to abuse exceptions is bad performance of them, especially in C#.

If the situation is exceptional, IE, almost never happens, or if it happens is programming error and will only occur in tests or production by a lapse of the programmer, okay. If you are accessing an external resource or the programmer has little control and it may fail, it makes sense to have an exception. If what you expect is the error is not exceptional situation. Data entries are expected to come wrong, it is not an exception to come wrong.

Using exceptions for flow control is wrong.

In this example all that can be replaced by:

if (Autenticacao.Validador(senhaNumerica)) {
    return (Autenticador.Senha = true);
} else {
    WriteLine("Senha não segue regras.");
    return false; //indica que não autenticou
}

There are other ways to do this without exception, better mechanisms are not lacking.

Obviously if an exception is thrown all validations have been made within the class. Then make and return an error, just don’t throw the exception. The mistake there in the top code is trying to do what the class already does for you.

An example using exception in a way that no one uses and is valid, although I do not think it appropriate to do it (people do not use because it is inappropriate, it is because they do not know how to do it), more like curiosity:

if (Autenticacao.Validador(senhaNumerica) is SenhaException excessao) {
    Autenticador.Senha = true;
    return true;
} else {
    WriteLine($"Senha não segue regras. Motivo: {excessao.Message}. Veja o erro\n{excecao.StackTrace()}");
    return false;
}

public SenhaException Validador(string senha) {
    if (!int.TryParse(SenhaNumerica, out var senhaConvertida)) return new SenhaException("A senha não é numérica");
    if (senha.Length <= 5) return new SenhaException("Senha não numérica.");
    if (senhaConvertida[0]) < 4) return new SenhaException("Não pode começar com número abaixo de 4.");
    if (!senha.Any(ch => !Char.IsLetterOrDigit(ch)) return new SenhaException("Não foi encontrado nenhum caractere especial");
}

Obviously I would need to create the SenhaException probably inherited from ApplicationException.

Of course it may be that you have no control over the class, you cannot fix your architecture error. Then capture the exception. This type of exception is what the Eric Lippert Noise Flame (vexing), then by someone’s wrong decision you’re bound to do it.

Just don’t go capture Exception that is wrong. In general the programmer does this by ignorance and laziness, mainly the latter, because several programmers right here read that they should not do this and continue to do so. Exception may have been the worst mechanism created in programming and certainly the most abused.

Letting consumer code handle errors is a conceptual error and a abstraction leak. It is the class of this object that should establish the rules of what is good or bad, not the consumer. Unless it’s a transitional rule and it only makes sense to use it there, which doesn’t seem to be the case. And if it is, the second code makes no sense.

Note that the second code, which deals with exception is not equivalent to the first since it does not treat each type of error separately. Except to be equivalent would be so:

var erros = Autenticacao.Validador(senhaNumerica);
if (erros.Count == 0) return (Autenticador.Senha = true);
foreach (var erro in erros) WriteLine(erro.Message);
return (Autenticador.Senha = false);

If you want to treat an error in a specific way it is possible to filter with the error object you have in each element of the list.

Maybe you don’t even need the text messages, this could come within the exception itself, for me the messages there is also a mistake in most cases, but not at all.

Even this code has problems. I find it strange a return loose worthless, comments demonstrate that there should be a method there, mixing business rule with presentation to the user.

There is another problem, only one exception will be thrown. To indicate the 4 errors there would have to create a single exception and internalize in it the 4 problems and deal with if each one, the logic is even more complex, understood as there is no escape from various checks?

If the original only throws one Exception is the extreme engineering error, this information is not useful, the main reason the exception is created is to have a richer information about the error. In link above which I speak of error codes I show that it has better mechanism to carry rich information about error.

Note that the code shown capturing exception will show that the password does not follow the rules even if the error is memory, even if it is a programming error, or something else not foreseen, for example database (going that one day the validator needs to access the database). That’s why many programs are chaos, because "stop overnight", people program to work, not to be right. What works one day doesn’t work the next.

Obviously for code that has no exception do the same also need 4 ifs. And the validator method would need to return an object that has the 4 invalidities. Something like this:

public List<ValidationError> Validador(string senha) {
    var erros = new List<ValidationError>();
    if (!int.TryParse(SenhaNumerica, out var senhaConvertida)) erros.Add(new ValidationError(SenhaNaoNumerica, "A senha não é numérica"));
    if (senha.Length <= 5) erros.Add(new ValidationError(SenhaMuitoPequena, "Senha não numérica."));
    if (senhaConvertida[0]) < 4) erros.Add(new ValidationError(SenhaInicioAbaixoDe4, "Não pode começar com número abaixo de 4."));
    if (!senha.Any(ch => !Char.IsLetterOrDigit(ch)) erros.Add(new ValidationError(SenhaSemCaractereEspecial, "Não foi encontrado nenhum caractere especial"));
    return erros;
}

Obviously it would have to create a structure for ValidationError that I would probably have these two fields (if I need more things maybe I need a class, which I would avoid almost at all costs), a field to hold one object that indicates the error canonically and another with the message. It is like an exception but very light. It could also be useful a type that loads all errors more abstractly than a list of it. Finally you would have to create these markers objects indicating what the error is. Could be an enumeration, but isolated objects is more object oriented.

Now you can consume more or less like this:

var 
    Autenticador.Senha = SenhaNumerica;
} catch (SenhaNaoNumericaException) {
    Console.WriteLine("A senha não é numérica");
} catch (SenhaMuitoPequenaException) {
    Console.WriteLine("Senha não numérica.");
} catch (SenhaInicioAbaixoDe4Exception) {
    Console.WriteLine("Não pode começar com número abaixo de 4.");
} catch (SenhaSemCaractereEspecialException) {
    Console.WriteLine("Não foi encontrado nenhum caractere especial");
}

These codes are simplified and naive. In a real system I would pay much more attention to how to do. In fact, I’d have so much reuse, it wouldn’t even be a problem. I note that most programmers repeat a lot of development effort, some even do 20 times more than they could if they sought the right path.

The question demonstrates a dichotomy that does not exist, is presenting codes that do very different things, they cannot be compared.

Even the original code could be better written, for example:

if (!int.TryParse(senhaNumerica, out var senhaValida)) WriteLine("A senha não é numérica");

I also put in the Github for future reference.

Note that even the nomenclatures are not good for C standards#.

One thing I notice here at Sopt is that programmers don’t make their exceptions. It’s a mixture of laziness and ignorance. If he doesn’t make an exception because he doesn’t understand, he should stay away from them at all. If you think it’s too much work, you should avoid them too and adopt other solutions, you should stop capturing where you don’t need to. I note that most codes could be smaller, simpler, more readable, more robust, more performative and more debugged. If programmers spent a few hours learning about exceptions in the right way it would save many hours in development.

Another detail that I show above how much more work it takes to program object-oriented and most inexperienced programmers (qualitatively) who say they program or want to program OO do not have the patience to deal with this extra work. I will not get into the merits whether or not it pays to have this extra work, it depends on the case. The way most program, does not compensate.

Anyway, you can write a lot more, but it gives a chapter of a book, maybe a book just about exceptions.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.