БлогNot. О красоте в программировании

О красоте в программировании

В конечном счёте, чувство прекрасного – вещь, принимающая наиболее странные формы. Вот и программисты, лелея своё весьма своеобразное понимание красоты, нередко спорят о "красивых" и "некрасивых" приёмах кодинга. Ну, если не просто банально меряются самомнениями. Без последнего тоже нельзя, ведь человек в принципе не может воспринимать и запоминать без эмоционального отношения, а спорить – без убеждений, основанных на предубеждениях.

На заре своего развития всякая технология – искусство, со своими понятиями о красоте и канонами прекрасного. Деградировав до скучного ремесла, она эти каноны теряет, но развившееся из былого искусства ремесленничество заполоняет мир бездарными поделками, на фоне которых старое мастерство опять начинает цениться, уже как античная редкость. Так было с каллиграфией, иконописью, резьбой по дереву или умением считать на канцелярских счётах, так происходит сейчас и с программированием.

Массовые решения, основанные на сильно избыточных, громоздких и некрасивых библиотеках кода, заставляют нас по-новому оценить штучную работу и редкие проблески продуманной оптимизации программ. Попытаемся выделить какие-то признаки, позволяющие отличить красивый код от просто правильного и массового.

1. По сути, красота всегда есть оптимально организованная избыточность, некая производная от сложности, вызов, который переросшая простую функциональность система бросает Вселенной. Голая женщина и играющий волчонок красивы - но только в мире людей и мире волков. С другой стороны, красота порождена беспощадным отбором, жёстко привязавшим её к среде, в которой она только и имеет смысл:

while (*s++=*d++); *s='\0';

(копирование строки на C) или

$s =~ s/^\s+|\s+$//g;

(реализация trim на Perl) - это красиво, потому что используются почти все мыслимые соотношения знаков, а за лаконичностью скрываются большая сложность и значительный объём умолчаний. Итак, красота - это много скрытого смысла за краткостью, почти как в

Ночь. Улица. Фонарь. Аптека.
Крюк. Коридор. Верёвка. Мыло.
Всё повторяется от века,
И повторенье тоже было

- ладно, конечно Блока хотел процитировать, а не себя :)

Лишний синтаксис, конечно, тоже имеет отрицательное для красоты значение - begin end всегда хуже { }, а repeat until () совсем уж безобразие по сравнению с do { } while ().

Сколько бы мы ни спорили, скажем, о PHP, язык, в котором надо такое делать с поступающими в программу данными, красивым быть не может, как не могут быть красивыми и защищённые решения в изначально открытой среде инета... если там что и мелькнуло небезобразное, это гены C и Perl.

2. Отождествлять при этом красоту с лаконичностью не стоит. Да, в красивом коде для достижения того же результата порой выполняется меньше действий, чем у конкурентов, так, выбор элементов с главной диагонали матрицы кодом вида

for (int i=0; i<n; i++)
for (int j=0; j<n; j++)
if (i==j) a[i][j]=...

не просто избыточен по сравнению с

for (int i=0; i<n; i++) a[i][i]=...

он ещё и раздражающе уродлив этой избыточностью.

Но красивое совсем необязательно проще и понятней, скорее, оно даёт новый взгляд на стандартное действие, скажем, умножение числа на 2 кодом

n<<=1;

вместо

n*=2;

красивей, но неочевидней первого варианта, как и проверка совпадения знаков переменных x и y в виде

if (x*y>0)

вместо

if (x>0 && y>0 || x<0 && y<0)

или обмен значений a и b кодом вида

b=a+b; a=b-a; b=b-a;

вместо привычного

c=a; a=b; b=c;

Пожалуй, тут можно заметить лишь тот общий принцип, что красота, подчиняясь природе и бритве Оккама, не склонна порождать лишних сущностей, скорее, она заставит имеющиеся сущности выполнять несвойственную им работу, совместив органы выделения с органами размножения...

3. Увы, из первого свойства красоты как сложности, порождённой большим числом умолчаний и скрытых обобщений, следует то, что красивые решения обычно хуже поддаются обобщению... они уже достигли предела своего развития при данных условиях, потому и красивы...

Так, определение чередования чётных и нечётных значений в одномерном массиве, сделанное красивым по второму признаку кодом вроде

int ch=a[0]%2;
for (int i=1; i<n; i++) {
 if (a[i]%2==ch) return 0;
 ch^=1;
}
return 1;

явно не поддастся также легко переделке на остаток от деления на 3, как тривиальное

for (int i=0; i<n-1; i++)
 if (a[i]%2==a[i+1]%2) return 0;
return 1;

Красоту легко только портить, но не переделывать.

4. Из ложного принципа "как бы не пришлось править однажды написанное" родилось ООП. Меж тем, принцип должен быть другим - править должно быть легко, и одно изменение логики всегда соответствует одному месту в программе. Элементарное

const int n=10; int a[n];

вместо размерной константы, заданной числом:

int a[10];

и другие столь же банальные принципы делают код красивей и желанней. Красивое, в общем, красиво целиком, в нём нельзя отрезать уши и приставить к попе. И на изменившиеся обстоятельства оно прореагирует минимально возможными правками. Принцип эволюции, впрочем, вытекающий из пункта 1.

5. Наконец, красивое обычно чревато иными применениями. Классический двойной цикл сравнения элементов массива по принципу "каждый с каждым"

for (int i=0; i<n-1; i++)
for (int j=i+1; j<n; j++)
 if (a[i] СРАВНЕНИЕ a[j]) ...

без изменений применим и к сортировке элементов вектора, и к вычислению любых парных характеристик, и даже к транспонированию матрицы, если внутри двойного цикла стоит перестановка местами элементов a[i][j] и a[j][i]. Он просто красивее уныло–избыточного "простого двойного".

У "красивого" обычно есть и "двойственная задача", тот же "каждый с каждым" на векторе мог бы быть записан и в виде

for (int i=1; i<n; i++)
for (int j=0; j<i; j++)
 if (a[i] СРАВНЕНИЕ a[j]) ...

соответствуя выбору элементов матрицы ниже, а не выше главной диагонали.

6. И снова "увы", красота, как и всё другое, достигшее полноты воплощения, склонна зацикливаться на своём совершенстве.

Каждый программист знает тот грустный факт, что закрытие "дыры" в программе всегда потенциально означает лишь "дыру" большего размера. Всё как в жизни - есть лишь вариативность в изначально заложенных пределах и ресурсах. Мы не творим, но "перераспределяем". Пожалуй, эта похожесть на жизнь – самая некрасивая сторона программирования.

Рассуждение пошло по кругу и стало пессимистичным, пожалуй, на этом его и закончу.

03.12.2014, 14:46 [10463 просмотра]


теги: программирование философия список алгоритм искусство

К этой статье пока нет комментариев, Ваш будет первым