Возвращение GOTO

Сейчас все понимают, что использовать оператор GOTO это не просто плохая, а ужасная практика. Дебаты по поводу его использования закончились в 80-х годах XX века и его исключили из большинства современных языков программирования. Но, как и положено настоящему злу, он сумел замаскироваться и воскреснуть в XXI веке под видом исключений.


Исключения, с одной стороны, являются достаточно простой концепцией в современных языках программирования. С другой же стороны, их часто используют неправильно. Есть простое и хорошо известное правило – исключения только для обработки поломок. И именно слишком вольная интерпретация понятия «поломка» приводит ко всем проблемам использования GOTO.


Теоретический пример


Разница между поломками и негативными бизнес-сценариями хорошо видна на окне входа в систему с очень простым сценарием использования:


  1. Пользователь вводит логин/пароль.
  2. Пользователь нажимает кнопку «Войти в систему».
  3. Клиентское приложение отправляет запрос на сервер.
  4. Сервер успешно проверяет логин/пароль (под успехом считает наличие соответствующей пары).
  5. Сервер отсылает клиенту информацию, что аутентификация прошла успешно и ссылку на страницу перехода.
  6. Клиент осуществляет переход на указанную страницу.

И одно негативное расширение:


4.1. Сервер не нашел соответствующую пару логин/пароль и посылает клиенту уведомление об этом.


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


Поломки, это: разрыв соединения между клиентом и севером, недоступность СУБД, неправильная схема в БД. И еще миллион причин, ломающих наши приложения и не имеющих никакого отношения к бизнес-логике пользователя.


В одном из проектов, в разработке которого я участвовал, была более сложная логика входа в систему. Введя 3 раза подряд неправильный пароль, пользователь временно блокировался на 15 минут. Попадая 3 раза подряд во временную блокировку, пользователь получал постоянную блокировку. Также были дополнительные правила в зависимости от типа пользователя. Реализация с помощью исключений привела к тому, что внесение новых правил было крайне затруднительно.


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


Пример Загрузка свойств


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


private WorkspaceProperties(){

    Properties loadedProperties = readPropertiesFromFile(WORK_PROPERTIES_PATH, true);
    //These mappings will replace any mappings that this hashtable had for any of the 
    //keys currently in the specified map.
    getProperties().putAll( loadedProperties );

    //Это файл имеет право отсутствовать
    loadedProperties = readPropertiesFromFile(MY_WORK_PROPERTIES_PATH, false);
    if (loadedProperties != null){
        getProperties().putAll( loadedProperties );
    }
    System.out.println("Loaded properties:" + getProperties());
}

/**
 * Возвращает свойства, загруженные из указанного файла.
 * @param filepath  
 * @param throwIfNotFound - кинуть FileNotFoundException, если файл не найден
 * @return Загруженные свойства или null, если файл не найден и !throwIfNotFound
 * @throws FileNotFoundException throwIfNotFound и файла с таким именем не надено
 * @throws IOException ошибка загрузки найденного файла
 */
private Properties readPropertiesFromFile(String filepath, boolean throwIfNotExists){
    Properties loadedProperties = new Properties();
    System.out.println("Try loading workspace properties" + filepath);

    InputStream is = null;
    InputStreamReader isr = null;
    try{
        int loadingTryLeft = 3;
        String relativePath = "";
        while (loadingTryLeft > 0){
            try{
                File file = new File(relativePath + filepath);
                is = new FileInputStream(file);
                isr = new InputStreamReader( is, "UTF-8");
                loadedProperties.load(isr);
                loadingTryLeft = 0;
            } catch( FileNotFoundException e) {             
                loadingTryLeft -= 1;
                if (loadingTryLeft > 0)
                    relativePath += "../";
                else
                    throw e;
            } finally {
                if (is != null)
                    is.close();
                if (isr != null)
                    isr.close();
            }
        }
        System.out.println("Found file " + filepath);
    } catch( FileNotFoundException e) {
        System.out.println("File not found " + filepath);
        if (throwIfNotExists)
            throw new RuntimeException("Can`t load workspace properties." + filepath + " not found", e );
    }catch (IOException e){
        throw new RuntimeException("Can`t read " + filepath, e);
    }
    return loadedProperties;
}

Итак, раскроем тайну – что же здесь происходит. Осуществляется загрузка свойств из двух файлов – обязательного WORK_PROPERTIES и дополнительного MY_WORK_PROPERTIES, добавляя в общее хранилище свойств. При этом есть нюанс – нам точно не известно, где лежит конкретный файл свойств – он может лежать как в текущем каталоге, так и в каталогах-предках (до трех уровней вверх).


Здесь смущает, как минимум, две вещи: параметр throwIfNotExists и большой блок логики в catch FileNotFoundException. Все это непрозрачно намекает – исключения используются для реализации бизнес-логики (а как иначе объяснить, что в одном сценарии выброс исключения – это поломка, а в другом – нет?).


Делаем правильный контракт


Сначала разберемся с throwIfNotExists. При работе с исключениями очень важно понимать – где именно его нужно обработать с точки зрения сценариев использования. В данном случае очевидно, что сам метод readPropertiesFromFile не может принять решение – когда отсутствие файла «плохо», а когда – «хорошо». Такое решение принимается в точке его вызова. По комментариям видно, что мы решаем – должен существовать этот файл или нет. Но на самом деле нам интересен не сам файл, а настройки из него. К сожалению, это никак не следует из кода.


Исправим оба этим недостатка:


Properties loadedProperties = readPropertiesFromFile(WORK_PROPERTIES_PATH);
if (loadedProperties.isEmpty()) {
    throw new RuntimeException("Can`t load workspace properties");
}
loadedProperties = readPropertiesFromFile(MY_WORK_PROPERTIES_PATH);
getProperties().putAll( loadedProperties );

Теперь четко показана семантика –
WORK_PROPERTIES обязательно должны быть заданы, а MY_WORK_PROPERTIES — нет. Также при рефакторинге я обратил внимание, что readPropertiesFromFile никогда не сможет вернуть null и воспользовался этим при чтении MY_WORK_PROPERTIES.


Проверяем не ломая


Предыдущий рефакторинг также затронул и реализацию, но не значительно. Я просто удалил блок обработки throwIfNotExists:


if (throwIfNotExists)
            throw new RuntimeException(…);

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


Существует функция проверки доступности файла для чтения File.canRead(). Используя ее можно избавиться от бизнес-логики в блоке catch


            try{
                File file = new File(relativePath + filepath);
                is = new FileInputStream(file);
                isr = new InputStreamReader( is, "UTF-8");
                loadedProperties.load(isr);
                loadingTryLeft = 0;
            } catch( FileNotFoundException e) {             
                loadingTryLeft -= 1;
                if (loadingTryLeft > 0)
                    relativePath += "../";
                else
                    throw e;
            } finally {
                if (is != null)
                    is.close();
                if (isr != null)
                    isr.close();
            }
        }

Изменив код, получаем следующее:


private Properties readPropertiesFromFile(String filepath) {
    Properties loadedProperties = new Properties();
    System.out.println("Try loading workspace properties" + filepath);

    try {
        int loadingTryLeft = 3;
        String relativePath = "";
        while (loadingTryLeft > 0) {
            File file = new File(relativePath + filepath);
            if (file.canRead()) {
                InputStream is = null;
                InputStreamReader isr = null;
                try {
                    is = new FileInputStream(file);
                    isr = new InputStreamReader(is, "UTF-8");
                    loadedProperties.load(isr);
                    loadingTryLeft = 0;
                } finally {
                    if (is != null)
                        is.close();
                    if (isr != null)
                        isr.close();
                }
            } else {
                loadingTryLeft -= 1;
                if (loadingTryLeft > 0) {
                    relativePath += "../";
                } else {
                    throw new FileNotFoundException();
                }
            }

        }

        System.out.println("Found file " + filepath);
    } catch (FileNotFoundException e) {
        System.out.println("File not found " + filepath);
    } catch (IOException e) {
        throw new RuntimeException("Can`t read " + filepath, e);
    }

    return loadedProperties;
}

Также я снизил уровень переменных (is, isr) до минимально допустимого.


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


Выявляем GOTO


Рассмотрим детально происходящее в ситуации, если файл не был найден:


} else {
    loadingTryLeft -= 1;
    if (loadingTryLeft > 0) {
        relativePath += "../";
    } else {
        throw new FileNotFoundException();
    }
}

Видно, что здесь исключение используется для того, чтобы прервать цикл выполнения и фактически выполняют функцию GOTO.


Для сомневающихся сделаем еще одно изменение. Вместо использования мелкого костыля в виде loadingTryLeft = 0 (костыль, потому что на самом деле успешная попытка неизменяет количество оставшихся попыток) явно укажем, что считывание файла приводит к выходу из функции (не забыв при этом написать сообщение):


try {
    is = new FileInputStream(file);
    isr = new InputStreamReader(is, "UTF-8");
    loadedProperties.load(isr);
    System.out.println("Found file " + filepath);                       
    return loadedProperties;
} finally {

Это позволяет нам заменить условие while (loadingTryLeft > 0) на while(true):


try {
    int loadingTryLeft = 3;
    String relativePath = "";
    while (true) {
        File file = new File(relativePath + filepath);
        if (file.canRead()) {
            InputStream is = null;
            InputStreamReader isr = null;
            try {
                is = new FileInputStream(file);
                isr = new InputStreamReader(is, "UTF-8");
                loadedProperties.load(isr);
                System.out.println("Found file " + filepath);
                return loadedProperties;
            } finally {
                if (is != null)
                    is.close();
                if (isr != null)
                    isr.close();
            }
        } else {
            loadingTryLeft -= 1;
            if (loadingTryLeft > 0) {
                relativePath += "../";
            } else {
                throw new FileNotFoundException(); // GOTO: FFN
            }
        }

    }

} catch (FileNotFoundException e) { // LABEL: FFN
    System.out.println("File not found " + filepath);
} catch (IOException e) {
    throw new RuntimeException("Can`t read " + filepath, e);
}

Чтобы избавиться от явного дурно пахнущего throw new FileNotFoundException, нужно вспомнить контракт функции. Функция в любом случае возвращает набор свойств, если не смогли считать файл – возвращаем его пустым. Поэтому нет никаких причин выбрасывать исключение и перехватывать его. Достаточно обычного условия while (loadingTryLeft > 0):


private Properties readPropertiesFromFile(String filepath) {
    Properties loadedProperties = new Properties();
    System.out.println("Try loading workspace properties" + filepath);

    try {
        int loadingTryLeft = 3;
        String relativePath = "";

        while (loadingTryLeft > 0) {
            File file = new File(relativePath + filepath);
            if (file.canRead()) {
                InputStream is = null;
                InputStreamReader isr = null;
                try {
                    is = new FileInputStream(file);
                    isr = new InputStreamReader(is, "UTF-8");
                    loadedProperties.load(isr);
                    System.out.println("Found file " + filepath);
                    return loadedProperties;
                } finally {
                    if (is != null)
                        is.close();
                    if (isr != null)
                        isr.close();
                }
            } else {
                loadingTryLeft -= 1;
                if (loadingTryLeft > 0)
                    relativePath += "../";
            }
        }

        System.out.println("file not found");
    } catch (IOException e) {
        throw new RuntimeException("Can`t read " + filepath, e);
    }

    return loadedProperties;
}

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



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


  • Название метода readPropertiesFromFile раскрывает его реализацию (кстати, равно как и throws FileNotFoundException). Лучше назвать более нейтрально и лаконично – loadProperties(…)
  • Метод одновременно и ищет, и считывает. Для меня это две разных обязанности, которые можно разделить в разных методах.
  • Изначально код писался под Java 6, а сейчас используется на Java 7. Это позволяет использовать closable resources.
  • По опыту знаю, что при выводе информации о найденном или не найденном файле лучше использовать полный путь к файлу, а не относительный.
  • if (loadingTryLeft > 0) relativePath += "../"; — если внимательно посмотреть код, то видно – эта проверка лишняя, т.к. при исчерпании лимита поиска все равно новое значение использовано не будет. А если в коде что-то лишнее, это мусор, который следует убрать.

Окончательная версия исходного кода:


private WorkspaceProperties() {
    super(new Properties());

    if (defaultInstance != null)
        throw new IllegalStateException();

    Properties loadedProperties = readPropertiesFromFile(WORK_PROPERTIES_PATH);
    if (loadedProperties.isEmpty()) {
        throw new RuntimeException("Can`t load workspace properties");
    }

    getProperties().putAll(loadedProperties);

    loadedProperties = readPropertiesFromFile(MY_WORK_PROPERTIES_PATH);
    getProperties().putAll(loadedProperties);

    System.out.println("Loaded properties:" + getProperties());
}

private Properties readPropertiesFromFile(String filepath) {
    System.out.println("Try loading workspace properties" + filepath);

    try {
        int loadingTryLeft = 3;
        String relativePath = "";

        while (loadingTryLeft > 0) {
            File file = new File(relativePath + filepath);
            if (file.canRead()) {
                return read(file);
            } else {
                relativePath += "../";
                loadingTryLeft -= 1;
            }
        }
        System.out.println("file not found");
    } catch (IOException e) {
        throw new RuntimeException("Can`t read " + filepath, e);
    }

    return new Properties();
}

private Properties read(File file) throws IOException {
    try (InputStream is = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(is, "UTF-8")) {
        Properties loadedProperties = new Properties();
        loadedProperties.load(isr);
        System.out.println("Found file " + file.getAbsolutePath());
        return loadedProperties;
    }
}

Резюме


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

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 191

    +34
    Сейчас все понимают, что использовать оператор GOTO это не просто плохая, а ужасная практика.

    да никто этого не понимает, никто, языки где был тот самый страшной goto пропали ещё в конце 80 вместе с обязательной нумерации строк и пререхода на процедурное программирование

    народ по привычке продолжает пересказывать страшилки про goto и приводить, этот единственный пример с выходом из цикла причём в яве для этого можно использовать переход по метке через break или continue

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

    p.s. устриц ел, писал на atari basic
    p.p.s. в современных бейсиках goto уже тоже давно нет в том самом опасном виде
      +3
      Интересен факт того, что в PHP оператор goto, как раз появился только в версии 5.3. До сих пор не понимаю для чего.
        –1
        языки где был тот самый страшной goto пропали ещё в конце 80
        PHP, С, C++, C# вроде никуда не пропали.
          +8
          в них можно делать goto по метке за пределы процедуры?
            +1
            C++
            The goto statement unconditionally transfers control to the statement labeled by the identifier. The identifier shall be a label (6.1) located in the current function.

            C
            The identifier in a goto statement shall name a label located somewhere in the enclosing function. A goto statement shall not jump from outside the scope of an identifier having a variably modified type to inside the scope of that identifier.
              +4
              В С/С++ можно вообще куда угодно с помощью longjmp. Классический goto только в пределах функции.
                0

                не совсем "куда угодно" правда

                +1
                PHP
                The target label must be within the same file and context, meaning that you cannot jump out of a function or method, nor can you jump into one.
                  +1
                  Легко!
                  void * ptr;
                  a() {
                   ptr = &&a;
                   a:;
                  }
                  
                  b() {
                    goto *ptr;
                  }
                  
                  
                    0
                    Это же gnu расширение возможностей языка.
                    https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
                      –1
                      Это так. И думается мне, что это и есть собственно язык :)
                        0
                        Конечно это язык. Только это GNU C, а не чистый C.
                          –1
                          Так и в вопросе не было уточенений! Вот — есть такое…
                +15
                Мы начинали с языка где goto вполне себе используется — assembler.
                Потом, что интересно, был basic потом c.
                Потом был период когда преподавали basic и это позволило увидеть много интересных вещей происходящих с goto.

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

                Но с этим есть две проблемы
                а) Зачастую с водой выплескивают и ребенка. Правильное использование goto в 1% случаев может сделать код проще, быстрее, компактнее и понятнее. Но goto нет, т.к. в 99% случаев его будут использовать неправильно.
                б) Народ не понимая сути проблемы с опасными практиками применения goto начинает эмулировать его другими способами, как верно подмечено в статье — например исключениями. В результате goto нет, а опасные практики есть.

                Ближайший аналог из чего-то относительно свежего — это register_globals в php. Они влияли только на безопасность неграмотно написанного кода и их отключили, потому что неграмотно написанного кода было слишком много.
                На самом деле депрессивная причина, когда удобный инструмент не используют только потому, что куча народа не умеет им правильно пользоваться.
                  +2
                  Интересно стало посмотреть на необходимость goto в Java.
                    +4
                    ext_loop: while (...) {
                      while (...) {
                        ...
                        if (...) break ext_loop;
                        ...
                      }
                    }
                    

                    Не всегда просто (и бесплатно) переписать часть кода так чтобы можно было без подобной конструкции обойтись, и тот факт что вместо «goto» используется «break» не меняет сути — и первое и второе перепрыгивают через кусок кода (как, собственно, вообще любые «break» и «continue», которые по сути те же самые «goto», хотя называются иначе).

                    Как выше уже отметили, не сам факт использования «goto» печален, а неадекватное его использование.
                      0
                      return это тоже GOTO в своем роде, мало кто этого понимает правда
                        0
                        если совсем точно проводить аналогии то return это resume/return из gosub
                          0
                          я имел в виду, что иногда встречаешься с кодом где излишне злоупотребляют return'ом и он читается ненамного лучше чем goto, например return в начале тела функции, в середине внутри двух-трех вложенных циклов и в конце тела.
                          а по сути да, return это прыжок в конец тела функции с каким-то значением
                            +6
                            например return в начале тела функции,

                            Так это же наоборот хорошо, так как «только начал читать метод/функцию, как сразу уже понятно, что при определенных условиях, можно уже не продолжать ее анализ».
                              0

                              Раньше старался писать "правильно", чтобы на функцию был только один return и в конце. Теперь же пишу как удобно и короче, поэтому return зачастую встречается и в начале. Из-за return в начале приходится городить блоки if else, из-за которых появляется дополнительный уровень вложенности.

                                +5
                                Из чего следует что «правильно» это «один return»?

                                Если функция вида:
                                f() {
                                  if (!condition) return;
                                  ...
                                  if (!condition2) return;
                                  ...
                                }
                                

                                то всё вполне «правильно», да и несколько return в середине (если уже ясен результат или выполнено всё что нужно) тоже вполне правильно, и совсем не хуже (если не лучше) чем:
                                f() {
                                  if (condition) {
                                    ...
                                    if (condition2) {
                                      ....
                                    }
                                  }
                                }
                                

                                return в начале (или перед куском кода) как раз часто позволяет избавится от дополнительной вложенности.

                                  0
                                  Из чего следует что «правильно» это «один return»?

                                  Из каких-то источников в интернете. Слово и обособлено в кавычки, потому что на самом деле не правильно, да и нельзя возводить какой-то принцип в абсолют.


                                  return в начале (или перед куском кода) как раз часто позволяет избавится от дополнительной вложенности.

                                  Я об этом и писал тоже.

                                    0
                                    Из чего следует что «правильно» это «один return»?
                                    Из каких-то источников в интернете. Слово и обособлено в кавычки, потому что на самом деле не правильно, да и нельзя возводить какой-то принцип в абсолют.
                                    Не уверен, насколько это правда, но вроде как с этим правилом такая же ситуация как и с правилом про goto. Изначально имелось ввиду «делайте return в самой функции, а не делайте goto в середину другой функции, где этот return потом есть».
                                    Структурированное программирование, в итоге, победило, и правило преобразовалось в «только один return в функции», так как о прыжках в середину других функций уже мало кто вспомнит…
                                      0

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

                                  0
                                  Уже много лет назад в c++.moderated была жаркая флейма по поводу multiple return vs. single return. Я из нее вынес примерно такую позицию: в C, где нет исключений, single return имеет смысл, если не требует акробатических трюков в коде. А в C++, где есть конструкторы/деструкторы и исключения и из блока можно вылететь в любом месте — разницы между множественным и единственным возвратом не много. В C goto, кстати, часто приводит и к single return'у, и без goto его не всегда легко достичь.
                                  В конечном случае решает понятность кода и эффективность. Может быть субъективным подходом, но в целом консенсус достижим.
                                  Кстати, отличие в сгенерированном коде multiple return от goto обычно чуть менее, чем отсутствует.
                                    0
                                    Кстати, отличие в сгенерированном коде multiple return от goto обычно чуть менее, чем отсутствует.

                                    ну так язык он для программиста (а не транслятора кода \ компилятора)

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

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

                                У нас принято делать ранний return для проверки валилности аргументов и контекста, для edge кейсов алгоритма типа логарифм 1 = 0, а в остальном рекомендуется использовать один, если это не сильно щагромождает код

                                  –3

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


                                  Надо в правилах, что ли, обозначить, на заглавной странице: «Дефолтсити: Москва. Дефолтлангадж: Джавапитонскрипт.»


                                  Я это к чему. На языках, на которых в последнее время пишу я — вообще нет такой конструкции «return».

                                    0

                                    Мы рады за вас, но какое это имеет отношение к обсуждению?

                                      –1

                                      В обсуждении всплыло слово «return», которое ведет к потере общности дискуссии в целом. Это неочевидно?

                                      +2

                                      Если всплыло упоминание, значит контекст был (или стал) про языки, где есть подобные конструкции. У нас это правило применяется на PHP, TypeScript, Java, Go и паре диалектов SQL, если ничего не забыл

                                      0

                                      В общем можно сформулировать: должен быть один главный return (не обязательно в конце, часто в теле цикла)

                                        +1

                                        Так и представил...


                                        func a(){
                                            if (!cond_1) auxiliaryReturn null;
                                            if (!cond_2) auxiliaryReturn null;
                                            doSomeStuff();
                                        
                                            mainReturn result;
                                        }
                                          0

                                          Ну как-то так, да. :) Если в контексте статьи оставаться, то 2 и 3 строчка будут выбрасыванием эксешена :)

                                  0
                                  ret/iret точнее.
                                  –1
                                  ext_loop: while (...) {
                                  while (...) {

                                  if (...) break ext_loop;

                                  }
                                  }

                                  вы привели какойто очень специфичный слуай, что у вас 2 штуки while и еще break на метку что по сути третий цикл, но замаскированный… эта маскировка она только усложняет понимание что тут по факту 3 while а не 2… и с какой семантикой? без контекста не оправдано, может быть ради оптимизации какого-то алгоритма перебора… ну наверняка есть другой вариант того же алгоритма со стеком или списком и одним циклом (или 2мя) а не 3мя, т.е. кидать в стек или в список и делать break из 2го цикла например

                                  хотя я могу путать… какова семантика «break ext_loop;» это выход на метку или выход из обоих циклов за раз? в последнем случае — решается return при if
                                    0

                                    Да два тут цикла, два. Оператор "break с меткой" — это всё ещё break, а не goto.


                                    А return не поможет потому что после цикла может ещё код быть.

                                      0
                                      Да два тут цикла, два. Оператор «break с меткой» — это всё ещё break, а не goto.

                                      «break с меткой» это *почти* тот же самый goto

                                      p.s. некоторая структурность все же есть — т.к. *обычно* выход не в произвольное место, а например для выхода из всех циклов (как в вашем примере)

                                      но понимание от этого всеравно усложняется — в более сложном случае, и написанном как goto — например

                                      public static void main(String[] args) {
                                          boolean t = true;
                                          first:
                                          {
                                            second:
                                            {
                                              third:
                                              {
                                                System.out.println("Перед оператором break.");
                                                if (t) {
                                                  break second; // выход из блока second
                                                }
                                                System.out.println("Данный оператор никогда не выполнится");
                                              }
                                              System.out.println("Данный оператор никогда не выполнится ");
                                            }
                                            System.out.println("Данный оператор размещен после блока second.");
                                          }
                                        }


                                      Выполнение этой программы генерирует следующий вывод:

                                      Перед оператором break.
                                      Данный оператор размещен после блока second.

                                      © javarush.ru/groups/posts/1389-operatorih-perekhoda
                                        0
                                        «break с меткой» это почти тот же самый goto

                                        Только если вообще любой break считать разновидностью goto.


                                        В данном случае их ключевое различие — в том, что break переходит только вперед и наружу, но никогда не переходит назад и не заходит внутрь блоков.

                                0
                                У register_globals другая проблема, почему от него отказались. На мой взгляд основная беда с ним, что его поведение зависит от настроек PHP конкретной хостинг-площадки и разработчик в большинстве случаев не мог на него повлиять. То есть не мог расчитывать, из каких именно и в каком порядке переменные из суперглобальных массивов туда попадут. А то, на что не можешь повлиять, лучше вообще не использовать.
                                Использование Goto же, вполне поддаётся контролю.
                                  +1

                                  Не просто повлиять не мог, а, скажем так, нетипичные настройки могли приводить к серьёзным уязвимостям безопасности. Например давать возможность перезаписать env переменные в query параметрах, от "безобидного" APP_DEBUG=1 до обхода аутентификации и авторизации

                                0
                                goto — просто эквивалент jmp в ассемблере. Смысл выкидывать слова из песни?

                                Бывает и for приводит к тяжелым последствиям, тем более, это слегка пропатченный while. Давайте его тоже запретим?

                                Все эти гонения на goto — это как корреляция «убийства и хлеб». Проблема в головах, а не в операторе.

                                  –6

                                  Вы, может быть, удивитесь, но в мире есть масса языков, в которых нет циклов (ни for, ни while, никаких), и нет return, и вот на них писать — сплошное наслаждение.

                                    0
                                    Не такая уж и масса и языки эти не в топе. В некоторых DSP нет for/while, но в их ассемблере их тоже нет. Языки экспертных систем тоже как-то обходились. Но это все специфика, а не мэйнстрим.
                                      –4

                                      А я думал, ФП уже лет пять, как мейнстрим. Да и платят лучше.

                                        –1
                                        Лучше, чем за джаву? Сомневаюсь. И шансы обучиться/трудоустроиться сильно меньше.
                                          –2
                                          Сомневаюсь.

                                          Обожаю взвешенные пуленепробиваемые аргументы.

                                            +1
                                            Ну, Вы первым начали.
                                            Достаточно показать статистику нуждаемости рынка труда в программистах с той или иной специализацией (на хабре были целые статьи про это) и помножить на медианную ЗП (пусть с тех же сайтов, раз более точных данных нет).
                                            Все годы больше всего было нужно джавистов и объем в $$ — самый большой. Причем, голод такой, что уважаемые фирмы, которые раньше стеснялись, теперь чуть ли не открыто пишут, что мол купить условный мяч — нажмите эту кнопку, а если вы джава программист — соседнюю. Т.е. на рекрутеров и сайты поиска работы уже не надеются.
                                              0
                                              больше всего было нужно джавистов и объем в $$ — самый большой

                                              И как из всего этого следует, что зарплата джависта выше? Функциональщиков ищут прицельно, в обход всех этих агентств, эйчаров, и больших кнопок справа. Хотя уже сейчас видно, что ситуация потихонечку меняется. Не, если надо устроиться в нейпомикуда миддлом — то джавистам проще, конечно, тут спору нет.

                                      0

                                      Жырные намёки в сторону ФП, где… внезапно, есть и for (map), и while, и прямо документировано в спецификациях языков, что концевая рекурсия реализуется через зацикливание (потому что иначе это смерть для стека).


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

                                        0
                                        есть и for (map), и while [...]

                                        Если вы считаете, что for — это map, то вам сто́ит расширить горизонты использования for. while есть только в форме reduce_while.


                                        [...] концевая рекурсия реализуется через зацикливание

                                        Что как реализуется — это вопрос тридцать пятый, потому что понятие «цикл» — тоже высокоуровневое, так-то все реализуется через jmp.


                                        Масса этих языков, кстати, довольно маленькая.

                                        Ну вот Twitter написан на скале. WhatsApp — на эрланге. Довольно много внутреннего финтеха — на доказательных языках, типа агды / кока. Хаскелл пролезает во все щели. Назовите хоть один язык, не вызывающий рвотного рефлекса одним синтаксисом, по сравению с которым эта масса невелика.

                                          –1

                                          Если вы считаете, что я не знаю, что такое ФП и не умею в него, то давайте на этом месте закончим разговор.


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


                                          А называть языки я даже не подумаю, потому что мне надо для этого залезть к вам в глотку и выяснять, с чего лично вас тошнит, а с чего нет.
                                          Меня, например, тошнит со скалы и эрланга. Почему вас с них не тошнит, я не знаю.
                                          С питона вас тошнит или нет?

                                            –1
                                            [...] давайте на этом месте закончим разговор.

                                            А и давайте.

                                              0

                                              Отождествлять map с циклом очень странно. Это очень частный случай цикла, который даёт вам смотреть только на один элемент, и посещает каждый элемент только один раз (ну и ещё там всякие законы должны выполняться, что map f . map g = map (f . g), или там map id = id).


                                              То, что map можно выразить через цикл (с оговорками), и то, что цикл можно выразить с безумными костылями через map (с оговорками и безумными костылями) — совсем другой разговор.

                                                0

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

                                                  0

                                                  К сожалению, не знаю такого. И, если честно, не думаю, что это реализуемо.


                                                  А скомпилировать хаскель-код в С вы и сейчас можете через -fvia-C, но не рекомендую это делать, это вам ничего хорошего не даст.

                                                    0

                                                    Просто не совсем понимаю, как тот же map реализовать иначе как через цикл или через SIMD-инструкции.

                                                      0

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

                                                        0

                                                        Верно, никак. Это частный случай цикла.


                                                        Правда, всё становится интереснее, если вы рассматриваете map для произвольных функторов, а не только для массивов или списков (и это я и имел в виду под оговорками).

                                                          0

                                                          Я хоть программист не настоящий, но эмбеддед мне сильно мозг поел: не могу осознать высокоуровневые абстракции, в том числе модную функционалку, не разобрав их реализацию на чём-то низкоуровневом. Можете привести пример мапа для произвольных функторов? Я не очень понимаю, в чём сложность, ну обернём в массив указателей или список.
                                                          PS Конечно, можно написать мап на С в чистом препроцессоре, но это будет а) ужасно, б) крайне геморно в отладке, в) не С, а скорее всего Рефал.

                                                            0
                                                            Можете привести пример мапа для произвольных функторов?

                                                            У каждого функтора будет свой мап. Например, у Maybe/optional это, условно на полупсевдокоде


                                                            auto fmap(optional<T> opt, auto func)
                                                            {
                                                                if (opt)
                                                                    return optional<U> { func(*opt) };
                                                                else
                                                                    return optional<U> {};
                                                            }

                                                            где U — возвращаемое значение функци func.


                                                            Для множеств (в смысле std::set) мапа нет, как и для ключей ассоциативных контейнеров. Для значений вот уже есть, но там придётся, например, бегать по деревьям, что одним циклом for (int i = 0; i < len; ++i) выражается довольно плохо.

                                                              0

                                                              Спасибо, теперь понятно. Кстати, а constan propagation в compile time, если opt ~= true, отработается?

                                                                0

                                                                Для более простых случаев после инлайнинга — да, вполне.

                                              0
                                              прямо документировано в спецификациях языков, что концевая рекурсия реализуется через зацикливание (потому что иначе это смерть для стека).

                                              В JavaScript задокументировали, заспецифировали, но вроде до сих пор нигде не реализовали

                                                +1

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

                                                  –1

                                                  TCO — часть спецификации языка, а не реализаций. Была впервые добавлена в спецификацию EcmaScript4, позднее выпилена оттуда и добавлена в EcmaScript6.


                                                  На сегодняшний день не поддерживается примерно никем.

                                                    +1

                                                    Можете привести ссылку на спецификацию? Я не вижу в ней ничего похожего на TCO.

                                                      0
                                                      Goals for ECMAScript 2015 include providing better support for large applications, library creation, and for use of ECMAScript as a compilation target for other languages. Some of its major enhancements include modules, class declarations, lexical block scoping, iterators and generators, promises for asynchronous programming, destructuring patterns, and proper tail calls.

                                                      — https://www.ecma-international.org/ecma-262/6.0/


                                                      Если что, PTC (Proper Tail Call) — альтернативное название TCO, используемое только екмокомитетом. Предполагаю потому, что JS весь весьма альтернативный.

                                                        +1

                                                        … и это единственное место, где tail call в документе упоминается. Нормативным раздел Introduction не является.

                                                          –1
                                                          Some of its major enhancements include [...].

                                                          Нормативным раздел Introduction не является.

                                                          А, так можно было, да?


                                                          Что не отменяет, кстати, постулата «TCO — часть спецификации языка».

                                                            0
                                                            A tail position call must either release any transient internal resources associated with the currently executing function execution context before invoking the target function or reuse those resources in support of the target function.

                                                            И прочие окрестности 14.6

                                                              0

                                                              О, спасибо. Почему-то я проглядел этот пункт...

                                            +4

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


                                            например такой паттерн:


                                            void foo() {
                                                 if (!init_resource1())
                                                     goto DEINIT1;
                                                 if (!init_resource2())
                                                     goto DEINIT2;
                                                 if (!init_resource3())
                                                     goto DEINIT3;
                                            
                                                 // тут код работающий с ресурсами 1, 2, 3
                                            
                                                 deinit_resource3();
                                                 DEINIT3: deinit_resource2()
                                                 DEINIT2: deinit_resource1();
                                                 DEINIT1:
                                            }
                                            
                                              +2
                                              вполне структурированный код на java или C# юзает try-with-resources (java) или using (c#)
                                              void foo() {
                                                  try (Res1 res1 = init_resource1())
                                                  {
                                                      try (Res2 res2 = init_resource2())
                                                      {
                                                          try (Res3 res3 = init_resource3())
                                                          {
                                                              // тут код работающий с ресурсами 1, 2, 3
                                                          }
                                                      }
                                                  }
                                              }


                                              что этот код не читаемый чтоли? или не лаконичный? уж полаконичней вашего исходного

                                              другое дело что современная реализация виртуальной машины или транслятора — могут сделать не очень оптимально, ну так это вопрос оптимальности этой машины

                                              т.е. по факту ваш аргумент — потому что в Линуксе сделано так, и там оптимально, а он такой авторитетный этот Линукс, что надо по нему все мерять… не все пишут Линукс, и не потому что дураки или недорасли, а потому что у многих задачи другие (не по-проще, а другие!)
                                                0

                                                Вы сперва допишите с деинитом, потом сравните :)


                                                ваш аргумент — потому что в Линуксе сделано так

                                                мой аргумент не такой.
                                                я говорил что goto очень во многих приложениях очень уместен и повышает читабельность.
                                                А дальше я привёл пример хорошего кода с goto — в ядре Linux: стековый init/deinit.


                                                а ещё я в своей статье про написание ботов приводил другой пример хорошего кода с goto: написание сложных чат-ботов в парадигме "храним состояние чата на стеке".

                                                  +1
                                                  я подразумевал деструктор, и порядок деструкторов при желании сможет обеспечить порядок деинициализации
                                                  но да это не про перфоманс в текущей имплементации деструкторов или исключений (если вдруг будет оно — в try — все равно деинициализация будет)
                                                    –4

                                                    ну вот когда напишете код с деструкторами и объектами, то увидите, что этот код стал намного сложнее вышеприведённого.


                                                    плюс Вы втащили в код ненужный ООП.


                                                    а далее — ещё надо прогарантировать порядок вызова деструкторов. что само по себе тоже не тривиальная задача

                                                      +1
                                                      если в деструкторе своя логика деструкции (что тоже плюс — логика скрыта, это не усложнение, это инкапсуляция), и такие объекты постоянно создаются и уничтожаются *в разных местах кода*, вам не придется оборачивать это в одинаковые паттерны в разных местах кода
                                                      !init_resource1())
                                                               goto DEINIT1;
                                                      // work
                                                           DEINIT3: deinit_resource2()

                                                      и от такого копипаста вы избавитесь, потому что паттерн вот таких вот goto будет скрыт в высокоуровневой конструкции
                                                      try (Res1 res1 = new Res1())
                                                      {
                                                      }

                                                      а порядок деструкторов, *при желании* в подходящем языке есть

                                                      более того, если объекты зависимые, то при конструировании 2го объекта — можно передавать ему обязанность по уничтожении и вложенного
                                                      т.е.
                                                      try (Res1 res1 = new Res1())
                                                      {
                                                        try (Res2 res2 = new Res2(res1))
                                                        {
                                                          // work res2
                                                        }
                                                      }
                                                      

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

                                                      в cpp на сколько я знаю тоже есть owner эксклюзивный, и передаются права на уничтожение — что сделано для упрощения и избежания от багов
                                                        –1
                                                        если в деструкторе своя логика деструкции (что тоже плюс — логика скрыта, это не усложнение, это инкапсуляция),

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


                                                        Причём функции deinit — это в общем случае — функции какого-то API, то есть написаны не нами. Перенесли их внутрь объектов — но не избавились от необходимости их вызова.


                                                        и это получится не инкапсуляция, а именно усложнение.


                                                        более того, если объекты зависимые, то при конструировании 2го объекта — можно передавать ему обязанность по уничтожении и вложенного

                                                        они условно взаимно зависимые


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

                                                          0
                                                          если Вы напишете всю логику (включая деинит) Вы получите что Ваш код суммарно занимает раза в три больше чем мой.

                                                          Только если логика в deinit индивидуальна для каждого случая (но и тут помогут лямбды).

                                                            0

                                                            ну вот напишите

                                                              0

                                                              Да не вопрос:


                                                              try (Res1 res1 = Res1.Create(deinit_resource1))
                                                              {
                                                                try (Res2 res2 = Res2.Create(deinit_resource2))
                                                                {
                                                                  // work 
                                                                }
                                                              }
                                                            0
                                                            выделяем три блока памяти (програмируем же ядро)
                                                            один за другим.
                                                            удаляя их в обратном порядке — гарантируем что энтропия нас не беспокоит. Проблемы с фрагметнацией минимизированы (пример с памятью — синтетический, но в ядре много подобных оптимизаций)

                                                            определитесь всетаки — зависимые или нет
                                                            1) в данном случае 3 блока памяти никак не зависимы, и удаляя один блок, у вас остаются другие 2, а значит тут порядок выделения блоков и освобождения — не важен
                                                            2) в случае важного порядка (уже не блоки памяти) — деструкторы, и\или вообще передача owner права и удаление всего из одного главного объекта (выше я писал)

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

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

                                                            Проблемы с фрагметнацией минимизированы (пример с памятью — синтетический, но в ядре много подобных оптимизаций)

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

                                                              я же пример привел: независимых, но тех что всё равно лучше возвращать системе в обратном порядке

                                                                0
                                                                программа понимается проще\сложней не от кол-ва кода

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

                                                                  0
                                                                  если Вы напишете всю логику (включая деинит) Вы получите что Ваш код суммарно занимает раза в три больше чем мой.

                                                                  ок давайте просуммируем
                                                                  try (Res1 res1 = Res1.Create(deinit_resource1))
                                                                  {
                                                                    try (Res2 res2 = Res2.Create(deinit_resource2))
                                                                    {
                                                                        try (Res3 res3 = Res3.Create(deinit_resource3))
                                                                        {
                                                                      // work 
                                                                        }
                                                                    }
                                                                  }

                                                                  вариант с лямбдами = 10 строк основной блок (work = 1 строка)

                                                                  плюс системное:
                                                                  если это java, то try как try-with-resources кол-ва кода сокращает
                                                                  можно 1 раз написать Res (вместо 3 классов Res1, Res2, Res3, каждый просто со своим деструктором например, или если нужны private свойства то отнаследовать, или нет — не суть, всеравно кода не будет больше, будут просто скобки а они не код а структура)
                                                                  class Res implements AutoCloseable {
                                                                  private Runnable destructor;
                                                                  public Res(Runnable destructor) {this.destructor = destructor;}
                                                                      public void close() throws Exception {
                                                                          destructor.run();
                                                                      }
                                                                  }

                                                                  7 класс + 10 основной = 17 в случае лямбды (не 3 класса, а 1 достаточен тут)

                                                                  считаем что без лямбд, каждый класс такой
                                                                  class Res1 implements AutoCloseable {
                                                                      public void close() throws Exception {
                                                                          // deinit_res1() или inline-код
                                                                      }
                                                                  }


                                                                  ну скажем 5 строк на класс лишних, причем куча скобов (они не усложняют)
                                                                  на 3 класса будет 5*3 = 15
                                                                  +10 основное
                                                                  суммарно 25

                                                                  а если сравнивать не с лямбдой, а с деструктором
                                                                  и на C++ например
                                                                  class Res1{
                                                                     public:
                                                                        Res1() {}
                                                                        ~Res1() { /** deinit_res1() или inline-код */}
                                                                  };
                                                                  

                                                                  ну те же 5 строк на класс
                                                                  и те же 25 строк суммарно

                                                                  vs ваш вариант = 11 строк
                                                                  if (!init_resource1())
                                                                           goto DEINIT1;
                                                                       if (!init_resource2())
                                                                           goto DEINIT2;
                                                                       if (!init_resource3())
                                                                           goto DEINIT3;
                                                                       // тут код работающий с ресурсами 1, 2, 3
                                                                       deinit_resource3();
                                                                       DEINIT3: deinit_resource2()
                                                                       DEINIT2: deinit_resource1();
                                                                       DEINIT1:


                                                                  причем у вас хитро метки стоят, а у нас лишние { и } скобочки, но пусть так

                                                                  в крайнем случае в 2 раза больше кода, а не в 3

                                                                  хотя и это можно написать по-другому
                                                                  и скобки за усложнения не считаются в случае ООП

                                                                  и в случае СИ и GOTO можно пробелов понаставить и их тоже считать, и метки вот так сделать

                                                                  if (!init_resource1()) {
                                                                           goto DEINIT1;
                                                                  }
                                                                       if (!init_resource2()) {
                                                                           goto DEINIT2;
                                                                  }
                                                                       if (!init_resource3()) {
                                                                           goto DEINIT3;
                                                                  }
                                                                  
                                                                       // тут код работающий с ресурсами 1, 2, 3
                                                                  
                                                                       deinit_resource3();
                                                                  DEINIT3:
                                                                       deinit_resource2()
                                                                  DEINIT2:
                                                                       deinit_resource1();
                                                                  DEINIT1:


                                                                  ваш код станет 18 строк, он сложней стал? или проще? 11 vs 18, но тут просто по-другому оформлено

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

                                                                  так что тоже не в 3 раза, а в самом крайнем случае плюс проценты, ну на системные структуры
                                                                  … а когда кода work в 10-100 раз больше чем 1 строка, то системные try \ деструкторы \ или goto или все такое — не заметны будут в общей массе кода, и кода будет примерно столько же \ на проценты больше в случае ООП, и то не всегда

                                                                  а теперь если мы развеяли миф что кода в 3 раза больше у нас, чем у вас с goto, получается что и восприятие нашего — проще — потому что более структурировано — есть нормальные try и деструкторы
                                                                    0
                                                                    ок давайте просуммируем

                                                                    давайте Вы корректно просуммируете?


                                                                    код и объявление классов Res1, Res2, Res3 методы которых Вы вызываете — тоже надо сюда суммировать.

                                                                      0
                                                                      я отредактировал комментарий, теперь все что вам хотелось там есть?
                                                                      (больше редактировать не могу, если вам не понравился синтаксис или гдето есть мелкая ошибка — суть от этого не поменяется существенно)

                                                                      а в случае лямбд код будет такой
                                                                      (чтоб не придирались, тут 3 раза 1 и тот же класс с параметром — что вызывать для получения ресурса, и что в деструкторе)
                                                                      try (Res res1 = new Res(init_res1, deinit_resource1))
                                                                      {
                                                                        try (Res res2 = new Res(init_res2, deinit_resource2))
                                                                        {
                                                                            try (Res res3 = new Res(init_res3, deinit_resource3))
                                                                            {
                                                                          // work 
                                                                            }
                                                                        }
                                                                      }
                                                                      


                                                                      и если у вас задача — блоки памяти выделять
                                                                      можно 1 класс сделать и передавать туда просто размер блока
                                                                      или один общий менеджер, с методом allocate(size) как malloc только со своим пулом например

                                                                      т.е. вообще более ООП-шно даже с памятью
                                                                      class Res implements AutoCloseable {
                                                                      private int startOfBlock;
                                                                      private int sizeOfBlock;
                                                                      private Manager memoryManager;
                                                                      public Res(int startOfBlock, int sizeOfBlock, Manager memoryManager) {
                                                                      this.startOfBlock = startOfBlock;
                                                                      this.sizeOfBlock = sizeOfBlock;
                                                                      this.memoryManager = memoryManager;
                                                                      }
                                                                          public void close() throws Exception {
                                                                              memoryManager.free(startOfBlock, sizeOfBlock);
                                                                          }
                                                                      }
                                                                      
                                                                      class Manager
                                                                      {
                                                                      private List<byte[]> blocks;
                                                                      // выделить память
                                                                      Res allocate(int size) {
                                                                      // пересчитать start
                                                                      return new Res(start, size, this);
                                                                      };
                                                                      
                                                                      void free() { /* вернуть память */ }
                                                                      }
                                                                      
                                                                      try (Res res1 = Manager.allocate(100))
                                                                      {
                                                                        try (Res res2 = Manager.allocate(200))
                                                                        {
                                                                            try (Res res3 = Manager.allocate(300))
                                                                            {
                                                                          // work 
                                                                            }
                                                                        }
                                                                      }
                                                                      

                                                                      да тут кода больше чем в 1м случае — но структура тут еще более понятная и более поддерживаемая и переиспользовать теперь этот менеджер можно в куче мест, а не копипастит эти init_resource1(), deinit_resource1() тыщу раз

                                                                      пусть даже 38 строк кода, плюс еще что комментом обозначено не заполнено

                                                                      зато у вас — не написана начинка init_resource1(), deinit_resource1()
                                                                      а у меня почти написана
                                                                      и если вы ее бы выложили — получилось бы что у вас и там простыни, которые вы скрывали
                                                                      так что суммарно еще посмотреть надо — где более читабельно и меньше копипаста будет
                                                                        –1

                                                                        Ваш пример теперь


                                                                        1. больше по объему кода (ок не в три раза, в два)
                                                                        2. включает не нужное для данной задачи ООП
                                                                        3. может дать сбой в порядке вызова деструкторов (в зависимости от кода использующего ресурсы 1,2,3). Сбой можно устранить внедрением четвертого объекта.
                                                                        4. значительно менее читабельный

                                                                        Каждый раз когда я вижу слово сlass в коде применённый не по делу на меня нападают тоска и уныние...


                                                                        Статью чтоли тиснуть на тему "избыточный ООП — зло!" ?

                                                                          0
                                                                          может дать сбой в порядке вызова деструкторов (в зависимости от кода использующего ресурсы 1,2,3).

                                                                          Каким образом?

                                                                            –1

                                                                            обсудили же выше

                                                                              0

                                                                              Что-то я так и не увидел от вас контрпримера.

                                                                                –1

                                                                                контрпример написали же Вы.


                                                                                т.е. вообще более ООП-шно даже с памятью

                                                                                код после этих строк у меня не влазит в экран.
                                                                                мой — влазит в половину.


                                                                                зачем там аж целых ДВА класса — я тупо понять не могу.
                                                                                то есть если придётся сопровождать такой код почему-то — разберусь. Сейчас — не могу

                                                                                  0
                                                                                  понятно, malloc+free вы тоже не видели? он тоже не влязит в экран, и исходники более-менее серьезной программы вы в жизни не писали, судя по заявлениям о кол-ве строк и экранов, досвидания :)

                                                                                  мой — влазит в половину.

                                                                                  да вашего кода там 0 — он НЕ ваш!
                                                                                  и даже не удосужились посмотреть сколько экранов занимает init_res и deinit_res — и не приложили их сюда! там наверняка больше в разы! :)
                                                                                    0
                                                                                    понятно, malloc+free вы тоже не видели? он тоже не влязит в экран

                                                                                    под malloc/free мой паттерн очень даже заточен.
                                                                                    он же взят из кода ядра, а там каждый модуль делает постоянно эти самые malloc/free (условные malloc/free, понятно что там они по другому зовутся).


                                                                                    кстати ещё хороший паттерн для goto — обработчик со стейтмашиной. Переходы между стейтами красиво оформляются как раз goto.


                                                                                    и ещё хороший паттерн для goto — программирование чата в парадигме:


                                                                                    user_message = await_user_message()
                                                                                    if (что-то там(user_message))
                                                                                        send_message('hello')
                                                                                    user_message = await_user_message()

                                                                                    сложные диалоги с ветками тут тоже красиво на goto раскладываются и тоже OOП не уместен

                                                                                      0
                                                                                      под malloc/free мой паттерн очень даже заточен.
                                                                                      он же взят из кода ядра, а там каждый модуль делает постоянно эти самые malloc/free

                                                                                      вы НЕ написали malloc/free, но используете чужое готовое, и заявляете что ваш код меньше занимает, при том что НЕ считаете кол-во чужого кода, это ваша логика
                                                                                      а я написал ВСЕ включая malloc manager (упрощенный, да), и вы заявили что кода больше, и он в 1 экран не влезает
                                                                                      эпик
                                                                                        0

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


                                                                                        я показал что оператор goto выпиливать "любыми средствами" — плохо. Разработчики таких вещей как стейтмашины или ядра операционных систем — со мной солидарны (это видно по коду который они пишут).


                                                                                        Если Вы считаете что это очень плохо — ну чтож. пробуйте писать без этих технологий.

                                                                                        0
                                                                                        кстати ещё хороший паттерн для goto — обработчик со стейтмашиной. Переходы между стейтами красиво оформляются как раз goto.

                                                                                        Странно, я думал, через разбор сумм-типа это выглядит более красиво.

                                                                                          0

                                                                                          Это разные красоты.


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


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

                                                                                            –1

                                                                                            ООП стейтмашины перед switch стейтмашинами имеют множество недостатков, главный из них — как раз плохая читаемость

                                                                                              0

                                                                                              Может, вы ещё и сгенерированный ragel'ом код читаете и понимаете?

                                                                                                0

                                                                                                заглядывал в сгенерированное рагелем
                                                                                                и писал подобное да.


                                                                                                это способ — написать самый быстрый и самый читабельный парсер.

                                                                                                  0

                                                                                                  Я таки предпочитаю кодогенерировать. Ну, то есть, не писать за рагел то, что рагел может сам написать.

                                                                                                    0

                                                                                                    я в целом тоже


                                                                                                    но если стейтмашина ожидается с множеством именно последовательных переходов и граф имеет мало ветвлений, то написать руками — получается существенно лучше чем у рагеля и даже читаемость выше

                                                                                0
                                                                                значительно менее
                                                                                читабельный

                                                                                Каждый раз когда я вижу слово сlass в коде применённый не по делу на меня нападают тоска и уныние...

                                                                                научитесь читать для начала классы, просто вы не умеете это делать, очевидно

                                                                                приведите хоть 1 пример системы, которую вы лично написали, без ООП, ну скажем на СИ, и не пример чужих кодов с goto, а потом мы посмотрим у кого компетенция выше, и кто тут лишнего или не лишнего применяет ООП, пожалуйста, иначе выглядит то что вы якобы профи, дошли что ООП тут хуже, а тут не хуже, с чего вы решили что вам решать, решать мне — и в моем классе все читаемо для меня и для многих других профи, я все сказал

                                                                                я даже очень сомневаюсь что ваш Manager (смотрите мой ООП пример) будет меньше строк занимать, зуб даю, что на СИ у вас будет больше чем malloc (или вы считаете что если вы юзаете malloc или любую библиотечную функцию — она магией работает и ее код не надо считать? ну давайте ее посчитаем тоже и строки сравним? и вообще кто по строкам судит, давайте по сущностям и цикломатической сложности считать, и интерфейсы соблюдать — тогда будет все понятно — где сложно а где просто)
                                                                                  –1
                                                                                  научитесь читать для начала классы, просто вы не умеете это делать, очевидно

                                                                                  учусь этому с 1996 года.


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


                                                                                  Ваш пример — из того же разряда.


                                                                                  ставишь задачу перед программистом: надо 2 умножить на 2 и напечатать результат.


                                                                                  Уходишь от него на неделю. Возвращаешься:


                                                                                  - задачу сделал? 
                                                                                  - нет
                                                                                  - какие-то проблемы?
                                                                                  - проблем нет, просто пока только фабрику классов написал, а к самой задаче только подбираться начал
                                                                                  - зачем тут фабрика классов?
                                                                                  <смотрит с непониманием>
                                                                                    0
                                                                                    Ваш пример — из того же разряда.

                                                                                    ставишь задачу перед программистом: надо 2 умножить на 2 и напечатать результат.

                                                                                    ха-ха выделение ресурсов, сравнимое с malloc, это у нас оказывается 2х2, ну удачи написать
                                                                                      –1

                                                                                      выделение ресурсов — это простая задача. сравнимая с 2 умножить на 2


                                                                                      что не так-то?

                                                                                        0
                                                                                        так заявить может только тот, кто использует чужой код этого malloc и выделения ресурсов

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

                                                                                        не напишете, а если и напишете — кода там будет дофига, ну посчитайте его, и скажите сколько тыщ строк занимает ваша программа
                                                                                        и сравните с моим менеджером (да он не полный, но это хотябы интерфейс, у вас же ничего! чисто чужое)
                                                                  +2
                                                                  а далее — ещё надо прогарантировать порядок вызова деструкторов. что само по себе тоже не тривиальная задача

                                                                  А что там нетривиального? При выходе из функции деструкторы будут вызваны в порядке, противоположном тому, в котором объекты создавались. Ничего специально для этого делать не надо, только код в деструкторе прописать ровно один раз на всё приложение.

                                                                    –1
                                                                    А что там нетривиального? При выходе из функции деструкторы будут вызваны в порядке, противоположном тому, в котором объекты создавались.

                                                                    зависит от многих факторов. не только от порядка.

                                                                      +1

                                                                      Вы, конечно же, сможете назвать ещё хотя бы 1 фактор?

                                                                        0

                                                                        да, вот Вам несколько


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

                                                                        например


                                                                        мы инстанцировали A, затем B, затем C


                                                                        затем создали еще 22 объекта имеющих связь с A и B
                                                                        предсказать в каком порядке удалятся A, B и C — может оказаться непросто

                                                                          +1
                                                                          мы инстанцировали A, затем B, затем C

                                                                          затем создали еще 22 объекта имеющих связь с A и B
                                                                          предсказать в каком порядке удалятся A, B и C — может оказаться непросто

                                                                          если предсказать сложно — надо сделать чтобы система делала все надежно за вас, используя
                                                                          — более подходящие структуры и понятие owner (shared_ptr, unique_ptr)
                                                                          — сборку мусора
                                                                          — другие алгоритмы
                                                                            0

                                                                            ну вот в примере это и сделано
                                                                            изящно, просто и с goto

                                                                              0
                                                                              во 1х в примере выше нет 22 объектов, и вы сравниваете несравнимое, вот покажите нам портянку где эти 22 объекта будут, и циклические зависимости (вы далее пишете), и мы покажем как это срефакторить на C++ \ Java \ C# с применением современных техник, без goto, с меньшим кол-во кода

                                                                              сейчас получается что кода одинаково (деструктор занимает 3 строчки макс, а в случае лямбды — у нас кода еще меньше раза в 2 чем у вас) в нашем и вашем случае с 3мя объектами A, B, C
                                                                                –1
                                                                                во 1х в примере выше нет 22 объектов

                                                                                мы вроде бы обсуждали подход к решению такого класса задач?


                                                                                22 объекта могут быть в области "тут код использующий ресурсы"


                                                                                и мы покажем как это срефакторить на C++ \ Java \ C# с применением современных техник, без goto, с меньшим кол-во кода

                                                                                начинали с Java. когда наткнулись на то что в Java есть кейзы в которых деструкторы не отработают — вставили в текст С++.


                                                                                я так понимаю Вы дальше желали бы именно С++ обсуждать? Поскольку Java как пример больше не подходит?

                                                                            0

                                                                            А каким образом эти 22 объекта повлияют на порядок уничтожения A, B и С?

                                                                              0

                                                                              если кто-то из них содержит циклическую зависимость (например!) то уничтожение цикла может быть перенесено на более позднее время, а ссылка на один из наших объектов перенесёт на более позднее время и его.

                                                                                0
                                                                                с goto и эти 22 объекта — у вас нет примера, и написать его даже если напишете — там будет лапша
                                                                                  0

                                                                                  Вы же предложили обобщенный формализованный способ решения этого класса задач? или уже нет?

                                                                                  0

                                                                                  Какая нафиг зависимость, о чем вы?


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


                                                                                  Во-вторых, откуда в объектах A, B и С возьмется ссылка на 22 более поздних объекта, если те на момент создания A, B и С не существовали?

                                                                                    –1
                                                                                    Во-первых, вы так и не рассказали каким образом зависимости объектов могут задержать деструктор.

                                                                                    Ваш деструктор вызывает кто? гарбадж коллектор жавы
                                                                                    а он в общем виде многопроходный


                                                                                    Во-вторых, откуда в объектах A, B и С возьмется ссылка на 22

                                                                                    наоборот — в 22 будут ссылки на А В С

                                                                                      0

                                                                                      В данном контексте "деструктор" следует читать как метод close, который вызывается конструкцией try-with-resource:


                                                                                      try (Res1 res1 = new Res1()) {
                                                                                        // ...
                                                                                      } // сюда компилятор добавит вызов res1.close()

                                                                                      И никакие 22 объекта никак этот вызов отменить не сумеют, если только не прибьют поток либо процесс средствами ОС или не устроят другое UB.


                                                                                      А финализатор, вызываемый сборщиком мусора — это просто подстраховка.

                                                                0

                                                                Можно переписать на языке с RAII (да хоть том же C++) и будет ещё проще.

                                                                  –1

                                                                  попробуйте сделать проще на С++


                                                                  порядок деинициализации ресурсов важен: часто это зависимые ресурсы

                                                              +5
                                                              Если уважаемый заглянет в код ядра linux, особенно в быстрые секции, например, драйверов, то ужаснётся обилию goto. Его вовсю используют, просто умело.
                                                                0
                                                                А в си goto используется внутри процедур? или можно делать goto по метке находящейся в другой процедуре/модуле? (именно это и есть тот самый ужасный паттерн который и повлек за собой такой страх этого оператора)
                                                                p.s. я си не знаю
                                                                  +2
                                                                  Если подумать логически, то простой goto так делать не должен уметь в контексте С-шных функций.
                                                                  Что не отменяет возможностей стрелять себе в ногу функциями setjmp() и longjmp().
                                                                    +2
                                                                    Что не отменяет возможностей стрелять себе в ногу функциями setjmp() и longjmp()

                                                                    ну фактически это и есть тот самый goto которого все боятся и ненавидят

                                                                    тем не менее про goto такие статьи появляются, а про longjmp нет.
                                                                      0
                                                                      setjump/lognjump — это скорее про continuation, а их со времен лиспа принято называть трудными и сложными, а не нежелательными)))
                                                                    0
                                                                    Я выше привел пример такого, но повторюсь
                                                                    void * ptr;
                                                                    a() {
                                                                     ptr = &&a;
                                                                     a:;
                                                                    }
                                                                    
                                                                    b() {
                                                                      goto *ptr;
                                                                    }
                                                                    
                                                                    
                                                                      0
                                                                      В условиях локальных меток (снова, со времен ассемблера) компилятор скажет аяяй. Если метки не локальные и видны из других функций, тут снова очко не в пользу программиста, если он не понимает что делает, это не проблема языка.

                                                                      +2
                                                                      Ну пока есть инструкции процессора условного-безусловного перехода по адресу, высокоуровневые обертки к ним никуда не денутся.
                                                                        +2
                                                                        Еще в разработке для встраиваемых устройств иногда идет битва за каждую лишнюю инструкцию или место в стеке. И иногда goto остается единственным решением, когда и код красив и понятен, и работает оптимально.
                                                                        0
                                                                        А вас switch в Java не смущает? Чисто с точки зрения подхода к правильности.
                                                                          +1

                                                                          А switch то вам чем не угодил?

                                                                          +1
                                                                          Вы предлагаете заменить плохие решения (кстати не такие уж и плохие), на худшие, и даже не принимаете во внимание, что можно-таки сделать по-человечески без goto.
                                                                            0

                                                                            В статье goto упомянут 7 раз, и лишь один из них — в коде (да и то в комментарии). Можете объяснить смысл претензии?

                                                                              0
                                                                              и лишь один из них — в коде

                                                                              ну так следуя описываемой страшилке, разработчики языков боятся называть этот оператор — goto чтобы недайбог не вызвать холивар что 'его запрещено использовать'
                                                                              при том его, правда зачастую урезанный и относительно безопасный вариант этого оператора, есть во многих языках
                                                                            +7

                                                                            Лихо вы обозвали исключения goto. Так ведь можно и любую event-based систему заклеймить.

                                                                              +3
                                                                              Насколько я понимаю, goto названы не исключения, а конкретный вариант использования исключений для того, чтобы делать на нём бизнес-логику.
                                                                                –1
                                                                                отчасти это то что вы имели ввиду, т.е. если использовать исключение внутри одного метода (т.е. бросать и ловить в нем) то это тоже самое что goto, только другой инструкцией

                                                                                но практически, если расширять проблему, исключения лишают структурности во многих случаях, т.е. вот у вас не ясно где кинется исключение и не ясно где поймается, и отследить все эти варианты в том же C# (unchecked exception) или Java (RuntimeException как вар unchecked) — сложно для понимания, т.е. структура программы рушится, да это удобно иногда, но иногда — это тот же самый goto, типо обойти исполнение множества методов и прыгнуть в конец к освобождению ресурсов и выходу из программы
                                                                              0
                                                                              Когда-то давным давно, когда Windows был еще 16-битным, в составе Windows SDK компания Microsoft поставляла файлик с исходником функции DefWindowProc. Без тех функций, которые она вызывала и может быть даже исходники были какие-то порезанные, но понять, в первом приближении как обрабатываются разные сообщения было можно.
                                                                              Так вот. В тексте этой процедуры была строчка:
                                                                              goto ICantImagineIveUsedGotoStatement;
                                                                              
                                                                              и соответствующая метка чуть ниже.

                                                                              К чему это я? Да к тому, что программирование достаточно сложная область, чтобы «простые правила» были применимы безусловно, всегда и во всем. Конечно, использование исключений для реализации бизнес-логики — это порочная практика и в данном случае автор стопроцентно прав. Но опыт показывает, что время от времени происходит нечто такое, что делает вполне оправданным нарушение самых «базовых» правил. Типа исключений в бизнес-логике, или даже использования оператора goto.
                                                                              Зачастую просто не имеет смысла переписывать здоровенный кусок нормально работающего кода ради того, чтобы обойтись без использования goto в угоду абстрактной «чистоте».
                                                                              Согласен с DMGarikk. Языки, в которых оператор goto создавал проблемы, давно уже не используются, а то, что в современных языках этот оператор все-таки присутствует, говорит, скорее, о том, что не так уж все с ним страшно.
                                                                                0
                                                                                Зачастую просто не имеет смысла переписывать здоровенный кусок нормально работающего кода ради того, чтобы обойтись без использования goto в угоду абстрактной «чистоте».

                                                                                скорее сводится к такому «зачастую рефакторить нет смысла, даже если код не идеальный»
                                                                                (избавление от goto входит в понятие «рефакторить», также как другие костыли)

                                                                                и да вы правы, но это не говорит о том что рефакторить не надо никогда, и уж темболее что если код рабочий — его не надо трогать
                                                                                если есть время и код будет многократно перечитываться — его очень желательно рефакторить
                                                                                +5
                                                                                Без GOTO невозможно эффективно реализовать VM для байт-кода. GOTO позволяет избавиться от «флаговых» переменных, единственная задача которых – совершить правильное ветвление после цикла. GOTO – более универсальный и выразительный инструмент, чем while/for/break/continue. Так что говорить что GOTO это однозначное зло как минимум странно.
                                                                                  –1
                                                                                  GOTO – более универсальный

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

                                                                                  чтобы не быть голословным, напишу только что ваш мозг постоянно отыскивает контексты, и если вы с кем-то разговариваете — вы без контекста не понимаете человека (или понимаете не так), даже на родном языке, и только после определенного кол-ва «прелюдии», происходит бац — контекст был найден, вы можете чтото переосмыслить, теперь у вас новые ассоциации — о чем говорил собеседник, о чем вы говорили, какоыва вообще проблема (а до этого он может даже не понять «вы о чем?»), можно сузить варианты обсуждения и пойти по более продуктивному пути диалога, а если это переводной язык — так там в зависимости от контекста меняется перевод слова — тоже не просто так

                                                                                  вот тоже самое с while
                                                                                  зачем усложнять себе жизнь и пихать туда goto, и потом отыскивать контекст
                                                                                  программист уже обозначил контекст — вот он while, вот у него круглые скобочки его границ, вон в нем условие выхода, и\или дополнительные — break \ continue (которые усложняют понимание! если они есть)

                                                                                  избавиться от «флаговых» переменных, единственная задача которых – совершить правильное ветвление после цикла.

                                                                                  избавляться от них надо разбиением на методы более правильно, и ветвлением после метода, а не ветвлением после цикла

                                                                                  Без GOTO невозможно эффективно реализовать VM для байт-кода.

                                                                                  большинство программистов не реализуют VM для байт-кода, значит для остальных ваш аргумент не действителен… они пишут структурированные программы с if\while\for и методами и классами с ООП (либо ктото в ФП)
                                                                                  +5
                                                                                  воскреснуть в XXI веке под видом исключений

                                                                                  Software exception handling developed in Lisp in the 1960s and 1970s. This originated in LISP 1.5 (1962).  — https://en.wikipedia.org/wiki/Exception_handling#History

                                                                                  Мда.

                                                                                    +1
                                                                                    А кто что считает по поводу того корректного применения onError в RxJava?
                                                                                    Лагерь сейчас разделился: кто-то говорит, что бизнес ошибки стоит засовывать в onError, а кто-то решает использовать врапперы типа Result(T data, Exception e).
                                                                                      +2
                                                                                      Автор, имхо, смешал в своем примере две проблемы в одну.

                                                                                      Первая проблема — использование эксепшена для выхода из цикла, которую автор назвал «большой блок логики в catch FileNotFoundException» — это действительно боль, и решение, приведенное автором, вполне разумно и логично.

                                                                                      Вторая проблема — наличие параметра boolean throwIfNotExists и тот факт, что метод readPropertiesFromFile() кидает эксепшн — вообще не является проблемой. Из сигнатуры явно следует, что метод может кинуть ексепшн, и пользователю метода предоставляется выбор, кидать эксепшн или не кидать. Проблемой, однако, является тот факт, что метод кидает RuntimeException в ситуации, когда файла нет — эта ситуация кажется относительно штатной, и метод должен кидать что-то более внятное. Причем метод кидает RuntimeException как в первоначальном примере, так и в исправленном.

                                                                                      Имхо, наилучшим решением было бы переписать метод как
                                                                                      private Properties readPropertiesFromFile(String filepath) throws IOException

                                                                                      Дайте клиенту возможность самому решать, что делать, если попытка чтения файла завершилось ошибкой!
                                                                                        –2
                                                                                        Проблемы goto нет и никогда не было. Проблема эта надуманная, так как всегда всё сводится к командам процессора, а одна из них — прямой переход по адресу, то есть goto! Ну и понятно, почему выигрывают языки, в которых (как в С) это явно реализовано…

                                                                                        Но статья то явно не о том…
                                                                                          –1
                                                                                          а давайте всем объектам давать тип 64-битное целое (sint64, со знаком), у нас же все сводится к современным 64-битным процессорам, и все int и boolean и float будет сами в них кодировать, и универсально же, и как удобно

                                                                                          смысл как раз в ограничениях (лимитах) и контексте
                                                                                          когда вы видите int64 и в одном случае в ней кодируется boolean, в другом целочисленный счетчик, а в третьем вещественное число — это затрудняет понимание

                                                                                          а если назначить правильно типы — понимание упрощается

                                                                                          ======

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

                                                                                          ======

                                                                                          еще можно все переменные на СИ называть i и выдавать тип int
                                                                                          int i1
                                                                                          int i2

                                                                                          int i99

                                                                                          прям обфускатор получается :)

                                                                                          универсально же, удобно же, нафиг осмысленные имена?

                                                                                          ======

                                                                                          аналогично про goto: если в однмо случае goto используется для перехода от 1 функции к другой, в другом случае этот же самый goto но в другом контексте — для выхода из цикла, в 3м случае им эмулируется if, и т.п. — как быстро вы сообразите читая программу, увидев конркетный goto — что он делает? без перечитывания кучи других строк кода?

                                                                                          дольше чем если структурировано

                                                                                          поэтому для одного — одно, для другого — другое
                                                                                          а не смешивать

                                                                                          Ну и понятно, почему выигрывают языки

                                                                                          что там у кого выигрывает? по читабельности выигрывает? не смешите

                                                                                          … а если вы про перфоманс — пруфы можно?

                                                                                          но даже если и так (гдето по перфомансу Си выиграет чем С++ без goto, или вообще на языке go) — читабельность будет явно хуже, и багов будет завались
                                                                                          0
                                                                                          Ну так-то стандартные return, break и т.п. тоже есть безусловные переходы и эквивалентны goto по смыслу. В коде их использование повсюду. Более того, даже существуют code style, навязывающие безусловные переходы.
                                                                                            0
                                                                                            они не есть goto, т.е. у return \ break \ if \ while и т.п. более очерчены лимиты и меньше универсальности, я товарищу выше отвечал
                                                                                            habr.com/ru/post/484840/#comment_21227246
                                                                                            +1
                                                                                            В PHP есть break и continue, но не все знают что у них есть параметр, определяющий на какой из уровней выходить
                                                                                            Такой вот мутировавший GOTO
                                                                                              0
                                                                                              И не только в php
                                                                                              И слава богу что не все знают…
                                                                                                0

                                                                                                Это не учитвая, что сам goto добавили не так давно.

                                                                                                +1
                                                                                                Исключения для управления потоком выполнения нехороши только потому, что приводят к неопределённым затратам времени на разворачивание стека. То есть в С++ и Java. В Python, например, никакого разворачивания стека не происходит, поэтому исключения стоят столько же, сколько вызовы − пренебрежимо мало в большинстве ситуаций. Поэтому управление потоком выполнения через исключения широко используется в Python, даже в системных библиотеках.

                                                                                                Я, кстати, не в первый раз замечаю, что плюсовики и джависты обобщают свой опыт на все ЯП, включая те, для которых он нерелевантен. Интересно, почему это?
                                                                                                  0

                                                                                                  Обработка исключений в python сопоставима с другими языками — бросить почти ни чего не стоит, а перехват обходится дорого. Есть буквально несколько оптимизаций для частных случаев (вроде StopIteration), которые в общем случае погоды не делают. В Java можно отключить захват стека при создании объекта-исключения, что увеличивает перформанс в разы. Но использовать исключения для control flow все равно не стоит.

                                                                                                    0
                                                                                                    бросить почти ни чего не стоит, а перехват обходится дорого.

                                                                                                    Во-первых, перехват эксепшна − это никаким боком не дорого в Python. Да, я читал документацию. Тем не менее, технически перехват эксепшна сводится к поиску в некоем фиксированном количестве словарей. Как и вызов функции/метода. То есть если вы хотите оптимизировать, например, выход из двойного цикла, который в других языках можно было бы оптимизировать с помощью goto, то выход с помощью эксепшна в Python будет как минимум так же эффективен, как вынос внутреннего цикла в отдельную функцию. Плюс, выход по эксепшну может быть даже более читаем, чем вариант с объявлением дополнительной функции.

                                                                                                    Во-вторых, «никакого разворачивания стека» и «не собирать стек трейс» − это совершенно несопоставимые вещи, вам не кажется?
                                                                                                      –2
                                                                                                      не дорого в Python

                                                                                                      А питон тут как самозародился? Или он, как рак, везде?

                                                                                                        0
                                                                                                        Правда ваша, кто про что, а шелудивый (я) про баню…

                                                                                                        С другой стороны, тут также самозародились C, Fortran, ассемблер, Go. А на горизонте маячат загадочные языки без return. Видимо, не я один проморгал тег “Java”.
                                                                                                          +1
                                                                                                          загадочные языки без return

                                                                                                          Все функциональные языки — довольно крупное подмножество вообще всех языков. Плюс LISP и бо́льшая половина процедурных.


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

                                                                                                        0

                                                                                                        Просто запустите тест http://gerg.ca/blog/attachments/try-except-speed.py. Если нет исключений, то try ничего не стоит. Если кидать часто, как в типичных сценариях для control flow, то разница с проверкой кода возврата оказывается значительной.

                                                                                                          +1
                                                                                                          Этот тест сравнивает эксепшн с if. if, конечно, менее затратен, чем поимка эксепшна, но не способствует читаемости кода. Я сравниваю поимку эксепшна с вызовом функции.

                                                                                                          Например, выход из двойного цикла.

                                                                                                          Вариант 1 − двойная проверка
                                                                                                          def t1():
                                                                                                              result = False
                                                                                                              for i in range(10):
                                                                                                                  for j in range(20):
                                                                                                                      if i == j == 5:
                                                                                                                          break
                                                                                                                  if i == j == 5:
                                                                                                                      result = True
                                                                                                                      break
                                                                                                              return result



                                                                                                          Вариант 2 − исключение
                                                                                                          def t2():
                                                                                                              result = False
                                                                                                              try:
                                                                                                                  for i in range(10):
                                                                                                                      for j in range(20):
                                                                                                                          if i == j == 5:
                                                                                                                              raise Exception
                                                                                                              except:
                                                                                                                  result = True
                                                                                                              return result



                                                                                                          Вариант 3 − вынос циклов в отдельную функцию
                                                                                                          def t3_inner():
                                                                                                              for i in range(10):
                                                                                                                  for j in range(20):
                                                                                                                      if i == j == 5:
                                                                                                                          return True
                                                                                                              return false
                                                                                                          
                                                                                                          
                                                                                                          def t3():
                                                                                                              result = False
                                                                                                              result = t3_inner()
                                                                                                              return result



                                                                                                          Вариант 4 − for… else
                                                                                                          def t4():
                                                                                                              result = False
                                                                                                              for i in range(10):
                                                                                                                  for j in range(20):
                                                                                                                      if i == j == 5:
                                                                                                                          break
                                                                                                                  else:
                                                                                                                      continue
                                                                                                                  result = True
                                                                                                                  break
                                                                                                              return result


                                                                                                          Все варианты выполняются за примерно одинаковое время, ±5%, если верить timeit. Вариант с эксепшном мне кажется наиболее читаемым. Последний вариант − самый быстрый, но и самый нечитаемый. В общем, затраты на обработку эксепшна вполне приемлемы.
                                                                                                            0

                                                                                                            Идеоматически в примере 2 кидают StopIteration. Это позволяет отличить такой вот псевдо-goto от прочих исключений, которые могут быть брошены бизнес логикой. В самом деле это работает почти без пенальти, как goto, но лишь пока исключение кидается и ловится внутри одной функции — нет нужды разворачивать стек. В других сценариях будет заметная разница.


                                                                                                            Лично по мне, если внутри функции появляются какие-то трюки, лучше подумать и сделать рефакторинг. Первое, что приходит на ум — убрать вложенность с помощью product(range(10), range(20)) из itertools. Вынести в отдельную функцию — вполне разумный вариант 3.

                                                                                                              –1
                                                                                                              «Вариант 1 − двойная проверка» по сути повторение (копипаст), от чего вы избавились в случае «Вариант 3 − вынос циклов в отдельную функцию» и от этого оно и читается проще чем 1й, и даже проще и лучше чем с exception
                                                                                                              (не имхо, по факту минус лишний if — или минс лишний except — это меньше кода читать и понимать быстрей)
                                                                                                              raise Exception
                                                                                                                  except:
                                                                                                                      result = True

                                                                                                              заменяется на «return false» или «return variable» (что было default задано в начале)

                                                                                                              т.е. вот идеальный вариант (и остальные варианты у вас точно также будут обернуты в функцию — с нормальным говорящим именем, т.е. не надо думать что тут функция это лишнее — нет не лишнее)
                                                                                                              def t3_inner():
                                                                                                                  for i in range(10):
                                                                                                                      for j in range(20):
                                                                                                                          if i == j == 5:
                                                                                                                              return True
                                                                                                                  return false


                                                                                                              можно было бы подумать что тут GOTO, но это не совсем так, тут более структурированно, всмысле нет проивзольных rise exception (throw) и catch и нет множества условий, а вполне очевидные 2 ветки исполнения — с 2мя тестовыми вариантами
                                                                                                        +2
                                                                                                        В Python, например, никакого разворачивания стека не происходит, поэтому исключения стоят столько же, сколько вызовы − пренебрежимо мало в большинстве ситуаций.

                                                                                                        "Пренебрежимо мало" в контексте Python надо читать, как "также дорого, как и всё остальное в Python".

                                                                                                        +3

                                                                                                        Пример с проверкой на существование файла не удачен. Т.к. с момента проверки до момента обращения файл могут удалить.


                                                                                                        Что касается исключений. Первая серьезная книга по прграммированию каоторую я прочитал была книга Барбары Лисков "Использование абстракций и спецификаций при разработке программ" на примере ею же разработанного очень красивого языка CLU который не получил большого распространения. Так вот там "исключения" трактовались как результат выполнения функции который принадлежит к другому множеству. Например рассмотрим функцию ПолучитьИндексЭлементВМассиве(). Тип целочисленный у функции. А что возвращать если элемент не найден? Уж не -1 ли? Или можетбыть сначала проверить есть ли элемент в массиве а потом найти его индекс? Или же вернуть кортеж (1, true) (nil, false)? Так вот оказывается что для этого как раз и удобно использовать исключения поэтому оператор throw можно воспринимать как return another value. То есть если мы нашли индекс — возвращаем индекс, а если не нашли то возвращаем исключение NotFound.


                                                                                                        Как мне кажется нелогичные вещи начинаются не на стороне вызова throw а на стороне блоков try/catch

                                                                                                          +1
                                                                                                          в этом плане Win32 API мне нравится — если функция сработала успешно — то она всегда вернет 0. Если была ошибка (исключение) то возвратится номер ошибки которые сравнивают с константой. Если ошибок несколько то будет битовый OR всех ошибок.

                                                                                                          А любые значения/структуры которые мы хотим получим передаются в функцию по ссылке.
                                                                                                          То, что весь публичный API работал одинаковым образом и был глобальный список возможных ошибок делало программирование очень структурированным и легко читаемым. Неважно какую часть огромного win API используешь, все легко читается и понятно.

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

                                                                                                            Справедливости ради следует отметить, что это соглашение появилось задолго до Win32 API…
                                                                                                              0
                                                                                                              если функция сработала успешно — то она всегда вернет 0

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

                                                                                                                0
                                                                                                                если бы вы программировали под winapi вы бы знали, что функции возвращают полезные данные через структуры/буферы, ссылки на которые вы передаете функции
                                                                                                                  0

                                                                                                                  Я программировал и знаю. И это не только в winapiю Но это не возврат значения, а изменения по ссылкам, которые переданы аргументами.

                                                                                                                    0

                                                                                                                    Вообще-то это ад. Потому что зачастую это приводит к конструктам типа void** или другая штука, что очень четко надо понимать какая функция выделяет память — вызывающая или вызываемая. Консистентностью в этом вопросе в win32apin в принципе не пахнет. Как и определением размера необходимого буфера и фактически записанного количества данных. В принципе, с этим жить можно (писали же относительно надёжные win32 программы), но интерфейс больно низкоуровневый получается и он не страхует программиста от ошибок...

                                                                                                                    0

                                                                                                                    И что такого полезного должна вернуть функция log_to_file, например, если не служебное значение «успех / код ошибки»?

                                                                                                                      +1

                                                                                                                      Количество записанных символов, например.

                                                                                                                    +3

                                                                                                                    WinAPI сделано в лучших традициях того времени, в которых несомненно есть рационализм. Но проверку кода возврата легко пропустить и программисту за это ни чего не будет, а с проверками код быстро превращается в месиво из бизнес-логики и control flow. Сигнатуры таких функций не поддаются стандартизации — out параметры могут быть в любом порядке и количестве, а вызовы не compose'ятся между собой. Поскольку out структура не принадлежит вызванной функции, довольно легко накосячить с потокобезопасностью. Собственно, от этого и пытались уйти, изобретая исключения.

                                                                                                                    +1

                                                                                                                    Проблема возникает из-за разного рода неопределенностей, для моделирования которых по-хорошему есть свои паттерны: значение существует или нет (Optional/MayBe), значение может существовать сейчас или в будущем (Task/Promise), нормальный ход вычислений или альтернативный (Either), есть результат или ошибка (Result) и т.п.

                                                                                                                      0
                                                                                                                      результат выполнения функции который принадлежит к другому множеству

                                                                                                                      в современном ООП реализуется классом, если конечно множества не совсем произвольные
                                                                                                                      т.е. если у вас есть бизнес-задача (я не о проблеме чтения файлов и сети, хотя и ее можно реализовать через то же самое, но там чаще exception бывают и с ними люди свыклись, хотя зря), то у нее должны быть понятные множества вход-выход и понятные результаты, скажем OK и ERROR, причем OK типа int, а ERROR просто как признак любой ошибки

                                                                                                                      тогда получим
                                                                                                                      FuncNameResult FuncName(input) { /* implement */}
                                                                                                                      
                                                                                                                      class FuncNameResult
                                                                                                                      {
                                                                                                                        public final boolean isOk; // или enum если вариантов ошибки много
                                                                                                                        public final int result; // нормальное значение (только если OK)
                                                                                                                      }
                                                                                                                      
                                                                                                                      FuncNameResult funcNameResult = FuncName(input);
                                                                                                                      if (funcNameResult .isOk) /* process OK */
                                                                                                                      else /* process error */
                                                                                                                      
                                                                                                                        0

                                                                                                                        return another value это скорее return Left("Error"), которые вовсю практикуются в современных языках и фреймворках. А исключения плохи тем что у них нелокальное действие на систему.

                                                                                                                        +3
                                                                                                                        Эх GOTO, сколько прелестных часов и даже дней было связано с ним в попытках разобраться что и куда идёт в программе на Фортране. Ведь там были такие замечательные операторы как computed GOTO, assigned GOTO и arithmetic IF:
                                                                                                                              READ(5,3)L         
                                                                                                                              IF(L.LT.0)GOTO160
                                                                                                                              IF(L.GT.4)GOTO180
                                                                                                                        10    FORMAT(I)
                                                                                                                              LP=L+1
                                                                                                                              GOTO(20,30,40,50,60),LP
                                                                                                                        20    P=1.0      
                                                                                                                              GOTO100  
                                                                                                                        30    P=X      
                                                                                                                              GOTO100  
                                                                                                                        40    P=1.5*X**2-0.5
                                                                                                                              GOTO100  
                                                                                                                        50    P=2.5*X**3-1.5*X      
                                                                                                                              GOTO100  
                                                                                                                        60    P=4.375*X**4-3.75*X**2+0.375 
                                                                                                                        100   IF(P)120,130,140
                                                                                                                        120   Q=-(PI/2.0)      
                                                                                                                              GOTO150
                                                                                                                        130   Q=0.0      
                                                                                                                              GOTO150
                                                                                                                        140   Q=PI/2.0
                                                                                                                        150   CONTINUE
                                                                                                                        160   WRITE(5,170)
                                                                                                                        170   FORMAT(5X,'L IS NEGATIVE')
                                                                                                                              GOTO200
                                                                                                                        180   WRITE(5,190)
                                                                                                                        190   FORMAT(5X,'L OUT OF RANGE')
                                                                                                                        200   CONTINUE
                                                                                                                        И да, почему без пробелов? Потому что в Фортране нет пробелов, точнее они игнорируются. Так что, «GO TO 1», «GOTO1» и «G OT O1» равнозначны. И в былые времена для экономии места они вообще не использовались:
                                                                                                                        Consistently separating words by spaces became a general custom about the tenth century A. D., and lasted until about 1957, when FORTRAN abandoned the practice. — Sun FORTRAN Reference Manual.