Что не так с комментариями, которые объясняют сложный код?

236

Многие люди утверждают, что «комментарии должны объяснять« почему », а не« как »». Другие говорят, что «код должен быть самодокументированным», а комментарии должны быть скудными. Роберт К. Мартин утверждает, что (перефразируя мои собственные слова) часто «комментарии - это извинения за плохо написанный код».

Мой вопрос заключается в следующем:

Что плохого в объяснении сложного алгоритма или длинного и запутанного фрагмента кода с описательным комментарием?

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

Английский «разработан», чтобы его могли легко понять люди. Java, Ruby или Perl, однако, были разработаны, чтобы сбалансировать удобочитаемость и читаемость компьютером, тем самым ставя под угрозу читабельность текста человеком. Человек может понять часть английского языка намного быстрее, чем он / она может понять часть кода с тем же значением (если операция не тривиальна).

Итак, после написания сложного фрагмента кода, написанного на частично читаемом человеком языке программирования, почему бы не добавить описательный и краткий комментарий, объясняющий работу кода на дружественном и понятном английском языке?

Некоторые скажут: «Код не должен быть трудным для понимания», «сделать функции маленькими», «использовать описательные имена», «не писать код для спагетти».

Но мы все знаем, что этого недостаточно. Это простые рекомендации - важные и полезные - но они не меняют того факта, что некоторые алгоритмы являются сложными. И поэтому их трудно понять, читая их построчно.

Неужели так сложно объяснить сложный алгоритм несколькими комментариями о его общей работе? Что плохого в объяснении сложного кода комментарием?

Авив Кон
источник
14
Если это так запутанно, попробуйте рефакторинг его на мелкие кусочки.
Вон Хилтс
151
В теории нет разницы между теорией и практикой. На практике есть.
Скотт Лидли
5
@mattnz: более конкретно, когда вы пишете комментарий, вы погружаетесь в проблему, которую решает этот код. При следующем посещении у вас будет меньше возможностей для решения этой проблемы .
Стив Джессоп
26
«Что» делает функция или метод, должно быть очевидно из его названия. Как это происходит, очевидно из его кода. Почему это сделано таким образом, какие неявные предположения были использованы, какие документы нужно прочитать, чтобы понять алгоритм и т. Д. - должны быть в комментариях.
SK-logic
11
Я чувствую, что многие из ответов ниже целенаправленно неверно истолковывают ваш вопрос. Нет ничего плохого в комментировании вашего кода. Если вы чувствуете, что вам нужно написать пояснительный комментарий, тогда вам нужно.
Тони Эннис

Ответы:

408

С точки зрения непрофессионала:

  • В комментариях нет ничего плохого . Что плохого в том, что вы пишете код, который нуждается в подобных комментариях, или предполагаете, что все в порядке, если вы пишете сложный код, если вы его легко объясняете на простом английском языке.
  • Комментарии не обновляются автоматически при изменении кода. Вот почему часто комментарии не синхронизируются с кодом.
  • Комментарии не облегчают тестирование кода.
  • Извиняюсь не плохо. То, что вы сделали, требует извинений (написание кода, который непросто понять) - плохо.
  • Программист, способный писать простой код для решения сложной проблемы, лучше, чем тот, кто пишет сложный код, а затем пишет длинный комментарий, объясняющий, что делает его код.

Нижняя линия:

Объяснять себя хорошо, не нужно делать это лучше.

Тулаинс Кордова
источник
91
Часто невозможно оправдать расходование кода переписывания денег работодателем, чтобы он был более понятным, когда хороший комментарий может выполнить работу за гораздо меньшее время. Послушный программист должен использовать ее / ее мнение каждый раз.
октября
34
@aecolley Написание понятного кода для начала еще лучше.
Тулаинс Кордова
127
Иногда самоочевидный код недостаточно эффективен, чтобы решить проблему с современными HW & SW. И бизнес-логика, как известно, извилистая. Подмножество проблем, которые имеют элегантные программные решения, значительно меньше, чем множество проблем, которые экономически полезно решать.
Скотт Лидли
62
@ rwong: и наоборот, я часто пишу больше комментариев в бизнес-логике, потому что важно показать, как именно код соответствует заявленным требованиям: «это линия, которая не дает нам всем попасть в тюрьму за мошенничество в рамках раздела независимо от Уголовный кодекс ". Если это просто алгоритм, программист может понять цель с нуля, если это абсолютно необходимо. Для бизнес-логики вам нужен юрист и клиент в одной комнате одновременно. Возможно, мой «здравый смысл» не в том смысле, в котором он находится у обычного программиста приложений ;-)
Стив Джессоп,
29
@ user61852 За исключением того, что самоочевидно для вас, который только что написал этот код и провел в нем последний $ период, не может быть самоочевидным для вас, который должен поддерживать или редактировать его через пять лет, не говоря уже о всех возможные люди, которые не вы, возможно, придется взглянуть на это. «Без объяснений» - это туманный священный грааль определений.
Шадур
110

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

Тем не менее, есть случаи, когда правильно выбранный комментарий является лучшим выбором.

  • Если сам алгоритм сложен и запутан, а не только его реализация - та, которую пишут в математических журналах и когда-либо называли Алгоритмом Мбого, - тогда вы помещаете комментарий в самом начале реализации, читая что-то вроде «Это алгоритм Mbogo для рефробничирования виджетов, первоначально описанный здесь: [URL-адрес статьи]. Эта реализация содержит уточнения Алисы и Кэрол [URL-адрес другой статьи]». Не пытайтесь вдаваться в подробности; если кому-то нужно больше подробностей, ему, вероятно, нужно прочитать всю статью.

  • Если вы взяли что-то, что может быть записано в виде одной или двух строк в какой-то специализированной записи, и расширили его до большого объема императивного кода, поместите эти одну или две строки специальной записи в комментарии над функцией - хороший способ скажи читателю, что он должен делать. Это исключение из аргумента «но что, если комментарий не синхронизируется с кодом», потому что в специализированной нотации, вероятно, гораздо проще находить ошибки, чем в коде. (Это наоборот, если вы написали спецификацию на английском языке.) Хороший пример здесь: https://dxr.mozilla.org/mozilla-central/source/layout/style/nsCSSScanner.cpp#1057 ...

    /**
     * Scan a unicode-range token.  These match the regular expression
     *
     *     u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
     *
     * However, some such tokens are "invalid".  There are three valid forms:
     *
     *     u+[0-9a-f]{x}              1 <= x <= 6
     *     u+[0-9a-f]{x}\?{y}         1 <= x+y <= 6
     *     u+[0-9a-f]{x}-[0-9a-f]{y}  1 <= x <= 6, 1 <= y <= 6
    
  • Если код в целом прост, но содержит одну или две вещи, которые выглядят чрезмерно запутанными, ненужными или просто неправильными, но должны быть такими по причинам, то вы помещаете комментарий сразу над подозрительно выглядящим битом, в котором Вы указываете причины . Вот простой пример, где единственное, что нужно объяснить, - это почему константа имеет определенное значение.

    /* s1*s2 <= SIZE_MAX if s1 < K and s2 < K, where K = sqrt(SIZE_MAX+1) */
    const size_t MUL_NO_OVERFLOW = ((size_t)1) << (sizeof(size_t) * 4);
    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
        nmemb > 0 && SIZE_MAX / nmemb < size)
      abort();
    
zwol
источник
25
Это безобразие, 4должно быть CHAR_BIT / 2;-)
Стив Джессоп,
@SteveJessop: Что-нибудь помешало бы реализации, где CHAR_BITSбыло 16, а sizeof (size_t) было 2, но максимальное значение size_t было, например, 2 ^ 20 [size_t, содержащее 12 битов заполнения]?
суперкат
2
@supercat я не вижу ничего , что , очевидно , исключает , что в C99, что означает , что пример является технически неверно. Это происходит от (слегка модифицированной) версии OpenBSD reallocarray, и OpenBSD, как правило, не верит в удовлетворение возможностей, которых нет в их ABI.
Звол
3
@Zack: если код разработан с учетом предположений POSIX, использование CHAR_BITS может создать впечатление, что код может работать со значениями, отличными от 8.
суперкат
2
@Zack: для того, чтобы беззнаковые типы с точной шириной были полезны, их семантика должна определяться независимо от размера int. Как это дано uint32_t x,y,z;, значение (x-y) > zзависит от размера int. Кроме того, язык, предназначенный для написания надежного кода, должен позволять программистам различать тип, в котором вычисления должны превышать диапазон типа, и должен скрытно переноситься, по сравнению с тем, в котором вычисления, превышающие диапазон типа, должны прерываться, по сравнению с тем, где вычисления не ожидается превышение диапазона типа, но ...
суперкат
61

Так что же плохого в объяснении сложного кода комментарием?

Это не вопрос правильного или неправильного, а «лучшая практика», как определено в статье Википедии :

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

Поэтому лучше всего сначала попытаться улучшить код и использовать английский, если это невозможно.

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

FMJaguar
источник
42
+1 за «гораздо чаще встречается закомментированный код, требующий рефакторинга, чем реорганизованный код, требующий комментариев»
Брэндон,
7
Хорошо, но как часто это комментарий: //This code seriously needs a refactor
Эрик Реппен
2
Конечно, любая так называемая лучшая практика, не подкрепленная строгими научными исследованиями, - это просто мнение.
Blrfl
54

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

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

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

Если бы все всегда писали идеальный код, то было бы очевидно, что код, который выглядит несовершенным, работает на основе хитрого вмешательства из реального мира, но это не так. Большинство программистов часто пишут запутанный или несколько запутанный код, поэтому, когда мы сталкиваемся с этим, это естественная склонность приводить его в порядок. Я клянусь, что мое прошлое я идиот, когда читаю старый код, который я написал.

Поэтому я не считаю комментарии извинением за плохой код, но, возможно, объяснением того, почему вы не сделали очевидного. Наличие // The standard approach doesn't work against the 64 bit version of the Frobosticate Libraryпозволит будущим разработчикам, включая вас самих, обратить внимание на эту часть кода и провести тестирование на этой библиотеке. Конечно, вы также можете поместить комментарии в свои коммиты контроля версий, но люди будут смотреть на них только после того, как что-то пойдет не так. Они будут читать комментарии к коду при изменении кода.

Люди, которые говорят нам, что мы всегда должны писать теоретически совершенный код, не всегда люди с большим опытом программирования в реальных условиях. Иногда вам нужно написать код, который выполняет до определенного уровня, иногда вам нужно взаимодействовать с несовершенными системами. Это не значит, что вы не можете сделать это элегантным и хорошо написанным способом, но неочевидные решения требуют объяснения.

Когда я пишу код для хобби-проектов, которые, как я знаю, больше никто не прочтет, я все равно комментирую части, которые меня смущают - например, любая трехмерная геометрия включает математику, с которой я не совсем дома - потому что я знаю, когда возвращаюсь через шесть месяцев я полностью забуду, как это делать. Это не извинение за плохой код, это признание личных ограничений. Все, что я хотел бы сделать, оставив это без комментариев, это создать больше работы для себя в будущем. Я не хочу, чтобы мое будущее я переучивалось без необходимости, если я могу избежать этого сейчас. Какую возможную ценность это будет иметь?

glenatron
источник
5
@ Кристиан это? Первая строка ссылается на это утверждение, конечно, но помимо этого оно немного шире, насколько я понимаю.
Гленатрон
9
«Я клянусь, что мое прошлое я действительно идиот, когда читаю старый код, который я написал». Четыре года в моей карьере разработчика, и я обнаружил, что это происходит, когда я смотрю на что-то старше 6 месяцев или около того.
Кен
6
Во многих случаях наиболее информативная и полезная историческая информация относится к вещам, которые рассматриваются, но решаются против. Есть много случаев, когда кто-то выбирает подход X для чего-то, а другой подход Y кажется более подходящим; в некоторых из этих случаев Y будет «почти» работать лучше, чем X, но у него, как оказалось, есть непреодолимые проблемы. Если Y избежать из-за этих проблем, такие знания могут помочь предотвратить потерю времени другими на неудачных попытках реализовать подход Y.
Суперкат
4
Изо дня в день я тоже часто использую комментарии о работе - их нет на долгое время, но добавление заметки TODO или короткого раздела, чтобы напомнить, что я собирался делать дальше, может быть полезным напоминание утром.
Гленатрон
1
@Lilienthal, я не думаю, что последний пункт ограничен личными проектами - он сказал: «... я все еще комментирую части, которые я нахожу непонятными».
Подстановочный
29

Потребность в комментариях обратно пропорциональна уровню абстракции кода.

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

main:   
; initializes the two numbers and the counter.  Note that this assumes
; that the counter and num1 and num2 areas are contiguous!
;
    mov ax,'00'                     ; initialize to all ASCII zeroes
    mov di,counter                  ; including the counter
    mov cx,digits+cntDigits/2       ; two bytes at a time
    cld                             ; initialize from low to high memory
    rep stosw                       ; write the data
    inc ax                          ; make sure ASCII zero is in al
    mov [num1 + digits - 1],al      ; last digit is one
    mov [num2 + digits - 1],al      ; 
    mov [counter + cntDigits - 1],al

    jmp .bottom         ; done with initialization, so begin

.top
    ; add num1 to num2
    mov di,num1+digits-1
    mov si,num2+digits-1
    mov cx,digits       ; 
    call    AddNumbers  ; num2 += num1
    mov bp,num2         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jz  .done           ;

    ; add num2 to num1
    mov di,num2+digits-1
    mov si,num1+digits-1
    mov cx,digits       ;
    call    AddNumbers  ; num1 += num2
.bottom
    mov bp,num1         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jnz .top            ;
.done
    call    CRLF        ; finish off with CRLF
    mov ax,4c00h        ; terminate
    int 21h             ;

Даже с комментариями, это может быть довольно сложно, чтобы впустить.

Современный пример: регулярные выражения часто представляют собой конструкции с очень низкой абстракцией (строчные буквы, цифры 0, 1, 2, новые строки и т. Д.). Они, вероятно, нуждаются в комментариях в виде примеров (Боб Мартин, IIRC, признает это). Вот регулярное выражение, которое (я думаю) должно соответствовать HTTP (S) и FTP URL:

^(((ht|f)tp(s?))\://)?(www.|[a-zA-Z].)[a-zA-Z0-9\-\.]+\.(com|edu|gov|m
+il|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(/($|[a-zA-Z0-9\.
+\,\;\?\'\\\+&amp;%\$#\=~_\-]+))*$

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

Я имею в виду Numerical Recipes в C переведен в основном дословно Numerical Recipes в C ++ , который я заключаю начал как Numerical Recipes (в FORTAN), со всеми переменными a, aa, b, c, ccи т.д. поддерживается с помощью каждой версии. Алгоритмы могли быть правильными, но они не использовали абстракции, предоставляемые языками. И они меня убили. Пример из статьи доктора Доббса - Быстрое преобразование Фурье :

void four1(double* data, unsigned long nn)
{
    unsigned long n, mmax, m, j, istep, i;
    double wtemp, wr, wpr, wpi, wi, theta;
    double tempr, tempi;

    // reverse-binary reindexing
    n = nn<<1;
    j=1;
    for (i=1; i<n; i+=2) {
        if (j>i) {
            swap(data[j-1], data[i-1]);
            swap(data[j], data[i]);
        }
        m = nn;
        while (m>=2 && j>m) {
            j -= m;
            m >>= 1;
        }
        j += m;
    };

    // here begins the Danielson-Lanczos section
    mmax=2;
    while (n>mmax) {
        istep = mmax<<1;
        theta = -(2*M_PI/mmax);
        wtemp = sin(0.5*theta);
        wpr = -2.0*wtemp*wtemp;
        wpi = sin(theta);
        wr = 1.0;
        wi = 0.0;
        for (m=1; m < mmax; m += 2) {
            for (i=m; i <= n; i += istep) {
                j=i+mmax;
                tempr = wr*data[j-1] - wi*data[j];
                tempi = wr * data[j] + wi*data[j-1];

                data[j-1] = data[i-1] - tempr;
                data[j] = data[i] - tempi;
                data[i-1] += tempr;
                data[i] += tempi;
            }
            wtemp=wr;
            wr += wr*wpr - wi*wpi;
            wi += wi*wpr + wtemp*wpi;
        }
        mmax=istep;
    }
}

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

Итак, отнимите: неидиоматический код, построенный из низкоуровневых строительных блоков, которых нельзя избежать, требует комментариев. И это необходимо WAAAAY меньше, чем это происходит.

Кристиан Х
источник
1
Никто не должен писать такие строки на языке ассемблера dec dword [term] ; decrement loop counter. С другой стороны, в вашем примере на ассемблере отсутствует комментарий перед каждым «абзацем кода», объясняющий, что делает следующий блок кода. В этом случае комментарий обычно будет эквивалентен одной строке в псевдокоде, например ;clear the screen, после 7 строк, которые фактически требуется для очистки экрана.
Скотт Уитлок
1
Да, я бы посчитал некоторые ненужные комментарии в образце сборки, но, если честно, он довольно хорошо отражает стиль сборки «Хорошо». Даже с прологом из одного или двух строк абзаца будет очень трудно следовать коду. Я понял образец ASM лучше, чем пример FFT. Я запрограммировал FFT на C ++ в аспирантуре, и это не выглядело так, но тогда мы использовали STL, итераторы, функторы и довольно много вызовов методов. Не так быстро, как монолитная функция, но намного проще для чтения. Я постараюсь добавить его в отличие от образца NRinC ++.
Кристиан Х
Вы имели в виду ^(((ht|f)tps?)\:\/\/)?(www\.)*[a-zA-Z0-9\-\.]+\.(com|edu|gov|mil|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\;\?\'\\\+&%\$#\=~_\-]+))*$? Будьте в курсе числовых адресов.
Изабера
Более или менее моя точка зрения: некоторые вещи, построенные из абстракций очень низкого уровня, не легко прочитать или проверить. Комментарии (и, чтобы не зацикливаться, ИСПЫТАНИЯ) могут быть полезными, а не ущербом. В то же время, если не использовать доступные абстракции более высокого уровня (: alpha:: num: там, где это возможно), становится сложнее понять даже с хорошими комментариями, чем использование абстракций более высокого уровня.
Кристиан Х
3
+1: "The need for comments is inversely proportional to the abstraction level of the code." почти все подводит итог.
Джеррат
21

Я не верю, что что-то не так с комментариями в коде. Идея, что комментарии как-то плохи, на мой взгляд, происходит из-за того, что некоторые программисты зашли слишком далеко. В этой отрасли очень много увлечений, особенно экстремальных взглядов. Где-то по пути прокомментированный код стал эквивалентен плохому коду, и я не уверен почему.

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

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

Рой
источник
18

Я думаю, что вы слишком много читаете о том, что он говорит. Ваша жалоба состоит из двух частей:

Что плохого в объяснении (1) сложного алгоритма или (2) длинного и сложного фрагмента кода с описательным комментарием?

(1) неизбежно. Я не думаю, что Мартин не согласится с тобой. Если вы пишете что-то вроде быстрого обратного квадратного корня , вам понадобятся некоторые комментарии, даже если это просто «злой взлом с плавающей запятой». За исключением чего-то простого, например DFS или бинарного поиска, маловероятно, что человек, читающий ваш код, будет иметь опыт работы с этим алгоритмом, и поэтому я думаю, что в комментариях должно быть хотя бы упоминание о том, что это такое.

Большая часть кода не (1), однако. Редко вы будете писать программное обеспечение, которое представляет собой не что иное, как свернутые вручную реализации мьютекса, непонятные операции линейной алгебры с плохой поддержкой библиотек и новые алгоритмы, известные только исследовательской группе вашей компании. Большая часть кода состоит из вызовов библиотеки / фреймворка / API, ввода-вывода, шаблонного кода и модульных тестов.

Это тот код, о котором говорит Мартин. И он отвечает на ваш вопрос цитатой из Кернигана и Плаугера в верхней части главы:

Не комментируйте плохой код - перепишите его.

Если в вашем коде есть длинные извилистые разделы, вы не смогли сохранить ваш код в чистоте . Лучшее решение этой проблемы - не писать комментарий в начале файла, чтобы помочь будущим разработчикам разобраться в нем; лучшее решение - переписать его.

И это именно то, что говорит Мартин:

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

Это ваше (2). Мартин соглашается с тем, что длинный, запутанный код действительно нуждается в комментариях - но он возлагает вину за этот код на плечи программиста, который его написал, а не на какую-то туманную идею, что «мы все знаем, что этого недостаточно». Он утверждает, что:

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

Патрик Коллинз
источник
3
Если бы разработчик, с которым я работал, просто написал «злой хакерский метод с плавающей запятой», чтобы объяснить быстрый алгоритм с квадратным корнем - я бы поговорил с ним. До тех пор, пока они содержат ссылку на более полезное место, я был бы счастлив.
Майкл Андерсон
8
Я не согласен в одном - комментарий, объясняющий, как работает что-то плохое, гораздо быстрее. Учитывая некоторый код, который, вероятно, больше не будет затронут (большая часть кода, я полагаю), тогда комментарий является лучшим бизнес-решением, чем большой рефакторинг, который часто вводит ошибки (поскольку исправление, которое убивает основанную на ошибке ошибку, все еще является ошибкой). Идеальный мир с совершенно понятным кодом нам недоступен.
gbjbaanb
2
@trysis, ха-ха, да, но в мире, где программисты ответственны, а не бизнесмены, они никогда не отправят, потому что они вечно позолотают постоянно обновляемую кодовую базу в тщетном стремлении к совершенству.
gbjbaanb
4
@PatrickCollins почти все, что я читаю в Интернете, о том, как сделать это правильно с первого раза. Почти никто не хочет писать статьи о наведении порядка! Физики говорят, что "дана идеальная сфера ...". Ученые говорят, что "дано новое
поле
2
Лучшее решение - переписать его, учитывая бесконечное время; но учитывая чужую кодовую базу, типичные корпоративные сроки и реальность; иногда лучше всего прокомментировать, добавить TODO: Refactor и добавить этот рефакторинг в следующую версию; и это исправление, которое должно было быть сделано вчера, сделано сейчас. Суть всего этого идеалистического разговора о просто рефакторинге заключается в том, что он не учитывает, как на самом деле все работает на рабочем месте; иногда существуют более высокие приоритеты и достаточно быстрые сроки, которые будут препятствовать исправлению устаревшего некачественного кода. Вот только как это.
hsanders
8

Что плохого в объяснении сложного алгоритма или длинного и запутанного фрагмента кода с описательным комментарием?

Ничего как такового. Документирование вашей работы - хорошая практика.

Тем не менее, у вас есть ложная дихотомия: написание чистого кода против написания документированного кода - эти два понятия не противоречат друг другу.

На чем вам следует сосредоточиться - это упрощать и абстрагировать сложный код в более простой код, вместо того, чтобы думать «сложный код - это хорошо, пока он комментируется».

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

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

Правда. Вот почему все ваши общедоступные алгоритмы API должны быть объяснены в документации.

Итак, после написания сложного фрагмента кода, написанного на частично читаемом человеком языке программирования, почему бы не добавить описательный и краткий комментарий, объясняющий работу кода на дружественном и понятном английском языке?

В идеале, после написания сложного фрагмента кода вы должны (не исчерпывающий список):

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

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

[...] некоторые алгоритмы являются сложными. И поэтому их трудно понять, читая их построчно.

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

Неужели так сложно объяснить сложный алгоритм несколькими комментариями о его общей работе?

Нет - это хорошо. Однако добавить несколько строк комментариев недостаточно.

Что плохого в объяснении сложного кода комментарием?

Тот факт, что у вас не должно быть сложного кода, если этого можно избежать.

Чтобы избежать сложного кода, формализуйте свои интерфейсы, тратите в ~ 8 раз больше на разработку API, чем тратите на реализацию (Степанов предложил потратить на интерфейс не менее 10 раз по сравнению с реализацией), и приступайте к разработке проекта со знанием того, что Вы создаете проект, а не просто пишете какой-то алгоритм.

Проект включает в себя документацию API, функциональную документацию, измерения кода / качества, управление проектом и так далее. Ни один из этих процессов не является одноразовым, быстрым шагом (все они требуют времени, требуют обдумывания и планирования, и все они требуют, чтобы вы периодически возвращались к ним и пересматривали / дополняли их подробностями).

utnapistim
источник
3
«Вы никогда не должны полагаться на чтение реализации, чтобы выяснить, что делает API». Иногда это причиняется вам апстримом, который вы намерены использовать. У меня был особенно неудовлетворительный проект, усеянный комментариями вида «следующий уродливый код Хита Робинсона существует, потому что simpleAPI () не работает должным образом на этом оборудовании, несмотря на заявления поставщика».
pjc50
6

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

Я бы посчитал это незначительным злоупотреблением "комментариями". Если программист хочет прочитать что-то вместо всего алгоритма, то для этого предназначена документация по функциям. Итак, документация по функции может фактически появляться в комментариях в источнике (возможно, для извлечения инструментами doc), но хотя синтаксически это комментарий для вашего компилятора, вы должны рассматривать их как отдельные вещи с разными целями. Я не думаю, что «комментарии должны быть скудными» обязательно означают «документация должна быть скудной» или даже «уведомления об авторских правах должны быть скудными»!

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

  • Комментарии не обязательно верны, тогда как код делает то, что делает. Таким образом, читатель берет ваше слово за это, и это не идеально.
  • Читатель еще не понимает сам код, поэтому до тех пор, пока он не вернется к нему позже, он по-прежнему не сможет его изменить или использовать повторно. В каком случае, что они делают, читая это?

Существуют исключения, но большинство читателей должны понимать сам код. Комментарии должны быть написаны, чтобы помочь этому, а не заменить его, поэтому вам обычно рекомендуется, чтобы в комментариях говорилось «почему вы это делаете». Читатель, который знает мотивы для следующих нескольких строк кода, имеет больше шансов увидеть, что они делают и как.

Стив Джессоп
источник
5
Одно полезное место для комментариев: в научном коде часто могут быть довольно сложные вычисления, включающие множество переменных. Для здравомыслия программиста имеет смысл держать имена переменных действительно короткими, чтобы вы могли смотреть на математику, а не на имена. Но это делает это действительно трудным для понимания читателем. Поэтому краткое описание того, что происходит (или, лучше, ссылка на уравнение в журнальной статье или подобное), может быть действительно полезным.
naught101
1
@ naught101: да, тем более что в документе, на который вы ссылаетесь, также, вероятно, используются однобуквенные имена переменных. Обычно легче увидеть, что код действительно следует документу, если вы используете одни и те же имена, но это противоречит цели самоочевидности кода ( вместо этого это объясняется в статье ). В этом случае комментарий, в котором определено каждое имя, говоря, что оно на самом деле означает, заменяет значимые имена.
Стив Джессоп
1
Когда я ищу что-то конкретное в коде (где обрабатывается этот конкретный случай?), Я не хочу читать и понимать параграфы кода, просто чтобы обнаружить, что это не то место. Мне нужны комментарии, которые в одной строке суммируют, что делает следующий абзац. Таким образом, я быстро найду части кода, связанные с моей проблемой, и пропущу неинтересные детали.
Florian F
1
@FlorianF: традиционный ответ заключается в том, что имена переменных и функций должны примерно указывать, о чем идет речь код, и, следовательно, позволяют вам просматривать вещи, которые, конечно, не соответствуют тому, что вы ищете. Я согласен с вами, что это не всегда удается, но я не согласен настолько сильно, что я думаю, что весь код необходимо прокомментировать, чтобы помочь в поиске или чтении. Но вы правы, это тот случай, когда кто-то читает ваш код (вроде как) и не должен его понимать.
Стив Джессоп
2
@ Снеговик Люди могут сделать это с именами переменных. Я видел код, где переменная listOfApples содержала список бананов. Кто-то скопировал код, обрабатывающий список яблок, и адаптировал его для бананов, не потрудившись изменить имена переменных.
Флориан Ф
5

Часто нам приходится делать сложные вещи. Это, безусловно, правильно документировать их для будущего понимания. Иногда правильное место для этой документации находится в коде, где документация может быть обновлена ​​с кодом. Но, безусловно, стоит рассмотреть отдельную документацию. Это также может быть легче представить другим людям, включая диаграммы, цветные рисунки и так далее. Тогда комментарий просто:

// This code implements the algorithm described in requirements document 239.

или даже просто

void doPRD239Algorithm() { ...

Конечно, люди довольны именованными функциями MatchStringKnuthMorrisPrattили encryptAESили partitionBSP. Более неясные имена заслуживают объяснения в комментарии. Вы также можете добавить библиографические данные и ссылку на статью, с которой вы реализовали алгоритм.

Если алгоритм является сложным, новым и неочевидным, он определенно стоит документа, даже если только для внутреннего обращения компании. Проверьте документ в системе контроля версий, если вы беспокоитесь о его потере.

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

/* Configure the beam controller and turn on the laser.
The sequence is timing-critical and this code must run with interrupts disabled.
Note that the constant 0xef45ab87 differs from the vendor documentation; the vendor
is wrong in this case.
Some of these operations write the same value multiple times. Do not attempt
to optimise this code by removing seemingly redundant operations.
*/
pjc50
источник
2
Я бы поспорил против именования функций / методов после их внутреннего алгоритма, большую часть времени используемый метод должен быть внутренним вопросом, во что бы то ни стало документировать начало вашей функции с использованием используемого метода, но не вызывайте его, doPRD239Algorithmкоторый говорит мне ничего о функции без необходимости искать алгоритм, причина MatchStringKnuthMorrisPrattи encryptAESработа в том, что они начинают с описания того, что они делают, а затем описывают методологию.
Scragar
5

Я не помню , где я читал, но там есть острая и четкая линия между тем, что должно появиться в вашем коде и что должно появиться в качестве комментария.

Я считаю, что вы должны прокомментировать свое намерение, а не свой алгоритм . Т.е. комментируйте то, что вы хотели сделать, а не то, что вы делаете .

Например:

// The getter.
public <V> V get(final K key, Class<V> type) {
  // Has it run yet?
  Future<Object> f = multitons.get(key);
  if (f == null) {
    // No! Make the task that runs it.
    FutureTask<Object> ft = new FutureTask<Object>(
            new Callable() {

              public Object call() throws Exception {
                // Only do the create when called to do so.
                return key.create();
              }

            });
    // Only put if not there.
    f = multitons.putIfAbsent(key, ft);
    if (f == null) {
      // We replaced null so we successfully put. We were first!
      f = ft;
      // Initiate the task.
      ft.run();
    }
  }
  try {
    /**
     * If code gets here and hangs due to f.status = 0 (FutureTask.NEW)
     * then you are trying to get from your Multiton in your creator.
     *
     * Cannot check for that without unnecessarily complex code.
     *
     * Perhaps could use get with timeout.
     */
    // Cast here to force the right type.
    return (V) f.get();
  } catch (Exception ex) {
    // Hide exceptions without discarding them.
    throw Throwables.asRuntimeException(ex);
  }
}

Здесь нет попытки указать, что выполняет каждый шаг, все, что он заявляет, - это то, что он должен делать.

PS: я нашел источник, на который ссылался - Ужасы кодирования: код рассказывает вам, как, комментарии говорят вам, почему

OldCurmudgeon
источник
8
Первый комментарий: он уже запущен? Что еще работает? То же самое для других комментариев. Для тех, кто не знает, что делает код, это бесполезно.
gnasher729
1
@ gnasher729 - Вне контекста почти любой комментарий будет бесполезен - этот код демонстрирует добавление комментариев, которые указывают на намерение, а не на попытку описания . Я сожалею, что это ничего не делает для вас.
OldCurmudgeon
2
У сопровождающего этого кода не будет контекста. Не очень сложно понять, что делает код, но комментарии не помогают. Если вы пишете комментарии, не торопитесь и сосредоточьтесь на них.
gnasher729
КСТАТИ - Комментарий « Выполнить еще» относится к Futureи указывает на то, что за get()ним следует проверка, чтобы nullопределить, был ли Futureуже запущен - правильно документирует намерение, а не процесс .
OldCurmudgeon
1
@OldCurmudgeon: Ваш ответ достаточно близок к тому, что я думал, что я просто добавлю этот комментарий в качестве примера вашей точки зрения. В то время как комментарий не нужен для объяснения чистого кода, комментарий хорош для объяснения того, почему кодирование было выполнено ОДНИМ СПОСОБОМ. Из моего ограниченного опыта комментарии часто полезны для объяснения особенностей набора данных, над которым работает код, или бизнес-правил, которые код должен обеспечивать. Комментирование кода, добавляемого для исправления ошибки, является хорошим примером, если эта ошибка произошла из-за неверного предположения о данных.
Рэндалл Стюарт
4

Но мы все знаем, что этого недостаточно.

В самом деле? С каких пор?

Хорошо разработанного кода с хорошими именами в большинстве случаев более чем достаточно. Аргументы против использования комментариев хорошо известны и документированы (как вы ссылаетесь).

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

Telastyn
источник
7
Я знаю, что этого недостаточно.
Florian F
2
С каких пор? Видимо, вы уже знаете ответ на этот вопрос. «Хорошо разработанного кода с хорошими именами в большинстве случаев более чем достаточно ». Так что, вероятно, этого недостаточно в меньшинстве случаев, что является именно тем, о чем спрашивает аскер.
Эллеседил
3
Я когда-либо пытаюсь расшифровать код других людей, которые, как я хотел, добавляли некоторые комментарии чаще, чем раз в два года.
Огр Псалом 33
@ OgrePsalm33 - У них есть небольшие методы и хорошие имена? Плохой код плох, независимо от комментариев.
Теластин
2
@Telastyn К сожалению, при работе с большой базой кода «маленькие» методы и «хорошие» имена являются субъективными для каждого разработчика (так что это хороший комментарий). Разработчик, пишущий код алгоритма графической обработки Flarbigan в течение 7 лет, может написать что-то совершенно понятное ему и аналогичным разработчикам, но было бы загадочно для нового парня, который последние 4 года занимался разработкой кода Perbian grid-инфраструктуры. Затем, 2 недели спустя, эксперт Flarbigan уходит.
Огр Псалом 33
2

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

При этом комментарии в источнике являются одной из форм документации для других программистов (включая вас). Если комментарии касаются более абстрактных вопросов, чем то, что делает код на каждом шаге, вы справляетесь лучше, чем в среднем. Этот уровень абстракции зависит от используемого вами инструмента. Комментарии, сопровождающие процедуры на ассемблере, обычно имеют более низкий уровень «абстракции», чем, например, этот APL A←0⋄A⊣{2⊤⍵:1+3×⍵⋄⍵÷2}⍣{⍺=A+←1}⎕. Я думаю, что это, вероятно, заслуживает комментария о проблеме, которую он призван решить, ммм?

Скотт Лидли
источник
2

Если код тривиален, он не нуждается в пояснительном комментарии. Если код нетривиален, пояснительный комментарий, скорее всего, также будет нетривиальным.

Теперь, проблема с нетривиальным естественным языком заключается в том, что многие из нас не очень хорошо умеют читать или писать. Я уверен, что ваши письменные коммуникативные навыки превосходны, но, тем не менее, кто-то с меньшим пониманием письменного языка может неправильно понять ваши слова.

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

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

Лично я бы не сказал, что вы никогда не должны писать комментарии. Только то, что вы должны учитывать, почему ваш код нуждается в комментарии, и как вы можете это исправить. Это, кажется, общая тема в ответах здесь.

Мартин
источник
Именно то, о чем я думал, когда я не согласился с утверждением «Человек может понять часть английского языка намного быстрее, чем он / она может понять фрагмент кода с тем же значением (если операция не тривиальна)» Код всегда менее двусмысленно и более кратко.
Стивенбайер
0

Еще один момент, о котором еще не говорилось, заключается в том, что иногда точное комментирование того, что делает фрагмент кода, может быть полезным в тех случаях, когда язык использует определенный синтаксис для нескольких целей. Например, предполагая, что все переменные имеют тип float, рассмотрим:

f1 = (float)(f2+f3); // Force result to be rounded to single precision
f4 = f1-f2;

Результатом явного приведения floatк floatявляется принудительное округление результата с одинарной точностью; таким образом, комментарий можно рассматривать просто как сообщение о том, что делает код. С другой стороны, сравните этот код с:

thing.someFloatProperty = (float)(f2*0.1); // Divide by ten

Здесь цель приведения состоит в том, чтобы не допустить, чтобы компилятор подавлял при наиболее эффективном способе точного вычисления (f2 / 10) [это более точно, чем умножение на 0,1f, и на большинстве машин это быстрее, чем деление на 10,0f].

Без комментария кто-то, кто просматривал предыдущий код, мог бы подумать, что приведение было добавлено из-за ошибочного убеждения, что это необходимо для того, чтобы компилятор не кричал и что он не нужен. Фактически, приведение служит для того, чтобы делать в точности то, что говорит языковая спецификация: заставить результат вычисления округляться до одинарной точности даже на машинах, где округление будет более дорогим, чем сохранение результата с более высокой точностью. Учитывая, что приведение к floatможет иметь несколько различных значений и целей, наличие комментария, указывающего, какое значение предназначено в конкретном сценарии, может помочь прояснить, что фактическое значение совпадает с намерением.

Supercat
источник
Я не уверен, что J. Random Programmer, посмотрев на второй пример, поймет, что константа написана 0.1 по уважительной причине, а не потому, что первоначальный программист забыл ввести «f».
Дэвид К
Особенно во время отладки, вы никогда не предполагаете, что что-то было сделано по уважительной причине.
gnasher729
@DavidK: Цель моего второго примера кода состояла в том, чтобы сравнить его с первым фрагментом кода. Во втором фрагменте кода намерение программиста, вероятно, состоит в том, чтобы иметь someFloatPropertyнаиболее точное представление о f2/10том, что он может; таким образом, основная цель второго приведения - просто скомпилировать код . В первом примере, однако, приведение явно не требуется для его обычной цели (изменение одного типа времени компиляции на другой), так как операнды уже есть float. Комментарий служит ясно , что приведение в необходимом для вторичной цели (округление).
суперкат
Я согласен с тем, что вам не нужно комментировать (float)актеры во втором примере. Вопрос о буквальной константе 0.1. Вы объяснили (в следующем абзаце текста), почему мы пишем 0.1: «это точнее, чем умножить на 0,1f». Я предполагаю, что это те слова, которые должны быть в комментарии.
Дэвид К,
@DavidK: Я бы, конечно, включил бы комментарий, если бы знал, что 0,1f будет неприемлемо неточным, и использовал бы 0,1f, если бы знал, что потеря точности будет приемлемой и что 0,1f на самом деле будет существенно быстрее, чем 0,1 . Если я не знаю, какая из этих вещей верна, я предпочел бы использовать привычку кодирования doubleдля констант или промежуточных вычислений, значение которых не может быть представлено как float[хотя в языках, которые требуют раздражающих явных приведений типа double-to-float, лень может потребоваться использование floatконстант не для скорости, а для минимизации раздражения].
Суперкат
-1

Комментарии, которые объясняют, что делает код, являются формой дублирования. Если вы измените код, а затем забудете обновить комментарии, это может привести к путанице. Я не говорю, не используйте их, просто используйте их разумно. Я подписываюсь на принцип дядюшки Боба: «Только комментируйте то, что код не может сказать».

murungu
источник