Один из пунктов контрольного списка Стива МакКоннелла - это вы не должны обезьянничать с индексом цикла (Глава 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();
С тех пор я столкнулся с проблемой проверки кода и обнаружил, что моя формулировка Зачем, это плохая практика, не была такой сильной, как мне бы хотелось. Я сказал, что сложнее понять ход цикла и, следовательно, труднее поддерживать и отлаживать и, в конечном итоге, дороже в течение всего времени существования кода.
Можно ли лучше объяснить, почему это плохая практика?





Я не уверен, полностью ли понимаю, что вам нужно, но, может быть, что-то вроде этого?
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. У вас есть идеи, как лучше сформулировать принцип «не монки с индексом цикла»?
Как насчет простого: если не нужно усложнять - не надо.
+1, даже если заказчик сказал, что это «другая проблема». Вы проясните, насколько ясно это могло быть написано. (Каламбур очень задуманный.)
Я считаю, что твоя артикуляция великолепна. Может быть, это можно сформулировать так:
Since the logic can be expressed much clearer, it should.
Я думаю, вы могли бы построить более сильный аргумент, обратившись к концепции Кнута о грамотное программирование, что программы должны быть написаны для компьютеров нет, но для передачи концепций другие программисты, таким образом, более простой цикл:
while (this.MyControl.TabPages.Count>0)
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}
более четко иллюстрирует цель - удалить первую вкладку, пока не останется ни одной. Я думаю, что большинство людей нащупали бы это намного быстрее, чем исходный пример.
Справедливо, но это побочная проблема (см. Мой комментарий под кодом), в любом случае спасибо
Я видел комментарий, я пытаюсь сформулировать более многословную версию принятого ответа: «Поскольку логика может быть выражена гораздо яснее, так и должно быть».
Это могло бы быть яснее:
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.
Хорошее место и хорошее общение. Я бы проголосовал за вас дважды, если бы мог.
Я думаю, что последняя строка должна быть «ошибки приводят к страданиям», но тем не менее весело! :)
Исходный код очень избыточен, чтобы изменить действие цикла 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 <" в последнем примере?
Единственная причина вообще возиться с индексом - это выборочное стирание данных. Даже в этом случае я бы предпочел сказать:
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.
Для справки, совет, как избежать обезьяны с указателем цикла, можно найти на странице 377 Code Complete 2 (2004) ISBN 0-7356-1967-0.