La batallita del agüelo (o algo así que dice el Guille):
Erase una vez que un tal Guille (el nombre
es ficticio, es por ponerle algún nombre) estaba haciendo un
programilla para un dispositivo móvil, concretamente para un Smartphone. El
entorno de desarrollo que estaba usando era el Visual Studio 2008 y el
lenguaje con el que escribía eso que estaba haciendo el Visual Basic.
Resulta que ese tal Guille terminó el programa, pero quería
probarlo en un Smartphone de verdad, no en un emulador, y
resulta que el único Smartphone que tenía a mano era uno algo
viejo con el Smartphone 2003, y resulta que ese vetusto aparato
no soportaba o permitía la instalación de la versión generada
con el Visual Studio 2008 (ni aún
poniéndolo para que usara la versión 2.0 del .NET, si es que
para los proyectos de dispositivos móviles se puede poner en
otra versión, que lo mismo ni se puede, en fin...)
En vista de que no funcionaba eso que había hecho (o eso es
lo que presumiblemente ocurría), se decidió hacer ese mismo
proyecto con el Visual Studio .NET 2003, que con esa versión si
que se podían usar los ejecutables en el Smartphone 2003. Ese
Visual Studio lo tenía en una máquina virtual de Virtual PC
(también podía haberla tenido en una de vmWare, pero resulta que
no, que era en una de Virtual PC, la versión 2007 con el SP1 concretamente),
en esa misma máquina virtual es donde tiene instalado el Visual Basic 6.0
SP6 y el compilador de BASIC para MS-DOS (el QuickBasic 7.1 o QBX). La
verdad es que tantos detalles no vienen a cuento, pero bueno...
Pues bien... se pone a convertir el proyecto hecho para VB2008 a uno
compatible con VB.NET 2003 (que es casi lo
mismo, solo que un par de versiones anterior, pero al cambiar eso de que ya
los lenguajes de .NET como VB no llevan el apéndice de .NET pues...),
pero resulta que en una parte del código tenía una instrucción
Continue For para saltarse una parte del código. Y resulta que la
instrucción Continue se añadió en la versión 2005 de VB,
por tanto la versión 2003 del VB no sabe nada de Continue,
así que... tuvo que buscar una alternativa... y aquí había dos cosas que se
podían hacer, a saber:
Nota para los que no saben para qué sirve Continue:
La instrucción Continue sirve para hacer que un bucle continúe la ejecución
sin necesidad de ejecutar el código que hay entre esa instrucción y la
instrucción usada para continuar el bucle.
Por ejemplo, si es un bucle For, se usará Continue For y lo que hará será
saltarse todo lo que haya desde esa instrucción hasta el Next
correspondiente.
- Poner todo lo que había que saltarse dentro de un bloque If Then con
una condición contraria a la que se hace para continuar la ejecución del
bucle.
- Usar una instrucción GOTO para saltarse todo ese código
Vale, está claro, que GOTO nunca se debe usar... de hecho esta persona (por
llamarlo de alguna forma) tampoco lo usaba desde sus primeros tiempos
de programación con BASIC... o eso es lo que dice... ¡a saber!
Aunque yo sé de buena fuente que al menos lo ha usado en los "ON ERROR GOTO
..." pero bueno... ahí casi tiene una excusa...
El tema es que un día, se le ocurre comentarlo públicamente en un evento
de un grupo de usuarios, y claro... en fin... esas cosas no se deben
decir... que el GOTO es ¡caca! eso no se hace... ¡nene malo!
(Son las 13.45. Y
ya que no estaba muy aclarado el comentario anterior, iba
a poner algo más, pero al pulsar INTRO para empezar a escribir el Expression
Web 2 se me ha vuelto a colgar, así que... en fin... lo dejamos así que
tampoco hay mucho que decir, salvo que algunos de los asistentes se llevaron
las manos a la cabeza e intentaron taparle los oídos a los asistentes,
jejejeje...)
Vamos a lo que vamos
Pero resulta que mirando el código generado por el compilador
de Visual Basic (y también por el
de C#, ya que C# aunque dispone de la instrucción
continue desde el principio de su vida, también dispone
de la instrucción goto desde sus inicios y...
en fin... si alguien piensa que la instrucción GOTO no debe
existir, pues... el que C# la incluya, tiene más "delito" a que
esa instrucción aún siga en Visual Basic, ya que a este último
lenguaje le viene de cuando aún era BASIC... en fin... no
desvariemos y volvamos al tema...), que como sabes el
código generado por el compilador es MS IL (o Intermediate
Language), y resulta que el código generado para el GOTO y el
generado para el Continue es el mismo... sí, ya que al fin y al
cabo eso es lo que se hace... ¡dar un salto a otra posición de
memoria cuando se cumple la condición!
Ahora (en un momento) te muestro tanto el código IL como el de
VB y C# de un ejemplo que usa un GOTO y otro que usa un Continue
para continuar en un bucle For, tanto para el código generado
por el compilador de VB como por el de C#.
Un poco de código, por favor
Estos dos métodos son los usados para las pruebas, primero te
lo muestro para VB y C# y después el código IL generado por el
compilador de C# (ya que así
algunos no podrán tener la excusa de que está como está porque
es el generado por VB, que hay aún gente que piensa que todo lo
que se hace con VB en principio no es bueno, y... en fin... que
no es oro todo lo que reluce).
Aquí tienes el código de una clase con los dos métodos,
primero el código de Visual Basic y después el de C#.
Class Prueba
Public Shared Sub PruebaGoto()
Dim a As Integer = 10
Dim t As Integer = 0
Dim n As Integer = 0
Console.WriteLine("En PruebaGoto, a = {0}", a)
For i As Integer = 1 To a * 2
If i = a Then GoTo continuar
t += 1
continuar:
n += 1
Next
Console.WriteLine(" t = {0}", t)
Console.WriteLine(" n = {0}", n)
End Sub
Public Shared Sub PruebaContinue()
Dim a As Integer = 10
Dim t As Integer = 0
Dim n As Integer = 0
Console.WriteLine("En PruebaContinue, a = {0}", a)
For i As Integer = 1 To a * 2
n += 1
If i = a Then Continue For
t += 1
Next
Console.WriteLine(" t = {0}", t)
Console.WriteLine(" n = {0}", n)
End Sub
End Class
static class Prueba
{
public static void PruebaGoto()
{
int a = 10;
int t = 0;
int n = 0;
Console.WriteLine("En PruebaGoto, a = {0}", a);
for (int i = 1; i <= a * 2; i++)
{
if (i == a) goto continuar;
t += 1;
continuar:
n += 1;
}
Console.WriteLine(" t = {0}", t);
Console.WriteLine(" n = {0}", n);
}
public static void PruebaContinue()
{
int a = 10;
int t = 0;
int n = 0;
Console.WriteLine("En PruebaContinue, a = {0}", a);
for (int i = 1; i <= a * 2; i++)
{
n += 1;
if (i == a) continue;
t += 1;
}
Console.WriteLine(" t = {0}", t);
Console.WriteLine(" n = {0}", n);
}
}
Y esto que sigue es parte del código IL generado por el compilador de C# en
modo depuración (debug), primero el trozo del método que usa la instrucción
continue y después la que usa la instrucción goto.
Nota:
Estos trozos de código IL corresponden al bucle for de cada uno
de los métodos.
IL_0018: nop
IL_0019: ldc.i4.1
IL_001a: stloc.3
IL_001b: br.s IL_003a
IL_001d: nop
IL_001e: ldloc.2
IL_001f: ldc.i4.1
IL_0020: add
IL_0021: stloc.2
IL_0022: ldloc.3
IL_0023: ldloc.0
IL_0024: ceq
IL_0026: ldc.i4.0
IL_0027: ceq
IL_0029: stloc.s CS$4$0000
IL_002b: ldloc.s CS$4$0000
IL_002d: brtrue.s IL_0031
IL_002f: br.s IL_0036
IL_0031: ldloc.1
IL_0032: ldc.i4.1
IL_0033: add
IL_0034: stloc.1
IL_0035: nop
IL_0036: ldloc.3
IL_0037: ldc.i4.1
IL_0038: add
IL_0039: stloc.3
IL_003a: ldloc.3
IL_003b: ldloc.0
IL_003c: ldc.i4.2
IL_003d: mul
IL_003e: cgt
IL_0040: ldc.i4.0
IL_0041: ceq
IL_0043: stloc.s CS$4$0000
IL_0045: ldloc.s CS$4$0000
IL_0047: brtrue.s IL_001d
IL_0018: nop
IL_0019: ldc.i4.1
IL_001a: stloc.3
IL_001b: br.s IL_003a
IL_001d: nop
IL_001e: ldloc.3
IL_001f: ldloc.0
IL_0020: ceq
IL_0022: ldc.i4.0
IL_0023: ceq
IL_0025: stloc.s CS$4$0000
IL_0027: ldloc.s CS$4$0000
IL_0029: brtrue.s IL_002d
IL_002b: br.s IL_0031
IL_002d: ldloc.1
IL_002e: ldc.i4.1
IL_002f: add
IL_0030: stloc.1
IL_0031: ldloc.2
IL_0032: ldc.i4.1
IL_0033: add
IL_0034: stloc.2
IL_0035: nop
IL_0036: ldloc.3
IL_0037: ldc.i4.1
IL_0038: add
IL_0039: stloc.3
IL_003a: ldloc.3
IL_003b: ldloc.0
IL_003c: ldc.i4.2
IL_003d: mul
IL_003e: cgt
IL_0040: ldc.i4.0
IL_0041: ceq
IL_0043: stloc.s CS$4$0000
IL_0045: ldloc.s CS$4$0000
IL_0047: brtrue.s IL_001d
Como puedes comprobar, la instrucción
brtrue.s es la que se encarga de ir a otra parte del código si
se cumple la condición (en este caso que el resultado del valor entero
comprobado sea distinto de cero).
Esto es lo que dice la ayuda de MSDN en línea sobre esta instrucción:
La instrucción máquina brtrue.s transfiere el control a
la instrucción máquina de destino especificada si value (tipo
native int) es distinto de cero (true). Si value es cero
(false), la ejecución continúa en la instrucción máquina
siguiente.
Como puedes comprobar por la última instrucción del código IL mostrado, la continuación del bucle
for también es un
"salto" en el código... para que veas que a bajo nivel las cosas
se hacen de otro modo, jejeje.
Recapitulando
Aunque hay que dejar el "cachondeo" y ponerse serio... a
ver... aunque para el código IL generado por los compiladores de
.NET, es lo mismo usar un Goto que un Continue, para nosotros,
es decir, para los que vamos a ver el código fuente, es mejor
que veamos un Continue antes de un GoTo, que todos sabemos (los
que sabemos de la existencia del GOTO y de cómo lo usaba alguna
gente hace muuuuchos, pero muchos, muuuuchos años, sí, muchos)
lo fatídico que puede resultar leer un programa que use la
instrucción GOTO, sobre todo si no se hace de forma
"estructurada", sí, ya sé que eso de estructurado y GOTO como
que no... pero bueno...
Así que... que esto no sirva de excusa para poner GOTOs en el código...
¿vale? pues eso...
Nos vemos.
Guillermo (¡vaya! ¡al final ya se sabe quién ha escrito esto! ;-))))