1. Java / Говнокод #7287

    +77

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    public static <T> T createInstance(String className, Object ... ctorParams)
    	{
    		Class<T> type;
    		try {
    			type = (Class <T>) Class.forName(className);
    		} 
    		catch (ClassNotFoundException e) { throw new RuntimeException(e); }
    		
    		Class <?> [] paramTypes = new Class [ctorParams.length];
    		for(int i = 0; i < ctorParams.length; i ++)
    			paramTypes[i] = (Class <?>) ctorParams[i].getClass();
    		
    		Constructor<T> ctor;
    		try {
    			ctor = type.getConstructor(paramTypes);
    		} 
    		catch (SecurityException e)    { throw new RuntimeException(e); }
    		catch (NoSuchMethodException e){ throw new RuntimeException(e); }
    		
    		T instance;
    		try {
    			instance = ctor.newInstance(ctorParams);
    		} 
    		catch (IllegalArgumentException e)  { throw new RuntimeException(e); }
    		catch (InstantiationException e)    { throw new RuntimeException(e); }
    		catch (IllegalAccessException e)    { throw new RuntimeException(e); }
    		catch (InvocationTargetException e) { throw new RuntimeException(e); }
    		
    		return instance;
    	}

    Тут само Java вынуждает говнокодить. О святая простота!

    Запостил: dveyarangi, 19 Июля 2011

    Комментарии (69) RSS

    • а что тут говнокодистого? Java - статичный язык, и механизм отражений - средство, позволяющее это преодолеть, и никто не сказал, что оно должно быть простым.
      хотите простоты? попробуйте груви
      Ответить
      • > Java - статичный язык, и механизм отражений - средство, позволяющее это преодолеть, и никто не сказал, что оно должно быть простым.

        патамушта слова "праграмист" рихмуется са словам "мазахист".
        Ответить
      • хотите простоты -- попробуйте .NET
        там такая функция встроенная, и вообще базовая библиотека классов более объёмная и на все случаи жизни.
        Ответить
        • простите, зптДА - это в соседнем треде
          груви генерирует такой же jvm-байткод, как и java

          > там такая функция встроенная
          о да, и без того прогеры плюются, что на уровне языка много чего странного понапихано
          Ответить
          • Я согласен, что там dsl-ов (типа linq) напихано слишком много, но так ведь никто и не принуждает их использовать. Они (Linq, dynamic и прочий кошмар) там используются же каждый в своём домене -- если ты, напр., не занимаешься базами данных или COM, то их и не используешь и не видишь. Плюются от этого ведь только говнотеоретики, это которые ругаются, что программа ест 100 мегабайт, хотя на их системах больше 30-50% RAM в конкретный момент времени вообще почти никогда не используется и тупо простаивает пустым.

            А Activator.CreateInstance -- действительно хорошая штука, я её использовал очень много раз.
            Ответить
            • да, и почему-то при установке фреймворка появляется два процесса сервиса оптимизации, которые постоянно висят в памяти и заметно (по Анвиру) кушают проц и хард при запуске какой-либо дотнет аппликации
              Ответить
              • у меня от java тоже висит процесс постоянно -- jusched
                наверное, и там и там можно отключить, но мне пофигу
                Ответить
    • В Java однозначно не хватает конструкции:
      try { ... } catch<? extends AnyException> (e) { ... }

      :)
      Ответить
      • catch (AnyException e)
        Ответить
      • catch(Exception e) - покроет 99% случаев, при крайней степени паранойи можно использовать catch(Throwable e) - 100% результат.
        Ответить
        • А что если типы ошибок инвариантны? Т.е. если нужно ловить A и C, но B не нужно, при том, что A, C и B все наследуют D?
          Ответить
          • тогда отдельно ловишь B и его пробрасываешь, все остальные ловишь как D

            catch(B e1) {
            throw e1;
            }
            catch(D e2) {
            // do something
            }
            Ответить
            • Ну так вопрос же был задан к тому, что налицо повторение абсолютно одинакового кода, и нет возможности этого избежать.
              Ответить
              • где повторение?
                Ответить
                • throw new RuntimeException(e);
                  throw new RuntimeException(e);
                  throw new RuntimeException(e);
                  throw new RuntimeException(e);
                  throw new RuntimeException(e);
                  Ответить
                  • try {
                    Class<T> type = (Class <T>) Class.forName(className);
                    Class <?> [] paramTypes = new Class [ctorParams.length];
                    for(int i = 0; i < ctorParams.length; i ++)
                    paramTypes[i] = (Class <?>) ctorParams[i].getClass();

                    Constructor<T> ctor = type.getConstructor(paramTypes);
                    return ctor.newInstance(ctorParams);
                    }
                    catch (Exception e) {
                    throw new RuntimeException(e);
                    }
                    Ответить
                    • Вас случаем не Авасом звать? :)
                      Ответить
                    • Я это говорил к тому, что приведенный случай инвариантности - не единственный, а просто, для примера, т.е. в данном случае у вас получится, но если при этом у вас будет еще и E тип ошибки, который не является подтипом D, но отреагировать на него нужно так же, как и на A и B, то ваше решение нельзя будет применить. Или, другой вариант, ошибки E, F, G так же наследуют D, но обрабатывать их нужно не таким (но одинаковым) способом, как, A и B которые все так же наследуют D.
                      Ответить
                      • Скажу честно, что за 6 лет программирования на java ни разу не вставала проблема ловить в одном месте кучу ошибок (тем более из одной иерархии), которые по разному (еще и группами) надо обрабатывать.

                        Проблема скорее надумана, чем реальна.
                        Ответить
                        • Ну мало ли, а я за всю жизнь ни разу не встретил OutOfMemoryError - ну вот просто не случалось, так что, отменить его теперь?
                          Ответить
                        • Бывает. Когда нужно по-разному реагировать на ошибки, вызванные некорректными пользовательскими данными (аккуратно сообщить ему об этом), ошибками ввода/вывода (проигнорировать или повторить) и прочими неожиданными (ругаться в логи и стучать администратору). Правда, они из совсем разных иерархий.
                          Ответить
                          • Вряд ли это делается в одном методе. Когда в одном методе надо ловить 20 оишбок и по разному их обрабатывать - это значит что-то с методом не в порядке.

                            С удовольствием взгляну на реальные примеры.
                            Ответить
                            • Не 20. Всего 3 типа ошибок — пользовательские, ввод/вывод и непредусмотренные ошибки (ещё можно выделить прерывание иногда или т.п.).

                              Просто пример — коммандный интерпретатор. Построчно читает ввод (здесь возможны ошибки в/в), парсит строку, по первой строке находит команду (возможна ошибка неверного имени команды), диспетчирезирует по нему и передаёт параметры вниз. В самом низу параметры парсятся специфичным способом и выбрасываются исключения (с необходимой контекстно-специфичной информацией) в случае неверных данных. Также могут возникнуть неожиданные исключения из-за ошибок программирования (подробности показывать пользователю нельзя).
                              Ответить
                              • вот и разбить метод на несколько методов. Почему-то написать метод состоящий из 2-3 строк кода считается чем-то неприличным. Однако, при таком детальном разбиении улучшается читаемость кода, изолируются уровни абстракции. Код становится более легко тестировать. Например:

                                public void doSomething() {
                                String input = readCommand();
                                Command command = parseCommand(input);
                                executeCommand(command);
                                }

                                private String readCommand() { ... // Обработать ошибки ввода/вывода }
                                private Command parseCommand(String input) { ... // Обработать ошибки ввода пользователя}
                                private void executeCommand(Command command) {... // обработывать ошибки в программировании}

                                На то чтобы понять, что делает метод doSomething() уйдут секудны. Если все будет в одном методе - можно и на час застрять.
                                Ответить
                                • Вот только при ошибке в readCommand parseCommand и executeCommand не должны выполняться. Как это обеспечить? Исключениями. А где они ловятся? То-то же.

                                  Ошибки ввода пользователя обрабатываются на самом нижнем уровне, в совсем другом классе, уже вовремя исполнения, потому, что только там известно, какие данные допустимы для этой команды.
                                  Ответить
                                  • если честно - не уловил мысль. можешь сформулировать иначе?
                                    Ответить
                                    • Какую команду и с какими параметрами выполнять, если произошла ошибка при чтении строки команды?
                                      Ответить
                                      • никакую. readCommand() в случае ошибок ввода/вывода должен выкидывать более высокоуровневую ошибку, например UserInputException. И вот эта ошибка должна уже обрабатываться выше: либо в doSomething(), либо еще выше
                                        Ответить
                                        • А если на эту ошибку реагировать нужно как-то иначе, чем на пользовательские ошибки? Например, отображать, но другим цветом. ;) И ещё есть непредусмотренные ошибки, необёрнутые в UserInputException.

                                          Что именно делать с разного типа ошибками — известно только на верхнем уровне, в точке перехвата. Вот пусть и будут разные перехватчики для разных типов.
                                          Ответить
                                          • опять не уловил мысль. На какую на эту? Что значит непредусмотренные? Приложение на верхнем уровне не должно оперировать низкоуровневыми ошибками. Верхний уровень должен оперировать ошибками бизнес-логики, а не NoSuchFileException и т. п.

                                            Приведу пример. Например, readCommand() читает данные из файла. Возможные проблемы: файл не существует, файл не может быть прочитан или вообще какая-нить неведомая фигня. В терминах бизнес логики, любая из этих ошибок, означает одно - мы не можем получить входные данные. Для сообщения используем new UserInputException(e). Например, на верхнем уровне нас интересует ситуация когда файл не найден, а все остальные для нас равнозначны. Тогда добавляем в наш UserException булево поле fileNotFound. И на верхнем уровне анализируем именно его, а не getCause().

                                            Также этот подход имеет еще один "+". К примеру, мы ожидам, что IllegalArgumentException может быть ТОЛЬКО внутри executeCommand. И если он там возникает мы оборачиваем его в CommandExecutionException и выкидываем наверх. Где его соответственно и ловим. Теперь, если на верхний уровень выпадет IllegalArgumentException это будет означать только одно - в приложении где-то баг, т. к. наши ожидания не оправдались.

                                            Еще раз повторюсь: не стоит смешивать уровни абстракции.
                                            Ответить
                                            • Смотрите, тут дело в том, что по факту, catch выражение, это практически метод с одним аргументом. Почему вы считаете, что для одной разновидности методов родовые переменные возможны, а для других - нет, я не знаю, но подозреваю, что это по причине, того, что вы пишете на языке, в котором это суровая действительность :)
                                              Кроме того, юмор был на самом деле по поводу типичного для Java стиля написания кода, когда каждая функция выбрасывает кучу разнообразных ошибок очень сильно запутывая логику программы в целом. Но если речь уже зашла о неограниченном и разнородном уровне абстракций... мне стало страшно :)
                                              Ответить
                                              • не уловил смысла фразы "для одной разновидности методов родовые переменные возможны, а для других - нет". пояснишь?

                                                А на счет "типичного для Java стиля написания кода, когда каждая функция выбрасывает кучу разнообразных ошибок..." - без комментариев =)
                                                Ответить
                                                • Родовые переменные это переменные тип которых вы указываете в <> скобках, их еще по-русски генериками называют :)
                                                  Т.е. предположим, что вам кажется нормальным код:
                                                  public <T> T veryGenericMethod(Т arg) { return (T)arg; }

                                                  а такой:
                                                  catch <T>(T e)
                                                  {
                                                      throw new VeryGenericException<T>();
                                                  }

                                                  :)
                                                  Ответить
                                                  • ммм... и чем это отличается от обычного catch?
                                                    Ответить
                                                    • вы выбрасываете новую ошибку, тип которой зависит от типа ошибки, которую вы обработали.
                                                      Ответить
                                                      • может приведете пример как бы это могло выглядеть в реальной ситуации? моей фантазии пока не хватает =)
                                                        Ответить
                                                        • Собственно идиоматический для Java rethrow - это и есть костыль от невозможности параметризировать ошибки. Т.е. каждый раз, когда вы делаете:
                                                          try { throw foo; }
                                                          catch (Foo e) { throw e; }

                                                          вы приделываете костыль, т.как ошибку достаточно бросить один раз, чтобы она дошла до самого верха, но изза того, что вы не можете отфильтровать только нужные, вам прийдется одну и ту же ошибку бросать много раз.
                                                          Ответить
                                                          • ммм. я вчера пил, поэтому сегодня туго соображаю. Из приведенного примера я, если честно, мало что понял. что значит "не можете отфильтровать только нужные" и "вам придется одну ошибку бросать много раз"? и с параметризацией ошибок тоже не понял. Чем она может помочь?

                                                            П. С. Вы случайно не сишник?
                                                            Ответить
                                                            • Нет, совсем не Си-шник даже :)
                                                              Вы в этом эпизоде писали:
                                                              http://govnokod.ru/7287#comment97878

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

                                                              Более коротко, вот этого кода:
                                                              catch(B e1) {
                                                              throw e1;
                                                              }
                                                              могло бы не быть, если бы вы могли более точно описать тип ошибки. Просто в сознании Java-программистов тип неотделим от класса или интерфейса, а родовая принадлежность воспринимается как шаблон, и типом не считаются. Для функций придуман механизм, который перекрывает необходимость в настоящих генериках процентов на 80, но в некоторых местах о нем просто "забыли", как, например, в catch.
                                                              Ответить
                                                              • Вроде мысль уловил. Типа хочется поймать, например, 3 конкретных ошибки, а все остальные пусть летят как есть? При этом хочется обрабатывать их в рамках одного catch блока, а не писать 3 catch блока с одинаковым содержимым?
                                                                Ответить
                                                                • ага, так и есть :)
                                                                  Ответить
                                                                  • Тогда генерики, в том виде как они сейчас есть, тут неприменимы.

                                                                    В Java 7 это решили так:
                                                                    catch(Exc1 | Exc2 | Exc3 e) {...}
                                                                    Ответить
                                                            • { Вы случайно не Middle C ? }
                                                              Ответить
                                            • Непредусмотренные — значит те, которых не должно быть, вызванные ошибками в программе (а ошибки будут). На них нужно адекватно прореагировать, проинформировать администратора/программиста и либо продолжить работу дальше, проигнорировав команду, либо закрыть сеанс.

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

                                              В случае предусмотренной ошибки, возникающей на нижнем уровне, формируется UserException с соответствующим сообщением, сопровождается интересующей информацией (например, для ошибок в параметрах будет свой подкласс UserArgumentException и атрибут — номер и значение параметра) и пробрасывается наверх. На нижнем уровне мы можем определить пользовательскую ошибку, но мы не должны беспокоиться, как донести её до пользователя.

                                              На верхнем уровне ловятся как ошибки ввода/вывода (часть можно обработать в подфункциях, не передавая наверх, часть влияет на главный цикл), так и UserException с подклассами, прилетевшие из обработки команд. И на каждое исключение реагирует по-своему — на UserArgumentException выводит строку с указанием ошибочного параметра, на общий UserException — сформированное сообщение об ошибке, на разрыв связи — заканчивает работу. Чем именно вызван UserException — на этом уровне неважно, есть сообщение и другие параметры, которые нужно отобразить (но способ отображения может зависеть от подтипа).
                                              Ответить
                                              • мне кажется, что мы уже просто друг друга не понимаем =) я бы с удовольствием взглянул на конкретный код (конечно, если это не коммерческая тайна)
                                                Ответить
        • > catch(Throwable e)
          и очень кошерно словить сюда какой-нибудь OutOfMemoryError или StackOverflowError
          Ответить
          • я же сказал "для параноиков", всегда хватает ловить Exception
            Ответить
            • не для параноиков, а для кретинов, потому что ловить какие-либо Error'ы чревато
              Ответить
    • В том как обрабатываются ошибки в этом примере, можно все обернуть в один try {...} catch (Exception e) {..}

      П. С. Переходи на Java 7. Там с этим делом чутка получше
      Ответить
    • Скорее всего код писался в IDE, которая подчеркнула красной волнистой линией, что код не скомпиляеццо, надо обернуть в try/catch, и предложила "Quick Fix - обернуть в try/catch" , ну и обернула. Поэтому имеем то что имеем.

      То что говнокод - это да. То что джава к этому побуждает - это НЕТ.

      П. С. Советую посмотреть try/catch паттерны.
      Ответить
      • Насколько мне известно, catch (Exception e) {..} это как раз таки анти-паттерн. Даже в данном случае, рано или поздно захочется придать понятности вылетающим ошибкам, а они разные по выразительности, например:
        * InvocationTargetException - нужен скорее e.getCause().getMessage()
        * InstantiationException выдает пустой getMessage()
        * IllegalArgumentException в данном контексте невнятен,

        Но вообще да, это все Eclipse :)
        Ответить
        • Уточнюсь немного. Когда я говорил про паттерны, я имел ввиду не catch(Exception e). Такое действительно надо делать весьма обдуманно.

          Я имел ввиду "throw new RuntimeException(e)". А если потребуется поменять RuntimeException еще на что-нибудь? Или логировать ошибку? В самом простом случае, можно было бы заменить "throw new RuntimeException(e)" на "logAndThrow(e)".

          private static void logAndThrow(Exception e) {
          logger.error("", e);
          throw new RuntimeException(e);
          }

          По крайней мере в случае смены типа ошибки изменения надо будет вносить лишь в одной строчке, а не в семи, как в исходном примере.
          Ответить
    • Ничего говнокодистого.
      Разве что надо было делать throw с указанием cause, чтобы исходную причину не потерять, и обернуть всё в один try/catch блок.
      Ответить
    • Шаблоны С++ начали перекочевывать даже в джаву. Тарас негодует.
      Ответить
    • >Тут само Java вынуждает говнокодить
      Никто никого не вынуждает, метод в идеале должен выкидывать из себя кастомный эксепшн приложения или (формально то же самое, архитектурно - неверно) возвращать нулл. Ну и блок try - catch тут нужен только один.
      Ответить

    Добавить комментарий