Pull to refresh

Comments 34

Спасибо за ссылку! Прогнал Rails-проект, получил интересные результаты :)
Когда мы эту статью обсуждали с друзьями на работе, родилась идея для автоматической оценки читабельности имен переменных и функций: проверять их на соответствие английским словам, переменные должны быть существительными и быть больше некоторой минимальной длины, функции состоять из связки английских глагола-существительного. В случае несоответствия слова можно подчеркивать красным, как в Word'е.
Это уже перебор, я считаю :)
Это не решает проблемы с тем, что названия переменных могут не следовать соглашениям, смыслу содержимого или контексту. Пример из жизни на руби:

partner = PartnershipProfile.find(profile_id)
if partner && @order.available_for_partner?(partner) # на самом деле в качестве аргумента для available_for_partner? подразумевался partner.user
  @order.mark_with_prid partner.id
end


Есть такое распространенное соглашение, что переменная с объектом называется снейк кейсом от класса. В этом примере соглашение изначально не соблюдалось (лень сделала свое дело), и в аргументы ушел PartnershipProfile, хотя должен был уйти User содержащийся в любом PartnershipProfile. Привело к ошибке на живом сервере.

Надеюсь не притянуто за уши*
Чтобы отследить такие вещи, анализатор должен понимать смысл написанного. Поэтому оставим эту задачу на откуп человеку. По крайней мере пока.
А чтобы помочь будущим разработчикам в анализе кода, можно заранее определиться с терминологией, на подобии того, как заранее продумываются интерфейсы. А то я часто встречал примеры (иногда и сам грешил), когда одна вещь в разных местах называется по-разному, разные вещи называются одинаково, а некоторые названия придумываются мимоходом в процессе кодирования. Мне вообще кажется, что задачи проектирования и реализации должны разграничиваться. Сначала придумываем, что и как хотим сделать, а потом делаем. Если пытаться проектировать «по ходу», получается плохо. Проверял.
Очень компактный код может быть очень сложным для чтения
float InvSqrt (float x){
    float xhalf = 0.5f*x;
    int i = *(int*)&x;
    i = 0x5f3759df - (i>>1);
    x = *(float*)&i;
    x = x*(1.5f - xhalf*x*x);
    return x;
}

Поэтому, в метрику нужно еще включить время, потраченное на чтение кода. И почему бы не использовать метрику основанную исключительно на времени потраченном на его понимание?
Я думал об этом. Но способ со временем подойдет только для небольших фрагментов кода, так как при анализе большого объема кода человек может отвлечься и забыть поставить таймер на паузу.
Напрасно название оставили. Было бы интересно поугадывать. Других примеров нет?
Уж очень маловероятно что кто-то догадался бы. Тут либо графики строить, либо знать, либо сотни лет сидеть курить что это такое).
Нет, и это с трудом нашел.
Вот мне и было интересно, сколько времени потребуется. Думаю, 5 минут хватило бы: если догадаться, что строчка x = x*(1.5f — xhalf*x*x); это уточняющая итерация, то получаем x=(3*x-a*x^3)/2, т.е. a*x^3=x, x=1/sqrt(a). Для контроля можно проверить, так ли выглядит метод Ньютона для этой функции. А потом еще и вычислить магическую константу и сравнить с приведенной в коде. Но для понимания, как это работает — всё это не нужно, и так очевидно. Вот если бы пришлось разбираться, почему не работает, или оценивать точность — надо было бы пройти каждый шаг, с графиками.
Ну к счастью такие функции как в примере либо хорошо известны(как ваш пример), либо тщательно задокументированы. Поэтому читать обычно нужно псевдокод.
Не помню, чтобы я раньше встречал эту функцию. Она «хорошо известна» в качестве чего? Примера эффективного кода? Примера нечитаемого кода? Часть стандартного курса по С? Или часть программистского фольклора?
И неужели бывают люди, способные придумать и реализовать такой метод, а потом его «тщательно задокументировать»?
Почитал. Смесь эффективного кода и фольклора. И действительно, очень тщательно задокументирована :)
Она хорошо известна как пример быстрого на ходу придуманного очень быстрого алгоритма решающего задачу вычисления вычисления обратного квадратного корня с достаточно высокой точностью. ЕМНИП это был очень серьёзный прорыв в 3д графике лет 10 или 15 назад. Даже на хабре была статья именно про эту функцию.

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

В данном случае автор алгоритма скорее всего мог просто описать суть алгоритма и дать отсылку на документ с его полным мат. обоснование.

А когда я говорил тщательно задокументированы, я имел в виду что вы открываете например
en.wikipedia.org/wiki/Fast_inverse_square_root
И всё там читаете.
Так я её открыл и прочитал. Секцию overview of the code " с оригинальными комментариями" — но это явно комментарии не автора, а одного из читателей. Остальная часть статьи, насколько я понял, последующие исследования этого артефакта (интересно, кстати, есть ли такие, кому интереснее их читать, чем разобраться самому — не могу представить себе их мотивы). Проблема с авторскими комментариями могла бы оказаться в том, что автору, придумавшему «ультраоптимизированный и непростой алгоритм» может не хватить воображения, чтобы понять, насколько непонятливыми могут быть читатели его кода. Он находится на два-три уровня выше их. Кроме того, на комментирование может не оказаться времени.
Интересно, как можно было «на ходу» подобрать константу, да так, что последователи не смогли её улучшить.
Насчет «не смогли улучшить» я был неправ: константа 0x5f375a85 даёт лучший результат (хотя Chris Lomont в своей статье считает, что правильное значение 0x5f375a86).
Лучшее враг хорошего, не переборщите в погоне за идеально читаемым текстом кодом.
Чтобы не переборщить, надо знать, когда остановиться. А для этого нужна метрика :)
Т.е. выходит, что самая идеальная программа — это один файл с одной функцией? Или я чего то не понял?
Нет, конечно! Если программа большая, то какого же размера будет эта функция, и сколько в ней будет переменных! Листать и переходить в ней придется много, из-за чего метрика уменьшится.
Говоря про главную функцию я имел ввиду, что если она понятно написана, то не обязательно нужно будет смотреть вызываемые функции. Из вызова (названия и параметров) будет понятно их назначение.
На мой взгляд, правильнее считать зависимости в коде. Думаю, именно увеличение зависимостей между компонентами затрудняет понимание кода больше всего.
Я не совсем это имел ввиду. Зависимости — это другая грань качества кода. Предложенная же метрика ориентированна именно на понятность для человека. Хотя, скорее всего, код с небольшим числом зависимостей будет и достаточно понятным
Хм, я предлагаю такой более краткий вариант — все функции должны быт «чистыми» и все данные должны быть иммутабельны? Это возможно позволит убрать все пункты? :)

Кстати, если бы вам пришлось оценить с точки зрения ваших пунктов вот такой код
let mapNeighbors l = zip3 (Nothing : map Just l) l (map Just $ tail l ++ [Nothing])

Он бы прошел пункты, например третий? Ну и первый со вторым :)
Интересно какие результаты будут для С-кода из GNU libc к примеру
А как же классическая единица измерения (не)понятности кода — WTF/час?
Вспомнилось добротное: «Качество кода определяется количество Чезанахов».
Примеры 1 и 2 показывают главную слабость попыток автоматически получать метрики понятности кода. Что такое «ни о чем не говорящее название» машина определить не может. Например, j в одном контексте не будет ни о чём говорить, а то и будет вводить в заблуждение человека, привыкшего к «типовому» использовании этого имени как второго индекса, а в другом (например, численном взятии двумерного интеграла или вычислении суммы матриц) будет вполне понятна (для владеющего предметной областью).

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

Плюс надо как-то учитывать «инлайновость», делает ли сворачивание тела, например, цикла в отдельную функцию код понятнее, или наоборот ещё больше запутанней. Что понятней:
for(sum = 0, i = 0; i < max_x, i++) {
  for(j = 0; j < max_y, j++) {
    sum += a[i][j];
  }
}

или
for(sum = 0, i = 0; i < max_x, i++) {
  sum += array_sum(a[i], max_y);
}

array_sum(a, len)
{
  for(sum = 0; i = 0; i < len, i++) {
    sum += a[i];
  }
  return sum;
}


Формальные метрики вполне могут показать, что второй вариант понятнее, но вот лично мне так не кажется. В тоже время если выражение внутри цикла будет не такое простое, то выделение вложенного цикла в отдельную функцию может сильно увеличить понятность.

Извините, я не понял, что вы имеете ввиду. По-моему, все приведенные вами примеры хорошо укладываются в предложенную метрику. Если бы вместо суммирования был какой-то сложный код, то его стоило бы вынести в функцию с понятным названием, и тогда можно было бы в ее реализацию не заглядывать.
По поводу сложности инструкций — я понял вашу мысль. Выше был комментарий, и я с ним согласен, что для небольших фрагментов кода можно считать не скроллы и переходы, а время потраченное на анализ кода.
Это два взаимоисключающих примера. В первом случае вложенность два, во втором — один. По идее второй лучше должен быть.
Да, согласен. А в сложных функциях, где вложенность неизбежна, она может компенсироваться функцией с понятным названием.
Без определения степени соответствия имен сущностей их назначению — такая метрика не будет работать.

Потому что меня не устраивает метрика, в которой данные функции имеют одинаковый вес:
int make_string() { return 1;}
string make_string() {return "";}
string ghiskjrfd() {return "";}
Кстати, дядя Боб так и говорит: «Код должен читать сверху вниз, причём, читающий должен иметь возможность в любой момент прекратить чтение также как при чтении газеты, т.е., читающий видит заголовки, краткое введение и проходит вглубь, если сам того захочет».

Интересно, что Кристин Горман выступила с критикой метода дяди Боба, который он назвал «Extract till you drop» (благодаря которому возникает код по которому можно идти сверху вниз, останавливаясь там, где ты хочешь, не углубляясь в ненужные детали). Однако в блоге Роя Ошероува я обнаружил запись, которая отвечает на её критику.

Выступление Кристины Горман.
Разъяснение смысла от Боба Мартина
Sign up to leave a comment.

Articles