Оживший юзерпик (tobotras) wrote,
Оживший юзерпик
tobotras

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

ППКС. Оригинал взят у avva в о читабельности кода
Я беседовал сегодня с Г. о разных способах выразить то же самое на 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. (Это не значит, конечно, что надо именно так писать - у дополнительной функции и дополнительного уровня косвенности есть своя когнитивная цена. Я лично так писать не стану.)

Вот так как-то.

Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 50 comments