Неужели люди могут писать безумный код со множеством побочных эффектов в одном выражении и не краснеть?

2017-07-25

В нашей внутренней рассылке по C# программисты часто спрашивают, как правильно интерпретировать выражения, подобные этим:
a -= a *= a;
p[x++] = ++x;

А я в свою очередь спрашиваю: «Кто в здравом уме пишет такой код?». У меня только одно объяснение: эти люди пытаются выиграть «Международный конкурс запутывания кода на С» или пишут программу-головоломку. В любом случае они должны понимать, что делают что-то очень странное. Серьёзно, неужели существует человек, которые пишет a -= a *= a и p[x++] = ++x; в уверенности, что его код великолепен?

Эрик Липперт (Eric Lippert) говорит: «Да, такие точно есть». В качестве примера он привел книгу одного автора, чей успех не вызывает вопросов: куплено более четырех миллионов экземпляров, и продажи продолжают расти. По мнению этого автора, чем код короче, тем быстрее он исполняется. Автор использовал несколько операций с побочными эффектами в одном выражении, тернарные операторы, как будто их скоро запретят, и в целом считал, что время выполнения пропорционально количеству точек с запятой, а от каждой переменной где-то в мире умирает щенок (на самом деле нет, пусть все щенята живут счастливо).

Разумеется, проявив усердие и смекалку, вы могли бы с помощью анализа потоков заставить компилятор выдать предупреждение вроде «Результат выполнения этой операции зависит от порядка вычислений», но тогда вы столкнётесь с другими проблемами.

Во-первых, обеспечена уйма ложных срабатываний. К примеру, вы можете написать: total_cost = p->base_price + p->calculate_tax();

В таком случае компилятор выдаст предупреждение, потому что обнаружит, что метод calculate_tax имеет тип, отличный от const, и поэтому выполнение метода может изменить значение base_price, а при этом важно учитывать, добавляете вы налог к первоначальной базовой цене или к обновленной. Но вы-то понимаете (обладая знаниями, недоступными компилятору), что метод calculate_tax изменяет налоговую юрисдикцию объекта, но не влияет на базовую цену. Значит, вы уверены, что срабатывание ложное.

Но подобных ложных срабатываний будет очень много (это неизбежно), и в конце концов пользователь просто отключит любые предупреждения.

Однако предположим, что вы передумали и теперь получаете предупреждения только о наиболее вопиющих случаях, когда переменная изменяется и сравнивается в одном выражении. «Внимание! Значение выражения зависит от порядка вычислений».

«Гений программирования» Вася Пупкин считает, что его код безупречен, а компилятор делает из мухи слона: «Да ведь понятно, что значение переменной сначала увеличивается, затем эта переменная используется для вычисления индекса элемента массива, а результат сохраняется обратно в переменную. Видишь, с порядком вычислений всё в порядке. Бестолковый компилятор!». «Гений программирования» Вася Пупкин отключает предупреждение. Конечно, «гений программирования» Вася Пупкин — случай безнадежный, поэтому особенно беспокоиться о нём не стоит.

Но есть ещё и Петя-новичок, который даже не понял смысла предупреждения: «Так, что у нас тут. Я компилировал эту функцию уже пять раз, и каждый раз получал один и тот же результат. Результат, на мой взгляд, вполне похож на правду. Думаю, это ложное предупреждение». Пользователи, для которых это предупреждение предназначалось, не обладают достаточными знаниями и опытом, чтобы извлечь из него пользу.

Разумеется, через некоторое время история повторяется. Кто-то снова спрашивает, почему конструкция типа x ^= y ^= x ^= y не работает в C#, хотя в C++ проблем не возникает. Это ещё одно доказательство того, что люди пишут код с большим числом побочных эффектов, будучи при этом полностью уверенными в том, что результат вполне понятен, и код должен работать как часы.

Раймонд Чен написал занимательную заметку для блога The Old New Thing. Благодарим за перевод представителей Microsoft.