среда, 9 декабря 2009 г.

XTerm более правильно работает с юникодом

Оказывается, если настроить и запустить терминалку xterm из под X-сервера, то проблем с utf-8 намного меньше, точнее, я их пока вообще не обнаружил.
Не знаю с чем это связано, но как-то в ней более гладко и грамотно реализован юникод.
Из минусов отмечу только необходимость ручного выбора уникодного шрифта и его размера в меню xterm при первом запуске.
Очевидно, указываю неверные опции командной строки и они не срабатывают.
Но это ерунда по сравнению с тем кайфом, который я теперь испытываю от работы.

среда, 2 декабря 2009 г.

Проблема с командой выхода из Emacs в среде Cygwin

Команда выхода из этого редактора назначена на комбинацию "Ctrl-x Ctrl-c", что по неведомой причине передаётся в программу, как "Ctrl-x Ctrl-g".
Первый способ выхода, который я обнаружил, это временно свернуть emacs, нажав Ctrl-Z, затем найти номер процесса через ps и после этого прибить через kill.
Второй, изменить переменную окружения CYGWIN.
Я добавил следующую строчку в Cygwin.bat непосредственно перед запуском bash.

set CYGWIN=tty notitle glob
 Теперь "Ctrl-x Ctrl-c" отрабатывает как следует и редактор закрывается.

четверг, 19 ноября 2009 г.

Mistique. Chapter 1: Foetus



Mistique. Chapter 1: Foetus - это атмосферный квест-ужастик в трёх измерениях.
Очухиваемся в ванной комнате. В уши льётся гнетущий эмбиент.


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


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


Но, примерно за 20-30 минут, решив нехитрые загадки, дверь удаётся вскрыть.
На чём первая глава и оканчивается. А я с нетерпением жду продолжение.

P.S. Самому скрины делать пока нет возможности, поэтому погуглив взял первые попавшиеся.

среда, 18 ноября 2009 г.

BeamReader

BeamReader - это изумительная вещица.
Из всех испробованных мной PDF-читалок в маркете, а также втроенного в Hero, BeamReader пока обладает непревзойдённой скоростью рендеринга страниц, масштабирования и скроллинга.
Из отмеченных недочётов только невозможность масштабирования известным двухпальцевым жестом, но это претензия к гуглу и HTC.

пятница, 13 ноября 2009 г.

Интерактивное перебазирование в Git.

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

TortoiseGit - замечательный клиент Git для Windows, пожалуй даже лучший.
Одна из удобнейших фишек объединение нескольких локальных коммитов в один.
Корпоративная политика в настоящее время запрещает ставить Git и TortoiseGit на основную машину разработчика.
А, в отсутствие админских прав, на работе, я не могу установить TortoiseGit самостоятельно.
Клиент имеется в виртуальной машине. Но только для того, чтобы поправить пару коммитов, стартовать виртуалку как-то лениво и непрактично.
На прочих осях, с которыми я работаю, TortoiseGit отсутствует, а местные клиенты такой фишки не имеют.

Я давно знаю про команду rebase и ключик "-i".
Но у меня всё как-то никак не получалось увидеть список этих "pick", "squash", "edit".

Первый мой просчёт в том, что для удобства работы, в качестве редактора я использовал Notepad++.
По, надеюсь, покамест неведомой мне причине, сотрудничество Git и Notepad++ при перебазировании хромает и редактор открывается просто пустым.
Поэтому откатываемся к vim.

Второй недосмотр, что я периодически забираю изменения из SVN: git checkout master && git svn rebase.
Затем синхронизирую хакаемый бранч по: git rebase master.
Таким образом в интерактивном режиме у меня список операций всегда был пуст.
Но гит показывал, что всё равно исправно перебазирует все коммиты, которые я просил.

Оказывается достаточно сделать хотя бы один коммит после перебазирования и тогда уже можно делать
git rebase -i HEAD~хотьсто.
Небольшо хинт для тех кто с vim на "Вы". Чтобы автоматом заменить все pick на squash, переходим в командную строку, нажав ":" (возможно понадобится выйти в режим просмотра сначала).
Пишем там:
%s/pick/squash/
Теперь остаётся только заменить первый squash на pick.
Можно использовать сокращения: "s" и "p", соответственно.

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

Как говорится, учите матчасть ;)

среда, 11 ноября 2009 г.

Git для Cygwin и русские или другие национальные буквы в именах файлов

Сделал приятное открытие для себя.
Например,  представим такой сценарий:
Имеем репозиторий git, который только что создан с помощью git init.
Теперь мы хотим создать и добавить в репозиторий файлик "прочти меня.txt". И видим такую картинку:
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       "\320\277\321\200\320\276\321\207\321\202\320\270 \320\274\320\265\320\275\321\217.txt"
nothing added to commit but untracked files present (use "git add" to track)

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

Если в .git/config в раздел [core] прописать:
quotepath = false
То происходит небольшое и ожидаемое, но всё-таки волшебство ;)
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       прочти меня.txt
nothing added to commit but untracked files present (use "git add" to track)

 Приятной разработки и вам!

пятница, 30 октября 2009 г.

Поздравления мне )

Ура!
Вчера я сдал экзамен SCJP 310-065.
И теперь считаюсь сертифицированным специалистом по Java.
Но я даже не думаю останавливаться на достигнутом.
Теперь начинается подготовка к SCWD.

четверг, 22 октября 2009 г.

Эволюционирующая архитектура и непредсказуемое проектирование: Проектирование через тестирование, Часть 1

Около полугода назад, мой коллега предложил немного подработать, сделав перевод статьи для очень уважаемого ресурса. Я выбрал три статьи:
  1. LXC: Kонтейнерные утилиты Linux
  2. Эволюционирующая архитектура и непредсказуемое проектирование: Исследование архитектуры и дизайна
  3. Эволюционирующая архитектура и непредсказуемое проектирование: Проектирование через тестирование, Часть 1
Статьи 1, 2, прошедшие отменную редактуру, такую не стыдно и похвалить (я, например, не изменял надписи на рисунках), уже доподлинно известно, что были приняты и ссылки ведут на публикации. Третья ещё находится на редактуре, как я полагаю.

Деньги же я получил только за одну статью, которая в списке под номером 1.
Своему коллеге я склонен доверять и в то, что он мог их присвоить я не верю.
Каюсь, сам не без изъяна, так как задержал перевод примерно на месяц.
Но ведь можно было как-то отреагировать... мда...
Ну да, ладно, то что деньги за проделанную работу до меня не дошли уже неважно, но зато я теперь обладаю моральным правом опубликовать свои старания. Может кому-то и пригодиться, читайте на здоровье.
Хотел опубликовать на Хабре, но там меня заминусовали ))



Эволюционирующая архитектура и непредсказуемое проектирование: Проектирование через тестирование, Часть 1

Позвольте тестам порулить и улучшить ваш дизайн
Нил Форд (Neal Ford)Архитектор ПО / Идейный вдохновитель (Meme Wrangler), ThoughtWorks Inc.

Резюме: Большинство разработчиков думает что тесты - это самое выгодное в использовании разработки через тестирование (TDD). Однако, если все делать правильно, то TDD полностью изменяет дизайн кода в лучшую сторону. Эта часть из серии статей об Эволюционирующей архитектуре и непредсказуемом проектировании проведет по обширном примеру, показывающему как дизайн может проявляться из-за разных, всплывающих при тестировании, штуковин. Тестирование лишь побочный эффект TDD; важная часть того, как код меняется к лучшему.
Дата оригинала:  24 Февраля 2009
Уровень:  средний
PDF:  A4 and Letter (146KB | 14 pages)Get Adobe® Reader®


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

Об этой серии

Цель этой серии дать свежий взгляд на часто обсуждаемые, но расплывчатые, концепции архитектуры и дизайна ПО. На конкретных примерах, Нил Форд (Neal Ford) дает солидный фундамент в быстрых методологиях эволюционирующей архитектуры и непредсказуемого проектирования. Откладывая принятие важных архитектурных и дизайнерских решений до последнего ответственного момента, можно предотвратить ненужную сложность, разрушающую программные проекты.
Важным словом в определении разработка через тестирование является через, показывающее, что именно тестирование управляет процессом разработки. Рисунок 1 показывает рабочий процесс TDD:

Рисунок 1. Рабочий процесс TDD

















Рабочий процесс на рисунке 1 это:
  1. Написание проваливающего теста.
  2. Написание кода, который его проходит.
  3. Повторение шагов 1 и 2.
  4. Продвижение вперед активным рефакторингом.
  5. Когда больше нет идей насчет тестов, значит готово.
Разработка через тестирование требует, чтобы тесты появились первыми. Только после того как тесты написаны (и провалены), пишется код под этот тест. Многие разработчики используют разновидность тестирования называемую поздними тестами (пост-тестами, TAD), когда сначала пишется код и потом покрывается юнит-тестами. В этом случае, не смотря на то, что тесты имеются, но аспекты непредсказуемого проектирования из TDD отсутствуют. Ничто не мешает написать совершенно отвратительный код и потом ломать голову, как его протестировать. Разрабатывая код первым, в него внедряются идеи о том как он должен работать, и затем тестируется. TDD требует идти от обратного: сначала написать тест, и позволить ему сообщать, когда код его проходит. Чтобы проиллюстрировать это важное отличие, рассмотрим распространённый пример.
Чтобы показать преимущества дизайна TDD, необходима проблема для решения. В своей книгеРазработка через тестирование (см. Источники), Кент Бэк (Kent Beck) использует для примера деньги — достаточно хорошая иллюстрация TDD, но слегка упрощенная. По-настоящему сложная задача - это найти пример, который не такой комплексный, чтобы потеряться в проблемной области, но достаточно полный, чтобы показать действительную ценность.
В конце концов, были выбраны совершенные числа. Для тех кто не силен в математике, концепция восходит к Эвклиду, который вывел одно из самых ранних доказательств существования совершенных чисел. Совершенное число равно сумме всех своих собственных делителей. Например, 6 - совершенное число, потому что делители 6-ти (исключая саму 6) - это 1, 2, и 3, а 1 + 2 + 3 = 6. Более алгоритмическое определение совершенного числа - это число, которое является суммой делителей (исключая само число), равной этому числу. В примере выше, вычисления такие: 1 + 2 + 3 + 6 - 6 = 6.
Решением для данной проблемной области будет: создать определитель совершенного числа. Реализуем решение двумя различными путями. Для начала, отключим ту часть мозга, которая хочет делать через TDD, и сначала напишем решение, а потом тесты для него. Когда будем развивать TDD версию решения, то сможем сравнить и противопоставить оба подхода.
Для примера, реализуем определитель совершенного числа на языке Java (версии 5 или новее, потому что используются аннотации), JUnit 4.x (самой новой версии), и сопоставители Hamcrest из Google code (см. Источники). Сопоставители Hamcrest представляют синтаксический сахар с человечным интерфейсом к стандартным сопоставителям JUnit. Например, вместо assertEquals(expected, actual), можно написать assertEquals(actual, is(expected)), что выглядит более осмысленно. Сопоставители Hamcrest поставляются вместе с JUnit 4.x (в виде статического импорта); если все еще используется JUnit 3.x, то можно скачать совместимую версию для него.
Листинг 1 показывает первую версию PerfectNumberFinder:

Листинг 1. Пост-тест PerfectNumberFinder
public class PerfectNumberFinder1 {
    public static boolean isPerfect(int number) {
        // получим делители
        List factors = new ArrayList();
        factors.add(1);
        factors.add(number);
        for (int i = 2; i < number; i++)
            if (number % i == 0)
                factors.add(i);

        // просуммируем делители
        int sum = 0;
        for (int n : factors)
            sum += n;

        // убедимся, что оно совершенное
        return sum - number == number;
    }
}

Конечно, это не особо эффектный код, но свою работу выполняет. Сначала создается динамический список всех делителей (ArrayList). Куда добавляется 1 и конечное число (придерживаясь формулы выше, и список всех делителей, включая 1 и само число). Теперь пройдем в цикле по другим возможным коэффициентам до числа, включительно, проверяя, что это делитель. И добавляем в список, если это так. Далее, суммируем все коэффициенты и пишем Java-версию формулы для определения совершенного числа.
Теперь, мне необходим юнит-тест, чтобы выяснить, работает оно или нет. Нужны как минимум два теста: один, чтобы видеть, что совершенные числа определяются верно и другой, чтобы предотвратить ложные срабатывания. Юнит-тесты в Листинге 2:

Листинг 2. Юнит-тесты для PerfectNumberFinder
public class PerfectNumberFinderTest {
    private static Integer[] PERFECT_NUMS = {6, 28, 496, 8128, 33550336};

    @Test public void test_perfection() {
        for (int i : PERFECT_NUMS)
            assertTrue(PerfectNumberFinder1.isPerfect(i));
    }

    @Test public void test_non_perfection() {
        Listexpected = new ArrayList(
                Arrays.asList(PERFECT_NUMS));
        for (int i = 2; i < 100000; i++) {
            if (expected.contains(i))
                assertTrue(PerfectNumberFinder1.isPerfect(i));
            else
                assertFalse(PerfectNumberFinder1.isPerfect(i));
        }
    }

    @Test public void test_perfection_for_2nd_version() {
        for (int i : PERFECT_NUMS)
            assertTrue(PerfectNumberFinder2.isPerfect(i));
    }

    @Test public void test_non_perfection_for_2nd_version() {
        List expected = new ArrayList(Arrays.asList(PERFECT_NUMS));
        for (int i = 2; i < 100000; i++) {
            if (expected.contains(i))
                assertTrue(PerfectNumberFinder2.isPerfect(i));
            else
                assertFalse(PerfectNumberFinder2.isPerfect(i));
        }
        assertTrue(PerfectNumberFinder2.isPerfect(PERFECT_NUMS[4]));
    }
}

Что это за "_" в именах тестов?

Помещение подчеркиваний перед именами методов в юнит-тестах - это одна из моих программных уловок. Конечно, стандарт Java задает правильный способ написания имен методов в стиле "кэмел". Но я утверждаю, что имена тестовых методов отличаются от имен обычных методов. Имена тестовых методов, должны служить явным признаком, что метод тестирующий, и потому они обладают длинными, наглядными именам, которые покажут именно то, что нужно, когда что-то сломается. Однако, чтение длинных верблюдообразных имен проблематично, особенно если юнит-тест с дюжиной или сотнями тестов, т.к. большинство имен начинаются с одинаковых по смыслу значений и различаются только в конце. Для улучшения читабельности, во всех моих проектах, я строго рекомендую использовать подчеркивания в именах тестов.
Этот код выводит правильные совершенные числа, но, так как проверяет много чисел, то работает очень медленно на негативных тестах. Вопросы быстродействия, всплывшие из юнит-тестов, возвращают меня в код, чтобы проверить, что можно подкрутить. Сейчас, выбирая делители, цикл в любом случае идет до целевого числа включительно. Но нужно ли так далеко заходить? Нет, если делители можно выбирать парами. Все делители состоят в парах (например, если целевое число 28, то когда найден делитель 2, то можно взять и 14). Поэтому, если делители можно выбирать парами, то шагов необходимо сделать равное квадратному корню из числа. Подводя итог, улучшаем алгоритм и рефакторим код до Листинга 3:

Листинг 3. Улучшенная версия алгоритма
public class PerfectNumberFinder2 {
    public static boolean isPerfect(int number) {
        // получим делители
        List factors = new ArrayList();
        factors.add(1);
        factors.add(number);
        for (int i = 2; i <= sqrt(number); i++)
            if (number % i == 0) {
                factors.add(i);
                factors.add(number / i);
            }

        // просуммируем делители
        int sum = 0;
        for (int n : factors)
            sum += n;

        // убедимся, что оно совершенное
        return sum - number == number;
    }
}

Этот код работает за ожидаемое время, но провалилась пара тестовых правил. Это случилось, когда, выбирая парами числа, случайно, при достижение квадратного корня числа, взяли их дважды. Например, для числа 16 квадратным корнем будет 4, которое неумышленно добавлено в список еще раз. Это легко чиниться, защитным условием, как показано на Листинге 4:

Листинг 4. Исправленный улучшенный алгоритм
for (int i = 2; i <= sqrt(number); i++)
    if (number % i == 0) {
        factors.add(i);
        if (number / i !=  i)
            factors.add(number / i);
    }

Теперь у меня есть версия пост-теста для определителя совершенных чисел. Это работает, но некоторые проблемы дизайна высовывают свои мерзкие головки. Сначала, используя комментарии, разобьем код на секции. Неизменный запах дурного кода - это крик помощи о рефакторинге на отдельные методы. Новый материал, который только что добавили, возможно, требует комментарий, разъясняющий назначение маленького защитного условия, но пока он останется в одиночестве. Самая большая проблема кроется в размере. Мое эмпирическое правило, для проектов на Java, гласит, что не должно быть методов длиннее десяти строк кода. Если метод превысил это число, то, как правило, метод делает более одной вещи, чего следует избегать. Этот метод открыто игнорирует эвристический подход, потому я сделаю следующую попытку, на этот раз используя TDD.
Для кодирования через TDD, заучите мантру: "Какая самая простая вещь, для которой можно написать тест?". В нашем случае: "это число совершенно или нет?". Ответ "Нет" — будет слишком расплывчатым. Необходимо разбить проблему и подумать, что значит "совершенное число". Я легко могу за несколько шагов определить совершенное число:
  • Мне нужны делители числа из запроса.
  • Необходимо определять, что число - это делитель.
  • Необходимо суммировать делители.
Возвращаясь к идее простейшей вещи, какой из элементов списка выглядит самым простым? Полагаю, это определение, что одно число является делителем другого, поэтому вот мой первый тест в Листинге 5:

Листинг 5. Тест "Это число - делитель?"
public class Classifier1Test {

    @Test public void is_1_a_factor_of_10() {
        assertTrue(Classifier1.isFactor(1, 10));
    }
}

С точки зрения той глупости, которая нужна мне, это простой незначительный тест. Чтобы скомпилировать этот код, у вас должен быть класс Classifier1 с методом isFactor() Тогда, необходим скелет структуры класса, прежде чем смогу получить красную полоску. Написание безумно простых тестов, позволит содержать структуру в порядке, до того как начнете существенно размышлять над проблемной областью. Я хочу думать только об одной вещи одновременно, и это поможет работать над скелетной структурой, без нужды волноваться о нюансах решаемой проблемы. Скомпилировав это и получив красную полоску, я готов написать код из Листинга 6:

Листинг 6. Первый проход метода "Делитель?"
public class Classifier1 {
    public static boolean isFactor(int factor, int number) {
        return number % factor == 0;
    }
}

Итак, он прост, элегантен, и работает. Теперь я могу приступать к следующей простейшей задаче: получение списка делителей числа. Тесты представлены в Листинге 7:

Листинг 7. Следующий тест: делители числа
@Test public void factors_for() {
    int[] expected = new int[] {1};
    assertThat(Classifier1.factorsFor(1), is(expected));
}

На листинге 7 самый простой пример, который я смог подобрать для данных делителей, и потому теперь могу написать простейший код, проходящий тест (и чуть позже усложню его). Следующий метод представлен в Листинге 8:

Листинг 8. Простой метод factorsFor()
public static int[] factorsFor(int number) {
    return new int[] {number};
}

Хотя этот метод работает, но намертво тормозит мое продвижение. Хорошей идеей выглядит сделать метод isFactor() статическим, т.к. он всего лишь возвращает что-то на основе входных данных. Однако, теперь придется сделать статическим еще и метод factorsFor(), а значит необходимо передавать обоим методам параметр number Как побочный эффект завышенной статичности, код становится слишком процедурным. Чтобы починить, займусь рефакторингом двух имеющихся методов, что просто, потому что в них до сих пор мало кода. Отредактированный классClassifier представлен в Листинге 9:

Листинг 9. Улучшенный класс Classifier
public class Classifier2 {
    private int _number;

    public Classifier2(int number) {
        _number = number;
    }

    public boolean isFactor(int factor) {
        return _number % factor == 0;
    }
}

Я сделал number переменной экземпляра внутри класса Classifier2, что дает избежать ее передачу как параметра куче статических методов.
Следующая задача в списке декомпозиции говорит, что нужно найти делители для числа. Таким образом, следующий тест (показан в Листинге 10) должен проверить это:

Листинг 10. Следующий тест: делители числа
@Test public void factors_for_6() {
    int[] expected = new int[] {1, 2, 3, 6};
    Classifier2 c = new Classifier2(6);
    assertThat(c.getFactors(), is(expected));
}

А теперь, в Листинге 11, попытаюсь реализовать метод, возвращающий массив делителей для заданного параметра:

Листинг 11. Первый проход метода getFactors()
public int[] getFactors() {
    List factors = new ArrayList();
    factors.add(1);
    factors.add(_number);
    for (int i = 2; i < _number; i++) {
        if (isFactor(i))
            factors.add(i);
    }
    int[] intListOfFactors = new int[factors.size()];
    int i = 0;
    for (Integer f : factors)
        intListOfFactors[i++] = f.intValue();
    return intListOfFactors;
}

Этот код проходит тест, но, если подумать, он ужасен! Такое случается, когда рассматриваете способы воплощения в коде, используя тесты. Так что же в нем страшного? Во-первых, он слишком большой, запутанный и страдает от проблемы "больше, чем одна вещь". Инстинкт подсказывает мне вернуться к int[], но это добавит много сложности в фундамент, и ничего мне не принесет. Если думать о будущих методах, которые могут вызывать существующий, то это значит заведомо вставать на скользкую дорожку. Чтобы добавить нечто комплексное в сустав скелета, должны быть сильные причины, и таких обоснований сейчас нет. Глядя на код, можно предположить, что произойдет еслиfactors существует как внутреннее состояние класса, и что позволяет разбить функциональность метода.
Одна из полезных характеристик этих поверхностных тестов, что метод по настоящему цельный. Кент Бек (Kent Beck) написал об этом в авторитетной книге Smalltalk Best Practice Patterns (см. Источники). В книги, Кент вводит шаблон метод композиций. Шаблон метода композиций определяет три ключевых утверждения:
  • Разделяйте программу на методы, выполняющие одну определимую задачу.
  • Держите на одном уровне абстракции все операции метода.
  • Программы естественным образом превратятся в множество мелких методов размером в несколько строк.
Метод композиций - это одна из полезных характеристик дизайна на которую провоцирует TDD, и который откровенно нарушен в методе getFactors() из Листинга 11. Это возможно починить, проделав следующие шаги:
  1. Перевести factors во внутреннее состояние.
  2. Переместить инициализацию factors в конструктор.
  3. Давайте уйдем от золочённого преобразования к коду int[] и рассмотрим его позже, когда оно станет выгодным.
  4. Добавим другой тест для addFactors().
Четвертый, совершенно неуловимый, но важный шаг. Написание дефектной версии кода, выявило, что первый шаг декомпозиции не завершен. Строка кода addFactors() зарытая в середине длинного метода - это тестируемое свойство. Это настолько элементарно, что не обратил внимания при первом рассмотрении проблемы, но теперь все очевидно. Как это обычно и бывает. Один из тестов может привести к последующей декомпозиции проблемы на все более мелкие и мелкие части, и каждая из них тестируемая.
До поры до времени, отложу широкую проблему getFactors() и всерьез займусь более мелкой новой. Таким образом, вот следующий тест addFactors() (показан в Листинге 12):

Листинг 12. Тест для addFactors()
@Test public void add_factors() {
    Classifier3 c = new Classifier3(6);
    c.addFactor(2);
    c.addFactor(3);
    assertThat(c.getFactors(), is(Arrays.asList(1, 2, 3, 6)));
}

Код теста, представленный в Листинге 13, сама простота:

Листинг 13. Простой код для добавления делителей
public void addFactor(int factor) {
    _factors.add(factor);
}

Запускаю юнит-тест, полный уверенности увидеть зеленую полоску, но он проваливается! Как такой простой тест может провалиться? Основная причина показана на Рисунке 2:

Рисунок 2. Основная причина проваленного юнит-теста













Предполагаемый список должен иметь значения 1, 2, 3, 6, а в действительности результат 1, 6, 2, 3. А, так это потому что изменил код для добавления 1 и самого числа в конструкторе. Одним из решений этой проблемы будет постоянно принимать на страхование мои ожидаемые предположения, что 1 и число всегда будут первыми. Но правильное ли это решение? Нет. Проблема намного более глубокая. Делители - это список из чисел? Нет, это набор из чисел. Мое первое (неверное) предположение привело к использованию списка из целых чисел для делителей, но это скверное абстрагирование. Перестроив код на использование наборов вместо списков, не только починили проблему, но используя более правильное абстрагирование, сделали решение лучше в целом.
Это именно тот вид дефектного суждения что тесты могут разоблачать, если тесты пишутся до кода, чтобы сбить с толку. Теперь, так как это простой тест, общий дизайн кода стал лучше, потому что найдено более подходящее абстрагирование.
До сих пор, я обсуждал непредсказуемое проектирование в контексте проблемной области совершенных чисел. В особенности замечу, что в первой версии решения (с пост-тестами) сделано некорректное допущение о типах данных. "Пост-тест" проверяет крупномодульную функциональность кода, а не индивидуальные части. TDD проверяет строительные блоки, из которых возникает крупномодульная функциональность, раскрывая дополнительные сведения в процессе работы.
В следующем выпуске, на проблеме совершенных чисел, я продолжу дальнейшее разъяснение примеров дизайна, которые могут проявится, если отказаться от вашего способа тестирования. Когда версия TDD будет завершена, я сравню некоторые показатели обоих наборов исходных коодов. А так же обращу внимание на некоторые другие каверзные вопросы TDD-дизайна, такие как, когда и где тестировать частные методы.

Изучите
  • Hamcrest matchers: Библиотека сопоставителей объектов, позволяющая декларативно задавать "совпало" при использовании в других фреймворках.

  • Test-Driven Development (Kent Beck, Addison-Wesley, 2003): Бек, создатель экстремального программирования, использует примеры на деньгах, чтобы разъяснить TDD.

  • Smalltalk Best Practice Patterns (Kent Beck, Prentice Hall, 1996): Узнайте больше о шаблоне composed method.

  • The Productive Programmer (Neal Ford, O'Reilly Media, 2008): Более развернутая версия примера из это статьи рассмотрена в главе "Test Driven Development" новой книги Нила Форда.

  • "Emergent Optimization in Test Driven Design" (Michael Feathers): Как тестирование помогает избегать необдуманной оптимизации.

  • Поищите в техническом книжном магазине книги по этой и другим техническим тематикам.

  • Раздел Java-технологий на developerWorks: Найдите сотни статей о каждом аспекте программирования на Java.

Получите продукты и технологии
  • JUnit: Скачайте JUnit.

Обсуждение

Нил Форд (Neal Ford) Архитектор ПО и Идейный вдохновитель (Meme Wrangler) в глобальной консалтинговой IT-компании ThoughtWorks. Он проектирует и разрабатывает приложения, образовательные материалы, статьи для журналов, обучающие системы, видеопрезентации, а так же является автором или редактором книг широкого круга технологий, включая новейшую The Productive Programmer. Специализируется на проектировании и разработке крупномасштабных корпоративных приложениях. Является международно признанным докладчиком на конференциях разработчиков по всему миру. Посмотрите его Веб-сайт.
Оригинал статьи: http://www.ibm.com/developerworks/java/library/j-eaed2/

воскресенье, 18 октября 2009 г.

Отключение расширений (extension) в Chromium Web Browser

Для этого необходимо в адресной строке написать "chrome:extensions", при этом адрес автоматически преобразуется в "chrome://extensions/". Можно сразу второй вариант использовать, но первый короче и проще запоминается.



Теперь ненужное расширение можно либо отключить временно (Disable), либо удалить навсегда (Uninstall).

четверг, 15 октября 2009 г.

Другие острова

Это статья является этаким вольным переводом-пересказом.



Оказывается, Ява - это не единственный остров, который как-то связан с разработками на Java? Вот вам ещё один, который называется "Ломбок". Именем этого острова назван замечательный проект, распространяемый по лицензии MIT.

Что такое Project Lombok?

Project Lombok это две вещи в одном: генератор кода на этапе компиляции и генератор кода на этапе разработки. Что и делает находку бриллиантовой. В сущности, Lombok интегрируется непосредственно в цикл компиляции Eclipse (манипулируя абстрактным деревом синтаксиса кода в процессе его набора) и моментально генерирует код на основе аннотаций. Сгенерированный код тут же становится доступен всем другим классам.

Какой код может генерировать Lombok из аннотаций? Самое важное, порождаются основные шаблонные вещи, которые обычно заставляют классы Java выглядеть дико многословно и монструозно, а именно:

  • получатели (getters) и устанавливалки (setters) для полей
  • Строковое представление класса POJO в toString()
  • hashCode() и equals()
В то же время, Lombok обеспечивает автоматическое управление ресурсами. Например, ваши потоки будут всегда закрываться безопасно, без необходимости использовать блоки try/catch/finally.


Установка Lombok и поддержка Maven

Lombok предоставляется в виде исполняемого файла JAR (lombok.jar). Правда, в настоящее время режим среды разработки работает только в Eclipse. При запуске файла JAR, появится простой мастер и попросит указать, где находятся исполняемые файлы Eclipse (см. Рисунок 1).






Рисунок 1. Мастер установки Lombok.



Просто укажите на исполняемые файлы Eclipse и нажмите "Install/Update". Вы должны будете делать это каждый раз для всех новых версий Lombok.

Чтобы включить поддержку Maven, просто добавьте репозиторий Lombok и его зависимости в файл pom.xml, следуя инструкциям на веб-сайте проекта. После чего, Lombok будет работать с жизненным циклом компиляции Maven "прямо из коробки".



Project Lombok в действии

Для полного понимания, каким образом Lombok истребляет массу строк кода в типичном классе POJO, рассмотрим следующую типичную сущность Person:


public class Person  {
   private Long personId;
   
   private String salutation;
   private String firstName;
   private String middleName;
   private String lastName;
   
   private String phoneNumber;
   private String email;
   private String addressLine1;
   private String addressLine2;
   private String city;
   private String state;
   private String country;
   
   private Calendar birthDate;
}


Для того чтобы этот класс стал настоящим правильным POJO, необходимо дописать для него код getters, setters, toString(), equals() и hashCode(). Если бы вы решили использовать в Eclipse функции автоматической генерации кода, то Person POJO со скоростью грибов разрастётся до более чем 240 строк кода, большинство из которых, хоть и необходимый, но фактически просто мусор, затрудняющий чтение и понимание. (см. Листинг 1).


public class Person  {

   private Long personId;
   
   private String salutation;
   private String firstName;
   private String middleName;
   private String lastName;
   
   private String phoneNumber;
   private String email;
   private String addressLine1;
   private String addressLine2;
   private String city;
   private String state;
   private String country;
   
   private Calendar birthDate;
   
   public Long getPersonId() {
      return personId;
   }

   public void setPersonId(Long personId) {
      this.personId = personId;
   }

   public String getSalutation() {
      return salutation;
   }

   public void setSalutation(String salutation) {
      this.salutation = salutation;
   }

   public String getFirstName() {
      return firstName;
   }

   public void setFirstName(String firstName) {
      this.firstName = firstName;
   }

   public String getMiddleName() {
      return middleName;
   }

   public void setMiddleName(String middleName) {
      this.middleName = middleName;
   }

   public String getLastName() {
      return lastName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getPhoneNumber() {
      return phoneNumber;
   }

   public void setPhoneNumber(String phoneNumber) {
      this.phoneNumber = phoneNumber;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(String email) {
      this.email = email;
   }

   public String getAddressLine1() {
      return addressLine1;
   }

   public void setAddressLine1(String addressLine1) {
      this.addressLine1 = addressLine1;
   }

   public String getAddressLine2() {
      return addressLine2;
   }

   public void setAddressLine2(String addressLine2) {
      this.addressLine2 = addressLine2;
   }

   public String getCity() {
      return city;
   }

   public void setCity(String city) {
      this.city = city;
   }

   public String getState() {
      return state;
   }

   public void setState(String state) {
      this.state = state;
   }

   public String getCountry() {
      return country;
   }

   public void setCountry(String country) {
      this.country = country;
   }

   public Calendar getBirthDate() {
      return birthDate;
   }

   public void setBirthDate(Calendar birthDate) {
      this.birthDate = birthDate;
   }

   @Override
   public String toString() {
      return "Person [addressLine1=" + addressLine1 + ", addressLine2="
            + addressLine2 + ", birthDate=" + birthDate + ", city=" + city
            + ", country=" + country + ", email=" + email + ", firstName="
            + firstName + ", lastName=" + lastName + ", middleName="
            + middleName + ", personId=" + personId + ", phoneNumber="
            + phoneNumber + ", salutation=" + salutation + ", state="
            + state + "]";
   }

   @Override
   public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result
            + ((addressLine1 == null) ? 0 : addressLine1.hashCode());
      result = prime * result
            + ((addressLine2 == null) ? 0 : addressLine2.hashCode());
      result = prime * result
            + ((birthDate == null) ? 0 : birthDate.hashCode());
      result = prime * result + ((city == null) ? 0 : city.hashCode());
      result = prime * result + ((country == null) ? 0 : country.hashCode());
      result = prime * result + ((email == null) ? 0 : email.hashCode());
      result = prime * result
            + ((firstName == null) ? 0 : firstName.hashCode());
      result = prime * result
            + ((lastName == null) ? 0 : lastName.hashCode());
      result = prime * result
            + ((middleName == null) ? 0 : middleName.hashCode());
      result = prime * result
            + ((personId == null) ? 0 : personId.hashCode());
      result = prime * result
            + ((phoneNumber == null) ? 0 : phoneNumber.hashCode());
      result = prime * result
            + ((salutation == null) ? 0 : salutation.hashCode());
      result = prime * result + ((state == null) ? 0 : state.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (getClass() != obj.getClass())
         return false;
      Person other = (Person) obj;
      if (addressLine1 == null) {
         if (other.addressLine1 != null)
            return false;
      } else if (!addressLine1.equals(other.addressLine1))
         return false;
      if (addressLine2 == null) {
         if (other.addressLine2 != null)
            return false;
      } else if (!addressLine2.equals(other.addressLine2))
         return false;
      if (birthDate == null) {
         if (other.birthDate != null)
            return false;
      } else if (!birthDate.equals(other.birthDate))
         return false;
      if (city == null) {
         if (other.city != null)
            return false;
      } else if (!city.equals(other.city))
         return false;
      if (country == null) {
         if (other.country != null)
            return false;
      } else if (!country.equals(other.country))
         return false;
      if (email == null) {
         if (other.email != null)
            return false;
      } else if (!email.equals(other.email))
         return false;
      if (firstName == null) {
         if (other.firstName != null)
            return false;
      } else if (!firstName.equals(other.firstName))
         return false;
      if (lastName == null) {
         if (other.lastName != null)
            return false;
      } else if (!lastName.equals(other.lastName))
         return false;
      if (middleName == null) {
         if (other.middleName != null)
            return false;
      } else if (!middleName.equals(other.middleName))
         return false;
      if (personId == null) {
         if (other.personId != null)
            return false;
      } else if (!personId.equals(other.personId))
         return false;
      if (phoneNumber == null) {
         if (other.phoneNumber != null)
            return false;
      } else if (!phoneNumber.equals(other.phoneNumber))
         return false;
      if (salutation == null) {
         if (other.salutation != null)
            return false;
      } else if (!salutation.equals(other.salutation))
         return false;
      if (state == null) {
         if (other.state != null)
            return false;
      } else if (!state.equals(other.state))
         return false;
      return true;
   }
}


А теперь, как выглядит тот же POJO после "Ломбокизации":


@Data
public class Person  {

   private Long personId;
   
   private String salutation;
   private String firstName;
   private String middleName;
   private String lastName;
   
   private String phoneNumber;
   private String email;
   private String addressLine1;
   private String addressLine2;
   private String city;
   private String state;
   private String country;
   
   private Calendar birthDate;
   
}


И всё! Когда Lombok установлен в вашей Eclipse Runtime, то простая аннотация @Data рисует волшебным образом весь тот код необходимого стандартного мусора. Что делает определение сущностей JPA/Hibernate невероятно легким и быстрым.

Но и это ещё не всё! Все методы getters и setters появятся в схеме, как если бы код существовал на самом деле (см. Рисунок 2).




Рисунок 2. Все методы getters и setters видны в схеме

А также их видно и при дополнении кода (см. Рисунок 3).





Рисунок 3. Все методы видны при дополнении кода


Если необходим более филигранный контроль, то Project Lombok предлагает следующие аннотации @Getter, @Setter, @ToString и @EqualsAndHashCode. А вышесказанное означает, что аннотация @Data комбинируя, заключает их все в себе. В нашем процессе разработки, аннотация @Data используется в 99% случаях.

Дополнительные особенности Lombok

Помимо основных характеристик, которые вы только что видели, Lombok поставляется с несколькими дополнениями:

  • Аннотация @Cleanup обеспечивает автоматическое управление ресурсами. Например, в следующем сниппете, @Cleanup гарантирует, что метод Close () потока будет выполнен до выхода из метода:


    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    

    И нет необходимости ждать Java 7, чтобы использовать автоматическое управление ресурсами уже сегодня в Java 6.


  • Аннотация @Synchronized это альтернатива ключевому слову synchronized, но лучше реализованная:


    @Synchronized
    public static void hello() {
       System.out.println("world");
    }


  • Аннотация @SneakyThrows отключает в Java проверяемые исключения:


    @SneakyThrows
    public void run() {
       throw new Throwable();
    }
    
    Решите вы или нет использовать эту функцию (у меня смешанные чувства по поводу нее), но такую возможность иметь полезно.


Планы на будущее: Добавление новых особенностей Java

На момент написания, актуальной версией Проекта Ломбок была 0.8.5. Краткосрочной перспективой команды Ломбок была стабилизация и безотказность работы в Eclipse. Хотя, имеются некоторые незначительные проблемы ранних версий (например, некоторые неожиданные предупреждения при компиляции), но простая перекомпиляция проекта позволяет быстро от них избавиться. В целом, выгода от использования Ломбок намного перевешивает имеющиеся небольшие сбои Eclipse (и которые довольно редки, по моему опыту). Долгосрочные же планы у Ломбок гораздо более грандиозные. Оба автора проекта Ломбок (Reinier Zwitserloot и Roel Spilker) хотят перехватить процесс компиляции в Eclipse до точки, где они действительно могут добавить новые функции в Java, в частности, реальное завершение. Узнайте больше об этой амбициозной цели в  этом обсуждении Google Groups.



Недостатки

Основной недостаток Ломбок очевиден: работа в режиме среды разработки поддерживается только в Eclipse. Если Eclipse не является вашей IDE, то на данный момент проект Ломбок это не ваш вариант. Возможно такая поддержка в будущем может появиться в средах NetBeans и IntelliJ, но она предусматривает несколько довольно тяжелых IDE-хаков для конкретной среды. Зато, все мастерские с Eclipse должны рассмотреть добавление Ломбок в своей ежедневный инструментарий прямо сегодня.



Прощай многословность POJO

С внедрением Проекта Ломбок, Reiner и Roel сделали многословие Java POJO достоянием истории. Когда вы добавите Ломбок к вашей ежедневной практике разработки, то просто не захотите возвращаться назад. Это очень просто и на наш взгляд, это самое революционное дополнение к экосистеме Java после рождения самого Eclipse.



Ссылки