Making exceptions is legal!
Cool approach 1 - avoid exceptions because they represent evil:
public Resultado baixaEstoque(Produto produto, int quantidade) {
Resultado resultado = new Resultado();
if (!produtoDisponivel(produto, quantidade)) {
resultado.isSucesso(false);
resultado.setMotivoFalha(
String.Format("Não há %d items de %s disponíveis em estoque.",
quantidade, produto.getNome()));
return resultado;
}
// ... (baixa o estoque de fato);
resultado.isSucesso(true);
}
The consumption at the highest level of the application is thus:
Resultado Resultado = estoque.baixaEstoque(produto, quantidade);
if (Resultado.isSucesso()) {
mensagens.Add("O produto foi baixado do estoque");
} else {
mensagens.Add(Resultado.getMotivoFalha());
}
I like the above code. No exceptions thrown, no try-catch
was used.
Now consider a slightly more complex operation like closing a sale:
public Resultado fechaVenda(Carrinho carrinho, Cliente cliente, FormaPagamento formaPagamento) {
Resultado resultado = crediario.reservaCredito(cliente,
carrinho.valorTotal(), formaPagamento);
if (!Resultado.isSucesso()) {
return resultado;
}
for (CarrinhoItem item : carrinho.getItems()) {
resultado = estoque.baixaEstoque(item.getProduto(), item.getQuantidade);
if (!Resultado.isSucesso()) {
return resultado;
}
}
resultado = crediario.debitaCliente(cliente, carrinho.valorTotal(), formaPagamento);
if (!Resultado.isSucesso()) {
return resultado;
}
resultado = despacho.ordenaDespacho(cliente, carrinho.getItems());
if (!Resultado.isSucesso()) {
return resultado;
}
// ... (mais operações de baixa de venda);
return resultado;
}
So, reporting success or failure as return of method left me with several ifs and enough repeated code that does not express the business rule but is there just in case something happens that does not allow the system to follow its standard path.
Cool Approach 2 - use exceptions rationally for a cleaner, more expressive design:
Now consider this same closing operation of a sale using exceptions, where each process casts business exception in cases of predicted exceptions (such as a product not being available in stock). In this approach, the stock drop, for example, is like this:
public void baixaEstoque(Produto produto, int quantidade) {
if (!produtoDisponivel(produto, quantidade)) {
throw new BusinessException(
String.Format("Não há %d items de %s disponíveis em estoque.",
quantidade, produto.getNome()));
}
// ... (baixa o estoque de fato);
}
Each process follows this same pattern, so that the complete process of closing sales is thus:
public void fechaVenda(Carrinho carrinho, Cliente cliente, FormaPagamento formaPagamento) {
crediario.reservaCredito(cliente, carrinho.valorTotal(), formaPagamento);
for (CarrinhoItem item : carrinho.getItems()) {
estoque.baixaEstoque(item.getProduto(), item.getQuantidade);
}
crediario.debitaCliente(cliente, carrinho.valorTotal(), formaPagamento);
despacho.ordenaDespacho(cliente, carrinho.getItems());
}
And the consumption at the highest level of the application is like this:
try {
vendas.fechaVenda(carrinho, cliente, formaPagamento);
mensagens.Add("O produto foi baixado do estoque");
} catch (Exception e)
mensagens.Add(e.getMessage());
}
Ideally the application has a generic exception treatment that will already log in and add the error message, so the code gets even cleaner:
vendas.fechaVenda(carrinho, cliente, formaPagamento);
mensagens.Add("Venda concluída.");
In this second approach I eliminated dozens of ifs (would be many more in a complete code) and at the same time did not include any try-catch
no part of my business code, even releasing useful exceptions!
In time: although the exceptions are foreseen, they are still exceptions. For example: a sale process does not reach its closure if the product is not previously available in stock, eventually it was reserved when added in the virtual cart. The product is no longer available at the time of your discharge is an exception that may occur in the case of a sales process with overbooking or in the case of competing sales when two bookings of the same product have already expired and the purchase was still carried through. So, may happen, but it’s an exception. Also, this exception is not determining the program flow - on the contrary, it is interrupting the program flow.
Conclusion 1:
Throwing exceptions is not wrong. On the contrary, it can help make code more expressive.
A premise of these first two approaches is that there is a transaction control around the higher level call, and that this transaction control ensures the consistency of the system state (executes a rollback) in case of failure. The second approach, for example, could rely on automatic and implicit transaction management of an EJB container, where no additional code would be required. In the first approach it would be necessary to include an explicit call to the rollback.
Weird approaches:
There is also the exception treatment by himself, without any purpose or that trial add robustness to code. Example:
try
{
// ... (código de negócio);
}
catch (Exception e)
{
e.printStackTrace();
}
// ... (continua com mais código de negócio ignorando completamente que houve exceção!);
Or else:
try
{
// ... (código de negócio);
}
catch (Exception e)
{
e.printStackTrace();
throw e;
}
And there’s also the exception treatment that controls the program flow in a weird way:
public Pessoa obtemPessoa(String cpf) {
try
{
// ... (busca pessoa);
}
catch (Exception e)
{
JOptionPane.showMessageDialog("CPF não cadastrado.");
return null;
}
}
In my second approach, in a way i used exceptions to control the program flow for I have made exceptions to stop this flow, and that is not wrong. It was an example of good use of language resources.
In this code above the exception is also being used to control the flow, but to give an alternative flow instead of simply interrupting it; and yet it does so using a very weird code that mixes presentation with business rules.
Conclusion 2:
In various languages, casting and capturing exceptions without understanding why and not being right in this why (many of the reasons are just myths or lack of knowledge) is the problem. Not the exceptions themselves.
Necessary approaches:
Sometimes we do not question an exception but this is the simplest feature or the only acceptable performance feature or even the only possible way that the language offers us.
Example in Java:
int valor;
try
{
valor = Integer.parseInt(str);
// ... (usa o valor);
}
catch (NumberFormatException e)
{
// ... (código alternativo para string não válida);
}
When in C#, I prefer to do so:
var numeroValido = Int32.TryParse(str, out valor);
if (numeroValido)
// ... (usa o valor);
else
// ... (código alternativo para string não válida);
The code in Java is not wrong because this is the Java way to do it. When there is a way as expressive as the C#, I will use it.
Conclusion 3:
Like it or not to cast and capture exception, if this is the way to solve a certain problem in that language, there is nothing to discuss - we will simply cast and capture the exception.
Responses to your specific points:
When I studied Java, I learned that exceptions should only be used in "exceptional" situations (i.e. an error, an unforeseen condition, etc.), never for normal flow control.
Incorrect. Exceptions in Java are there to be used. Neither of my first two approaches is incorrect in a given scenario. So instead of "ever use" I prefer "use in a judicious manner".
Releasing an exception is less performative (creating an exception involves mounting a stack trace, among other things)
Correct. In my approach using exceptions, only 1 exception will eventually be thrown at each process execution, and in a process that depends on user interaction this cost is not sensitive. If you have a large batch of calls to a function, such as an integration, it is not a good idea to cast millions of exceptions. It turns out that from a design point of view will also not be a good idea, because in a batch integration you do not need an exception but a log. In time: anyway I’ve seen integration failing and releasing millions of exceptions and yet the end of the world did not come.
Using exceptions like normal flow can be less "elegant" and "correct".
True. But I have used exceptions in my second approach and it is not inelegant, nor is it incorrect. So again, the problem is not with the exceptions, but with the use we can make of it - as with any other feature of a programming language. Even the most basic feature of most languages, the IF, is extremely misused, and you don’t see many people saying never use it.
The ideal being to test the preconditions before making an operation or calling a method. Or, to signal an error to the caller, use an error code or similar.
Are design decisions, which consider requirements even non-functional, it is not possible to generalize what is right or wrong. In my first approach I used error code, in the second I used exceptions. In my opinion, the second approach is better (cleaner and more expressive code). And in your?
Only in the really unpredictable cases would it be appropriate to use a Try....
On the contrary, is precisely when you predict an error that you should worry about capturing the error, whether at the highest level of the application to show a message or more localized, in the business code, to tread an alternative path.
If you don’t know which mistake to wait for, better no try-catch
. Let the application pop or show a message at the highest level of the application user friendly and informative at the same time for support.
However, when learning Python I came across a very different philosophy: in this language there are many cases where the recommended way (and sometimes the only way) to treat an unwanted condition is to try to run it and, if something goes wrong, capture the exception.
If this is the Python way, for several factors that I don’t know, or sometimes it’s the only way, simply there is nothing to discuss - it will have to be done this way.
But assuming that not only new languages can be created, but existing ones can be improved, I ask: there are clear advantages of one of these paradigms over the other?
It’s language that will say. Go (or go lang) has no exception but has other sophisticated features that Java and C# does not yet have. If the discussion can be brought to a specific language, then yes we can describe the advantages of one paradigm over the other.
What parameters should I observe when deciding between one approach and another?
When you can choose the approach, the only parameter is the code expression. It turns out that any good design judgment is sometimes simply thrown away by another that sometimes has more: performance. Let this not serve as a license to throw all the good design in the trash because the system needs to be ultra performatic. There are standards, precisely of design, to ensure that the non-functional requirements of a critical process do not affect the design of the entire system.
Finally, is there some reasonable middle ground that combines the benefits of both without the onus falling on the programmer?
Generalizing this question a little more than its original purpose, the answer is nay. The burden of the decision will always stay with the programmer. There is no mathematical recommendation that fits all cases. I don’t have much doubt about casting or how to treat exceptions, however I have enormous difficulty in passing on this security. Every programmer needs to worry about doing things in an ever better way so as to develop an experience that leads to a good and simple design, using or not exceptions.
Final conclusion
Maybe the book The Pragmatic Programmer delivered gold in a simple and symmetrical way between all languages with exceptions:
According to the book (referenced by Martin Fowler):
We believe that exceptions should rarely be used as part of the normal flow of a program: exceptions should be reserved for unexpected events. Assume that an undetected exception will terminate your program and ask yourself, "Will this code continue to run if I remove all exception treatments?" If the answer is "no", then perhaps the exceptions are being used in non-exclusive circumstances.
Code example that meets this premise:
public synchronized void baixaEstoque(Produto produto, int quantidade) {
if (!produtoDisponivel(produto, quantidade)) {
throw new BusinessException("Produto não disponível nesta quantidade");
}
// ... (baixa o estoque de fato);
}
Consumer code at the highest application level:
if (estoque.produtoDisponivel(produto, quantidade)) {
ReservaProduto reserva = estoque.reservaProduto(produto, quantidade);
} else {
mensagens.add("Produto não disponível nesta quantidade");
}
// e, em outra requisição:
try {
estoque.baixaProduto(reserva.getProdutoReservado(), reserva.getQuantidadeReservada());
} catch (BusinessException e) {
mensagens.add(e.getMessage());
}
The stock expects that what is not available will not be downloaded, but as it is its function to treat competition, it knows that there is the unfortunate possibility that the product will no longer be there when the effective low.
However, if I remove the exception treatment (remove both the throw
as to the catch
), on the happy path (the default and most likely path) the code will continue to work.
Code example that DOES NOT meet this premise:
public Cliente obtemCliente(String cpf) {
Cliente cliente = db.find(Cliente.class, cpf);
if (cliente == null) {
throw new CpfNaoCadastradoException(cpf);
}
return cliente;
}
The e-commerce just went live. The first user filled a cart and when ordered to close the purchase your CPF was requested. And then the following code was invoked:
try {
Cliente cliente = obtemCliente(cpf);
} catch (CpfNaoCadastradoException) {
redirecionaParaCadastroCliente(cpf);
}
solicitaLogin(cliente);
In the above example, if I remove the exception handling the code will not run even in the first attempt to use the system.
Testing site boundaries (and computing) as always @mgibsonbr hehehe. I don’t know how to answer your question other than to say that there is every kind of religion and every kind of toy in the world. Python and Java are very different toys, the Python people tend to seek clean solutions from the point of view of LOC and readability; the Java people are more concerned with architecture Nterprise and well-defined flows. The standard for error handling follows the ecosystem culture.
– Anthony Accioly
That said, I’ve seen Python projects packed with tests to avoid
try / except
and Java projects where nothing is tested and all exceptions are wrapped inRuntimeException
... I think we’re going to end up falling into a real discussion about schools of exception flow :).– Anthony Accioly
@Anthonyaccioly Hehe does not doubt, but this only helps to legitimize the question: after all, it is not obvious that wrap everything in
RuntimeException
is it bad practice? Already do tests to avoid exceptions, not so obvious... There is an ideal point, but I lack more objective parameters to identify it than simply mine feeling case by case. I myself feel strange, always recommending to Java programmers (with whom I worked a long time ago) to avoid the abuse of exceptions, whereas in day-to-day working with Python I find myself forced to use them in a very different way... : P– mgibsonbr
@mgibsonbr Is it not the case to add the tag Python? Or even Python AND Java? The problems that are solved with exception flow in these languages are solved in a very distinct and more expressive way (regardless of right or wrong the code simply gets cooler) in C# or Ruby, for example.
– Caffé
@Caffé Personally, I preferred a more language-independent response, not only to avoid a "Gorilla vs. Shark" but mainly because my interest is in something that I can apply in a broader context (hence the tag
design-de-linguagem
and notcaracterística-linguagem
). A comparison/explanation about how the exceptions fit into the philosophy of these languages is only of little use to me (unless the arguments presented are capable of being extrapolated). However, if the community feels that the way it is becomes too broad, I have no objection to them.– mgibsonbr
@mgibsonbr I do not know if the context of this question can be broader than that of a single language. My suggestion is precisely to reduce the scope so as to avoid falling into comparisons. If the Python style is to use exceptions (all of a sudden more for lack of points resources in languages than for style), this is not the case for other languages. Where in Python it is more practical to use exceptions, in other languages there are much more practical and expressive ready resources. There is also a difference in cost per exception between languages - it is not in all of them that an exception is expensive.
– Caffé
@mgibsonbr, also came to take (and to be honest, still take) this matter of wrapping
RuntimeExceptions
generic by anti-pattern obvious. I went to complain at the source (our, it’s been 4 years) and I took a reversed. As I said, schools of thought :).– Anthony Accioly