Я слышал, что некоторые перерывы не плохая практика. Что насчет этого? - PullRequest
8 голосов
/ 30 июля 2010

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

Я немного сбит с толку относительно того, что является / не является плохой практикой в ​​этом случае.

Для Project Euler : Проблема 7, я создал код ниже. Задача состояла в том, чтобы найти 10001-е простое число.

int index = 2, count = 1, numPrime = 1;

while (true) {
    index++;

    if (isPrime(index)) {
        count = index;
        numPrime++;
    }

    if (numPrime >= 10001)
        break;
}

System.out.println(count);

Это возвращает правильный ответ (в 21 мс), но я пропускаю серьезное предупреждение? Можно на 100% создать цикл while без перерыва, но я считаю, что за ним немного легче следовать.

Является ли способ использования break; плохой практикой? Я знаю, что всегда можно обойтись, но разве это так ужасно?

Большое спасибо

Жюстьян

EDIT

Вот мой код isPrime (). С таким же успехом я мог бы оптимизировать это.

public static boolean isPrime(long num) {  
    if (num == 2)
        return true;

    if (num % 2 == 0 || num <= 0)
        return false;

    for (long i = 3; i * i <= num; i += 2)
        if (num % i == 0)
            return false;

    return true;
}

Ответы [ 9 ]

22 голосов
/ 30 июля 2010

Я не уверен, что перерывы вообще плохая практика, но я думаю, что это один.

Это немного глупо, потому что в этом нет необходимости. Ваш while (true) ... break полностью эквивалентен:

while (numPrime < 10001) {
   ...
}

но гораздо менее интуитивно понятен.

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

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

Было бы трудно (и я бы сказал, бесполезно) попытаться придумать жесткие правила о том, когда использовать break, а когда извлекать переменные и использовать вместо них условные выражения. Часто легко сказать, что является самым простым вариантом, но не всегда. Это один из тех примеров, где опыт действительно имеет значение - чем больше хорошего / плохого кода вы прочитали и написали сами, тем больше будет ваша личная библиотека хороших и плохих примеров, и тем легче будет вам сказать, что «лучше» "* способ представления данной концепции:

* Конечно, это субъективно!

15 голосов
/ 30 июля 2010

В этом случае мне кажется, что было бы проще просто изменить условие while:

while (numPrime < 10001) {

Это обычно тот случай, когда цикл while(true) заканчивается на

if (condition)
{
    break;
}

... хотя вам нужно проверить, выполняет ли что-либо еще в теле цикла continue.

В качестве альтернативы, вы можете немного изменить его структуру:

int current = 1;
for (int i = 0; i < 10001; i++)
{
    current++;
    while (!isPrime(current))
    {
        current++;
    }
}

Тогда current будет ответом в конце.

Я обычно предпочитаю цикл for циклу while, когда вы пытаетесь сделать что-то определенное количество раз. В этом случае «что-то» - это «найти следующее простое число».

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

3 голосов
/ 30 июля 2010

Есть пара условий, когда имеет смысл использовать перерыв. Один из них - когда вам нужно выполнить циклы N.5 - то есть вы будете выполнять цикл несколько раз, но в последний раз вы всегда будете выполнять где-то в середине тела цикла. Вы можете избегать использования разрыва в этом случае, но это часто приводит к запутыванию кода или приводит к дублированию. Например:

while (true) {
    first part;
    if (finished) 
       break;
    second part;
}

можно превратить во что-то вроде:

first part;
while (!finished) {
    second part;
    first part;
}

или

while (!finished) {
    first part;
    if (!finished)
        second part;
}

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

2 голосов
/ 30 июля 2010

Не сильно отличается от всего, что было сказано ранее, но с точки зрения читаемости, прозрачной логической точки зрения, я бы порекомендовал

long i,primes=0;
for (i=2;primes<10001;i++) {
    if (isPrime(i))
        primes++;
}

Я - ответ.

2 голосов
/ 30 июля 2010

Вот что было изобретено / сделано для:

do {
//...
} while(numPrime < 10001);

Это бит while(true), который я считаю плохой практикой, что, конечно, приводит к break.

2 голосов
/ 30 июля 2010

если вы можете уйти без перерыва, то сделайте это.

до

 while (numPrime < 10001) ...
0 голосов
/ 31 июля 2010

Это решение довольно быстрое:

#include <stdio.h>

unsigned long isqrt(unsigned long n) {
    // http://snippets.dzone.com/posts/show/2715
    unsigned long a;
    for (a = 0; n >= (2*a)+1; n -= (2*a++) + 1);
    return a;
}

void nPrimes(const long N, long *const primes)
{
    unsigned long n, count, i, root;

    primes[0] = 2;
    count = 1;

    for (n = 3; count < N; n+=2) {

        root = isqrt(n);

        for (i = 0; primes[i] <= root; i++) {
            if ((n % primes[i]) == 0) goto notPrime;
        }
/*      printf("Prime #%lu is: %lu\n", count+1, n);*/
        primes[count++] = n;

        notPrime: ;
    }
}

int main (int argc, char **argv)
{
    long N;

    if (argc > 1) {
        N = atoi(argv[1]);
    } else {
        N = 10001;
    }

    long primes[N];

    nPrimes(N, primes);

    printf("Prime #%lu is: %lu\n", N, primes[N-1]);
}

Примечания:

  • isqrt (n) это floor (sqrt (n)).Это позволяет избежать операций с плавающей точкой, которые нам в любом случае не нужны.Алгоритм от http://snippets.dzone.com/posts/show/2715.
  • nPrimes хранит список простых чисел.Это позволяет вам тестировать только простые делители, что исключает подавляющее большинство из этих дорогостоящих операций с модами.
  • Даже простые числа проверяются только до isqrt (n).
  • Это уродливый переход, но C не имеет имен продолжения, и переменная флага была бы пустой тратой времени.
  • Массив простых чисел инициализируется с 2, и он проверяет только нечетные числа из 3. Исключение кратных 3аналогичным образом выполнимо, но не имеет тривиальной сложности.
  • Вызов isqrt можно устранить, сохранив таблицу квадратов простых чисел.Это почти наверняка излишне, но вы можете, если хотите.
  • На моей не новой машине это работает в мгновение ока.
0 голосов
/ 30 июля 2010

Как уже отмечали другие, приведенный вами пример плохой, потому что вы можете легко перенести тест в WHILE.

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

while (true)
{
  String inLine=inStream.readLine();
  if (inLine.startsWith("End"))
    break;
  ... process line ...
}

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

Можно, конечно, написать функцию, которая читает строку и выполняет необходимый анализ, например:

while (!endOfInterestingData(inStream))
{
  ... process ...
}

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

0 голосов
/ 30 июля 2010

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

Я действительно думаю, что есть определенное использование для "do {} while (1);" петля. Среди них:

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

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

Если кто-то не слишком обеспокоен скоростью, можно избежать использования любой формы goto , преждевременного выхода return , или, в этом отношении, используя более одного while - и это с простым условием. Просто напишите всю свою программу как конечный автомат:

void do_whatever(void)
{
  int current_state=1;
  do
  {
    next_state = 0;
    if (current_state == 1)
    {
      do_some_stuff();
      next_state = 2;
    }
    if (current_state == 2)
    {
      do_some_more_stuff();
      next_state = 3;
    }
    ...
    current_state = next_state;
  } while(current_state);
}

Бывают случаи, когда такое кодирование полезно (особенно если «while» может быть извлечено из подпрограммы do_whwhat () в цикл, который запускает несколько подпрограмм с одинаковым кодированием «одновременно»). И никогда не нужно использовать что-то вроде «goto». Но для удобства чтения конструкции структурированного программирования гораздо приятнее.

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

  if (index >= numItems)
  {
    createNewItem(index);
    break;
  }

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

...