Anatoly Vorobey (avva) wrote,
Anatoly Vorobey
avva

Categories:

о читабельности кода

Я беседовал сегодня с Г. о разных способах выразить то же самое на C/C++ и смежных языках, и действительно ли это влияет на быстроту и качество чтения и понимания кода. Вообще говоря в последние годы я обнаруживаю в себе все больше и больше неприязни к "хитрым" способам что-то выразить в коде, более лаконичным, чем простой и наивный способ сказать то же самое, даже если он занимает больше строк кода. Почти все "хитрые" трюки привлекают к себе внимание и задерживают это внимания на себе, снижают скорость чтения кода и ясность его понимания. Но ровно то же самое я мог бы сказать и пять лет назад, а вместе с тем мое отношение изменилось еще дальше в сторону против трюков; наверное, мне нравится в коде еще большая ясность и прозрачность, чем раньше, и "трюками" я считаю вещи, которые другие программисты, наверное, сочтут совсем обыденными. Не захожу ли я в этом слишком далеко?

Вот два примера - мелких, и, может, даже мелочных, но иллюстрируют тему.

Пару лет назад мы говорили с Г. ровно о том же и тогда я сказал ему: в юности меня раздражала идиома работы с указателем if (p != NULL), и вместо нее в своем личном коде я всегда писал if (p) (и еще указателям присваивал 0, а не NULL). А сейчас, сказал я, предпочту первый вариант. Попытался объяснить, почему: когда читаешь такой код, как if(p), то пусть на долю секунды, но твое внимание отвлекается на него и ты делаешь приведение типов "в уме". Я могу сколь угодно твердо знать и помнить, что именно означает указатель в булевом контексте, но когда я читаю строки кода до того, я все равно помню, что вот это - указатель, а вот это - булево (логическое) выражение, и они совершенно разные вещи. Оператор != дает мне перейти от одного к другому, совершенно не задумываясь об этом переходе, именно потому, что он есть, этот оператор, он явно говорит: сейчас я вам дам булевое значение. А в случае if(p) мне нужно этот переход сделать самому, и это хоть на крохотную долю секунды, но отвлекает меня от действительно важных вещей.

Сегодня мы вспомнили ту старую беседу, потому что обсуждали похожий пример, в котором моя точка зрения показалась Г. совсем уж возмутительной (в случае с if(p) он говорит, не соглашается с моим выбором, но понимает логику). У меня есть в конце работы функции переменная, скажем, results. Функция возвращает булевое значение. Как мне написать выход:


1. return results > 0;


или


2. if (results > 0) {
  return true;
} else {
  return false;
}


(не обращайте внимания на скобки во втором варианте, это эстетика, если они вам не нравятся, представьте его без них). Я сказал, что хоть самому это странно говорить, пожалуй, предпочту второй вариант. Г. вначале подумал, что я пошутил так. А я не шучу. Логика та же самая. У меня нет никаких проблем понять первую форму, более того, мне естественно именно первую форму написать и это мое первое побуждение, но когда я думаю о том, как через полгода буду этот код читать (или кто-то другой будет), то, поколебавшись, выбираю вторую форму. Это не очевидный для меня выбор, понятно, что жалко тратить несколько строк, там где одной, вполне очевидной, хватает, но все-таки ясность чтения пересиливает.

У меня есть два аргумента против первой формы. Во-первых, почему это "трюк", почему привлекает внимание? Потому что мы относимся (кстати, прошу помнить, что я говорю о себе, все эти "мы" условные и гипотетиечские, если у вас это устроено по-другому, я не против), так вот, мы относимся к булевым значениям иначе, чем к целым числам, строкам, числам плавающей точкой. Все эти типы для нас - единицы информации, а булевы значения, как бы это сказать, единицы решения (information units и decision units). Мы привыкли в коде видеть булевы значения в одном из трех контекстов: 1) литералы true/false в аргументах и возвратных значениях функций; 2) внутри контрольных структур: if while for итд.; 3) аргументы логических операторов == != < итд. Причем третий контекст обычно содержится внутри второго, контекста "решения, что делать", а первый тоже можно считать его под-видом, только отдаленным во времени, но особенно прозрачным образом (скажем, если мы вернули true, то ожидаем, что кто-то тут же с этим значением сделает что-то "решительное", связанное с решением). Все другие использования булевых значений оказываются "странными" и привлекают наше внимание. Я не хочу эту "странность" преувеличивать, она, может, минимальна, и конечно при чтении все очевидно, но все же она есть. Иногда эта странность оправдана, потому что ее требует логика задачи. Скажем, при вычислении сложных и запутанных булевых значений стоит положить промежуточный результат в переменную. Или хранить в булевой переменной done условие окончания цикла. Или держать вектор булевых значений для чего-то. Во всех этих случаях, конечно, надо использовать булевые значение в этих немного более редких контекстах (хотя насчет "done" я не уверен - он настолько обычен, что редким трудно счесть, может, его стоит записать в собратья первого пункта выше), что поделать, это нужно "для работы". А в "return results > 0" это использование булевого значения скорее несколько фривольное, чем "для работы", поэтому крохотная задержка внимания, которого оно требует, неоправдано.

О втором аргументе я подумал чуть позже, и вот он какой. Я пытался наблюдать за собой, как именно я воспринимаю "return results > 0". И вдруг понял, как это охарактеризовать: зная, что функция возвращает булевое значение, я неизбежно, читая эту строку, делаю в уме такой небольшой танец на месте. Я думаю (пусть не словами, а сильно сокращенными ощущениями, но все же): "если больше нуля, то true, если нет, то false". Если так, то так, а если нет, то этак. Шаг влево - так, шаг вправо - этак. Выходит, что я в уме продумываю ровно то же, что и при чтении более длинной формы с if (results > 0). Но если она все равно у меня в уме возникает, так пусть и на экране будет. Несовпадение знаков на экране и продумывания в уме и вызывает это крохотную задержку при чтении, и ситуация тут аналогична if(p). А вот, скажем, если бы было написано что-то вроде "return IsPositive(results);", то это у меня не вызывает танца в уме, тут нет никакой задержки. Танец как бы откладывается до того момента, когда я прочитаю код IsPositive. (Это не значит, конечно, что надо именно так писать - у дополнительной функции и дополнительного уровня косвенности есть своя когнитивная цена. Я лично так писать не стану.)

Вот так как-то.
Tags: программирование
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 215 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →