I begin by quoting a comment given in original English OS question:
if statements are evil in the way Hammers are evil. Some people Might
misuse them, but they’re an Essential tool. - Dominic Rodger Oct 12
'09 at 12:08
In free translation: "IF commands are as demonic as hammers. Some people misuse them, but they are essential tools."
This comment makes sense in that without a flow control structure it is simply not possible to build a computer program that is useful, after all the troubleshooting depends on decisions in some scope.
Although Ifs are really fundamental elements in any programming language, the lines of argument that classify IF commands as bad - like the Anti-if campaign cited in the question - are not as dogmatic as they seem. As indicated on the website of the Anti-if Campaign, its motto is this:
The Goal of the Anti-if Campaign is to raise Awareness of effective
use of software design Principles and Practices, first of all removing
bad, Dangerous Ifs.
In free translation: "The goal of the Anti-if campaign is to sensitize developers about the effective use of software design principles and practices, in particular the removal of bad or unsafe Ifs."
Despite using a bold marketing strategy (by saying that IF is demonic - or evil in the original in English), what these campaigns propose is to be careful with the use of conditionals in situations where they may be being misused or even dangerous in order to hinder the maintenance of the code.
The classic example is based on a single class (the original source of that code deserves a +1 just for being plainly inspired in jokes of the British humor group Monty Python!):
public class Passaro {
private double m_dVelocidadeBase;
private double m_dFatorPeso;
private int m_iNumCocos;
private boolean m_bPregado;
private double m_dVoltagemChoque;
private TipoPassaro m_eTipo;
. . .
double getVelocidade() {
switch (m_eTipo) {
case TipoPassaro.ANDORINHA_EUROPEIA:
return m_dVelocidadeBase;
case TipoPassaro.ANDORINHA_AFRICANA:
return m_dVelocidadeBase - (m_dFatorPeso * m_iNumCocos);
case TipoPassaro.PAPAGAIO_AZUL_NORUEGUES:
return m_bPregado ? 0 : m_dVelocidadeBase * m_dVoltagemChoque;
}
throw new RuntimeException ("Oops! Essa parte do código não deveria ser executada.");
}
. . .
}
In this example, the class Passaro
can represent different types of bird instances (among them, the European and African swallows and the Norwegian Blue parrot), and therefore the method getVelocidade
has a great complexity related to the different ways in which the speed of the bird is calculated with respect to its type. This complexity is expressed in the need to have a number of decisions (several if
or also a switch
) with respect to the type of bird. If other methods exist in that class (such as, getPiado
, getFome
, etc.), most likely they will also have Ifs to deal with the differences, and the complexity of the solution and maintenance difficulty will only increase (because if a new type of bird needs to be added, all the places/methods with Ifs will need to be changed).
The suggested way around the problem is due to the appropriate use of the concepts of inheritance, abstraction, and polymorphism of Object Orientation. Instead of having a single class Passaro
and instantiate it for each different type of bird, it can become an abstract class (i.e., that cannot/should not be instantiated) or an interface, so that it contains the properties and signatures of standard methods among the different types of birds. You can then create new classes, one for each type of bird itself (classes AndorinhaEuropeia
, AndorinhaAfricana
and PapagaioAzulNoruegues
, in the example), so that they inherit from the base class Passaro
and implement the specificities of each type in their own superscript methods.
The result (also based on the original source mentioned above) would be as follows:
public abstract class Passaro {
private double m_dVelocidadeBase;
. . .
double getVelocidade() {
return m_dVelocidadeBase;
}
. . .
}
public class AndorinhaEuropeia extends Passaro {
. . .
// Apenas um exemplo, pois é desnecessária a reimplementação nesse caso,
// uma vez que ela não altera o método original da classe pai.
double getVelocidade() {
return super.getVelocidade();
}
. . .
}
public class AndorinhaAfricana extends Passaro {
private double m_dFatorPeso;
private int m_iNumCocos;
. . .
double getVelocidade() {
return super.getVelocidade() - (m_dFatorPeso * m_iNumCocos);
}
. . .
}
public class PapagaioAzulNoruegues extends Passaro {
private boolean m_bPregado;
private double m_dVoltagemChoque;
. . .
double getVelocidade() {
return m_bPregado ? 0 : super.getVelocidade() * m_dVoltagemChoque;
}
. . .
}
Thus, Ifs are not needed because each specific class (inherited from Passaro
) implements its own way of calculating speed. In addition, object orientation polymorphism allows you to treat birds in a generalized way, so you can deal with the differences without having to make explicit Ifs:
Passaro oUmPassaro = new AndorinhaAfricana();
Passaro oOutroPassaro = new PapagaioAzulNoruegues();
Passaro oPassaroQualquer = fabricanteDePassaros.criaUmPassaro(); // Método independente de criação
System.out.println(oUmPassaro.getVelocidade());
System.out.println(oOutroPassaro.getVelocidade());
System.out.println(oPassaroQualquer.getVelocidade());
If we compare the two approaches considering a possible future need to add a new type of bird (say, the Tucano), which has its own ways of calculating speed (beak size maybe influences?), we will note that in the first approach it will be necessary to add a new type to the enumeration, and it will be mainly necessary to add a new case (ie a new IF) to the method getVelocidade
class Passaro
. In the second approach, the class Passaro
does not need to be changed, just create a new class Tucano
and inherit it from Passaro
.
This distinction not only influences the amount of code to be written (and here one could argue that adding an IF is much easier - and uses fewer lines of code - than creating a whole new class) and the ease of maintenance (again, one could argue that it is easier to locate where to change if all the code is in one class), but on other important issues. For example, in languages such as C++, recompiling the code after a change in a class used in multiple locations causes a longer build time (as more dependencies are recompiled). If approach 2 is used, the files that depend on the other classes (other than the Tucano) do not need to be recompiled. It should be clear that this has an impact also on the tests already performed. The more localized a change is, the less chance it has of causing defects (bugs) regression.
Note also that this example is very specific. In this scenario, the differences in the speed calculation are due to distinct behaviors between objects, which in itself already indicates that it makes a lot of sense to build a class hierarchy. This means that in addition to the benefits argued the resulting code is potentially a simpler and clearer understanding.
So by no means does that mean that the IF command is evil. Only that its use in examples like this does not seem to be the most indicated way of solving the problem, especially in object-oriented languages. Surely one can provide examples where using a sequence of Ifs is more straightforward and simple than building an entire class structure. What occurs to me right now is an example of displaying error messages:
public void displayErrorMessage(Error e) {
if(e.Type() == IOError.FILE_IS_READ_ONLY) {
// Mensagem de aviso, solicitando fechar outras aplicações e tentar novamente
}
else if(e.Type() == IOError.FILE_EXISTS) {
// Mensagem de aviso importante, solicitando anuência
}
else if(e.Type() == IOError.ERROR_WRITING_DATA) {
// Mensagem crítica, informando impossibilidade de gravação
}
. . .
}
Of course it would be possible to construct this same decision through a hierarchy of classes, but this does not seem really necessary in this example, especially if the class Error
is native to language. Although the class is defined by the programmer himself (in which one could inherit the various types of errors and specialize a getErrorMessage method in a similar way to the one previously exemplified), the questions that fit here are: How many new IO error messages could appear in the future? there is indeed some possibility of change where Ifs will actually harm the understanding or maintenance of that code?
In closing, I’d like to quote this other example where the campaign is to avoid the use of Fors. In the example cited, it is proposed to exchange this code:
public class Department {
private List<Resource> resources = new ArrayList<Resource>();
public void addResource(Resource resource) {
this.resources.add(resource);
}
public void printSlips() {
for (Resource resource : resources) {
if(resource.lastContract().deadline().after(new Date())) {
System.out.println(resource.name());
System.out.println(resource.salary());
}
}
}
}
for that:
public class Department {
private List<Resource> resources = new ArrayList<Resource>();
public void addResource(Resource resource) {
this.resources.add(resource);
}
public void printSlips() {
new ResourceOrderedCollection(this.resources).select(new InForcePredicate()).forEachDo(new PrintSlip());
}
}
with the creation of three classes and two interfaces. The problem pointed out is that originally the method printSlips
does more than it should, because it iterates between resources (the for
), selects which resources should be printed (the if
) and has the responsibility to print the component items of each resource (the various System.out.println
). The solution creates a class to contain and iterate over the resources, one to define the selection and one to print resources.
This alternative is nice, but there are others. For example, the class itself Resource
could have a method toString
which already prints what should be printed, and the method printSlips
could receive as parameter a class instance for checking the print conditions (i.e., a filter).
Anyway, which is the best one? Read the original post and draw your own conclusions. Again, this does not mean that the FOR is evil, demonic or bad. :)
It is the general idea of exchanging ifs for a hierarchy of classes. There will be cases where it is worthwhile and cases where it is not worthwhile. Even with OO o if will always exist, because somewhere you will have to choose what type of instantiating object.
– C. E. Gesser
Yeah, it’s not an anti-if campaign, but a campaign use OO correctly...
– Felipe Avelar
there is a English version of the anti if campaign (or ours is a version of theirs?) which explains with a wealth of details the reason of the campaign. It would be nice to have these explanations and examples in Portuguese.
– Math
@Math I didn’t like the campaign site (in English). It doesn’t have an objective and easy to find justification text! Or you have a direct link? I didn’t find.
– bfavaretto
@bfavaretto the part that justifies is the tab Get Started. I at least liked it, maybe by having self-identified very quickly, rs..
– Math
The excess of If’s in addition to increasing the complexity of the code, increases the difficulty in understanding and when making future maintenance. If you are using concepts such as Object Orientation, there are ways to supply an eventual chain of If’s. Take a look at this article here that I think it’s pretty cool. It’s been a long time in my favorites. : ) http://blog.caelum.com.br/como-nao-aprenderorientacao-objetos-o-excesso-de-ifs/
– Marco Paulo Ollivier
I think it’s more a matter of subjective and personal taste. In some cases polymorphism is preferred (when this different behavior is actually something intellectually differentiable in terms of being a distinct class), but this is not the general rule. For example: the person class: there are people who like rock, others like samba, etc. Is it right to create a class for each different behavior? A more appropriate question: "How to route code efficiently"
– user178974