tipo - ¿Por qué Java permite el control de caracteres en sus identificadores?
tipos de variables en java ejemplos (3)
La sección 3.8 de la especificación del lenguaje Java se remite a Character.isJavaIdentifierStart() y Character.isJavaIdentifierPart() . Este último, entre otras condiciones, tiene Character.isIdentifierIgnorable() , que permite caracteres de control que no son de espacio en blanco (incluido el rango C1 completo, consulte el enlace de la lista).
El misterio
Al explorar con precisión qué caracteres estaban permitidos en los identificadores de Java, me encontré con algo tan extremadamente curioso que parece casi seguro que sea un error.
Esperaba encontrar que los identificadores de Java cumplían con el requisito de que comenzaran con caracteres que tienen la propiedad Unicode ID_Start
y son seguidos por aquellos con la propiedad ID_Continue
, con una excepción otorgada para subrayados ID_Continue
y para signos de dólar. Eso no demostró ser el caso, y lo que encontré está en desacuerdo extremo con esa u otra idea de un identificador normal del que he oído hablar.
Demostración corta
Considere la siguiente demostración que demuestra que un carácter ASCII ESC (octal 033) está permitido en los identificadores de Java:
$ perl -le ''print qq(public class escape { public static void main(String argv[]) { String var_/033 = "i am escape: /033"; System.out.println(var_/033); }})'' > escape.java
$ javac escape.java
$ java escape | cat -v
i am escape: ^[
Sin embargo, es incluso peor que eso. Casi infinitamente peor, de hecho. ¡Incluso los NULL están permitidos! Y miles de otros puntos de código que ni siquiera son caracteres de identificación. He probado esto en Solaris, Linux y una Mac con Darwin, y todos dan los mismos resultados.
Demo larga
Aquí hay un programa de prueba que mostrará todos estos puntos de código inesperados que Java permite de manera extravagante como parte de un nombre de identificador legal.
#!/usr/bin/env perl
#
# test-java-idchars - find which bogus code points Java allows in its identifiers
#
# usage: test-java-idchars [low high]
# e.g.: test-java-idchars 0 255
#
# Without arguments, tests Unicode code points
# from 0 .. 0x1000. You may go further with a
# higher explicit argument.
#
# Produces a report at the end.
#
# You can ^C it prematurely to end the program then
# and get a report of its progress up to that point.
#
# Tom Christiansen
# [email protected]
# Sat Jan 29 10:41:09 MST 2011
use strict;
use warnings;
use encoding "Latin1";
use open IO => ":utf8";
use charnames ();
$| = 1;
my @legal;
my ($start, $stop) = (0, 0x1000);
if (@ARGV != 0) {
if (@ARGV == 1) {
for (($stop) = @ARGV) {
$_ = oct if /^0/; # support 0OCTAL, 0xHEX, 0bBINARY
}
}
elsif (@ARGV == 2) {
for (($start, $stop) = @ARGV) {
$_ = oct if /^0/;
}
}
else {
die "usage: $0 [ [start] stop ]/n";
}
}
for my $cp ( $start .. $stop ) {
my $char = chr($cp);
next if $char =~ /[/s/w]/;
my $type = "?";
for ($char) {
$type = "Letter" if //pL/;
$type = "Mark" if //pM/;
$type = "Number" if //pN/;
$type = "Punctuation" if //pP/;
$type = "Symbol" if //pS/;
$type = "Separator" if //pZ/;
$type = "Control" if //pC/;
}
my $name = $cp ? (charnames::viacode($cp) || "<missing>") : "NULL";
next if $name eq "<missing>" && $cp > 0xFF;
my $msg = sprintf("U+%04X %s", $cp, $name);
print "testing //p{$type} $msg...";
open(TESTPROGRAM, ">:utf8", "testchar.java") || die $!;
print TESTPROGRAM <<"End_of_Java_Program";
public class testchar {
public static void main(String argv[]) {
String var_$char = "variable name ends in $msg";
System.out.println(var_$char);
}
}
End_of_Java_Program
close(TESTPROGRAM) || die $!;
system q{
( javac -encoding UTF-8 testchar.java /
&& /
java -Dfile.encoding=UTF-8 testchar | grep variable /
) >/dev/null 2>&1
};
push @legal, sprintf("U+%04X", $cp) if $? == 0;
if ($? && $? < 128) {
print "<interrupted>/n";
exit; # from a ^C
}
printf "is %s in Java identifiers./n",
($? == 0) ? uc "legal" : "forbidden";
}
END {
print "Legal but evil code points: @legal/n";
}
Aquí hay una muestra de cómo ejecutar ese programa solo en los primeros 33 puntos de código que no son ni espacios en blanco ni caracteres de identificación:
$ perl test-java-idchars 0 0x20
testing /p{Control} U+0000 NULL...is LEGAL in Java identifiers.
testing /p{Control} U+0001 START OF HEADING...is LEGAL in Java identifiers.
testing /p{Control} U+0002 START OF TEXT...is LEGAL in Java identifiers.
testing /p{Control} U+0003 END OF TEXT...is LEGAL in Java identifiers.
testing /p{Control} U+0004 END OF TRANSMISSION...is LEGAL in Java identifiers.
testing /p{Control} U+0005 ENQUIRY...is LEGAL in Java identifiers.
testing /p{Control} U+0006 ACKNOWLEDGE...is LEGAL in Java identifiers.
testing /p{Control} U+0007 BELL...is LEGAL in Java identifiers.
testing /p{Control} U+0008 BACKSPACE...is LEGAL in Java identifiers.
testing /p{Control} U+000B LINE TABULATION...is forbidden in Java identifiers.
testing /p{Control} U+000E SHIFT OUT...is LEGAL in Java identifiers.
testing /p{Control} U+000F SHIFT IN...is LEGAL in Java identifiers.
testing /p{Control} U+0010 DATA LINK ESCAPE...is LEGAL in Java identifiers.
testing /p{Control} U+0011 DEVICE CONTROL ONE...is LEGAL in Java identifiers.
testing /p{Control} U+0012 DEVICE CONTROL TWO...is LEGAL in Java identifiers.
testing /p{Control} U+0013 DEVICE CONTROL THREE...is LEGAL in Java identifiers.
testing /p{Control} U+0014 DEVICE CONTROL FOUR...is LEGAL in Java identifiers.
testing /p{Control} U+0015 NEGATIVE ACKNOWLEDGE...is LEGAL in Java identifiers.
testing /p{Control} U+0016 SYNCHRONOUS IDLE...is LEGAL in Java identifiers.
testing /p{Control} U+0017 END OF TRANSMISSION BLOCK...is LEGAL in Java identifiers.
testing /p{Control} U+0018 CANCEL...is LEGAL in Java identifiers.
testing /p{Control} U+0019 END OF MEDIUM...is LEGAL in Java identifiers.
testing /p{Control} U+001A SUBSTITUTE...is LEGAL in Java identifiers.
testing /p{Control} U+001B ESCAPE...is LEGAL in Java identifiers.
testing /p{Control} U+001C INFORMATION SEPARATOR FOUR...is forbidden in Java identifiers.
testing /p{Control} U+001D INFORMATION SEPARATOR THREE...is forbidden in Java identifiers.
testing /p{Control} U+001E INFORMATION SEPARATOR TWO...is forbidden in Java identifiers.
testing /p{Control} U+001F INFORMATION SEPARATOR ONE...is forbidden in Java identifiers.
Legal but evil code points: U+0000 U+0001 U+0002 U+0003 U+0004 U+0005 U+0006 U+0007 U+0008 U+000E U+000F U+0010 U+0011 U+0012 U+0013 U+0014 U+0015 U+0016 U+0017 U+0018 U+0019 U+001A U+001B
Y aquí hay otra demo:
$ perl test-java-idchars 0x600 0x700 | grep -i legal
testing /p{Control} U+0600 ARABIC NUMBER SIGN...is LEGAL in Java identifiers.
testing /p{Control} U+0601 ARABIC SIGN SANAH...is LEGAL in Java identifiers.
testing /p{Control} U+0602 ARABIC FOOTNOTE MARKER...is LEGAL in Java identifiers.
testing /p{Control} U+0603 ARABIC SIGN SAFHA...is LEGAL in Java identifiers.
testing /p{Control} U+06DD ARABIC END OF AYAH...is LEGAL in Java identifiers.
Legal but evil code points: U+0600 U+0601 U+0602 U+0603 U+06DD
La pregunta
¿Alguien puede explicar este comportamiento aparentemente loco? Hay muchos, muchos, muchos otros puntos de código inexplicablemente permitidos en todo el lugar, comenzando con U + 0000, que es quizás el más extraño de todos. Si lo ejecuta en los primeros puntos de código de 0x1000, verá aparecer ciertos patrones, como permitir todos y cada uno de los puntos de código con la propiedad Current_Symbol
. Pero mucho más es completamente inexplicable, al menos yo.
No veo cuál es el problema. ¿Cómo te afecta de todos modos?
Si un desarrollador quiere ofuscar su código, puede hacerlo con ASCII.
Si un desarrollador desea que su código sea comprensible, usará la lingua franca de la industria: inglés. No solo los identificadores son solo ASCII, sino también palabras comunes en inglés. De lo contrario, nadie usará o leerá su código, puede usar cualquier personaje loco que le guste.
Otra pregunta podría ser: ¿Por qué Java no debería permitir el control de caracteres en sus identificadores?
Un buen principio al diseñar un idioma u otro sistema es no prohibir nada sin una buena causa, ya que nunca se sabe cómo se podría usar, y cuanto menos reglas implementen y los usuarios tengan que lidiar mejor, mejor.
Es cierto que no debería aprovecharse de esto, integrando escapes en sus nombres de variable, y no verá ninguna biblioteca popular que exponga clases con caracteres nulos en ellas.
Ciertamente, esto podría ser objeto de abuso, pero no es el trabajo de los diseñadores de lenguaje el proteger a los programadores de ellos mismos de esta manera, de la misma manera que no forzando la sangría apropiada o nombres variables bien elegidos.