[Перевод] Вредные заклинания в программировании
С тех пор, как я посмотрел легендарное видео Wat Гэри Бернхардта, меня завораживает странное поведение некоторых языков программирования. Некоторые из них таят больше сюрпризов, чем другие. Например, для Java написана целая книга с описанием пограничных ситуаций и странной специфики. Для C++ вы просто можете почитать сами спецификации всего за $200.
Далее поделюсь с вами моей коллекцией самых неожиданных, забавных и всё-таки валидных «заклинаний» программирования. По сути, использование этих особенностей поведения ЯП считается пагубным, поскольку ваш код никоим образом не должен быть непредсказуемым. Хорошо, что многие линтеры уже осведомлены и готовы посмеяться над вами, если попробуете какое-то из перечисленных дурачеств. Но как говорится, знание — сила, так что начнём.
Вражеское переназначение True в Python 2
Рифмуется с true, так что вы знаете, что это poo («какашка»).
>>> True = False
>>> True
False
К счастью, такой код выводит SyntaxError
в версии Python 3, поскольку True, False и None теперь стали зарезервированными словами. Такая шалость всё-таки далека от подлости в C++, когда вы вставляете #define true false
в стандартный заголовочный файл на рабочей машине коллеги.
Призрачное взаимодействие с объектом в Java и Python
Семантика ==
часто озадачивает начинающих Java-программистов, но ещё более усложняет ситуацию непостоянство оператора даже в тривиальных ситуациях, пусть это и сделано для производительности.
Integer a = 100;
Integer b = 100;
System.out.print(a == b); // prints true
Integer c = 200;
Integer d = 200;
System.out.print(c == d); // prints false
JVM использует однотипный справочник для значений в диапазоне [-128, 127]
. Что ещё более странно, так это соответствующее поведение Python.
>>> x = 256
>>> y = 256
>>> x is y
True
>>> x = 257
>>> y = 257
>>> x is y
False
Пока ничего слишком удивительного.
>>> x = -5
>>> y = -5
>>> x is y
True
>>> x = -6
>>> y = -6
>>> x is y
False
Похоже, нижний предел для интерпретатора Python такой же… -5
. Целые числа в диапазоне [-5, 256]
получают одинаковые ID. Но всё равно это работает как-то странно.
>>> x = -10
>>> y = -10
>>> x is y
False
>>> x, y = [-10, -10]
>>> x is y
True
Видимо, применение деструктурирующего присваивания сразу меняет правила. Я не уверен, почему так происходит, и даже задал вопрос на Stack Overflow в попытке разобраться. Может быть, повторяющиеся значения в списке указывают на тот же объект для экономии памяти.
Обратная запись с индексом в C
Обратная запись с индексом мгновенно доставляет головную боль любому разработчику.
int x[1] = { 0xdeadbeef };
printf("%x\n", 0[x]); // prints deadbeef
Причина работа такого кода в том, что array[index]
на самом деле просто синтаксический сахар для *(array + index)
. Благодаря коммутативному свойству сложения можно поменять их местами и получить тот же результат.
Оператор «перехода» в C
На первый взгляд оператор -->
выглядит как синтаксическая ошибка. Но когда вы понимаете, что он нормально компилируется, то начинаете думать, что это недокументированная функция языка. К счастью, это ни то, ни другое.
for (x = 3; x --> 0;) {
printf("%d ", x); // prints 2 1 0
}
«Оператор» -->
— это на самом деле два оператора, которые в этом контексте разбираются как (x--) > 0
. Известно, что такая штука вызывает немалую путаницу при использовании в продакшне — чистое зло.
Оператор sizeof
в C
Оператор sizeof
обрабатывается в процессе компиляции, что даёт ему интересные свойства.
int x = 0;
sizeof(x += 1);
if (x == 0) {
printf("wtf?"); // this will be printed
}
Поскольку объекты оператора sizeof
анализируются в процессе компиляции, то выражение (x += 1)
никогда не будет запущено. Также любопытно: исследования показывают, что printf("wtf?")
— самая популярная строчка кода, которая никогда не поступает в продакшн.
Начало индексов с единицы в Lua, Smalltalk, MATLAB и др…
На форумах /r/programminghumor полно мемов об «индексах, которыеначинаются с единицы». Поразительно, но немало языков программирования в реальности используют 1-индексированные массивы. Более полный список см. здесь.
0 соответствует true
в Ruby
…и только в Ruby. *
if 0 then print 'thanks, ruby' end # prints thanks, ruby
* правка: В обсуждении Reddit мне указали, что такое справедливо также для Lua, Lisp и Erlang.
Триграфы, диграфы и токены в C
По историческим причинам в C остались альтернативные варианты написания для нецифробуквенных символов.
Триграф | Символ | Диграф | Символ | Токен | Символ | ||
---|---|---|---|---|---|---|---|
??= |
# |
<: |
] |
%:%: |
## |
||
??/ |
\ |
:> |
[ |
compl |
~ |
||
??' |
^ |
<% |
{ |
not |
! |
||
??( |
[ |
%> |
} |
bitand |
& |
||
??) |
] |
%: |
# |
bitor |
| |
||
??! |
| |
and |
&& |
||||
??< |
{ |
or |
|| |
||||
??> |
} |
xor |
^ |
||||
??- |
~ |
and_eq |
&= |
||||
or_eq |
|= |
||||||
xor_eq |
^= |
||||||
not_eq |
!= |
if (true and true) { // same as if (true && true)
printf("thanks, c");
}
Некоторое чужеродное оборудование вроде IBM 3270 не позволяло набрать некоторые часто используемые символы в C/C++, так что ввели использование диагрфы, триграфы и токены, чтобы сохранить совместимость с определёнными кодировками.
Надеюсь, статья была интересной. Можете почитать обсуждение на Reddit.