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

    +74

    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
    public class Encoder {
    	public static void encode(final OutputStream out, Node node) throws IOException {
    		node.accept(new NodeVisitor() {
    			@Override
    			public void string(StringNode node) {
    				byte[] value = node.toByteArray();
    				out.write(Integer.toString(value.length).getBytes(Constants.CHARSET));
    				out.write(':');
    				out.write(value);
    			}
                            // ... другие методы для других типов нод ...
                    }
            }
    }

    Решил поменять в паре-тройке модулей пачки ифов на паттерн visitor... И получил пинка от жабы ;(

    write() кидает IOException, а значит и метод string() в анонимном классе тоже должен кидать, и метод string() в интерфейсе NodeVisitor тоже... Но ведь соседним посетителям этот IOException нахер не сдался...

    Checked exceptions - зло.

    Запостил: bormand, 12 Октября 2013

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

    • Хм, можно попробовать на месте ловить этот IOException и запаковывать его в unchecked exception, потом где-нибудь распаковывать и кидать IOException снова.
      Ответить
      • Да можно, конечно, но это превратит код в полный пиздец... В каждом методе visitor'а будет болтаться заворачивание в потомка RuntimeException, а accept будет вытаскивать из этого потомка оригинал и перевбрасывать его...

        Уж лучше я откачу все обратно, на версию без visitor'а. Она была понятней:
        if (node instanceof StringNode) {
            StringNode stringNode = (StringNode)node;
            // work with string node
        }
        // etc
        Ответить
        • А throws IOException повсюду ещё не пиздец? :) А если другие классы исключений появятся?
          Ответить
          • > А throws IOException повсюду ещё не пиздец
            А я и не собираюсь добавлять его в интерфейс visitor'а, ибо это совсем уж пиздец и нарушение логики. Еще более полный, чем туннелирование через RuntimeException ;)

            Я просто откачу на if'ы и затаю обиду на подлую жабу...
            Ответить
            • > Я просто откачу на if'ы и затаю обиду на подлую жабу...

              Practice makes perfect!
              Ответить
            • хм... могут ли быть подобные проблемы причиной почему у нас на проекте жабщики врапят повально все, и весь код пользуется исключительно одним классом эксэпшена?
              Ответить
              • использование одного класса exception обычно является следствием нежелания писать свои
                Ответить
            • Чей ты так. Юзай Посетителя, блеать!)
              Ответить
        • http://govnokod.ru/10918#comment141649
          Ответить
        • Либо так, либо throws Exception в методах visitor'а и accept() throws NodeVisitorException. Потом при желании (или необходимости, если NodeVisitorException не RuntimeException):

          try {
            node.accept(...);
          } catch (NodeVisitorException e) {
            Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
            Throwables.propagateIfPossible(e.getCause());
            throw AssertionError();
          }
          Ответить
    • Кажется, самое время для хака 3.1415 с выбрасыванием невыбрасываемого.
      Ответить
    • Звучит как: "Хотел отстрелить себе ногу, а прострелил яйцо."
      Ответить
    • А мне тут не checked глаз режет.
      >node.toByteArray();
      >getBytes(Constants.CHARSET));
      И самое интересное:
      >out.write(':');
      Неявная конвертация чара в int. Да и еще мимо чарсетов.
      Сразу в первой строке OutputStream настрожил.

      А теперь к тому как бороться. Во-первых не надо конвертить ничего в байты, надо юзать Writerы/CharBuffer.
      Давно сделал себе статик хелпер-методы:
      appendAll(Appedable a, CharSequence... str), append(Appedable a, CharSequence str), append(Appedable a, char c).

      В них кинул нормальные исключения, добавил варарг - не люблю писать кучу раз write, append, хоть в Appendable и флюент-интерфейс.
      Ну и Writer extends Appedable. Потому туда тоже пишем appendом.

      Конечно человека который в год пишет строк десять на яве и не имеет под рукой либы с хелперами такие вещи это жопа, да.
      Ответить
      • > Сразу в первой строке OutputStream настрожил.
        А там формат двоичный (торрентовский bencode http://bittorrent.org/beps/bep_0003.html)... И "строки" это блобы на самом деле, у которых внутри массив байтов. В которых те же sha-1 в двоичном виде могут валяться. Поэтому через writer'а они не пролезут.

        > Неявная конвертация чара в int.
        Ну не гонять же перекодировщик ради одного ascii символа?
        Ответить
        • >не гонять же перекодировщик ради одного ascii символа?
          А если там не однобайтный, а джвухбайтный уникод?
          Стрёмно как-то.
          А еще порядки байт. Может лучше саппендить с предыдущей строкой?
          Ответить
          • > А еще порядки байт.
            > А если там не однобайтный, а джвухбайтный уникод?
            Протокол двоичный, и согласно спеке там должен стоять именно один байт с ascii кодом ':'. Ну можно написать его числом, чтобы не смущало, но зачем? Саппендить с длиной блоба, стоящей до ':' тоже можно... А вот с тем, что стоит справа - не стоит, ибо справа от нее блоб (в терминологии bencoding названный string).
            Ответить
            • >Протокол двоичный
              Тогда не пойму смысла конвертации стринга в массив байт выше:
              >.getBytes(Constants.CHARSET)
              Можно ебашить чары сразу в поток. Выиграем, не создавая лишний объект.
              Ответить
              • > Можно ебашить чары сразу в поток.
                Хм, но у него же нет метода для записи чаров. Циклом по одному символу разве будет эффективнее?
                Ответить
                • >Циклом по одному символу разве будет эффективнее?
                  А разве массив байт внутри getBytes наполняется как-то по другому:?
                  Плюс не тратим время на конверсию (кстати когда-то замерял, на некоторых чарсетах ощутимо ).
                  Ответить
                  • > Плюс не тратим время на конверсию
                    Да UTF-8 по идее должен быть ненамного медленнее ascii. Там же конвертация из юникодных поинтов - банальное байтоебство со сдвигами и масками...

                    > А разве массив байт внутри getBytes наполняется как-то по другому
                    Там вызова write нету ;) Хотя... с другой стороны он вполне может заинлайниться.
                    Ответить
        • Для записи формата юзается библиотека с методами типа writeInt() и все. Или это она и есть?
          Ответить
      • Ну разве что юзать небуферизованный writer, и часть писать через него, а часть напрямую в OutputStream... Но что-то меня смущает такая схема.
        Ответить
      • >Давно сделал себе статик хелпер-методы:
        Вся суть жабы. Выплавляем сталь в каждом дворе.
        Ответить
    • Посетитель какой-то не в ту сторону. Смысл писать реализацию того, что происходит с конкретным посещаемым объектном в посетителе? Смысл же наоборот, чтобу у каждого посещаемого был какой-то более-менее одинаковый метод, который постетитель бы вызывал. Если конкретно кодировщик, то я бы сделал что-то типа:
      class Encoder {
      public static ByteBuffer encode(ByteBuffer input, List<Node> nodes) {
         for (Node node : nodes) node.writeTo(input);
         return input;
      }
      }

      Или что-то такое.
      Ответить
      • Смысл посетителя как раз в том, чтобы добавлять виртуальные операции к иерархии без изменения самой иерархии. К библиотечному Node виртуальный writeTo не приклеить. Так что всё в ту сторону.
        Ответить
        • Так это другой шаблон, if-else называется. Как же он посещать будет, если у посещаемых нет ничего общего?
          Смотрим в педивикию на хрестоматийный пример, и видим как в нем все посещаемые обязуются реализовать метод вызываемый постетителем (accept). Без этого шаблон не взлетит какбы.
          Ответить
          • > все посещаемые обязуются реализовать метод вызываемый постетителем (accept)
            Все реализации нод (StringNode, ListNode и т.п.) реализуют интерфейс Node, у которого, внезапно, есть метод void accept(NodeVisitor visitor) см. строку 3.

            Что тут отличается от хрестоматийного примера?
            Ответить
            • Так смысл в том, чтобы описать работу с конкретными разновидностями Node внутри этой самой accept. Если их описывать внутри посетителя, то вместо желаемой модулярности получим тот же самый if-else только размазаный по перегруженым методам посетителя.
              Таким образом мы не только не добьемся цели: возможности по-отдельности изменять обработку каждого посещаемого, а сделаем сопровождение такого кода более сложным: у нас будет куча лишнего кода в виде перегруженых string.

              Следующий в статье лисповый вариант лучше демонстрирует работу шаблона. Там методы посещаемых объектов не вызывают дальше метод одного какого-то другого объекта, а просто уже непосредственно выполняют нужную задачу.
              Ответить
              • > if-else только размазаный по перегруженым методам посетителя
                Это и есть суть классического шаблона visitor.

                Лисповый вариант в статье использует мультиметоды, которые не поддерживаются в мейнстримовых языках. Они как раз скрывают таблицу диспетчеризации по типам. Собственно, visitor это один из способов реализации double-dispatch engine, мультиметодов для бедных.
                Ответить
                • Конкретно для того, чтобы реализовать лисповый вариант мультиметоды не нужны. Достаточно просто виртуального метода и конкретных реализаций у разновидностей Node. Ява вполне себе может в динамический диспатч (в оригинальном примерe диспатч у accept все равно динамический).
                  Ответить
                  • > Достаточно просто виртуального метода и конкретных реализаций у разновидностей Node.
                    Научить ноду писаться в двоичном формате, писаться в json, дампаться на экран в красивом формате, танцевать кан-кан и играть на балалайке? :)

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

                    Вот visitor и выворачивает эту схему, позволяя добавлять действия не меняя код Node. И внося вполне понятный недостаток - новые сабклассы Node добавлять становится сложнее.

                    Собственно и проблема языков без множественной диспетчеризации:
                    - либо легко добавлять новые сабклассы Node, но сложно добавлять методы
                    - либо, юзая посетителя, легко добавлять методы, но сложно добавлять новые сабклассы
                    Ответить
                    • Нет, не нужно так делать.
                      interface Node {
                      ByteBuffer writeTo(ByteBuffer input);
                      }
                      class StringNode implements Node {
                          @Override
                          ByteBuffer writeTo(ByteBuffer input) {
                              input.add(...);
                          }
                      }

                      и т.д. И тогда то, как каждый вид узлов сериализуется / печатается будет описан в соответствующем ему узле. Что впоследствии значит, что если нужно будет поменять то, как печатается какой-то один из узлов, не нужно будет менять класс, который их всех печатает.
                      Ответить
                • Смысл visit функции в примере на Яве в том, чтобы можно было использовать разные реализации посетителя, что в примере борманда никак не предусмотрено / очевидно не нужно.
                  Ответить
                  • > никак не предусмотрено
                    ???

                    Может быть вас смутил синтаксис жабьего анонимного класса?

                    NodeVisitor это интерфейс. А запись new NodeVisitor() { // перегрузки методов } это и есть одна из его реализаций (в данном случае она сохраняет дерево в поток в формате bencoding). В соседних файлах есть и другие реализации, выполняющие другие действия над нодами.
                    Ответить
                    • А, ну если посетителей много, тогда имеет смысл. Тогда это решение по какому принципу делить / чего больше: посетителей или посещаемых. Просто из кода не видно, что посетителей много / что это вообще нужно.
                      Ответить
                      • > Тогда это решение по какому принципу делить / чего больше: посетителей или посещаемых.
                        Ну скорее не по количеству, а по частоте изменений/добавлений. Если чаще добавляют методы - то visitor, если чаще добавляют сабклассы - то обычные виртуальные методы... Если часто добавляют и то и то - проще повеситься или сменить язык ;)

                        Все-таки отсутствие множественной диспетчеризации сказывается.
                        Ответить
              • > получим тот же самый if-else только размазаный по перегруженым методам посетителя.
                Дык в том и суть паттерна visitor :) Отделить проверки типов и обход структуры от действий над нодами... Сам обход остается внутри accept'ов, проверки типов становятся ненужными за счет виртуальных вызовов, а действия реализуются снаружи в сабклассах NodeVisitor.

                > лисповый вариант
                А вот лиспу паттерн visitor, имхо, вообще нахрен не сдался. visitor это ж костыль для ООП языков, которые не умеют во множественную диспетчеризацию...
                Ответить
    • про зло checked exceptions еще Howard Lewis Ship писал: http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html
      Ответить
      • > In many cases, a simple boolean, indicating success or failure, would be much better than an exception, and accomplish the same goals with far less cost.
        А не съебать ли ему обратно на сишку?
        Ответить
        • Соглашусь. Хоть и исключения vs. коды ошибок и предмет знатного холивара, не имеющий консенсуса, в чужой монастырь со своим уставом не лезут. Пишешь на жабе - пиши как все на жабе говно.
          Ответить
          • Консенсус простой: там, где это критически не сказывается на производительности - однозначно исключения.
            Практически в каждой проге на си, работающей с сетью, можно найти место, в котором она падает в бесконечный цикл с потреблением 99% cpu, потому, что кто-то не проверил код возврата чтения из сокета.
            Ответить
            • Но в том же Go отказались от исключений. Наверное, они что-то знали. panic/recover есть, но он для несколько другого, как я понял. Всякие Write, Read и пр. возвращают именно код ошибки.
              Ответить
              • > они что-то знали
                Они знали, что task-based concurrency лучше сочетается с явным возвратом ошибок. Кроме того, овер 9000 программистов не могут корректно обрабатывать исключения, и корректность их обработки можно легко нарушить случайным фиксом.

                С другой стороны, меня лично немного напрягает постоянно долбить проверки на ошибки. Судя по гитхабу, многие вообще их игнорят.
                Ответить
                • В хаскеле четка сделано. Ошибку не проворонишь без исключений. Через MayBe, Either или прочие монады. Обязательно потребует извлечь и проверить результат или монадическая последовательность сама проверит результат и применит ту обработку, которая соответствует данной монаде.
                  Ответить
                  • Хачкель функиональный язык, а я имел в виду императивно-ОО язык.
                    Ответить
                    • опиять этот кяфир своя голева подняль!
                      Закирой, закирой свой паганий рот, нечястивец!
                      Ответить
      • о круто, я люблю, когда обсирают исключения, а есть на русском?
        Ответить
    • Лови лайк кароч, только не потрать его весь сразу!
      Ответить
      • ПРивет, дружочек мой. Как ты? Нам тебя очень не хватало.
        Ответить
        • Я, PragramistOtBoga, находясь в здравом уме и твердой памяти, торжественно заявляю: Привет, все отлично.
          Ответить
          • а где капс?
            Ответить
            • Я провел с ним воспитательную беседу и подкармливаю его пирожными, взамен он обещал троллить тоньше.
              Ответить

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