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

О книге Кириевски "Рефакторинг с использованием шаблонов"

Продолжая историю с шаблонами у меня наконец дошли руки до давно мною купленной книги "Рефакторинг с использованием шаблонов" Джошуя Кериевски.

Книга очень сильна по своей структуре напоминает книгу Фаулера о рефакторинге - те же перечисление различных рефакторингов вместе с правилами их применения и примерами. Все примеры, кстати, на java.

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

Из плюсов отметил бы не плохие примеры использования TDD при разработки, сами примеры взяты из реальной практики и книга не затянута. Так же книга содержит вполне умеренные дозы UML. Из недостатков: раздела "механика" зачастую тривиальны и просто занимают кучу места не неся какой-то реальной пользы (впрочем тогда их можно просто пропускать).

В общем отличная книга - после Фаулера и Гаммы с Ко обязательна к прочтению.

воскресенье, 11 ноября 2012 г.

Опять double

Недавно занимался проблемой кластеризации наборов объектов из нескольких разных источников. После написания первой версии алгоритма решил проверить алгоритмы на вшивость класторизовав данные от одного источника с самими собой. Понятно, что я рассчитывал 100% совпадение всех объектов и какого же было мое удивление когда из ~250 тысяч объектов не класторизованными оказались примерно 7 тысяч объектов. Чудеса. Принялся копать и искать волшебника.

Изучение логов прояснило ситуацию.

Одним из факторов при кластеризации является дистанция между объектами, вычисленная на основе долготы и широты. Код для этого дела широко распространен в интернете и выглядит примерно так:
   static private double distance(double lat1, double lon1, double lat2, double lon2) {
        double theta = lon1 - lon2;
        double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
        dist = Math.acos(dist);
        dist = rad2deg(dist);
        dist = dist * 60 * 1.1515 * 1.609344;
        return dist;
    }
Расстояние возвращается в километрах. Функция работает вполне себе нормально в большинстве случаев. Но логи подсказали написать вот такой волшебный тест:
@Test
public void testEqualDistance2() throws Exception {
        SomeObject object1 = new SomeObject();
        SomeObject object2 = new SomeObject();

        double lat = 51.4902008376945;
        double lon = 4.28921666956459;
        object1.setLatitude(lat);
        object1.setLongitude(lon);

        object2.setLatitude(lat);
        object2.setLongitude(lon);

        System.out.println("Distance between " + object1 + " and " + object2 + ": " + object1.distance(object2) + " reverse=" +
                object2.distance(object1));

        assertEquals(object2.distance(object1), 0.0, 0.00000001);
}

И о чудо - тест проваливается - и отладчик показывает что расстояние между object1 и object2 равно NaN. Приплыли. Проход в дебаггере показал, что брался арккосинус от значения едва больше единицы (что-то в вроде 1.00000000002) что оказалось фатальным. Добавление

 
dist = Math.min(dist, 1);
перед 4 строкой спасло отца русской демократии и чудеса закончились.

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