Почему «обезьяна с индексом цикла» - это плохо?

Один из пунктов контрольного списка Стива МакКоннелла - это вы не должны обезьянничать с индексом цикла (Глава 16, стр. 25, Индексы цикла, формат PDF).

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

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

        for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ )
        {
            this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
            i--;
        }

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

Этот цикл можно было бы записать как

        while(MyControl.TabPages.Count > 0)
            MyControl.TabPages.RemoveAt(0);

И поскольку элемент управления был фактически написан примерно в то же время, что и цикл, его можно было даже записать как

        MyControl.TabPages.Clear();

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

Можно ли лучше объяснить, почему это плохая практика?

Для справки, совет, как избежать обезьяны с указателем цикла, можно найти на странице 377 Code Complete 2 (2004) ISBN 0-7356-1967-0.

Ed Guiness 19.07.2016 18:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
17
1
2 316
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Я не уверен, полностью ли понимаю, что вам нужно, но, может быть, что-то вроде этого?

for ( int i=this.MyControl.TabPages.Count - 1 ; i >= 0  ; i-- )
{
    this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
}

или просто это из коллекции TabPages реализует IList:

    this.MyControl.TabPages.Clear();

Что ж, это добавляет путаницы с небольшой целью - вы могли бы так же легко написать:

while(MyControl.TabPages.Count > 0)
{
    MyControl.TabPages.Remove(MyControl.TabPages[0]);
}

или (проще)

while(MyControl.TabPages.Count > 0)
{
    MyControl.TabPages.RemoveAt(0);
}

или (простейший)

MyControl.TabPages.Clear();

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

В самом деле, как я указал в своем вопросе, было бы лучше использовать цикл while. У вас есть идеи, как лучше сформулировать принцип «не монки с индексом цикла»?

Ed Guiness 19.01.2009 12:51

Как насчет простого: если не нужно усложнять - не надо.

Marc Gravell 19.01.2009 12:53

+1, даже если заказчик сказал, что это «другая проблема». Вы проясните, насколько ясно это могло быть написано. (Каламбур очень задуманный.)

PEZ 19.01.2009 12:58
Ответ принят как подходящий

Я считаю, что твоя артикуляция великолепна. Может быть, это можно сформулировать так:

Since the logic can be expressed much clearer, it should.

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

 while (this.MyControl.TabPages.Count>0)
 {
            this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
 }

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

Справедливо, но это побочная проблема (см. Мой комментарий под кодом), в любом случае спасибо

Ed Guiness 19.01.2009 13:03

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

Paul Dixon 19.01.2009 13:21

Это могло бы быть яснее:

while (this.MyControl.TabPages.Count > 0)
{
  this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}

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

Все дело в ожидании.

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

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

complexity leads to misunderstanding

misunderstanding leads to bugs

bugs leads to the dark side.

Хорошее место и хорошее общение. Я бы проголосовал за вас дважды, если бы мог.

Raithlin 19.01.2009 13:34

Я думаю, что последняя строка должна быть «ошибки приводят к страданиям», но тем не менее весело! :)

Andrew Hare 20.01.2009 16:24

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

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

Я думаю, что достаточно было указать на тот факт, что итерации цикла контролируются не «i ++», как можно было бы ожидать, а сумасшедшей установкой «i--».

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

Я согласен с твоим вызовом. Если они хотят сохранить цикл for, код:

for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ ) {
    this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
    i--;
}

сокращается следующим образом:

for ( int i=0 ; i < this.MyControl.TabPages.Count ; ) {
    this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
}

а затем:

for ( ; 0 < this.MyControl.TabPages.Count ; ) {
    this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}

Но цикл пока или метод Ясно(), если он существует, явно предпочтительнее.

должно ли это быть "0 <" в последнем примере?

Marc Gravell 19.01.2009 13:22

Единственная причина вообще возиться с индексом - это выборочное стирание данных. Даже в этом случае я бы предпочел сказать:

  i=0;
  while(i < MyControl.Tabpages.Count)
    if (wantToDelete(MyControl.Tabpages(i))
      MyControl.Tabpages.RemoveAt(i);
    else
      i++;
rather than jinxing the loop index after each removal. Or, better yet, have the index count downward so that when an item is removed it won't affect the index of future items needing removal. If many items are deleted, this may also help minimize the amount of time spent moving items around after each deletion.

Другие вопросы по теме