...

суббота, 29 октября 2016 г.

[Перевод] Я унылый программист и горжусь этим

Чисто экспериментальные приёмы портирования Stream API из Java 8 на Java 6

Год назад я рассказывал о том, как с помощью Maven и Retrolambda портировать своё приложение, использующее языковые средства Java 8, а также сопутствующие “не совсем Java 8” библиотеки, на Android. К сожалению, новые Java 8 API использовать не удастся ввиду банального их отсутствия на более старой целевой платформе. Но, поскольку сама идея не покидала меня продолжительное время, мне стало интересным: можно ли портировать, например, Stream API на более старую платформу и не ограничиваться самими только возможностями языка вроде лямбда-выражений.


В конечном итоге, такая идея подразумевает следующее: как и в предыдущем случае, нужно с помощью доступных инструментов, в частности старой-доброй Retrolambda, переписать байткод Stream API таким образом, чтобы код, использующий этот API, мог работать и на старых версиях Java. Почему именно Java 6? Честно говоря, с этой версией Java я проработал дольшее время, Java 5 я не застал, а Java 7 для меня скорее как пролетела мимо.



Также сразу повторюсь, что все инструкции, приведённые в этой статье, носят чисто экспериментальный характер, и вряд ли — практический. В первую очередь из-за того, что придётся пользоваться boot-classloader-ом, что не всегда приемлимо или возможно вообще. А во-вторых, сама реализация идеи откровенно сыровата и в ней присутствует множество неудобств и не совсем очевидных подводных камней.


Инструменты


Итак, набор необходимых инструментов представлен следующими основныним пакетами:



И сопутствующие инструменты, вовлечённые в эксперимент:



Помимо более старых версий OpenJDK, пример портирования будет осуществляться с помощью Ant, а не Maven. Я хоть и приверженец convention over configuration и уже лет пять-шесть не пользуюсь Ant, для решения именно этой задачи мне Ant кажется куда более удобным инструментом. В первую очередь из-за простоты, а также из-за тонкой настройки, что, по правде говоря, труднодостижимо в Maven, скорости работы и кросс-платформенности (shell-скрипты были бы ещё короче, но я также часто использую Windows без Cygwin и похожех примочек).


В качестве proof of concept будет использоваться простой пример на Stream API.


package test;

import java.util.stream.Stream;

import static java.lang.System.out;

public final class EntryPoint {

    private EntryPoint() {
    }

    public static void main(final String... args) {
        runAs("stream", () -> Stream.of(args).map(String::toUpperCase).forEach(EntryPoint::dump));
    }

    private static void runAs(final String name, final Runnable runnable) {
        out.println("pre: " + name);
        runnable.run();
        out.println("post: " + name);
    }

    private static void dump(final Object o) {
        out.println(">" + o);
    }

}

Несколько слов о том, как будет проходить эксперимент. Ant-овский build.xml разделён на множество шагов или этапов, каждому из которых в процессе портирования отведена своя собственная директория. Это, по крайней мере мне, здорово упрощает процесс поиска решения и отладки, прослеживать изменения от шага к шагу.


Процесс портирования


Шаг 0. Init


Как обычно, первым делом в Ant почти всегда идёт создание целевой директории.


<target name="init" description="Initializes the workspace">
    <mkdir dir="${targetDir}"/>
</target>

Шаг 1. Grab


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


С другой стороны, есть некоторый смысл попробовать стянуть весь пакет java.util.stream и потом потратить ещё больше времени на подтягивание других зависимостей (и, наверняка, обработку инструментами типа ProGuard). Но я решил пойти на другое простое ухищрение: вложенные и внутренние классы я просто копирую с помощью маски $**. Это очень существенно экономит время и список. Некоторые классы, существовавшие и в более старых версиях Java, скорее всего, нужно будет скопировать также, поскольку в Java 8 они обрели новые возможности. Это касается, например, нового метода по-умолчанию Map.putIfAbsent(Object,Object), который не задействован в тесте, но требуется для его корректной работы.


<target name="01-grab" depends="init" description="Step 01: Grab some JRE 8 classes">
    <unzip src="${java.home}/lib/rt.jar" dest="${step01TargetDir}">
        <patternset>
            <include name="java/lang/AutoCloseable.class"/>
            <include name="java/lang/Iterable.class"/>
            <include name="java/util/Arrays.class"/>
            <include name="java/util/AbstractMap.class"/>
            <include name="java/util/EnumMap.class"/>
            <include name="java/util/EnumMap$**.class"/>
            <include name="java/util/function/Consumer.class"/>
            <include name="java/util/function/Function.class"/>
            <include name="java/util/function/Supplier.class"/>
            <include name="java/util/Iterator.class"/>
            <include name="java/util/Map.class"/>
            <include name="java/util/Objects.class"/>
            <include name="java/util/Spliterator.class"/>
            <include name="java/util/Spliterator$**.class"/>
            <include name="java/util/Spliterators.class"/>
            <include name="java/util/Spliterators$**.class"/>
            <include name="java/util/stream/AbstractPipeline.class"/>
            <include name="java/util/stream/BaseStream.class"/>
            <include name="java/util/stream/ForEachOps.class"/>
            <include name="java/util/stream/ForEachOps$**.class"/>
            <include name="java/util/stream/PipelineHelper.class"/>
            <include name="java/util/stream/ReferencePipeline.class"/>
            <include name="java/util/stream/ReferencePipeline$**.class"/>
            <include name="java/util/stream/Sink.class"/>
            <include name="java/util/stream/Sink$**.class"/>
            <include name="java/util/stream/Stream.class"/>
            <include name="java/util/stream/StreamShape.class"/>
            <include name="java/util/stream/StreamOpFlag.class"/>
            <include name="java/util/stream/StreamOpFlag$**.class"/>
            <include name="java/util/stream/StreamSupport.class"/>
            <include name="java/util/stream/TerminalSink.class"/>
            <include name="java/util/stream/TerminalOp.class"/>
        </patternset>
    </unzip>
</target>

Действительно, весьма впечатляющий список классов, нужный только для простых, как сперва кажется, map() и forEach().


Шаг 2. Compile


Скучная компиляция тестового кода. Проще некуда.


<target name="02-compile" depends="01-grab" description="Step 02: Compiles the source code dependent on the grabbed JRE 8 classes">
    <mkdir dir="${step02TargetDir}"/>
    <javac srcdir="${srcDir}" destdir="${step02TargetDir}" source="1.8" target="1.8"/>
</target>

Шаг 3. Merge


Этот шаг может показаться немного странным, поскольку он просто сливает воедино результат копирования классов из Java 8 rt.jar и тестового примера. На самом деле это нужно для нескольких следующих шагов, которые перемещают Java-пакеты для их правильной последующей обработки.


<target name="03-merge" depends="02-compile" description="Step 03: Merge into a single JAR in order to relocate Java 8 packages properly">
    <zip basedir="${step01TargetDir}" destfile="${step03TargetFile}"/>
    <zip basedir="${step02TargetDir}" destfile="${step03TargetFile}" update="true"/>
</target>

Шаг 4. Shade


Для Maven существует один интересный плагин, который умеет перемещать пакеты, изменяя байткод class-файлов напрямую. Я не знаю, может я плохо искал в Интернете, существует ли его Ant-овский аналог, но мне не осталось ничего другого, кроме как самому написать небольшое расширение для Ant, являющееся простым адаптером для Maven-плагина с единственной возможностью: только перемещение пакетов. Другие возможности maven-shade-plugin отсутствуют.


На этом этапе для того, чтобы дальше можно было воспользоваться Retrolambda, нужно переименовать все пакеты java.* во что-либо типа ~.java.* (да-да, именно “тильда” — ведь почему бы и нет?). Дело в том, что Retrolambda полагается на работу класса java.lang.invoke.MethodHandles, который запрещает использование классов с пакетов java.*sun.*, как это есть в Oracle JDK/JRE). Поэтому временное перемещение пакетов просто явлется способом “ослепить” java.lang.invoke.MethodHandles.


Как и в шаге №1, мне пришлось указать полный список классов по-отдельности через include-список. Если этого не сделать и опустить список полностью, shade в класс-файлах также переместит и те классы, которые не планируется подвергать обработке. В таком случае, например, java.lang.String станет ~.java.lang.String (по крайней мере, это чётко видно из декомпилированных с помощью javap классов), что сломает Retrolambda, которая просто молча перестанет преобразовавывать код и не сгенерирует ниодного класса для лямбд/invokedynamic. Прописывать все классы в exclude-список считаю более нецелесообразным, потому что их просто сложнее искать и пришлось бы ковыряться в class-файлах с помощью javap в поисках лишней тильды.


<target name="04-shade" depends="03-merge" description="Step 04: Rename java.* to ~.java.* in order to let RetroLambda work since MethodHandles require non-java packages">
    <shade jar="${step03TargetFile}" uberJar="${step04TargetFile}">
        <relocation pattern="java" shadedPattern="~.java">
            <include value="java.lang.AutoCloseable"/>
            <include value="java.lang.Iterable"/>
            <include value="java.util.Arrays"/>
            <include value="java.util.AbstractMap"/>
            <include value="java.util.EnumMap"/>
            <include value="java.util.EnumMap$**"/>
            <include value="java.util.function.Consumer"/>
            <include value="java.util.function.Function"/>
            <include value="java.util.function.Supplier"/>
            <include value="java.util.Iterator"/>
            <include value="java.util.Map"/>
            <include value="java.util.Objects"/>
            <include value="java.util.Spliterator"/>
            <include value="java.util.Spliterator$**"/>
            <include value="java.util.Spliterators"/>
            <include value="java.util.Spliterators$**"/>
            <include value="java.util.stream.AbstractPipeline"/>
            <include value="java.util.stream.BaseStream"/>
            <include value="java.util.stream.ForEachOps"/>
            <include value="java.util.stream.ForEachOps$**"/>
            <include value="java.util.stream.PipelineHelper"/>
            <include value="java.util.stream.ReferencePipeline"/>
            <include value="java.util.stream.ReferencePipeline$**"/>
            <include value="java.util.stream.Sink"/>
            <include value="java.util.stream.Sink$**"/>
            <include value="java.util.stream.Stream"/>
            <include value="java.util.stream.StreamShape"/>
            <include value="java.util.stream.StreamOpFlag"/>
            <include value="java.util.stream.StreamOpFlag$**"/>
            <include value="java.util.stream.StreamSupport"/>
            <include value="java.util.stream.TerminalSink"/>
            <include value="java.util.stream.TerminalOp"/>
        </relocation>
    </shade>
</target>

Небольшое отступление. Теоретически, дублирование списка в Ant можно решить с помощью элементов, поддерживающих refid, но это не получится по нескольким причинам:


  • <relocation> не поддерживает refid в первую очередь потому, что аналог этого аттрибута просто отсутствует в Maven-реализации. И я бы хотел, чтобы две реализации были похожи друг на друга один в один. По крайней мере, сейчас.
  • Анатомически <relocation> и <patternset> различаются. В первом применяется <include name=”...”, а во втором — <include value=”...”>. Здесь, подозреваю, мой косяк, и я не слишком следовал общепринятым соглашениям.
  • SimpleRelocator, используемый плагином для Maven, по видимому, не поддерживает пути к класс-файлам. Поэтому во втором случае названия классов нужно прописывать формате, где разделителем является точка, а не косая черта. Ещё одна несовместимость. Конечно, можно написать свою реализацию правил перемещения, но у меня, наверняка, если бы это не противоречило никаким правилам Maven-плагина, возник бы соблазн предложить такое расширение разработчикам maven-shade-plugin. Но, имея даже минимальный опыт, могу сказать, что даже в случае положительного ответа на такой запрос, это заняло бы кучу времени. Просто экономия времени.

Так что все эти недостатки решаются, но явно не в рамках этой статьи.


Шаг 5. Unzip


Следующий шаг распаковывает JAR-файл с перемещёнными пакетами, поскольку Retrolambda может работать только с директориями.


<target name="05-unzip" depends="04-shade" description="Step 05: Unpacking shaded JAR in order to let Retrolamda work">
    <unzip src="${step04TargetFile}" dest="${step05TargetDir}"/>
</target>

Шаг 6. Retrolambda


Само сердце эксперимента: преобразование байткода версии 52 (Java 8) в версию 50 (Java 6). Причём из-за использованых выше ухищрений, Retrolambda (или, стало быть, JDK 8) спокойно и уже без лишних вопросов проинструментирует классы. Также обязательно нужно включить поддержку методов по-умолчанию, потому что множество нового функионала в Java 8 строится именно на них. Поскольку JRE 7 и ниже не умеет работать с такими методами, Retrolambda просто копирует реализацию такого метода для каждого класса, в котором он не был переопределён (это, кстати говоря, означает, что применять Retrolambda нужно только для связки “конечное приложение и его библиотеки”, иначе скорее всего можно столкнуться с проблемой, когда реализация default-метода попросту будет отсутствовать).


<target name="06-retrolambda" depends="05-unzip" description="Step 06: Perform downgrade from Java 8 to Java 6 bytecode">
    <java jar="${retrolambdaJar}" fork="true" failonerror="true">
        <sysProperty key="retrolambda.bytecodeVersion" value="50"/>
        <sysProperty key="retrolambda.classpath" value="${step05TargetDir}"/>
        <sysProperty key="retrolambda.defaultMethods" value="true"/>
        <sysProperty key="retrolambda.inputDir" value="${step05TargetDir}"/>
        <sysProperty key="retrolambda.outputDir" value="${step06TargetDir}"/>
    </java>
</target>

Шаг 7. Zip


Собираем проинструментированную версию обратно в один файл, чтобы запустить shade-плагин в обратном направлении:


<target name="07-zip" depends="06-retrolambda" description="Step 07: Pack the downgraded classes back before unshading">
    <zip basedir="${step06TargetDir}" destfile="${step07TargetFile}"/>
</target>

Шаг 8. Unshade


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


<target name="08-unshade" depends="07-zip" description="Step 08: Relocate the ~.java package back to the java package">
    <shade jar="${step07TargetFile}" uberJar="${step08TargetFile}">
        <relocation pattern="~.java" shadedPattern="java"/>
    </shade>
</target>

Шаг 9. Unpack


В этом шаге классы просто распаковываются для последующей сборки двух отдельных JAR-файлов. Снова ничего интересного.


<target name="09-unpack" depends="08-unshade" description="Step 09: Unpack the unshaded JAR in order to create two separate JAR files">
    <unzip src="${step08TargetFile}" dest="${step09TargetDir}"/>
</target>

Шаги 10 и 11. Pack


Собираем все классы воедино, но отдельно — “новый рантайм” и само тестовое приложение. И в который раз — весьма тривиальный и неинтересный шаг.


<target name="10-pack" depends="09-unpack" description="Step 10: Pack the downgraded Java 8 runtime classes">
    <zip basedir="${step09TargetDir}" destfile="${step10TargetFile}">
        <include name="java/**"/>
    </zip>
</target>

<target name="11-pack" depends="09-unpack" description="Step 11: Pack the downgraded application classes">
    <zip basedir="${step09TargetDir}" destfile="${step11TargetFile}">
        <include name="test/**"/>
    </zip>
</target>

Тестирование результата


Вот и всё. В целевой директории лежит крошечный порт небольшого аспекта из реального Stream API, и он может запуститься на Java 6! Для этого создадим ещё одно правило для Ant-а:


<target name="run-as-java-6" description="Runs the target artifact in Java 6">
    <fail unless="env.JDK_6_HOME" message="JDK_6_HOME not set"/>
    <java jvm="${env.JDK_6_HOME}/bin/java" classpath="${step11TargetFile}" classname="${mainClass}" fork="true" failonerror="true">
        <jvmarg value="-Xbootclasspath/p:${step10TargetFile}"/>
        <arg value="foo"/>
        <arg value="bar"/>
        <arg value="baz"/>
    </java>
</target>

И вот тут нужно обратить просто особое внимание на использование не совсем стандартного -Xbootclasspath/p. Вкратце, его суть заключается в следующем: он позволяет JVM указать, откуда нужно загружать базовые классы в первую очередь. При этом, остальные классы из оригинального rt.jar будут лениво загружаться из $JAVA_HOME/jre/lib/rt.jar по мере необходимости. Убедиться в этом можно, используя ключ -verbose:class при запуске JVM.


Запуск самого примера также требует переменной окружения JDK_6_HOME, указывающей на JDK 6 или JRE 6. Теперь при вызове run-as-java-6 результат успешного портирования будет выведен на стандартный вывод:


PRE: stream
>FOO
>BAR
>BAZ
POST: stream

Работает? Да!


Заключение


Привыкнув в написанию кода на Java 8, хочется, чтобы этот код работал и на более старых версиях Java. Особенно, если в наличии есть довольно старая и увесистая кодовая база. И если в Интернете часто можно увидеть вопрос о том, существует ли вообще возможность работать именно со Stream API на более старых версиях Java, всегда скажут, что нет. Ну, почти что нет. И будут правы. Конечно, предлагаются альтернативные библиотеки со схожим функционалом, работающие на старых JRE. Мне лично больше всего импонирует Google Guava, и я часто использую её, когда Java 8 недостаточно.


Экспериментальный хак есть экспериментальный хак, и я сомневаюсь, что дальше демонстрации есть большой смысл идти дальше. Но, в целях исследования и духа экcпериментаторства, почему бы и нет? Ознакомиться с экспериментом поближе можно на GitHub.


Нерешённые и нерешаемые вопросы


Помимо проблемы с refid в Ant, открытыми для меня лично остаются несколько вопросов:


Работает ли этот пример на других реализациях JVM?

Работает на Oracle JVM, но лицензия Oracle запрещает развёртывание приложений, заменяющих часть rt.jar с использованием -Xbootclasspath.


Можно ли сформировать список классов зависимостей автоматически, не прибегая к ручному перебору?

Мне лично неизвестны автоматические методы такого анализа. Можно попробовать стянуть весь пакет java.util.stream.* целиком, но и проблем, думаю, будет больше.


Есть ли возможность запустить этот пример на Dalvik VM?

Имеется в виду Android. Я пробовал пропускать результаты через dx и запускать Dalvik VM с -Xbootclasspath прямо на реальном устройстве, но Dalvik упорно игнорирует такую просьбу. Подозреваю, причиной этого является то, что приложения для Dalvik VM форкаются от Zygote, которая, очевидно, ничего не подозревает о таких намерениях. Больше почитать о том, почему это сделать нельзя и чем это чревато, можно почитать на StackOverflow. И если бы и удалось запустить dalvikvm с -Xbootclasspath, я подозреваю, потребовался бы некий лончер и для самого приложения, который бы этот boot classpath и подменял. Такой сценарий, по всей видимости, не предоставляется возможным.


А как с GWT?

А это совершенно другая история и другой подход. Буквально на днях состоялся долгожданный релиз GWT 2.8.0 (к сожалению, версия 2.7.0 ещё два года назад), в которой полноценно реализованы лямбды и прочие возможности для исходников, написанных на Java 8. Впрочем, это всё было и до релиза в SNAPSHOT-версиях. Возиться с байткодом в GWT нельзя, потому как GWT работает только с исходным кодом. Для портирования Stream API на клиентскую сторону придётся, я думаю, просто собрать часть исходников из JDK 8, предварительно пропустив их через некий препроцессор, который преобразует исходники в удобоваримый для GWT вид (пример портирования RxJava).

Комментарии (0)

    Let's block ads! (Why?)

    [Перевод] Как работает Git

    400 музыкальных инструментов для Javascript

    WebAudioFont — новая технология сходная с DLS, но предназначенная для применения в веб-приложениях Javascript.

    На данный момент позволяет использовать 400 (четыре сотни) музыкальных инструментов для воспроизведения звука или сгенерированной музыки.

    image


    По сути, WebAudioFont это набор файлов Javascript, которые содержат массив аудиоданных и описание правил синтеза (AHDSR, частоту и пр.).
    Для воспроизведения используется плеер на основе Web Audio API.
    Все возможности Web Audio API доступны для используемых инструментов — можно подключать фильтры, плавно менять громкость и частоту, добавлять эхо и пр.
    Синтез звука производится по принципу Wavetable.

    Hello world


    Минимальный код для воспроизведения звука
    <html>
        <head>
            <script src='WebAudioFontPlayer.js'></script>
            <script src='webaudiofont/32.0.Accoustic_32Bsaccousticbs_461_460_45127.js'></script>
            <script>
                var AudioContextFunc = window.AudioContext || window.webkitAudioContext;
                var audioContext = new AudioContextFunc();
                var player=new WebAudioFontPlayer();
            </script>
        </head>
        <body>
            <p><a href='javascript:player.queueWaveTable(audioContext, audioContext.destination, _tone_Accoustic_32Bsaccousticbs_461_460_45127, 0, 12*4+7, 2);'>click!</a></p>
        </body>
    </html>
    
    

    Получится примерно такая страница.

    Подробнее о коде:

    <script src='WebAudioFontPlayer.js'></script> 
    
    

    — подключаем плеер WebAudioFontPlayer.js
    <script src='webaudiofont/32.0.Accoustic_32Bsaccousticbs_461_460_45127.js'></script> 
    
    

    — подключаем выбранный инструмент из файла 32.0.Accoustic_32Bsaccousticbs_461_460_45127.js
    <a href='javascript:player.queueWaveTable(audioContext, audioContext.destination, _tone_Accoustic_32Bsaccousticbs_461_460_45127, 0, 12*4+7, 2);'>click!</a>
    
    

    — по клику на ссылку вызываем проигрывание ноты.

    queueWaveTable (audioContext, target, preset, when, pitch, duration, continuous) это основная (да и, скорей всего, единственная необходимая) функция плеера. Её параметры подробней:

    • audioContext — текущий AudioContext из Web Audio API
    • target — узел через который будет воспроизводиться звук, в простейшем случае это audioContext.destination
    • preset — переменная содержащая пресет (в примере это _tone_Accoustic_32Bsaccousticbs_461_460_45127)
    • when — время начала воспроизведения в секундах относительно audioContext.currentTime
    • pitch — высота ноты
    • duration — длительность ноты в секундах
    • continuous — true если нужно игнорировать AHDSR

    Исходный код и дополнительную информацию с примерами можно посмотреть на сайте проекта WebAudioFont.

    Для каких приложений можно использовать WebAudioFont?


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

    image

    На сайте проекта есть дополнительные примеры по областям применения:

    Отличие от других библиотек


    WebAudioFont не является библиотекой или фреймворком, хотя и состоит их кода Javascript. Это большой набор музыкальных инструментов которые вы можете использовать в своих приложениях подключив парой строчек:
    image

    — в каталоге всего 170 инструментов с разыми вариантами звучания (по 2-3 на каждый инструмент).

    Комментарии (0)

      Let's block ads! (Why?)

      пятница, 28 октября 2016 г.

      Security Week 43: атака на серверы Dyn, жизнь поддельного техсаппорта, уязвимость в Linux

      Главной новостью недели стала атака на DNS-серверы компании Dyn в прошлую пятницу. Мощная DDoS-атака началась утром по времени США, проходила в два этапа и на несколько часов привела к проблемам с доступом или же к полной недоступности множества сайтов-клиентов Dyn (новость). Среди пострадавших — Twitter, Reddit, Github, Soundcloud, Spotify и другие. Фактически все указанные сайты работали, были недоступны обслуживающие их DNS-серверы, но на стороне пользователя невозможность транслировать имя веб-сайта в IP-адрес выглядела так же, как если бы сервис ушел в офлайн целиком. Как обычно, недоступность крупной соцсети привела к глюкам и падению сайтов, которые изначально были вообще не при чем (например, подвисший код элементов Twitter мешал загружаться сайту The Register).

      Позже подтвердились предположения об источнике атаки (новость) — это был ботнет IoT-устройств Mirai, ранее отметившийся массивной атакой на блог Брайана Кребса. Код Mirai был выложен в открытый доступ, что привело к заметному росту числа атакованных устройств. Их и так было немало: 380 тысяч по утверждению изначального «владельца» исходников.

      Термин «взлом» к подверженным устройствам не совсем применим: в большинстве случаев эксплуатируются банальные уязвимости и зашитые пароли. OEM-производитель Xiongmai, частично ответственный за небезопасные прошивки устройств, даже начал в США отзывную кампанию, которая, впрочем, относится только к нескольким тысячам IP-видеокамер. По остальным выпущены рекомендации и обновления прошивок. Проблема в том, что вряд ли все владельцы устройств будут обновлять устройства.

      Тема уязвимого интернета вещей продолжает развиваться, при том, что сама эпоха IoT еще даже не наступила толком. Надеюсь, что проблема уязвимых-по-умолчанию устройств, которые либо трудно, либо неудобно обновлять, начнет решаться. Атака на Dyn также послужит полезным опытом по усилению защиты от DDoS. Придется: по данным компании Level3, что в DDoS-атаке участвовало всего около 10% устройств из ботнета размером в полмиллиона.

      Microsoft предупреждает о поддельном антивирусе Security Essentials со встроенным телефонным мошенничеством

      Новость. Пост в блоге Microsoft.

      Эксперты из Microsoft подняли на этой неделе интересную тему телефонного кибер-мошенничества. Типовая для западных стран (насколько мне известно, в России схема непопулярна) «атака» происходит следующим образом: вам звонят якобы из техподдержки Microsoft, сообщают, что на компьютере все плохо (он заражен вирусом или что-то пооригинальнее) и предлагают удаленно решить проблему. Дальше возможны варианты — как со скачиванием вредоносной программы, так и с удаленным подключением к вашему десктопу. Или с оплатой за «удаление вируса», либо за установку поддельного ПО.

      В данном случае все работает немного по-другому: в сети распространяется поддельный инсталлятор Microsoft Security Essentials. Расчет на то, что в Windows 8 и 10 этот антивирус был заменен на Windows Defender, но кто-то мог запомнить старое название и «найти» программу в сети. После установки пользователю демонстрируется поддельный синий экран смерти с телефоном «техподдержки», дальнейшая обработка жертв переносится в офлайн. Наши коллеги из штатов проверили — еще в понедельник телефон работал, и там утверждали, что совершенно точно сертифицированы и авторизованы. Обычно так или иначе такие телефонные мошенники пытаются что-нибудь пользователю продать.

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

      Уязвимость в ядре Linux позволяет получить привилегии рута локально

      Новость. Сайт уязвимости от первооткрывателя (с логотипом!). Информация об уязвимости в трекере Red Hat.

      Уязвимость «Грязная корова» или Dirty Cow была названа так потому, что в ней задействован механизм Copy-On-Write. COW используется для оптимизации потребления ресурсов, в случае если разные процессы запрашивают один и тот же набор данных (например, на диске). В обычной ситуации потребовалось бы каждый раз создавать копию данных, но в случае COW копия создается только когда процесс пытается изменить информацию. Ошибка в ядре Linux позволяет создать race condition, результатом которого будет запись в оригинальный файл, а не в копию, даже если у иницииатора записи нет на это прав.

      Интересные детали есть на странице коммита, исправляющего уязвимость. Линус Торвальдс пишет, что пытался исправить проблему еще 11 лет назад, но тогда возникли какие-то сложности c системами на архитектуре S390. Возможно тогда на это не обратили внимание, так как практическая возможность «состояния гонки» зависит в том числе от производительности системы, и на тот момент атака казалась не реализуемой. В итоге уязвимость существует во всех версиях ядра Linux с 2007 года, и на данный момент запатчена не везде. Все бы ничего, но есть свидетельства наличия эксплойта «в полях».

      Что еще произошло:
      Google создал нейросеть, которая полностью самостоятельно разработала новый алгоритм шифрования (защищаясь от другого протоAI, пытавшегося расшифровать данные). Исследователи, создавшие нейросеть, не до конца уверены, как именно работает алгоритм. «Точно не XOR».

      Экстренный патч для Adobe Flash.

      Древности


      Семейство «Saratoga»

      Семейство резидентных опасных вирусов. Стандартным образом поражают каждый второй или десятый EXE-файл (в зависимости от версии вируса) при его выполнении или загрузке в память. После каждого удачного заражения файла объявляют один из свободных кластеров текущего диска «плохим» (так называемый псевдосбойный кластер).

      Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 44.

      Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.

      Комментарии (0)

        Let's block ads! (Why?)

        Как подружить Bagri и MongoDB

        Примерно месяц назад, я рассказал Хабру о проекте Bagri: NoSQL базе данных с открытым кодом, построенной поверх распределенного кэша.

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

        image


        На данный момент Bagri публикует два API для подключения к внешним системам: DataFormat API и DataStore API.

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

        Второй API служит для загрузки/сохранения/удаления документов из внешних систем хранения. Зачастую, для подключения к новому источнику документов необходимо реализовать оба интерфейса.

        Я покажу, как реализовать DataStore connector к MongoDB и использовать его в качестве системы хранения документов. В данном случае реализации DataFormat API не требуется, поскольку Mongo предоставляет документы в JSON формате, который изначально поддерживается системой.

        Сразу хочу сделать пару замечаний:

        1. Практическая польза от такого коннектора? Очевидно, Mongo можно просто использовать в качестве централизованного хранилища документов. Так же он может быть полезен в сценариях, описанных в данной статье, когда данные уже хранятся в Mongo, но ее возможностей стало не хватать для развития функционала системы;
        2. Я не являюсь знатоком MongoDB, если есть более оптимальные способы работы с ней, я буду рад их услышать;

        Итак, начнем.

        DataStore API предполагает реализацию интерфейса com.bagri.xdm.cache.api.DocumentStore:

        public interface DocumentStore {
                
                /**
                 * Lifecycle method. Invoked when the store initialized. 
                 * 
                 * @param context the environment context
                 */
                void init(Map<String, Object> context);
                
                /**
                 * Lifecycle method. Invoked when parent schema is closing
                 */
                void close();
        
                /**
                 * Load document from persistent store
                 * 
                 * @param key the document key
                 * @return XDM Document instance if corresponding document found, null otherwise
                 */
                Document loadDocument(DocumentKey key);
        
                /**
                 * Load bunch of documents from persistent store
                 * 
                 * @param keys the collection of document keys to load
                 * @return the map of loaded documents with their keys
                 */
                Map<DocumentKey, Document> loadAllDocuments(Collection<DocumentKey> keys);
        
                /**
                 * Load document keys. Can do it in synch or asynch way.
                 * 
                 * @return iterator over found document keys
                 */
                Iterable<DocumentKey> loadAllDocumentKeys();
        
                /**
                 * Stores document to persistent store.
                 * 
                 * @param key the document key
                 * @param value the XDM document instance
                 */
                void storeDocument(DocumentKey key, Document value);
        
                /**
                 * Stores bunch of documents to persistent store
                 * 
                 * @param entries the map of document keys and corresponding document instances
                 */
                void storeAllDocuments(Map<DocumentKey, Document> entries);
        
                /**
                 * Deletes document from persistent store
                 * 
                 * @param key the document key
                 */
                void deleteDocument(DocumentKey key);
        
                /**
                 * Deletes bunch o documents from persistent store 
                 * 
                 * @param keys the keys identifying documents to be deleted 
                 */
                void deleteAllDocuments(Collection<DocumentKey> keys);
                        
        }
        
        

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

        Для подключения к Mongo нам понадобятся адрес сервера mongod, имя базы данных и имена коллекций, из которых мы хотим загружать документы. Определим имена для этих параметров: mongo.db.uri, mongo.db.database, mongo.db.collections. Тогда код инициализации подключения к серверу mongo может выглядеть таким образом:

        public class MongoDBStore extends DocumentStoreBase implements DocumentStore {
                
                private MongoClient client;
                private MongoDatabase db;
                private Map<String, MongoCollection<org.bson.Document>> clMap = new HashMap<>();
                
                @Override
                public void init(Map<String, Object> context) {
                    super.setContext(context);
                    String uri = (String) context.get("mongo.db.uri"); 
                    MongoClientURI mcUri = new MongoClientURI(uri);
                    client = new MongoClient(mcUri);
        
                    String dbName = (String) context.get("mongo.db.database");
                    db = client.getDatabase(dbName);
                        
                    String clNames = (String) context.get("mongo.db.collections");
                    boolean all = "*".equals(clNames);
                    List<String> clns = Arrays.asList(clNames.split(","));
                    for (String clName: db.listCollectionNames()) {
                        if (all || clns.contains(clName)) {
                            MongoCollection<org.bson.Document> cln = db.getCollection(clName); 
                            clMap.put(clName, cln);
                        }
                    }
                }
        
                @Override
                public void close() {
                        client.close();
                }
        }
        
        

        Метод init принимает параметры подключения к серверу mongod из контекста, устанавливает соединение и кэширует объект MongoCollection для каждой коллекции, объявленной для загрузки. Теперь нам нужно реализовать метод для загрузки всех ключей документов.
                private String getMappingKey(String id, String cln) {
                      return id + "::" + cln;
                }
                
                private String[] getMappingParts(String keyMap) {
                      return keyMap.split("::");
                }
        
                @Override
                public Iterable<DocumentKey> loadAllDocumentKeys() {
                    SchemaRepository repo = getRepository();
                    if (repo == null) {
                        return null;
                    }
        
                    String id;
                    DocumentKey key;
                    Map<DocumentKey, String> keyMap = new HashMap<>();
                    for (MongoCollection<org.bson.Document> cln: clMap.values()) {
                        String clName = cln.getNamespace().getCollectionName();
                        // load _ids only
                        MongoCursor<org.bson.Document> cursor =   cln.find().projection(include(“_id”)).iterator();
                        
                        while (cursor.hasNext()) {
                              org.bson.Document doc = cursor.next();
                              id = doc.get(“_id”).toString();
                              // TODO: handle possible duplicates via revisions
                              key = repo.getFactory().newDocumentKey(id, 0, 1);
                              keyMap.put(key, getMappingKey(id, clName));
                        }
                    }
                    PopulationManagement popManager = repo.getPopulationManagement();
                    popManager.setKeyMappings(keyMap);
                    return keyMap.keySet();
                }
        
        

        И методы загрузки документов, соответствующих загруженным ключам:
               @Override
                public Map<DocumentKey, Document> loadAllDocuments(Collection<DocumentKey> keys) {
                    Map<DocumentKey, Document> entries = new HashMap<>(keys.size());
                    for (DocumentKey key: keys) {
                        Document doc = loadDocument(key);
                        if (doc != null) {
                              entries.put(key, doc);
                        }
                    }
                    return entries;
                }
        
                @Override
                public Document loadDocument(DocumentKey key) {
                  SchemaRepository repo = getRepository();
                  Document doc = null;
                
                  PopulationManagement popManager = repo.getPopulationManagement(); 
                  String id = popManager.getKeyMapping(key); 
                  if (id == null) {
                      return null;
                  }
                  String[] mParts = getMappingParts(id);
                  Document newDoc = null;
        
                  int[] clns = null;
                  com.bagri.xdm.system.Collection xcl = repo.getSchema().getCollection(mParts[1]);
                  if (xcl != null) {
                        clns = new int[] {xcl.getId()};
                  }
                  MongoCollection<org.bson.Document> cln = clMap.get(mParts[1]);
        
                  Object oid;
                  Date creDate;
                  try {
                        oid = new ObjectId(mParts[0]);
                        creDate = ((ObjectId) oid).getDate();
                  } catch (IllegalArgumentException ex) {
                        oid = mParts[0];
                        creDate = new Date();
                  }
                  org.bson.Document mongoDoc = cln.find(eq("_id", oid)).first();
                  String content = mongoDoc.toJson(new JsonWriterSettings(true));
        
                  try {
                        DocumentManagementl docMgr = (DocumentManagement) repo.getDocumentManagement(); 
                        newDoc = docMgr.createDocument(key, mParts[0], content, “JSON”, creDate, "owner", 1, clns, true);
                  } catch (XDMException ex) {
                        // TODO: log error, but do not stop the whole loading
                  }
                  return doc;
              }
        
        

        Для начала этого достаточно, методы storeDocument/storeAllDocuments и deleteDocument/deleteAllDocuments предлагаю реализовать читателю самостоятельно. Также прошу учесть, что приведенный выше код предназначен только в целях демонстрации процесса реализации коннектора и не обрабатывает различные исключительные ситуации и возможные дополнительные параметры конфигурации. Полный код коннектора можно посмотреть и собрать из репозитория bagri-extensions.

        Теперь нам нужно зарегистрировать DataStore connector и объявить схему, которая будет его использовать. Для этого нам необходимо добавить конфигурацию коннектора в файл <BAGRI_HOME>/config/config.xml, в секцию dataStores:

        <dataStore name="mongo">
            <version>1</version>
            <createdAt>2016-08-01T16:17:20.542+03:00</createdAt>
            <createdBy>admin</createdBy>
            <description>MongoDB data store</description>
            <enabled>true</enabled>
            <storeClass>com.bagri.samples.MongoDBStore</storeClass>
            <properties>
                <entry name="mongo.db.uri">http://mongodblocalhost:27017</entry>
                <entry name="mongo.db.database">test</entry>
                <entry name="mongo.db.collections">*</entry>
             </properties>
        </dataStore>
        
        

        Мы протестируем работу коннектора на примере коллекции restaurants, рассматриваемой во многих примерах работы с MongoDB. Загрузите коллекцию тестовых документов в Mongo, как показано здесь: http://ift.tt/2eDGWXu. Теперь зарегистрируем схему для работы с MongoDB и настроим ее на загрузку данных из данной коллекции. В том же файле config.xml добавим новую схему в раздел schemas:

        Добавляем следующие параметры:

        <entry name="xdm.schema.store.enabled">true</entry>
        <entry name="xdm.schema.store.type">mongo</entry>
        <entry name="mongo.db.collections">restaurants</entry>
        
        

        Раздел schemas в config.xml с новыми параметрами
        <schema name="Mongo" active="true">
                    <version>1</version>
                    <createdAt>2016-08-01T21:30:58.096+04:00</createdAt>
                    <createdBy>admin</createdBy>
                    <description>Schema for MongoDB</description>
                    <properties>
                        <entry name="xdm.schema.store.tx.buffer.size">1024</entry>
                        <entry name="xdm.schema.data.backup.read">false</entry>
                        <entry name="xdm.schema.trans.backup.async">0</entry>
                        <entry name="xdm.schema.store.enabled">true</entry>
                        <entry name="xdm.schema.thread.pool">10</entry>
                        <entry name="xdm.schema.data.stats.enabled">true</entry>
                        <entry name="xdm.schema.query.cache">true</entry>
                        <entry name="xdm.schema.store.type">mongo</entry>
                        <entry name="mongo.db.collections">restaurants</entry>
                        <entry name="xdm.schema.format.default">JSON</entry>
                        <entry name="xdm.schema.ports.first">10300</entry>
                        <entry name="xdm.schema.ports.last">10400</entry>
                        <entry name="xdm.schema.population.size">1</entry>
                        <entry name="xdm.schema.population.buffer.size">1000000</entry>
                        <entry name="xdm.schema.data.backup.async">1</entry>
                        <entry name="xdm.schema.store.data.path">../data/mongo</entry>
                        <entry name="xdm.schema.dict.backup.sync">0</entry>
                        <entry name="xdm.schema.trans.backup.sync">1</entry>
                        <entry name="xdm.schema.query.backup.sync">0</entry>
                        <entry name="xdm.schema.buffer.size">16</entry>
                        <entry name="xdm.schema.dict.backup.async">1</entry>
                        <entry name="xdm.schema.dict.backup.read">true</entry>
                        <entry name="xdm.schema.trans.backup.read">false</entry>
                        <entry name="xdm.schema.query.backup.async">0</entry>
                        <entry name="xdm.schema.members">localhost</entry>
                        <entry name="xdm.schema.data.backup.sync">0</entry>
                        <entry name="xdm.schema.partition.count">157</entry>
                        <entry name="xdm.schema.query.backup.read">true</entry>
                        <entry name="xdm.schema.transaction.timeout">0</entry>
                        <entry name="xdm.schema.health.threshold.low">25</entry>
                        <entry name="xdm.schema.health.threshold.high">0</entry>
                        <entry name="xdm.schema.query.parallel">true</entry>
                        <entry name="xdm.schema.partition.pool">32</entry>
                        <entry name="xqj.schema.baseUri">file:///../data/mongo/</entry>
                        <entry name="xqj.schema.orderingMode">2</entry>
                        <entry name="xqj.schema.queryLanguageTypeAndVersion">1</entry>
                        <entry name="xqj.schema.bindingMode">0</entry>
                        <entry name="xqj.schema.boundarySpacePolicy">1</entry>
                        <entry name="xqj.schema.scrollability">1</entry>
                        <entry name="xqj.schema.holdability">2</entry>
                        <entry name="xqj.schema.copyNamespacesModePreserve">1</entry>
                        <entry name="xqj.schema.queryTimeout">0</entry>
                        <entry name="xqj.schema.defaultFunctionNamespace"></entry>
                        <entry name="xqj.schema.defaultElementTypeNamespace"></entry>
                        <entry name="xqj.schema.copyNamespacesModeInherit">1</entry>
                        <entry name="xqj.schema.defaultOrderForEmptySequences">2</entry>
                        <entry name="xqj.schema.defaultCollationUri"></entry>
                        <entry name="xqj.schema.constructionMode">1</entry>
                    </properties>
                    <collections>
                        <collection id="1" name="restaurants">
                            <version>1</version>
                            <createdAt>2016-08-01T01:01:26.965+03:00</createdAt>
                            <createdBy>admin</createdBy>
                            <description>Mongo restaurants collection</description>
                            <enabled>true</enabled>
                        </collection>
                    </collections>
                    <fragments/>
                    <indexes/>
                    <triggers/>
                </schema>
        
        


        Кроме того, нужно создать новый файл с профилем сервера. В каталоге <BAGRI_HOME>/config создайте файл mongo.properties и укажите в нем используемую сервером схему:
        xdm.cluster.node.schemas=Mongo
        
        

        Удостоверьтесь, что сервер MongoDB запущен и ждет подключений по адресу, указанному в настройках коннектора. Теперь можно стартовать сервер Bagri. В каталоге <BAGRI_HOME>/bin выполните команду >bgcache.cmd mongo (on Windows) или >./bgcache.sh mongo (on Linux). Данный скрипт стартует одиночный сервер Bagri с настройками из профиля mongo.properties. По окончании загрузки серверный лог должен содержать следующие строчки:

        image

        показывающие, что в коннектор инициализировал схему Mongo и загрузил в нее 25359 документов из внешнего сервера MongoDB.

        Теперь я покажу, как можно манипулировать документами JSON с помощью запросов XQuery.

        Для интерактивного выполнения запросов XQuery нам понадобится клиент, позволяющий это делать. С дистрибутивом Bagri поставляется plugin к VisualVM, предоставляющий данную функциональность. Инструкцию по его установке смотрите здесь.

        Запустите административный сервер Bagri <BAGRI_HOME>/bin/bgadmin. Откройте приложение VisualVM, подключитесь к административному серверу Bagri Manager и выберите схему Mongo. Закладка DocumentManagement позволяет работать с документами и коллекциями:

        image

        , а закладка QueryManagement с запросами XQuery. Выполните следующий простой запрос для выборки ресторана по его идентификатору:

        declare namespace m="http://ift.tt/1IAFejR"; 
        let $props := map{'method': 'json', 'indent': fn:true()}
        
        for $uri in fn:uri-collection("restaurants")
        let $map := fn:json-doc($uri)
        where m:get($map, 'restaurant_id') = '40362098'
        return (fn:serialize($map, $props), '\&\#xa;')
        
        

        * Обратите внимание, что символ перевода строки в самой последней строке экранирован \, так как Хабр превращает его в действительный перевод строки, так что при выполнении запроса символ \ нужно убрать.

        image

        Или другой, для выборки ресторанов по типу кухни:

        declare namespace m="http://ift.tt/1IAFejR"; 
        let $props := map{'method': 'json'}
        
        for $uri in fn:uri-collection("restaurants")
        let $map := fn:json-doc($uri)
        where m:get($map, 'cuisine') = 'Bakery'
        return (fn:serialize($map, $props), '\&\#xa;')
        
        

        image

        XQuery легко позволяет делать любые выборки, доступные в Mongo (кроме запросов по гео-индексам, прямо из коробки они еще не поддерживаются).

        Теперь я покажу запросы, которые не поддерживаются в MongoDB: JOIN. Для этого можно привести данные в коллекции restaurants к более нормализованному виду, например отделить отзывы о ресторанах от данных по самому ресторану и сохранить их в разных коллекциях.

        Выполните данный запрос и сохраните результаты в файл, потом сделайте импорт полученных данных в MongoDB, в коллекцию rest-short.

        declare namespace m="http://ift.tt/1IAFejR";
        let $props := map{'method': 'json'}
        
        for $uri in fn:uri-collection("restaurants")
        let $rest := fn:json-doc($uri)
        let $rest := m:remove($rest, '_id')
        let $rest := m:remove($rest, 'grades')
        return (fn:serialize($rest, $props), '\&\#xa;')
        
        

        Следующий запрос выводит данные по отзывам. Так же сохраните их в отдельный файл и затем импортируйте в MongoDB в коллекцию grades.
        declare namespace a="http://ift.tt/2dTXPct";
        declare namespace m="http://ift.tt/1IAFejR";
        let $props := map{'method': 'json'}
        
        for $uri in fn:uri-collection("restaurants")
        let $rest := fn:json-doc($uri)
        let $grades := m:get($rest, 'grades')
        return 
          for $i in (1 to a:size($grades))
            let $grade := a:get($grades, $i)
            let $date := m:get($grade, 'date')
            return ('{"restaurant_id": "', m:get($rest, 'restaurant_id'), 
                '", "date": ', fn:serialize($date, $props), 
                 ', "grade": "', m:get($grade, 'grade'), 
                '", "score": "', m:get($grade, 'score'), '"}', '\&\#xa;')
        
        

        Теперь поправьте настройки схемы, чтобы заявить новые коллекции для загрузки:
        <schema name="Mongo" active="true">
                     ………...
                    <properties>
                        <entry name="xdm.schema.store.collections">rest-short, grades</entry>
                         ……...
                    </properties>
                    <collections>
                        <collection id="2" name="rest-short">
                            <version>1</version>
                            <createdAt>2016-08-01T01:01:26.965+03:00</createdAt>
                            <createdBy>admin</createdBy>
                            <description>Restaurant headers collection</description>
                            <enabled>true</enabled>
                        </collection>
                        <collection id="3" name="grades">
                            <version>1</version>
                            <createdAt>2016-08-01T01:01:26.965+03:00</createdAt>
                            <createdBy>admin</createdBy>
                            <description>Restaurant grades collection</description>
                            <enabled>true</enabled>
                        </collection>
                    </collections>
                    <fragments/>
                    <indexes/>
                    <triggers/>
        </schema>
        
        

        Рестартуйте сервер Bagri для загрузки новых коллекций с данными. Теперь можно проверить, как работают join’ы. Выполните следующий запрос для формирования полной структуры restaurants из двух коллекций:
        declare namespace m="http://ift.tt/1IAFejR";
        let $props := map{'method': 'json'}
        
        for $ruri in fn:uri-collection("rest-short")
        let $rest := fn:json-doc($ruri)
        let $rid := m:get($rest, 'restaurant_id')
        let $addr := m:get($rest, 'address')
        let $txt := ('{"restaurant_id": "', $rid,
                '", "cuisine": "', m:get($rest, 'cuisine'), 
                '", "name": "', m:get($rest, 'name'), 
                '", "borough": "', m:get($rest, 'borough'), 
                '", "address": ', fn:serialize($addr, $props),
                ', "grades": [')
        return ($txt, fn:string-join(
          for $guri in fn:uri-collection("grades")
          let $grade := fn:json-doc($guri)
          let $gid := m:get($grade, 'restaurant_id')
          where $gid = $rid
          return fn:serialize(m:remove(m:remove($grade, '_id'), 'restaurant_id'), $props), ', '), ']}\&\#xa;')
        
        

        Итак, мы с вами рассмотрели как можно реализовать DataStore connector к MongoDB и использовать его в качестве системы хранения документов. Надеюсь эта статья сможет стать для вас отправной точкой для написания других расширений Багри или просто побудит вас более подробно ознакомиться с этим интересным продуктом. Проекту всегда требуются Java разработчки заинтересованные в развитии Bagri, более подробно на код проекта можно посмотреть на Гитхабе.

        Комментарии (0)

          Let's block ads! (Why?)

          Сделано в МТИ: система контроля версий Gitless


          Все вы знаете систему Git. Хотя бы слышали — это наверняка. Разработчики, которые пользуются системой, ее или любят, или ругают за сложный интерфейс и баги. Система управления версиями Git де-факто является стандартом в индустрии. У разработчика могут быть мнения о преимуществах Mercurial, но чаще всего приходится мириться с требованием уметь пользоваться Git. Как у любой сложной системы, у нее множество полезных и необходимых функций. Однако, до гениальной простоты добираются не все, поэтому существующая реализация оставляла пространство для совершенствования.

          Простыми словами — мудреным приложением было трудно пользоваться. Поэтому в лаборатории Массачусетского Технологического Института взялись за улучшения и отсекли все «проблемные элементы» (ведь то, что для одного проблема, для другого легко может преимуществом). Улучшенную и упрощенную версию назвали Gitless. Её разрабатывали с учетом 2400 вопросов, связанных с Git и взятых с сайта разработчиков StackOverflow.

          Команда авторов вычленила самые проблемные места в Git, включая две концепции staging и stashing. Затем они предложили изменения, призванные решить известные проблемы.

          Что не так с Git


          Многие пользователи жаловались, что Git нуждается в новом интерфейсе. Специалисты даже составили документ Что не так с Git? Концептуальный анализ дизайна. Авторы: S. Perez De Rosso и D. Jackson.

          Пример


          git checkout < file > // отбросить все изменения в одном файле с последней выгрузки в систему
          git reset --hard    // отбросить все изменения во всех файлах с последней выгрузки в систему
          
          

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

          Большинство пользователей Git применяют его для небольшого числа команд, а оставшиеся единицы знают платформу на более глубоком уровне. Получается, что в основном платформа нужна для базовых функций, а большой пласт возможностей остается для слишком узкого круга. Это говорит о неправильной работе Git.

          Краткое сравнение базовых функций с предыдущией версией


          Одной из ярких характеристик Gitless является то, что версия игнорирует функцию под названием staging. Она позволяет сохранять отдельные части файла. Удобно, но может создавать проблемные ситуации. Ключевое отличие между этой и функцией stashing заключается в том, что вторая скрывает изменения из рабочей области.

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

          Функция staging индексирует изменения, внесенные в файл. Если вы пометили файлы staged, Git понимает, что вы подготовили их к выгрузке.

          В Gitless нет концепции stashing. Представьте следующую ситуацию. Вы находитесь в разгаре разработки проекта и должны переключиться в другую его ветку, но вы еще не выгрузили в систему свою наполовину выполненную работу. Функция stashing берет сделанные вами изменения и сохраняет их в стек с неоконченными изменениями, которые вы можете восстановить позднее.

          Автор руководства по Gitless сообщает, что проблема появляется при переключении между ветками. Может быть сложно запоминать какой из stashes где находится. Ну и вершиной всего этого стало то, что функция не помогает в случае когда вы в процессе мерджа, включающего в себя конфликтные файлы. Таково мнение Переза де Россо.

          Благодаря Gitless эта проблема решается. Ветки стали полностью автономными по отношению друг к другу. Это делает работу намного проще и позволяет разработчикам избегать путаницы, когда нужно постоянно переключаться между задачами.

          Сохранение изменений


          Gitless прячет область стадий в целом, что делает процесс более прозрачным и менее сложным для пользователя. Для решения задач есть намного более гибкие команды «commit». Причем они позволят делать такие действия, как выделение сегментов кода для коммита.

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

          Разветвление процессов разработки


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

          Проще говоря, в версии Gitless вам не нужно помнить о незагруженных в систему изменениях, которые находятся в конфликте с изменениями в ветке назначения.

          Также вы сможете отложить решение конфликтной ситуации, если у вас середина мержда или fuse. Конфликт останется пока вы не переключитесь обратно.

          Работа с удаленными репозиториями


          Вот синхронизация с прочими репозиториями происходит в обоих программах одинаково.

          Ещё одно преимущество новой версии — возможность переключаться к старой без потери кода. При этом ваши коллеги могут быть даже не в курсе, что вы пользуетесь другим ПО.

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

          Что в итоге


          Получилось приложение, которое сохраняет функционал Git, но в то же время стало более простым в изучении и использовании командами разработки. На самом деле и до Gitless уже были попытки улучшить Git. Но по словам Филипа Гуо (он ассистент профессора когнитивной науки в Калифорнийском университете Сан-Диего) эта версия впервые достигла целей по преображению интерфейса и действительному решению главны проблем.
          Проект использовал строгие методы по созданию программного обеспечения. Это необходимо для вычленения недостатков в одном из наиболее широко применяемых во всем мире программных проектов. В прошлом множество пользователей приводили смешные аргументы как за, так и против Git, но все они не были основаны на научном подходе.

          На примере Gitless становится очевидно, что подход упрощения возможно применять и к другим сложным системам. Например, Google Inbox и Dropbox.

          Комментарии (0)

            Let's block ads! (Why?)

            Видео докладов с Techleads Meetup #1

            15-го октября в Badoo прошел пилотный Techleads Meetup. А сегодня мы выкладываем видео выступлений.

            Познавательного Вам просмотра!

            «Мобильный веб: назад в будущее»
            Виталий Шароватов, Mobile Web Team Lead и Руслан Байрамкулов, Senior Mobile Web QA Engineer (Badoo)



            «Как автотесты ускоряют релизы в OK.ru»
            Никита Макаров, руководитель отдела тестирования (Одноклассники)



            «Багфиксинг процесса разработки в iOS: взгляд с двух сторон»
            Екатерина Николаенко, iOS QA Lead и Катерина Трофименко, iOS Developer (Badoo)



            «Технологии vs коммуникации: что важнее?»
            Альгис Фатеев, руководитель тестирования (Avito)



            Видео и презентации с прошлых конференций и митапов вы найдете на нашем сайте BadooTech.

            Комментарии (0)

              Let's block ads! (Why?)

              [Перевод] Что делает игры смешными? Комизм и юмор в видеоиграх. Часть первая

              Joker 2016: Вертикальное масштабирование

              Помимо «чисел Joker 2016», перечисленных нами заранее, теперь можно назвать ещё одно: на конференции наши фотографы сделали более 5000 снимков. Неудивительно, что на обработку ушло ощутимое время, и на Хабре уже успел появиться отзыв глазами участника от dbelob — а теперь мы публикуем свой.

              Резкие отличия Joker 2016 от предыдущих «Джокеров» бросались в глаза сразу. В чём они состояли, о чём рассказывали на конференции в этот раз, и какие доклады по зрительским оценкам оказались лучшими?

              Первый день


              Иногда не требуется даже дожидаться открытия конференции, чтобы ощущить её отличие от прошлогодней: в случае с Joker 2015 достаточно было просто прийти на площадку. В этот раз, собрав более 1000 человек, петербургская конференция переросла размеры «Park Inn Пулковской» и прошла в «ЭкспоФоруме». Оказавшись под его высоченными потолками, сложно было не впечатлиться масштабом. Можно сказать, что у конференции произошло вертикальное масштабирование во всех смыслах.

              В разы увеличилась площадь. Это позволило размахнуться как следует, разместив множество нового: от ретро-компьютеров и фотобудки до отдельного мини-трека для IT-директоров и конкурса iCanCode. На одном и том же мероприятии присутствовали и шлем виртуальной реальности VFX1 из 90-х, и появившийся в 2016-м HTC Vive, так что можно было лично оценить путь, проделанный VR за двадцать лет. И, наконец, с площадью увеличилось главное: число треков возросло с четырёх до целых шести.

              Ещё одно отличие возникло с открытием конференции, и оно имело значение не для присутствовавших в «ЭкспоФоруме», а для всех остальных: впервые в истории Joker видеотрансляция одного из треков была бесплатной. Но в этом случае, кроме новых возможностей, появились и новые сложности. С трансляцией возникли технические проблемы (предоставленный нам канал на upload оказался меньше, чем должен был), заставившие немало людей побегать в мыле. Позже ситуацию удалось исправить, но некоторое время всё ощущалось так, как на этом нашем твите.

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

              Что до содержания доклада, то ключевым его слайдом стала «Кривая им. Ш». Разделив работу над оптимизацией на несколько разных «зон», Шипилёв решительно заявил, что поначалу речь идёт «просто о том, какую часть говнокода переписывать в первую очередь». Это означает, что полезен будет любой профайлер, а все слова про «преждевременную оптимизацию как корень всех зол» не более чем ментальная ловушка. Вот позже, когда очевидные ошибки устранены и проект уже перешёл из «зелёной зоны» в «жёлтую», всё становится сложнее.

              А после кейноута пришло время расходиться по залам, и тут ощущалась ещё одна сложность. Поскольку на этом Joker треков было целых шесть, выбор стал гораздо богаче, а в результате сделать его стало сложнее: куда ни пойди, упускаешь что-то интересное. И в этом тексте тоже упомянута лишь небольшая часть докладов, иначе его попросту сложно было бы дочитать. Но всем бы такие сложности, как «слишком много интересного»! В конце концов, всё пропущенное участники смогут посмотреть в записи.

              Так что много любопытного происходило параллельно: пока в одном из залов тему производительности развивал Андрей apangin Паньгин (Одноклассники), в другом выступление Баруха jbaruch Садогурского (JFrog) и Виктора gAmUssA Гамова (Hazelcast) тоже перекликалось с кейноутом, но совсем иначе. Их предыдущие выступления с Groovy-паззлерами были так тепло приняты, что теперь они выступали уже с третьей порцией паззлеров, и в одном из них предлагали определить, какого докладчика выбрал бы для кейноута такой groovy-код:

              Интересные факты о Kotlin можно было узнать ещё до начала доклада Андрея abreslav Бреслава (JetBrains), руководящего разработкой этого языка: в ожидании времени начала Андрей охотно отвечал на вопросы из зала. Поэтому зрители узнали любопытные детали не только о корутинах, которым был посвящён доклад (язык обзаведётся ими в 2017-м с релизом версии 1.1), но и о другом. Например, что работа над JavaScript в Kotlin продвигается полным ходом, а вот цель добавить в язык pattern matching не ставится: «На заре проекта пытались сделать, но тогда от этого отказались, а сейчас мы без этого не страдаем, да и люди в целом тоже. Потребность в полноценном pattern matching есть в основном у тех, кто пользовался Haskell или Scala и теперь считает, что это must have».

              Когда пришло время обеда, не обошлось без очередей, но при этом в зале для обеда и кофе-брейков была оборудована сцена подкаста «Разбор полётов» — так что, ожидая своей очереди, можно было слушать бодрые диалоги резидентов и гостей «Разбора». Видеозаписи этих диалогов уже доступны зрителям Joker, а спецвыпуск самого подкаста, записанный на конференции, доступен всем.

              У Тагира lany Валеева (JetBrains) собрался полный зал на «причудах Stream API», показывавших, сколько в этом API контринтуитивных ситуаций. Это вызывало реакцию «я-то считал использование стримов безопасным, как наивен я был», получался забавный результат: доклад одного из главных в мире энтузиастов стримов отпугивал людей от них. Более того: в YouTrack можно найти тикет, по которому получается, что с помощью Тагира IntelliJ IDEA станет помогать людям убирать стримы из кода. Что дальше, Мартин Одерски начнёт помогать людям мигрировать со Scala?

              Рассказ об invokedynamic был от Чарльза Наттера (Red Hat). Понятно, что эта тема близка человеку, много лет работающему над JRuby («до появления invokedynamic JVM была построена вокруг Java, а другим языкам приходилось прибегать к грязным трюкам»). Но любопытно было узнать из его слайда, что он ещё в 2011-м твитнул «это изменит лицо платформы», сделав довольно точный прогноз: спустя пять лет invokedynamic называют «секретным оружием» Java, проторившим дорогу лямбдам.

              А закрывающий кейноут первого дня органично переходил в вечеринку. Сначала Сэм Аарон объяснял, почему создал инструмент для музыкального лайвкодинга Sonic Pi: «Использовать языки программирования только для задач энтерпрайза — это всё равно что использовать русский или английский только для юридических документов». Затем демонстрировал основные принципы работы с ним и призвал желающих помочь с переводом туториала на русский (проект ориентирован и на детей, так что английского тут недостаточно).

              Затем, переместившись в другой зал, он принялся за собственно лайвкодинг, отыграв двухчасовой сет. Код крупно выводился на экран, и временами Аарон обращался к зрителям с помощью комментариев в нём. Наттер, стоявший во время сета в первом ряду, снял фрагмент выступления телефоном в 4K-разрешении:

              Второй день


              Большую сцену следующим утром занял тот же Чарльз Наттер, открывая день кейноутом «From Java to Assembly: Down the Rabbit Hole». На втором дне были студенческие треки, так что среди зрителей было много начинающих — и доклад соответствовал этому, начинаясь с объяснения ключевых терминов вроде «байткода». Но по мере углубления в кроличью нору у «взрослых» тоже появлялось о чём послушать: многие ли пользуются PrintAssembly?

              Ожидалось, что далее пути студентов и «взрослых» полностью разойдутся по разным трекам. Однако жизнь внесла свои коррективы: оказалось, что опытные разработчики жаждут слушать Владимира vlkrasil Красильщика даже в том случае, когда его доклад ориентирован на начинающих. В итоге на его выступлении «Анти-введение в Big Data» был аншлаг: там собрались и новички, и профессионалы.

              Резко контрастировало с этим выступление Алексея Шипилёва о Java Memory Model: поскольку разжёвывать основы было некогда, доклад сразу требовал от зрителей определённого уровня понимания JMM, и там начинающим делать было нечего. Тем временем предыдущее выступление Шипилёва продолжало откликаться в сердцах. Например, Алексей zaleslaw Зиновьев в своём докладе о Spark 2.0 объяснял: «После кейноута все только и говорят о том, у кого оптимизация в какой зоне находится. Так вот, в бигдате вам повезёт, если вы окажетесь хоть в какой-то зоне!»

              А между докладами зрители пользовались продолжительными кофе-брейками для двух вещей. Во-первых, можно было как следует изучить всё находящееся на большой территории: и подойти к стендам спонсоров, и сфотографироваться с со «спикерами-супергероями». А во-вторых, как следует поспрашивать самих спикеров в дискуссионных зонах, не опаздывая на следующий доклад.

              Позже среди самих этих «следующих докладов» было выступление Сандера Мака о модулях Java 9, а мало кто в мире знает о них столько: сейчас Мак пишет о них книгу, а по работе давно столкнулся с фреймворком OSGi, предвосхитившим Jigsaw. Опираясь на свой опыт, он объяснял: «Зачем вообще понадобился Jigsaw при существовании OSGi? Потому что без Jigsaw не модуляризовать непосредственно JDK. А почему тогда не использовать его только для JDK, оставив OSGi для всего остального? Хороший вопрос, вообще-то можно было бы сделать и так, но сказывается то, что OSGi не пользуется большой популярностью».

              Олега m0nstermind Анастасьева из «Одноклассников» можно было увидеть в этом докладе на первом ряду, а он только недавно заявил нам «переход к модулям в нашем случае не даст преимуществ, оправдывающих его». Тут было не удержаться от вопроса «Ну как, Мак не переубедил в этом?» Ответ Олега оказался примерно таким:

              Любителям Gradle на этом Joker и так было что послушать (речь о нём заходила многократно), но доклад о нововведениях ветки 3.x был особенным: рассказывал непосредственно участник команды Gradle Рене Грошке. Для начала он уточнил, почему версия 3.0 получила такой номер, если не лопается от новых возможностей: «У нас major release делается не в связи с новыми фичами, а в связи с прекращением поддержки deprecated-фич. Поэтому версия 2.0 в своё время многих разозлила!»

              Если слайды о composite builds из недавней версии 3.1 вопросов ни у кого не вызывали, то изображение ниже Грошке показал со словами «Это первая конференция, на которой я ожидаю, что люди узнают изображённое». А вы узнаёте или нет? Почему посетители петербургской Joker знают это лучше других?

              Узнать правду
              На снимке видно часть острова Котлин, находящегося в Петербурге и подарившего название языку Kotlin. В этом году в Gradle стали использовать Kotlin для билд-скриптов.

              Тем временем конференция подошла к концу, и завершал её кейноут Гиля Тене (Azul Systems) о «прагматичном перфомансе». Тене призывал оторваться от миллисекунд и задуматься, что нам вообще нужно: «Глупо сравнивать скорость в отрыве от практических задач. Возьмём спортивный автомобиль и мопед — что из них быстрее? А теперь сравним их в условиях пробки: на чём реально быстрее доберёшься до работы? С перфомансом то же самое. То, сколько запросов в секунду может обрабатывать система — это теоретический подход. Прагматичный такой: “Должны ли все её ответы до единого быть правильными?”»

              Остаётся подвести итоги — вот какие доклады конференции стали лучшими по оценкам зрителей:

              1. Близкие Контакты JMM-степени — Алексей Шипилёв (Red Hat)
              2. Причуды Stream API — Тагир Валеев (JetBrains)
              3. Анти-введение в Big Data — Владимир Красильщик (Яндекс)
              4. Advanced search for your legacy application — David Pilato (elastic)
              5. Асинхронно, но понятно: сопрограммы в Kotlin — Андрей Бреслав (JetBrains)

              В общем, масштабирование произошло успешно: без сложностей не обошлось, но их в таком случае стоит ожидать, и в итоге они были преодолены. Теперь будем ждать Java-разработчиков весной в Москве и Новосибирске: уже открыт приём докладов и на JPoint, и на JBreak. А напоследок — интересный штрих: спикер Рафаэль Уинтерхалтер (Scienta) в твиттере поделился тем, что дала конференция ему. Такое за деньги не купишь, да.

              Комментарии (0)

                Let's block ads! (Why?)

                [Из песочницы] NaNoGenMo: как компьютеры пишут новеллы

                Ноябрь считается месяцем литературного творчества. Каждый год в интернете проходит мероприятие NaNoWriMo (National Novel Writing Month). Участники должны до конца месяца написать новеллу длиной не менее 50000 слов. За 17 лет в нем поучаствовали больше 20000 человек.


                В 2013 году у программистов появилось аналогичное соревнование — NaNoGenMo (National Novel Generation Month). Задача NaNoGenMo — написать программу, которая сгенерирует новеллу длиной 50000 слов или больше. При этом требования к новелле довольно слабые — подойдет любой текст достаточной длины. Как вы увидите, это может быть сборник рассказов, пьеса, кулинарная книга, словарь или туристический путеводитель. На самом деле, произведение не обязано даже быть текстовым.


                image

                Графическая новелла «Сгенерированный детектив»

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


                print 'мяу ' * 50000
                

                С другой стороны, читать такую новеллу будет скучно уже начиная с третьего слова. Участники NaNoGenMo пытаются решить эту проблему. Они придумывают литературные и технические ходы, которые позволили бы удержать внимание читателя. Это уже гораздо сложнее. На практике, если можно с интересом прочитать хотя бы несколько страниц новеллы — это можно считать успехом.


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


                Марковские цепи


                Цепи Маркова — классический способ генерации текста. Он хорошо описан здесь. У цепей Маркова есть проблема: если выбрать для N-грамм большое значение N (обычно 5 и больше), то в тексте проявляются большие куски исходного корпуса, если взять N меньше, то он получается откровенно бессмысленным.


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


                Другие произведения, основанные на цепях Маркова: эротические рассказы от Agrajag-Petunia, речи Рейгана (с примесью работ Шопенгауэра) от VincentToups, автобиографии на английском 19 века от lizrush.


                Расширение шаблонов


                Чаще всего осмысленные фрагменты текста генерируют с помощью шаблонов. Представим, что у нас есть такая грамматика:


                sentence = '<greeting>, <world_phrase>!'
                greeting = ['Привет', 'Приветик', 'Здравствуй', 'Добрый день']
                world_phrase = ['<happy_adj> мир', '<sad_adj> мир', 'мир']
                happy_adj = ['прекрасный', 'светлый', 'добрый']
                sad_adj = ['унылый', 'жестокий', 'мрачный']
                

                На основе нее мы можем сгенерировать много разных предложений — «Привет, унылый мир!» или «Добрый день, прекрасный мир!». Чем богаче грамматика, тем более интересные тексты она выдает.


                В «The Gamebook of Dungeon Tropes» от maetl с помощью шаблонов генерируются описания подземелий.


                В «Атеистах, которые верят в Бога» от tra38 используются данные какой-то переписи населения США. Герои новеллы — люди, которые сказали, что они атеисты, но верят в Бога. Они один за другим читают шаблонные лекции, в которые подставлены ответы на вопросы из переписи.


                Шаблоны используются в новелле «Где-то что-то» от BenKybartas, сборнике «5000 рассказов» от tinyworlds, эротической новелле «Оргазмотрон» от enkiv2 (простите, больше эротики не будет) и спортивном комментарии «Федерация лжеамериканского футбола» от creade.


                Рекурсия


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


                Простой вариант: в «The transorbital anaphase provine biforn the pure-bred synostosis» от samcoppini берется одно предложение (оно вынесено в заголовок). А затем с помощью словаря дается определение некоторых слов. Для слов из определений тоже даются определения. И так пока не наберется пятьдесят тысяч.


                «Гигант без сердца» от MichaelPaulukonis рассказывает вариации норвежской сказки о принце, который спас своих братьев от злого гиганта. Но в конце этот принц сам становится гигантом и похищает детей другого короля. И вся история повторяется, но уже немного по-другому.


                Новелла «Надежды и воспоминания» от cpressey состоит из нескольких событий и диалога двух персонажей. По ходу новеллы они встречают вампира, зомби, дракона и других чудовищ. А всё остальное время они вспоминают свои встречи и предполагают, кого они увидят в будущем. А потом вспоминают, как вспоминали какую-то встречу, предполагают, будут ли они вспоминать, как предполагали другую встречу и так далее.


                Похожа по структуре «Redwreath and Goldstar Have Traveled to Deathsgate» от erkyrath. Там тоже два персонажа ведут разговор. Они очень вежливые — спрашивают: «Могу ли я задать вопрос?» и «Правильно ли я понимаю, что...». И так пока в какой-то момент не доходит до такой реплики:


                «You want to know whether I am asking whether you are asking whether you shall tell me whether you want to know whether I believe I can answer that?»

                «Сто шестьдесят пять дней Рождества» от hugovk — это продолжение английской народной песни «Двенадцать дней Рождества», где продолжают дарить новые и новые подарки (и их количество тоже растет).


                Переработка существующих произведений


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


                Моби Дик на кошачьем языке от hugovk. Это одна из самых известных работ NaNoGenMo про которую писали The Guardian, Vox и The Atlanctic. Просто приведу цитату:


                Meow me Meeeeow. Meow meeow mew--meoow meow mew meow meeeeooow--meeeow meeeow me me meeow me me meoow, mew meeeoow meeeooooow me meooooow me me meeow, M meeooow M meoow meow meoow m meooow mew mew mew meooow meow me mew meeow.

                Сравните с оригиналом:


                Call me Ishmael. Some years ago--never mind how long precisely--having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.

                «Приключения Шарлотты Холмс» от emdaniels. Автор поменяла пол всех персонажей в рассказах о Шерлоке Холмсе. Звучит не очень серьезно, но на самом деле это непростая лингвистическая задача. C которой автор справилась не до конца (поэтому пришлось придумать местоимение herr).


                To Charlotte Holmes he is always THE man. I have seldom heard her mention him under any other name. In herr eyes he eclipses and predominates the whole of him sex. It was not that she felt any emotion akin to love for Ivan Adler.

                «Гомерическое ультранасилие» от lilinx. Из текста Илиады выбрали все предложения, в которых есть слово «ударить» в одной из форм. Получилось описание длинной и жестокой бойни, в которой уже даже непонятно, кто с кем сражается.


                «Моби Дик, или image» от pteichman — еще один вариант Моби Дика, в котором некоторые слова заменили соответствующими эмодзи.


                Другие произведения: «Приключениях Тома Сойера» с героями из романа про Конана от MrDrews, «Гамлет» от dkurth, пропущенный через несколько этапов машинного перевода, «Гордость и предубеждение» с лексикой из Твиттера от michelleful, «Превращение» Кафки, в котором каждое слово заменено более абстрактным, от jonkagstrom.


                Не обязательно использовать только одно произведение. Например, сборник коротких рассказов, состоящих из шести слов, от hugovk.


                А в «Нашем прибытии» (PDF) от aparrish из корпуса «Проекта Гутенберг» выбраны предложения, в которых описываются какие-то природные объекты или явления. Собранные вместе, они составляют дневник некой экспедиции. Получилось очень красиво, эта новелла занимает одно из мест в моем личном топе новелл с NaNoGenMo.


                Переработка других данных


                Не обязательно брать в качестве основы литературное произведение. moonmilk составил новеллу из твитов участников NaNoWriMo (PDF) (это то соревнование, где люди сами пишут новеллы), а jimkinsey — из вопросов, которые ученые задают в аннотациях статей (PDF):


                In what sense is this a novel situation? Should conflicts in the private domain be exempted from public scrutiny in cases where individual are being deprived of their basic legal rights? How many students study abroad and where do they go? Furthermore if the existing policies are found to be ineffective, what all policy measures can be suggested to the Government to put an end to this evil practice? Who are user entrepreneurs?

                Сгенерированные новостные отчеты о NaNoGenMo от enkiv2 трудно отличить от настоящих статей.


                В «Словаре языка D'skuban» от samcoppini словам из выдуманного компьютером языка даны определения терминов из реального словаря.


                Новелла «Искатель» (PDF) от thricedotted рассказывает о том, как компьютер учится делать всё, что делают люди, с помощью сайта wikiHow.


                Симуляция


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


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


                Похожим образом герои новеллы flexo бродят по арене и дерутся при встрече.


                Кроме сражений, есть и другие симуляции. В «Вечере дождливого дня» от cpressey Алиса и Боб играют в карты. В новелле nothings Ханна решает задачу о Ханойской башне (для 50000 слов хватило двенадцати дисков). Во «Флоре и фауне» от amarriner ботаник ищет выход из лабиринта, встречая по пути животных и растения.


                Иногда авторы симулируют перемещения по реальному миру. В «Вокруг света за X статей Википедии» от kevandotorg Филеас Фогг и Паспарту совершают кругосветное путешествие, рассказывая факты о местах, которые они посещают. «Книга Элизы» от greg-kennedy приводит подробный маршрут сорокалетнего путешествия Моисея по пустыне (даже есть карта!).


                Высокоуровневая генерация сюжета


                Другой подход к построению сюжета — сформировать основные сюжетные точки, а потом уже развернуть их в подробный текст. cpressey реализовал это во «Времени судьбы» и описал в посте, как работает его построение сюжета. Компилятор истории начинает с простой последовательности:


                [ПредставлениеГероев, *, Развязка]
                

                Вместо звездочки компилятор может вставлять любые события со своим началом и концом:


                [ПредставлениеГероев, [СокровищаУкрадены, *, СокровищаНайдены, *], Развязка] 
                

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


                ПредставлениеГероев = [ПредставлениеДетектива, ПредставлениеГрабителя]
                СокровищаУкрадены = [ГрабительБеретСокровища, ГрабительСбегает]
                СокровищаНайдены = [ДетективЛовитГрабителя, ДетективЗабираетСокровища]
                Развязка = [ДетективИдетДомой, ГрабительСбегает]
                

                После этого остается превратить каждое событие в его итоговое описание в новелле.


                Другие форматы


                Иногда авторы отходят от формата новеллы. Я уже упоминал несколько подобных, но вот еще:


                • «Пустыни Запада» (PDF) от mewo2 — путеводитель по выдуманному миру, для которого специально генерируются карта и язык. И для карты, и для языка подробно описано, как именно они генерируются.
                • «Великая книга трансмутаций» (PDF) от TobiasWehrum — сборник алхимических рецептов. Чтобы подбирать ингридиенты, используется какой-то корпус связанных понятий.
                • Для «Обложки „И восходит солнце“» adregan взял изображение, а потом написал название цвета для каждого пикселя. Кстати, картинку потом смогли восстановить обратно.
                • В «Отправьте по телефону» от hugovk приведен диалог двух человек, один из которых диктует другому программу. «Эс как доллар», только еще хуже. Немного красивой рекурсии: программа, которую диктуют, и сгенерировала этот диалог.

                Графические произведения


                Не всегда авторы ограничиваются текстовыми новеллами. doldrumorchids для «Людей нет» берет картинки с гугл-панорам, распознает объекты на них, а потом вставляет описания подобных объектов из произведений с «Проекта Гутенберг».


                «Серафимы» от lizadaly — это загадочный манускрипт с картинками, написанный на неизвестном языке (с символами из рукописи Войнича).


                «Что-то, благодарение и ничего» от zachwhalen и «Сгенерированный детектив» от atduskgreg — графические новеллы (комиксы). В первой текста нет, во второй он есть. Но оба автора постарались над стилизацией картинок, и получилось атмосферно.


                Нейронные сети


                Немного о плохом. Нейронные сети за последнее время научились многому: побеждать в го, накладывать фильтры на фотографии и сортировать огурцы. Но с генерацией текстов у них, похоже, как-то не клеится. В 2015 было несколько произведений, написанных нейронками (переосмысление Лавкрафта от R-Gerard, переосмысление Жюля Верна от estayton и что-то графическое от spikelynch). Все они меня не впечатлили. Думаю, это значит, что у нейронных сетей всё впереди, и в будущем мы еще увидим что-то более осмысленное от них.


                Что дальше


                До начала нового NaNoGenMo осталось несколько дней. Вот репозиторий для него. Если вы хотите в нем поучаствовать — создайте в нем issue с заголовком вроде «intent to participate». В самом issue вы сможете обсудить свои идеи с другими участниками, а после завершения работы туда надо будет выложить ссылку на код и сгенерированную новеллу. Желаю успеха всем, кто решится!


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


                Ссылки


                1. Репозитории прошлых лет: 2013, 2014, 2015.
                2. Серия статей, из которой я узнал про NaNoGenMo: 1, 2, 3, 4. В этом посте я использовал некоторые примеры оттуда.

                Комментарии (0)

                  Let's block ads! (Why?)