Prácticas de Programación Infames: OpenJDK

Más de una vez (y de dos) he oído hablar sobre extrañas normas no escritas (o eso espero) sobre «buenas prácticas» de programación. De entre todas ellas, me permito destacar:

  • El break solo se puede usar en los switch (en cada case).
  • Usar el continue es un pecado capital, al infierno de cabeza.
  • El return solo puede ir en la última línea de una función o método, y únicamente para retornar algo. De lo contrario sufrirás indecible tormento.
  • Y la clásica: Usar el goto está penado con sufrimiento eterno.

Por curiosidad le he estado echando un vistazo a OpenJDK, que es la implementación de Java que liberó Sun no hace tanto (y de la que sigue siendo propietario). El código es en su mayoría Java, C y C++. He sacado unos números (no me atrevo a decir métricas) sobre el directorio source/openjdk/jdk/src. Conste que esto es muy burdo y que no he hecho ningún tipo de filtrado, así que los números reales pueden variar bastante, aunque creo que sirven para hacerse una idea aproximada.

Primero un cálculo de LOC, para saber de qué cantidad de código estamos hablando.

$ sloccount *
SLOC	Directory	SLOC-by-Language (Sorted)
1521726 share           java=1269400,ansic=226529,cpp=25724,cs=72,sh=1
230177  solaris         ansic=158218,java=71765,asm=157,sh=21,awk=16
100139  windows         cpp=53140,ansic=24593,java=22406
0       linux           (none)
 
Totals grouped by language (dominant language first):
java:       1363571 (73.63%)
ansic:       409340 (22.10%)
cpp:          78864 (4.26%)
asm:            157 (0.01%)
cs:              72 (0.00%)
sh:              22 (0.00%)
awk:             16 (0.00%)
 
Total Physical Source Lines of Code (SLOC) = 1,852,042

Y unos “grep’s” con resultados curiosos:

$ grep -R "switch (" *  | wc -l
2251
$ grep -R "case " *  | wc -l
18609
$ grep -R "break;" *  | wc -l
11232

No es fácil saber si esos break‘s corresponden todos a esos case. El siguiente dato demuestra que al menos hay algunos que no se usan únicamente para terminar un case. Esto solo cuenta aquellas sentencias en el que el if y el break están en la misma línea. Echando un vistazo al código es fácil encontrar break dentro del bloque de un if o else pero eso no es tan fácil de contabilizar con grep.

$ grep -R "if" * | grep "break;" | wc -l
201

Y por último:

$ grep -R "continue;" *  | wc -l
1600
$ grep -R "goto " * | wc -l
447
$ grep -R "return" * | wc -l        
133092

Contabilizar el número de funciones y métodos no es nada fácil, por lo que no es fácil saber si hay más de un return por función. Sin embargo, el siguiente dato deja claro que al menos utilizan bastante return para terminar “contranatura” funciones y métodos void:

$ grep -R "return;" * | wc -l
6800

Y lo mismo que dije del break es aplicable para detectar el uso de continue y return en una sentencia condicional:

$ grep -R "if" * | grep "return" | wc -l
11913
#  grep -R "if" * | grep "continue;" | wc -l
239

Prometo refinar estos números cuando tenga tiempo.

Conclusión: La gente de Sun no tiene ni idea de programar…

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Conclusión

Cuántas chorradas escribe la gente...

Por lo visto según varios

Por lo visto según varios estudios, el 80% de las personas (adultas) no pilla la ironía a menos que esté en un contexto humorístico y una gran parte de esos, ni así. Da miedo pensar que tanta gente se tome ironías tan burras como hay por ahí en serio. Éste era uno, está claro.

Imagen de Lk2

Me da a mi....

Que no has pillado la fina ironía de la conclusión... obviamente no se pone en duda la calidad de la gente de Sun programando, si no esas teorías sobre la programación donde usar saltos incondicionales esta mal visto incondicionalmente, valga la redundancia.

Imagen de david.villa

fina?

De fina nada. No hay que ser ningún lumbreras para darse cuenta de la ironía. Pero ya sabes, "cuánta gente escribe chorradas..."

No soy portavoz de ningún colectivo, grupo o facción. Mi opinión es personal e intransferible.

Imagen de nacho

goto hell

Me gustaría contribuir con algún ejemplo más, que me he encontrado recientemente. Hace poco he estado buceando por el código de libparted, que es una librería de particionado de discos duros (algo bastante serio) y que sirve de back-end para programas como GParted y parted. He aquí los resultados:

$ sloccount libparted
SLOC	Directory	SLOC-by-Language (Sorted)
16514   fs              ansic=16514
10568   labels          ansic=10568
3227    arch            ansic=3227
3197    top_dir         ansic=3161,asm=36
798     cs              ansic=798
290     tests           ansic=280,sh=10
 
Totals grouped by language (dominant language first):
ansic:        34548 (99.87%)
asm:             36 (0.10%)
sh:              10 (0.03%)
 
Total Physical Source Lines of Code (SLOC)                = 34,594
 
$ grep -R "switch (" *  | wc -l
107
 
$ grep -R "case " *  | wc -l
563
 
$ grep -R "break;" *  | wc -l
204
 
$ grep -R "if" * | grep "break;" | wc -l
20
 
$ grep -R "continue;" *  | wc -l
109
 
$ grep -R "goto " * | wc -l
609
 
$ grep -R "return" * | wc -l   
3619
 
$ grep -R "return;" * | wc -l
36
 
$ grep -R "if" * | grep "return" | wc -l
168
 
$ grep -R "if" * | grep "continue;" | wc -l
2

Obviamente, son muchas menos líneas de código totales, por lo que los resultados son menores en valor absoluto.

En otro orden de cosas, y sobre el uso de gotos, breaks, y demás: si están en el lenguaje es porque alguien ha decidido que deben estar y que son útiles, por lo que prohibirlas sería intentar ser más papista que el Papa. La cuestión está en saber usarlas. Un goto bien usado no tiene porqué ser demoníaco, pero tampoco es plan llenar el código de gotos que, a mi entender, "ensucian" el código. Hay muchas formas de hacer las cosas. Por ejemplo:

int i=0;
do {
    // sentencias
    i += 1;
while(i<10);

Otro ejemplo:

if(x==1 || x==2 || x==3 || x==4 || x==5 || x==6 || x==7 || x==8){
/* no hacer nada */
} else {
    printf("La coordenada x no es correcta");
}

Ambos son ejemplos válidos y que hacen lo que tienen que hacer, pero todos los escribiríamos de otra forma, ¿a que sí? (Bueno, todos no)

Nacho

Imagen de brue

a mi nunca me dio clase ...

... Dijkstra, pero vamos, que antes de programar algo nuevo intento ver cómo lo han hecho los que han llegado a soluciones óptimas o al menos buenas. Una cosa que no entiendo es lo del return en la última linea. Si por ejemplo quiero devolver verdadero o falso, por qué voy a usar una variable para asignarle 1 o 0 ? Directamente el return, y no hay "tu tía". No hay por qué gastar variables innecesarias, eso si que me parece un pecado capital :) .. y si son globlales más aun.

Me gustaría ver las mismas estadísticas del emacs, de linux y de GCC.

Espero que cumplan a "rajatabla" los mandamientos de primero de la ESI de la UCLM de España ...

Sinceramente, hay normas que para mí, se pueden saltar, con mucho cuidado y comentando BIEN el código, si se requiere, por ejemplo, para aumentar el rendimiento. De todos modos, hoy en día, con compiladores como GCC esto empieza a dejar de tener sentido alguno. Otra cosa es el ASM... y los getters y setters ... :P

En resumen, lo más importante es saber lo que estás haciendo ya que no me imagino un programa en tiempo real haciendo algo como ...

for (i=0; i < 20000000; i++)
vectorfisica.at(vectorobjetos.at(variable)->getobjeto(&obj))->dostep()

... y a veces no es malo tener las capacidades de C++ cerca, así que de vez en cuando hay que hacer algunos trucos sucios para llegar al objetivo final.

Lo que me lleva a pensar que o el Java ese es una mierda, o es un juego... mi lógica aplastante compite con la de Prolog.

brue

Yo hablo por mi

y con conocimiento de causa de que muchas de las cosas que ha puesto sobre "prácticas lícitas de programación" se has he comentado yo, pero si, en clase de programación cuando empecé la carrera (bueno, y en algunas asignaturas mas) te decía algo así que como viesen un goto, continue o break (especial énfasis en ésta última sentencia) fuera de un switch te fueses despidiendo de aprobar la asignatura.

Luego te pones a programar de verdad, viendo código de otros que tu llegas a considerar como gurús (y que sigue siendo así) y ves que usan gotos, continues, breaks, etc y no solo hacen mas ligero ése código, sino que además mas entendible (y no "spaggetti-code" como nos decían en primero).

Conclusión: No estaria de mas, que los profesores de programación tanto de aquí como de otras escuelas (que lo he visto por muchos sitios) diesen una idea un poco mas global de la programación y no tan mecánica. Desde mi punto de vista es cierto que no puedes usar breaks y demas sentencias al buen tuntún, pero tampoco me parece demás poner alguno por ahí cuando haga falta. He comprobado que hacen mas fácil la tarea de programar y el código mas legible y mantenible (prefiero dos if anidados y un break o dos a 5 if anidados, por mucha moto que me quieran vender), aspecto muy importante, pero que parece que se desvirtua un poco pensando que lo importante es que funcione.

Y ojo, que esto no es una crítica a los profesores de programación ni de aquí ni de ningún sitio, que ya nos conocemos y hay quien está un poco al acecho para tirarte a los leones, si acaso una sugerencia. Algo tendrán que saber sobre el tema para dar clases ¿no?

The cause of the problem is:
The vendor put the bug there.
-- Meta amigo informático --

Imagen de Lk2

Puedo entenderlo

Puedo entender que te suspendan si usas "gotos" y demás... en los problemas que nos planteaban en primero no eran necesarios y así conseguían que los alumnos no usáramos atajos que, para esos problemas, si que podían convertir el código en "spaghetti code".

Pero otra cosa es la forma de enseñarlo: no puedes enseñar en primero que el "goto = caca", porque no es así. A veces es necesario y mejora la legibilidad, como dices. Otra cosa es que no se use como solución en cierto tipo de problemas, pero otras veces hace que todo sea más fácil y mejor.

Yo por ejemplo lo de no tener más de un return en una función siempre lo vi bastante estúpido... ¡con la de secuencias de control que podían ahorrarse!

Imagen de david.villa

Depende

Obviamente el goto es peligroso y en manos inexpertas puede ser un arma de destrucción masiva. Es lo mismo que pasa con los punteros. Yo siempre pongo un símil: La dinamita es muy peligrosa, pero no por eso se prohibe su uso, dado que es muy útil e incluso irreemplazable para muchas aplicaciones (minería, demoliciones). Lo que se hace es controlar que solo el personal cualificado tenga permiso para usarla. Pues con el goto y los punteros se puede hacer lo mismo. Si hay personal cualificado que sabe lo que hace (programadores que puedan llamarse así) no hay motivo para prohibirlos.

Hay un buen motivo para que Java no tenga goto y no es porque sea pecado. Uno de los pocos usos aceptables de goto en programación estructurada es para romper bucles anidados, dado que el break convencional solo rompe el bucle en el que se encuentra.

for (…) {
    for (…) {
        if (noséqué) goto fuera;
     }
}
 
fuera:

Sin embargo en Java esto se puede hacer sin goto puesto que tiene label break y también label continue que no son otra cosa que gotos con el disfraz políticamente correcto, que no tiene nada de malo.

fuera: for (…) {
           for (…) {
               if (noséqué) break fuera;
           }
        }

Sin embargo, no estoy de acuerdo con lo dices de los atajos. ¿Qué hay de malo en utilizar una solución más sencilla, un camino más corto si está a tu alcance?. Y tampoco lo del código spaghetti. Una función que haga un uso inteligente de break, continue, return y excepciones (si están disponibles) será mucho más corta, legible y mantenible que la versión sin ellos. Es decir, es el hecho de no usarlos lo que convierte el código en espaguetti.

No soy portavoz de ningún colectivo, grupo o facción. Mi opinión es personal e intransferible.

Si hablamos de programación

Si hablamos de programación estructurada (block structured programming) hay muchos otros usos permitidos del goto. En realidad cuando se habla de “usos permitidos” del goto se suelen referir a contextos en los que el lenguaje concreto no incluye una construcción más apropiada. Por ejemplo esto es perfectamente conforme a las reglas de la programación estructurada:

#include <stdio.h>
int main() {
    int i = 0;
main_for_0_9:
    printf("%d\n", i);
    if (++i < 10) goto main_for_0_9;
    return 0;
} 

Hay un bloque main con un único punto de entrada y de salida y dentro de él hay una inicialización, otro bloque que va desde la etiqueta hasta el if incluidos, y una sentencia return.

Pero probablemente es mucho más legible y menos propenso a errores utilizar el soporte de C para bloques (llavecitas y funciones) y para bucles:

#include <stdio.h>
int main() {
    int i = 0;
    for (i=0; i<10; ++i)
        printf("%d\n", i);
    return 0;
} 

El problema es que C, a diferencia de Ada, o Java, no tiene un soporte razonablemente completo de bucles estructurados y por tanto hay muchas situaciones en las que hay que usar goto. Un bucle estructurado es un bloque de este estilo

comienzo_bucle:
    sentencias antes de condicion...
    if (condición) goto fin_bucle;
    sentencias después de condición...
    goto comienzo_bucle;
fin_bucle:

Como veis es un bloque con un único punto de entrada y un único punto de salida y, salvo que se haga un poco el salvaje con efectos de lado, será conforme a los principios de la block structured programming. Sin embargo C solo tiene soporte directo para dos extremos de esta construcción: cuando no hay sentencias antes de la condición (for y while), y cuando no hay sentencias después de la condición (do ... while). La mayoría de las veces podremos apañarnos con:

for(;;) {
    sentencias antes de condicion...
    if (condicion) break;
    sentencias despues de condicion...
}

Pero el “uso aceptable” de goto descrito por David no es estructurado en el caso general, puesto que el bucle interno pasa a tener dos puntos de salida.

Otro tema sería preguntarse qué tiene de bueno que sea estructurado en bloques. Se trata de una técnica más, como la programación orientada a objetos, la programación genérica, etc. Es útil para lo que es útil y no es la panacea que resuelve todos nuestros problemas. Por ejemplo, en un programa estructurado en bloques no podríamos usar excepciones (no muy realista, ¿eh?). A cambio ganamos que se puede convertir en una FSM muy facilmente y verificarlo formalmente. ¿Cuántos de vosotros utilizais verificación formal de vuestros programas? A lo mejor deberíamos plantearnos si de verdad interesa que nuestros programas sean estructurados.

Pero ojo, que no sea estructurado en bloques no implica que tenga que ser modelo chapucilla, spaguetti, o similar. Se trata de que sea fácil de escribir, de leer y de mantener. Y ahí el sentido común es el mejor consejero.

Imagen de david.villa

Creo

que has dado justo en el clavo. ¿De verdad hay que conseguir a toda costa que un programa siga a rajatabla los principios de la programación estructurada, o de cualquier otra? Sin pararse a pensar si es bueno o es malo ¿Cómo si fuera una religión? Porque si fuera así, como tú apuntas, lo primero sería prohibir el uso de excepciones, ya que permiten múltiples puntos de salida de un bloque y lo que es “peor”, el flujo de ejecución puede continuar en la función llamante, o en otro módulo o incluso llegar a terminar el proceso.

Creo sinceramente que el objetivo de la “enseñanza” de la programación, como en casi cualquier faceta del desarrollo personal o intelectual, debería perseguir que el alumno desarrolle un enfoque crítico con lo que lee y con lo que escribe; que lo cuestione todo y a todos, en lugar de aprender normas sin llegar a preguntarse si realmente tienen algún sentido u objetivo.

PS: Por cierto, mira Listados de código en CRySoL, que ahora tenemos coloreado de sintaxis dependiente del lenguaje y todo Eye-wink

No soy portavoz de ningún colectivo, grupo o facción. Mi opinión es personal e intransferible.

Evidentemente

La forma mas "políticamente correcta" no es siempre la mas adecuada (a colación de lo que decías de usar un atajo si está disponible). Prueba de ello es la famosa historia (leyenda urbana o no) que circula sobre Niels Bohr.
El usar algo muy enrevesado no implica mayor "inteligencia". Al fin y al cabo se viola la Ley del Mínimo esfuerzo (máxima de la naturaleza, que como sabemos "es sabia").

The cause of the problem is:
The vendor put the bug there.
-- Meta amigo informático --

A mi me parece más bien que

A mi me parece más bien que algunos tienen una idea más que peregrina y arbitraria de lo qué es saber programar Eye-wink

Imagen de manueldavid

Lo fundamental es usar el sentido común (obviamente :-) ).

Al igual que la mayoría de vosotros me enseñaron que el "goto = vas al infiernete". Más de una calavera me dibujaron en mis prácticas impresas por utilizar un goto o por utilizar un break fuera de un switch.

Lo a que a mí me pasaba es que como dice David, veía el atajo antes que cualquier otra cosa, y una vez visto consideraba estúpido cualquier otro camino.

En cuanto a hacer código espagueti, se puede hacer código espagueti con gotos, sin gotos, con llamadas a una función y dentro de esa a otra función, y dentro de esa a otra y dentro de esa llamamos a la primera, y fijaros que aquí no estoy utilizando ningún goto, pero no estoy utilizando el sentido común.

Yo si fuera profesor, dejaría utilizar por supuesto todas las instrucciones que tiene disponible un lenguaje, sólo exigiría sentido común al utilizarlas.

Saludos.

No sueño con grandes palacios, no sueño con grandes lujos, no sueño con grandes poderes, porque yo sólo sueño con grandes sueños