Small scopes and SRP rocks, isn’t it?

We discussing recent post by “OOP” lover Yegor Bugayenko.

My thoughts that it’s not about FP or OOP, objects or functions, it’s all about SRP. Like we have two initialization blocks in one method, and then we test results of these blocks. We can move this blocks in lambdas/function/classes, but in general it’s just two functions that produces a and b and then we compare a and b. After working with Kotlin I’ll write this test like:

@Test
void testIntStream() {
    final long seed = System.currentTimeMillis();

    assertEquals(
        run(() -> {
            final Random r1 = new Random(seed);
            final int[] a = new int[SIZE];
            for (int i = 0; i < SIZE; i++) {
                a[i] = r1.nextInt();
            }
            return a;
        }),
        run(() -> {
            final Random r2 = new Random(seed);
            return r2.ints().limit(SIZE).toArray();
        })
    );
}

Just like in Yegor’s solution my test have only one statement: assertEquals.
My approach much more clean, and for every new test I don’t write any boilerplate to the glory of “OOP” God.

Just like Yegor’s solution, my approach have same benefits over original test:

  • Reusability — I can easy extract lambda from test, and reuse between number of test.
  • Brevity — It’s much less code with in place lambdas.
  • Readability
  • Immutability

In post Yegor’s approach seems good, but it doesn’t scale to real applications.

run function for the reference:

public static <R> R run(Producer<R> producer) {
    return producer.produce();
}

Kotlin version

So also I’d like to add Kotlin example for this test, it’s much more readable even than Java version with lambdas (no surprise here)

@Test fun testIntStream() {
        val seed = System.currentTimeMillis()

        assertArrayEquals(
            expected = {
                val r1 = Random(seed)
                IntArray(SIZE).also { array ->
                    for (i in 0..SIZE - 1) {
                        array[i] = r1.nextInt()
                    }
                }
            },
            actual = {
                val r2 = Random(seed)
                r2.ints().limit(SIZE.toLong()).toArray()
            }
        )
    }

I am using JUnit 5 for this test, and was expecting that JUnit 5 has methods that accepts lambdas for assert, but unfortunately – there are no such signature. So I added it:

fun assertArrayEquals(expected: () -> IntArray, actual: () -> IntArray) {
    Assertions.assertArrayEquals(expected.invoke(), actual.invoke())
}

I think will be nice to have support library for JUnit 5 with such methods.

Изучаем Bash. Как не отправить все в /dev/null

В скрипте инициализации RHEL допущена ошибка, приводящая к удалению всех файлов

Не делать assert’ов в критических точках приложения – отличный способ выстрелить в ногу с рикошетом в голову.

Пример из новости выше:

[code lang=”bash”] stop
rm -rf $SQUID_PIDFILE_DIR/*
start[/code]

Без контекста заметить проблему трудно, но точно известно если переменная SQUID_PIDFILE_DIR не будет определена мы выполним:

[code lang=”bash”] rm -rf /*[/code]

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

[code lang=”bash”] stop
[ -z “$SQUID_PIDFILE_DIR” ] || exit 42
rm -rf $SQUID_PIDFILE_DIR/*
start[/code]

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

Про контрактное программирование на Java можно почитать здесь.

Git Bash for Windows

В целом командная строка под оффтопик это страх и ужас. sh.exe конечно же спасает, но отсутствие тесной интеграции с системой (в linux можно делать все из консоли) создает неудобства. В общем пользователям git bash for win посвящается: запускаем несколько окон с гитом в нужных проектах из bat файла:

rem git-win.bat
cd C:\dev\projects\project1
start "" "%SYSTEMDRIVE%\Program Files (x86)\Git\bin\sh.exe" --login -i

cd C:\dev\projects\project2
start "" "%SYSTEMDRIVE%\Program Files (x86)\Git\bin\sh.exe" --login -i

Если у вас 32-битная система(интересно, остались еще такие?) то этот файл

rem git-win-x86.bat
cd C:\dev\projects\project1
start "" "%ProgramFiles%\Git\bin\sh.exe" --login -i

cd C:\dev\projects\project2
start "" "%ProgramFiles%\Git\bin\sh.exe" --login -i

Geek Cup

geek_cup

1. Одинаковые шестнадцатеричные цифры представлены одной и той же буквой:

(DRINKCOFFEE << F) + (BEHAPPY << D) = ITRANSITION

2. Предыдущее поколение в “Game of Life” с минимальным количеством живых клеток:

□□□□□□□□□□□□□□□□□□□□
□□■□□■□□□■□□■□□■□□■□
□■□□■□■□■□□■□□■□□■□□
□■□□■□■□■■□■■□■■□■■□
□■□□■□■□■□□■□□■□□■□□
□□■□□■□□■□□■□□□■□□■□
□□□□□□□□□□□□□□□□□□□□

3.

puts ->(v){r,c,l=0<=>v.abs,1,->(n,g;r,c){g>=~-n||g<=1?1:->(){r,c=1,2;->(){->{r=r+l[n-g,c]if(c+g<=n)}[];c+=1;}[]while(c<=g);r}[];};r+=l[v,c]and(c=c+1)while(c-v<=0)l=->(l){l+=1}[r]}[0xc0ffee]

Первые грабли Java 8

java-prof-by

Моя первая попытка выступить на Java Professionals BY: Meetup #2 вышла комом, с самого начала все не заладилось и я смог победить волнение и кашу в голове только к концу доклада. Опыт получен, и надеюсь аудитория поняла и простила меня 😀

10341492_793072000739301_8268639565730347492_n

На самом деле проблем в Java 8 нету 😉 Есть проблемы перехода с предыдущих версий языка, а остальное – проблемы неправильного использования.

Bug or Feature?

К выпуску Java 8 Oracle предоставила гайд: Compatibility Guide for JDK 8, но также надо помнить, что переходя с Java 6 на Java 8 нужно учитывать также несовместимости 6 и 7 версии (тем кто верит, что Java на 100% обратно совместима лучше не открывать ссылку) и так далее.

Что же должно волновать обычного разработчика, при переходе с 7 на 8 Java?

Пофикшенные баги наподобие этих:

JDK-6559590 – изменилось поведение String.split(“”), как следствие в некоторых случаях можно словить ArrayIndexOutOfBoundsException, а что еще хуже в тихую закораптить данные (например не используя первый символ, который всего был пустой строкой).

user@r2d2:~$ cat Test.java 
import java.util.Arrays;

public class Test {

    public static void main(String[] args) {
        System.out.println(Arrays.toString("123".split("")));
    }
}
user@r2d2:~$ /usr/lib/jvm/java-8-oracle/bin/javac Test.java
user@r2d2:~$ /usr/lib/jvm/java-8-oracle/jre/bin/java Test
[1, 2, 3]
user@r2d2:~$ /usr/lib/jvm/java-7-oracle/bin/javac Test.java
user@r2d2:~$ /usr/lib/jvm/java-7-oracle/jre/bin/java Test
[, 1, 2, 3]

JDK-8021591 – в коллекциях CopyOnWriteArrayList/CopyOnWriteArraySet/AbstractCollection методы removeAll(Collection)/retainAll(Collection) не кидали NPE при передачи в них null. Как следствие перейдя с 4,5,6,7 на 8-ку можно ловить NPE пачками.

// AbstractCollection.java
// Java 7 ---------------------------------------- / Java 8 ----------------------------------
public boolean removeAll(Collection<?> c) {        public boolean removeAll(Collection<?> c) {
                                                  >    Objects.requireNonNull(c);
    boolean modified = false;                          boolean modified = false;
    Iterator<?> it = iterator();                       Iterator<?> it = iterator();
    while (it.hasNext()) {                             while (it.hasNext()) {
        if (c.contains(it.next())) {                       if (c.contains(it.next())) {
            it.remove();                                       it.remove();
            modified = true;                                   modified = true;
        }                                                  }
    }                                                  }
    return modified;                                   return modified;
}                                                  }

// Same in retainAll method

Default methods

Должны использоваться аккуратно! Нужно понимать, что цель default методов в возможности расширения интерфейса, без потери совместимости со старым кодом:

Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.

What’s New in JDK 8

Использование default методов не по назначению может привести к излишнему усложнению кода:

public class DefaultMethods {

    public static void main(String[] args) {

        // WTF is happens here!?
        Tiger tiger = new Tiger();
        tiger.meow();
        tiger.sayMeow();
        tiger.saySuperMeow();

        Cat cat = new Tiger();
        cat.meow();
        cat.sayMeow();
    }
}

class Cat implements Feline {
    public void sayMeow() {
        meow();
    }
}

class Tiger extends Cat implements Predator {
    public void saySuperMeow() {
        super.meow();
    }
}

interface Feline {
    default void meow() {
        System.out.println("Meow");
    }
}

interface Predator extends Feline {
    default void meow() {
        System.out.println("Rrrr");
    }
}

Разобраться в этой жести нам помогут простые правила (которые описаны в jsr335-final/spec/H.html):

  • A concrete implementation in the class wins
  • The lowest implementation in the implemented interfaces wins
  • If there are multiple implementations available through different interfaces that are on different paths up through the class hierarchy, the program doesn’t compile

Оставлю возможность понять что тут происходит вам самим. Для тех кому лень запускать ниже приведен вывод:

Rrrr
Rrrr
Meow
Rrrr
Rrrr

Executing Streams in Parallel

Определенно Stream API это крутое нововведение в Java 8. Особенно многим понравилось возможность “ускорить” вычисления используя параллельные стримы. К сожалению в действительности это все не так однозначно, и сейчас я это продемонстрирую:

import java.util.stream.IntStream;

public class JavaStreams {

    public static void main(String[] args) {
        test();
        test();
        test();
    }

    public static void test() {

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .map(x -> x * 2)
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run1. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .map(x -> {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                        return 42;
                    })
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run2. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();


    }

}

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

Run1. Execution time: 7ms. Result: 9900
Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 0ms. Result: 9900
Run2. Execution time: 9919ms. Result: 4158
Run2. Execution time: 9921ms. Result: 4158
Run2. Execution time: 9915ms. Result: 4158

Очевидно что 9 секунд на отдачу результата во втором случае это очень много. Давайте применим параллельные стримы:

import java.util.stream.IntStream;

public class JavaStreams {

    public static void main(String[] args) {
        test();
        test();
        test();
    }

    public static void test() {

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .parallel()
                    .map(x -> x * 2)
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run1. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .parallel()
                    .map(x -> {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                        return 42;
                    })
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run2. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();


    }

}

Run1. Execution time: 8ms. Result: 9900
Run1. Execution time: 21ms. Result: 9900
Run1. Execution time: 4936ms. Result: 9900
Run2. Execution time: 5024ms. Result: 4158
Run2. Execution time: 5013ms. Result: 4158
Run2. Execution time: 5026ms. Result: 4158

Замечательно! С 9 секунд до 5! Отличный результат. Стойте, WAT?

Wat

4936ms? WAT? Запустим еще раз:

Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 28ms. Result: 9900
Run2. Execution time: 4308ms. Result: 4158
Run2. Execution time: 4921ms. Result: 4158
Run2. Execution time: 5510ms. Result: 4158

И еще один разок:

Run1. Execution time: 6ms. Result: 9900
Run1. Execution time: 11ms. Result: 9900
Run2. Execution time: 5016ms. Result: 4158
Run1. Execution time: 5533ms. Result: 9900
Run2. Execution time: 5621ms. Result: 4158
Run2. Execution time: 6418ms. Result: 4158

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

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

Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 1ms. Result: 9900
Run1. Execution time: 1ms. Result: 9900
Run2. Execution time: 5016ms. Result: 4158
Run2. Execution time: 6825ms. Result: 4158
Run2. Execution time: 6916ms. Result: 4158

fry

Как же работают стримы внутри? Заглянем в имплементацию Stream, а точнее BaseStream, которая представлена классом AbstractPipeline. В нем есть метод parallel() в котором выставляется внутренне свойство parallel в true и возвращается стрим. Где же используется данное свойство parallel? Как мы знаем стримы ленивые и если в стриме не будет указан терминальная операция, то он не будет выполняться. Какие мы знаем терминальные операции? Самая очевидная операция это reduce, которая вызывает метод AbstractPipeline#evaluate передавая туда операцию ReduceOps. Соответственно перед выполнением в зависимости от свойства parallel стрим будет выполняться либо параллельно либо последовательно. Итак наша дорога через интерфейс TerminalOp пришла к одной из реализаций: ReduceOp и методу evaluateParallel, который возвращает ReduceTask#invoke#get. Сам ReduceTask – является ForkJoinTask который будет выполняться на ForkJoinPool. Но мы же не создаем свой пул, какой пул используется? А используется ForkJoinPool#commonPool(размер которого равен количеству процессоров минус 1), который один на всех. Собственно в этом и корень всех проблем – долгох живущие параллельные стримы “съедают” пул и не дают другим параллельным стримам выполниться.

Что делать? Не использовать parallel stream в многопользовательских приложениях и для маленьких задач.

Q. Как запустить стрим в своем пуле?
A. Создать свой ForkJoinPool и отправить на выполнение таск с Stream:

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
forkJoinPool.submit(() ->
    //parallel task here, for example
    IntStream.range(1, 1_000_000).parallel().filter(PrimesPrint::isPrime).collect(toList())
).get();

Q. Одна из библиотек внутри использует parallel stream, что делать?
A. Писать разработчикам библиотеки и бить их по пальцам. Конечно можно ограничить пул: -Djava.util.concurrent.ForkJoinPool.common.parallelism=1, но это только усугубит ситуацию. Конечно можно и увеличить пул, в таком случае производительность будет низкая но более предсказуема. В целом я не нашел способа глобально запретить стримам использовать параллелизм.

Поиграемся с размером пула:

parallelism=1

Run1. Execution time: 13ms. Result: 9900
Run1. Execution time: 4924ms. Result: 9900
Run1. Execution time: 4918ms. Result: 9900
Run2. Execution time: 5018ms. Result: 4158
Run2. Execution time: 9919ms. Result: 4158
Run2. Execution time: 13548ms. Result: 4158

parallelism=10

Run1. Execution time: 12ms. Result: 9900
Run1. Execution time: 18ms. Result: 9900
Run2. Execution time: 2011ms. Result: 4158
Run1. Execution time: 2022ms. Result: 9900
Run2. Execution time: 2311ms. Result: 4158
Run2. Execution time: 2431ms. Result: 4158

parallelism=100

Run1. Execution time: 91ms. Result: 9900
Run1. Execution time: 118ms. Result: 9900
Run1. Execution time: 111ms. Result: 9900
Run2. Execution time: 414ms. Result: 4158
Run2. Execution time: 399ms. Result: 4158
Run2. Execution time: 382ms. Result: 4158

parallelism=1000

Run1. Execution time: 333ms. Result: 9900
Run1. Execution time: 356ms. Result: 9900
Run1. Execution time: 362ms. Result: 9900
Run2. Execution time: 439ms. Result: 4158
Run2. Execution time: 475ms. Result: 4158
Run2. Execution time: 468ms. Result: 4158

parallelism=10000

Run1. Execution time: 365ms. Result: 9900
Run1. Execution time: 448ms. Result: 9900
Run1. Execution time: 450ms. Result: 9900
Run2. Execution time: 542ms. Result: 4158
Run2. Execution time: 535ms. Result: 4158
Run2. Execution time: 543ms. Result: 4158

Все тесты выполнялись на Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz

Чтобы далеко не ходить оставлю ссылку на доклад второго спикера “Алексей Руцкой: Как развиваться Java-разработчику

На этом спасибо за внимание и я всегда рад вашим комментариям 🙂

Задача про Паровозики

Задачу эту я услышал сегодня на Ciklum Java Saturday (мини-конференция).

Итак

locomotive

У нас имеется бесконечная координатная прямая. На ней два паровоза и между ними на неизвестном расстоянии находится станция.

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

  • L – передвинуться в левую ячейку
  • R – передвинуться в правую ячейку
  • if (station) {L/R/goto(n)} – если поезд находится на станции то выполнить единственную команду команду
  • goto(n) – перейти на выполнение n-ой строки исходного кода

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

Пример программы:

1. L
2. R
3. if (station) {goto(4)}
4. goto(1)

Которая ничего не делает 🙂

===========================
Решение:

1. L
2. if (station) {goto(4)}
3. goto (1)
4. L
5. goto (4)

===========================

upd. Аккуратно, в комментариях тоже есть решение 🙂

Изучаем GIT – the stupid content tracker


Вы наверно знаете что такое СУВ(VCS) и даже если вы ещё не начали использовать системы контроля версий, этот пост для вас.

Пожалуй стоит начать с выбора VCS. Как и многие другие для себя я выбрал Git(читается как ‘Гит’), распределенную систему управления версиями, которую написал Линус Торвальдс и Джунио Хамано с поддержкой сообщества.

Почему Git? Для начала послушайте или почитайте, что говорит о Git сам Линус. Также стоит обратить внимание на популярность системы. Сам для себя я невольно выбрал Git около 5-ли нет назад, когда посмотрел это видео.

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

На данный момент вы уже должны понимать что такое Git и с чем его едят. Иметь представление чем оперирует система и как её можно использовать. Пора начать получать реальные знания!

Первое что я рекомендую прочитать: Pro Git и Волшебство Git. Их большой плюс для начинающего – русский язык. Далее можно пробежать по статьям на хабре и попробовать что-нибудь из этого.

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

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

  1. Loeliger J. – Version Control with Git [2009, PDF, ENG]
  2. The Pragmatic Programmers – Swicegood T. – Pragmatic Version Control Using Git [2008, PDF, ENG]
  3. The Pragmatic Programmers – Swicegood T. – Pragmatic Guide to Git [2010, PDF, ENG]
  4. [TekPub] Mastering Git [2010, Video, ENG]

А также честно купить

И на последок:
Git Flow
Удачная модель ветвления для Git [EN]
Командная работа в Git
Постигаем Git
gitfm – персональные рекомендации, на основе вашего профиля в GitHub
Learn Git Branching
Интерактивный справочник команд
Кишки Git
GitHub Help
Git Tower.

Git-Master Ibragimov Ruslan

Серия курсов на code school:
Try Git – Базовые умения
Git Real – Необходимый минимум
Git Real 2 – Продвинутый уровень: interactive rebase, filter-branch, stashing, submodules, reflog
Mastering GitHub – Нацелен на обучение коллаборации

piwik vs adblock

Отказавшись от Google Analitycs (GA) в пользу собственного инстанса piwik я получил больше контроля над статистикой, а также не делюсь информацией о своих посетителях с кем-либо.

У GA есть существенный недостаток (для владельца ресурса), он успешной блокируется AdBlock’ом и многими другими плагинами. Я сам пользуюсь AdBlock’ом, и готов делиться статистикой с владельцами ресурсов, но не с третьими лицами.

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

Что можно сделать, чтобы уйти из под фильтров? Правильно, изменить имена файлов.

Я решил эту проблему с помощью конфига nginx. Я пока только изучаю его работу, и может пользуюсь не самым правильным способом, но способ работает 😉

Имеется конфиг сервера


    server {
        server_name piwik.domain.com;

        ...
    }

Удобный субдомен, но AdBlock так не считает. Что ж, поменяем его на другой:


    server {
        server_name piwik.domain.com pw.domain.com;

        ...
    }

Отлично, теперь осталось изменить имена файлов:


    server {
        server_name piwik.domain.com pw.domain.com;

        location = /jquery.min.js {
                 rewrite ^ /piwik.js;
        }

        location = /jquery.min.php {
                 rewrite ^ /piwik.php;
        }

        ...
    }

Теперь в сниппете который предоставляет piwik нужно заменить piwik.domain.com на pw.domain.com, piwik.js на jquery.min.js, а piwik.php на jquery.min.php.

Пользуйтесь данными знаниями во благо 😉

p.s. Данный подход можно легко забанить. Взгляните на awstats и другие утилиты которые парсят логи для 100% подсчета посетителей.