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

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

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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);

Ответ 4

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

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);
}

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

Ответ 5

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

//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-критериев с более чем тремя частями", просто для получения комментариев, семантически пустых следующего вида

i++; //increment i by adding 1 to i previous value

Ответ 6

Сделав это

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

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

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

bool ElementBlahBlah? (elementIndex, lastElementIndex);

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

Итак, чтобы:

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

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

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

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

Ответ 11

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

bool success = false;

чтобы начать. код затухания, пока я не изменю его на:

success = true;

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

return success;

Ответ 12

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

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

например:

void DoSomeMethod(boolean needDelete) { ... }

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

// useless
boolean shouldNotDelete = false;
DoSomeMethod(shouldNotDelete);

Ответ 13

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

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;
};

Ответ 14

Я редко создаю отдельные переменные. То, что я делаю, когда тесты усложняются, - это гнездо 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)
...

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