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

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

Самодокументирующийся код

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

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

Улучшение самодокументирующегося кода

Легко утверждать, что следующий тривиальный пример самодокументируется:

function divide( dividend, divisor )
	return dividend / divisor
end

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

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

Стандартные комментарии

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

Традиционное комментирование требует поддержки, а также дополнительного времени для добавления. Есть люди, которые используют его как костыль и будут продолжать использовать такие переменные, как a или b для других целей. -тривиальные сценарии и они просто отмечают это в комментариях. Слишком много комментариев в данном разделе также может затруднить чтение. Некоторые разработчики используют комментарии как костыль и прерывают процесс до такой степени, что теряют поток.

Написание лучших комментариев

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

Если мы возьмем нашу предыдущую функцию, мы можем закомментировать ее следующим образом:

--function divide_comments
--takes: scalars: a, b
--returns: a/b
--warning: this function has no error handling for wrong types on a or b, or if b == 0
function divide_comments( a, b )
	return a/b
end

Это тривиальный пример, поэтому я использовал a и b. Мы можем явно видеть, что эта функция не обрабатывает if b, наш делитель равен 0. У нас также нет обработки неправильных типов.

Комментирование редко бывает плохим, если оно не является чрезмерным и разрозненным. Проблема, с которой сталкиваются многие программисты, заключается в том, чтобы найти этот баланс. В нашем примере из предыдущей функции есть 4 строки комментариев для 3 строк фактического кода. Делает ли это наш код сложнее или легче для чтения? Ответ: «Это зависит от того, что мы делаем». Будут сценарии, в которых несколько дополнительных строк существенно упростят понимание с первого взгляда.

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

--function foobar
--foobar algorithm
--takes: array a, array b
--returns: scalar, number foobar
--expects sanitized arrays
--runs our patented foobar algorithm: [internal wiki link or description goes here]
function foobar( a, b )
	local foo = 0
	
	--generates foo part 1: [link to documentation or description of what and why we're doing it]
	for i, av in ipairs( a ) do
		for j, bv in ipairs( b ) do
			foo = foo + i + j / 2
			foo = foo + ( av * i ) - (bv * j / 4 ) + math.sqrt( av * bv * i * j )
			
			--foo part 1 adjustment table
			if foo > 100 then
				foo = foo - 99
			elseif foo > 20 then
				foo = foo - 18
			end
		end
	end
	
	--modifies foo part 1 to get bar adjustment: [link to documentation or description of what and why we're doing this]
	for i, av in ipairs( a ) do
		foo = foo + math.sqrt( av ) - math.log( av )
		
		--bar adjustment table
		if math.log( av ) > 25 then
			foo = foo + 6
		elseif math.log( av ) > 20 then
			foo = foo + 5
		elseif math.log( av ) > 15 then
			foo = foo + 4
		end
	end
	
	return foo
end

Я намеренно создал совершенно бессмысленный алгоритм (и мы делаем вид, что a и b означают что-то важное для алгоритма). Что оно делает? У нас есть заголовок, который сообщает нам и как его использовать, что он принимает и возвращает, любые конкретные условия, а также ссылку на внутреннюю документацию о том, для чего он предназначен (или хорошее описание). Мы также прокомментировали, что представляет собой каждый из основных разделов, поэтому, если есть корректировки, мы знаем, где искать.

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

Что из них легче читать?

--function square
--takes: scalar, number: term
--gives us the square of a number as defined as term * term
--returns a number which is our square
--error checks if a number is correct or not before returning value
--returns nil if term is not a number
function square( term )
	--check our type, if it's not number return nil
	if( type( term ) ~= "number" ) then
		return nil --return nil since the term is not a number
	end
	
	--make sure we cast term to a number explicitly
	local tempterm = tonumber( term )
	--get our square by multiple tempterm by itself
	local squaredterm = tempterm * tempterm
	
	--return our value
	return squaredterm
end

Or:

--function square
--returns the square of term
--checks if term is a number
function square( term )
	if( type( term ) ~= "number" ) then
		return nil
	end
	
	return term * term
end

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

Лучше использовать самодокументирующийся код или комментарии?

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

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

Маленькие команды

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

Более короткие заголовки для функций, которые подробно описывают базовое использование, ускоряют использование. Для нашей предыдущей функции square мы дали быстрый заголовок, чтобы мы знали, с чем мы работаем:

--function square
--returns the square of term
--checks if term is a number

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

Большие команды

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

В примере с нашей чрезмерно многословной квадратной функцией заголовка было бы более чем достаточно для более нетривиального реального кода:

--function square
--takes: scalar, number: term
--gives us the square of a number as defined as term * term
--returns a number which is our square
--error checks if a number is correct or not before returning value
--returns nil if term is not a number

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

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

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

Размер проекта

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

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

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

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

Применение улучшенного комментирования

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

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

Если вы в основном используете только самодокументированный код, начните добавлять заголовки функций и небольшие примечания, объясняющие отдельные алгоритмы. Сделайте это привычкой, чтобы не нарушать процесс написания кода во время документирования или документировать сразу после логического сегмента. Если вы в первую очередь полагаетесь на комментарии, попробуйте дождаться завершения написания блока, чтобы задокументировать его действия. Подождав, вам придется перечитать свой код. Что не подходит или плохо читается в самом коде? Измените его, чтобы он был чище.

Если вы используете оба подхода вместе, вы обнаружите, где вы, естественно, хотите добавить комментарии, а где код говорит сам за себя. Вы также привыкаете к более формальной документации, которая является выгодой для командной среды. Излишнее многословие можно отбросить, слишком малое - требует изобретать колесо. Сделайте ваш код простым для понимания и поддержки.

Первоначально опубликовано на https://somedudesays.com.