Должны ли программисты использовать логические переменные для документирования своего кода?

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

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
       ...
}

Он предлагает:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

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


person froadie    schedule 19.03.2010    source источник
comment
@Paul R - упс, спасибо. Я начал с самодокументирования и понял, что для этого нет тега, поэтому попытался изменить его на самодокументирование, которое уже существовало. :)   -  person froadie    schedule 19.03.2010
comment
Разве не лучше просто комментировать то, что происходит в тесте?   -  person J S    schedule 19.03.2010
comment
Когда это уместно, я также стараюсь делать это, особенно если мне нужно проверить условие(я) в разных частях метода. Это также гораздо более описательно.   -  person geffchang    schedule 19.03.2010
comment
Это кажется мне логичным, хорошей практикой и очень самодокументируемым. Просто исходя из вашего опыта, есть вероятность, что это будет так же легко понятно другим программистам, которые впервые столкнутся с этим.   -  person    schedule 20.03.2010
comment
Некоторые инструменты автоматического рефакторинга позволяют с помощью одной команды переключаться между двумя формами. Например, вы бы выполнили рефакторинг elementIndex == lastElementIndex в исходном коде с извлечением локальной переменной. Или во втором коде вы должны выбрать RepeatEntry в операторе if и реорганизовать его, чтобы он был встроенным.   -  person JeffH    schedule 22.03.2010
comment
Почему этот вопрос до сих пор открыт?   -  person orangepips    schedule 02.02.2011


Ответы (14)


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

Некоторые языки, которые не имеют концепции «присваивания» как таковой, такие как Haskell, даже вводят специальные конструкции, позволяющие вам использовать технику «присвоить имя подвыражению» (предложение where в Haskell) — кажется, говорит некоторая популярность рассматриваемой техники!-)

person Alex Martelli    schedule 19.03.2010
comment
если это проще и легче читать, я говорю, что это довольно очевидный случай беспроигрышного варианта :) - person djcouchycouch; 19.03.2010
comment
Я согласен. Во многих случаях вы, вероятно, могли бы обойтись коротким комментарием, но я не понимаю, как это может на самом деле вредить. - person Max E.; 20.03.2010

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

Это помогает удобочитаемости, и когда логика меняется, ее нужно изменить только в одном месте.

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

person Oded    schedule 19.03.2010
comment
Спасибо за ответ! Что касается методов многократного использования, у которых есть еще одна (не связанная с этим) веская причина для исключения ... Итак, я полагаю, что мой вопрос действительно заключается в том, следует ли вам учитывать одноразовые логические выражения, когда нет другой причины, кроме удобочитаемости. (Что, конечно, само по себе является достаточно серьезной причиной :)) Спасибо, что указали на это. - person froadie; 19.03.2010
comment
+1 за сокращение изменений кода, необходимых при изменении логики, и высмеивание программистов с тысячью строк. - person Jeffrey L Whitledge; 19.03.2010

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

В вашем примере я смотрю на код и спрашиваю себя: «Хорошо, почему человек видит значение меньше 0?» Во втором вы ясно говорите мне, что некоторые процессы завершились, когда это происходит. Во втором не угадаешь, каковы были твои намерения.

Для меня самым важным является, когда я вижу такой метод: DoSomeMethod(true); Почему он автоматически устанавливается в значение true? Это гораздо более читабельно, как

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);
person kemiller2002    schedule 19.03.2010
comment
Я не люблю логические параметры именно по этой причине. В итоге вы получаете такие вызовы, как createOrder(true, false, true, true, false) и что это значит? Я предпочитаю использовать перечисления, поэтому вы говорите что-то вроде createOrder(Source.MAIL_ORDER, BackOrder.NO, CustomOrder.CUSTOM, PayType.CREDIT). - person Jay; 19.03.2010
comment
Но если вы последуете примеру Кевина, это эквивалентно вашему. Какая разница, может ли переменная принимать 2 значения или более 2? - person Mark Ruzon; 19.03.2010
comment
С Jay's вы можете иметь преимущество в том, что в определенных случаях вы будете более четкими. Например, при использовании PayType. Если бы это было логическое значение, параметр, вероятно, назывался бы isPayTypeCredit. Вы не знаете, что такое альтернатива. С помощью перечисления вы можете четко увидеть, какие варианты PayType есть: Credit, Check, Cash и выбрать правильный. - person kemiller2002; 19.03.2010
comment
++ также перечисление не допускает присваивания null, поэтому управление значением буквально завершено, а автодокументация перечисления на высоте. - person Hardryv; 24.03.2010
comment
Objective-C и Smalltalk действительно решают эту проблему, в Objective-C: [Object createOrderWithSource:YES backOrder:NO custom:YES type:kCreditCard]; - person Grant Paul; 28.03.2010

Предоставленный образец:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

Также можно переписать для использования методов, которые улучшают читаемость и сохраняют логическую логику (как указал Конрад):

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){
   ...
}

...

private bool IsFinished(int elementIndex) {
    return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
}

private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) {
    return (elementIndex == lastElementIndex);
}

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

person Prutswonder    schedule 19.03.2010
comment
Если вы столкнулись с большим количеством шума в коде с C#, вы также можете воспользоваться частичными классами и переместить шум в частичный, и если люди заинтересованы в том, что проверяет IsFinished, к ним легко перейти. - person Chris Marisic; 19.03.2010

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

//No clue what the parts might mean.
if(price>0 && (customer.IsAlive || IsDay(Thursday)))

=>

first_condition = price>0
second_condition =customer.IsAlive || IsDay(Thursday)

//I'm still not enlightened.
if(first_condition && second_condition)

Я указываю на это, потому что общепринято создавать такие правила, как «комментировать весь ваш код», «использовать именованные логические значения для всех if-критериев с более чем 3 частями» только для получения семантически пустых комментариев следующего вида.

i++; //increment i by adding 1 to i's previous value
person MatthewMartin    schedule 19.03.2010
comment
Вы неправильно сгруппировали условия в своем примере, не так ли? '&&' связывает сильнее, чем '||' в большинстве языков, которые их используют (скрипты оболочки являются исключением). - person Jonathan Leffler; 20.03.2010
comment
— добавил Паренс. Таким образом, это было бы еще одним ударом по разбиению на именованные переменные, оно дает возможность изменить группировку неочевидным образом. - person MatthewMartin; 23.03.2010

Делая это

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

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

Вы даже можете определить весь блок как предикат:

bool ElementBlahBlah? (elementIndex, lastElementIndex);

и делать больше вещей (позже) в этой функции.

person Nick Dandoulakis    schedule 19.03.2010
comment
И что еще более важно, следующий разработчик, который посмотрит код, тоже поймет, что вы имели в виду! Это отличная практика, и я делаю это все время. - person Chris Thornton; 19.03.2010
comment
Кроме того, этим следующим разработчиком часто можете быть вы сами, снова просматривая код через несколько месяцев (или даже несколько недель) и радуясь, что он хорошо задокументирован. - person Louis; 28.11.2014

Если выражение сложное, я либо перемещаю его в другую функцию, которая возвращает bool, например, isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash), либо пересматриваю код, чтобы такое сложное выражение не требовалось.

person Benedict Cohen    schedule 19.03.2010

Помните, что таким образом вы вычисляете больше, чем необходимо. Из-за удаления условий из кода вы всегда вычисляете их оба (без короткого замыкания).

Чтобы:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
   ...
}

После преобразования становится:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) |
   (elementIndex == lastElementIndex)){
   ...
}

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

person Konrad Garus    schedule 19.03.2010
comment
Правда - я заметил это только сейчас, когда вернулся к части своего кода, чтобы реорганизовать пару операторов if, используя этот метод. Был оператор if, использующий оценку короткого замыкания с помощью && (вторая часть выдает NPE, если первая часть ложна), и мой код терпел неудачу, когда я рефакторил это (потому что это было всегда оценивая оба и сохраняя их в логических переменных). Хороший вопрос, спасибо! Но мне было интересно, когда я пробовал это - есть ли способ сохранить логику в переменной и отложить ее оценку до фактического оператора if? - person froadie; 19.03.2010
comment
Я действительно сомневаюсь, что компилятор решит это. Если второй вызов неэффективен, обычно это связано с вызовом какой-либо функции, и, насколько мне известно, ни один компилятор не пытается определить, не вызывает ли вызываемая функция побочных эффектов. - person J S; 19.03.2010
comment
Вы можете вложить ЕСЛИ и не выполнять последующие вычисления, если первого теста недостаточно, чтобы решить, следует ли продолжать. - person Jay; 19.03.2010
comment
@froadie Некоторые языки, такие как Kotlin (и вскоре Dart), допускают ленивые переменные, которые будут вычисляться только при использовании. В качестве альтернативы, размещение логики в функции вместо переменной будет иметь тот же эффект. Просто, знаешь, на случай, если ты все еще захочешь узнать 10 лет спустя. - person hacker1024; 31.12.2020

Я думаю, что лучше создавать функции/методы вместо временных переменных. Таким образом, удобочитаемость повышается еще и потому, что методы становятся короче. В книге Мартина Фаулера «Рефакторинг» есть хорошие советы по улучшению качества кода. Рефакторинг, связанный с вашим конкретным примером, называется «Заменить Temp запросом» и «Метод извлечения».

person mkj    schedule 19.03.2010
comment
Вы хотите сказать, что, загромождая классовое пространство множеством одноразовых функций, вы повышаете удобочитаемость? Пожалуйста, объясни. - person Zano; 19.03.2010
comment
Это всегда компромисс. Удобочитаемость оригинальной функции улучшится. Если исходная функция короткая, возможно, она того не стоит. - person mkj; 19.03.2010
comment
Также я думаю, что загромождение пространства классов зависит от используемого языка и того, как вы разбиваете свой код. - person mkj; 19.03.2010

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

person GrowlingDog    schedule 19.03.2010

если метод требует уведомления об успехе: (примеры на С#) мне нравится использовать метод

bool success = false;

начать. код неверен, пока я не изменю его на:

success = true;

затем в конце:

return success;
person Chris Hayes    schedule 23.03.2010

Я думаю, это зависит от того, какой стиль предпочитаете вы/ваша команда. Рефакторинг «Введите переменную» может быть полезен, но иногда нет :)

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

Например:

void DoSomeMethod(boolean needDelete) { ... }

// useful
boolean deleteOnCompletion = true;
if ( someCondition ) {
    deleteOnCompletion = false;
}
DoSomeMethod(deleteOnCompletion);

// useless
boolean shouldNotDelete = false;
DoSomeMethod(shouldNotDelete);
person dchekmarev    schedule 19.03.2010

По моему опыту, я часто возвращался к некоторым старым сценариям и задавался вопросом: «О чем, черт возьми, я думал тогда?». Например:

Math.p = function Math_p(a) {
    var r = 1, b = [], m = Math;
    a = m.js.copy(arguments);
    while (a.length) {
        b = b.concat(a.shift());
    }
    while (b.length) {
        r *= b.shift();
    }
    return r;
};

что не так интуитивно понятно, как:

/**
 * An extension to the Math object that accepts Arrays or Numbers
 * as an argument and returns the product of all numbers.
 * @param(Array) a A Number or an Array of numbers.
 * @return(Number) Returns the product of all numbers.
 */
Math.product = function Math_product(a) {
    var product = 1, numbers = [];
    a = argumentsToArray(arguments);
    while (a.length) {
        numbers = numbers.concat(a.shift());
    }
    while (numbers.length) {
        product *= numbers.shift();
    }
    return product;
};
person rolandog    schedule 19.03.2010
comment
-1. Честно говоря, это немного связано с исходным вопросом. Вопрос о другом, очень конкретном, а не о том, как писать хороший код вообще. - person P Shved; 19.03.2010

Я редко создаю отдельные переменные. Что я делаю, когда тесты усложняются, так это встраиваю IF и добавляю комментарии. Нравиться

boolean processElement=false;
if (elementIndex < 0) // Do we have a valid element?
{
  processElement=true;
}
else if (elementIndex==lastElementIndex) // Is it the one we want?
{
  processElement=true;
}
if (processElement)
...

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

person Jay    schedule 19.03.2010