вторник, 24 ноября 2009 г.

Старое заблуждение о выносе локальной переменной за пределы цикла

Очень старое заблуждение, которое активно пропагандируется некоторыми некомпетентными преподавателями вузов. Заблуждение иллюстрируется следующим кодом:
    public void test1()
    {
        for (int i = 0; i < 20; i++)
        {
            int j = i;
            j++;
        }
    }

    public void test2()
    {
        int j;
        for (int i = 0; i < 20; i++)
        {
            j = i;
            j++;
        }
    }
Считается, что нужно самому выносить локальную переменную из цикла, иначе память под неё будет выделяться на каждой итерации. На самом деле это совсем не так. Компилятор без труда оптимизирует данный кусочек кода. Память будет выделена всего лишь один раз. Причем, это касается не только Java, но и других языков, таких как C, C++ и т.д. Это типичная оптимизация.
Для сомневающихся привожу байт-код обоих методов:
 // Stack: 2, Locals: 3
  public void test1();
     0  iconst_0
     1  istore_1 [i]
     2  goto 13
     5  iload_1 [i]
     6  istore_2 [j]
     7  iinc 2 1 [j]
    10  iinc 1 1 [i]
    13  iload_1 [i]
    14  bipush 20
    16  if_icmplt 5
    19  return
      Line numbers:
        [pc: 0, line: 14]
        [pc: 5, line: 16]
        [pc: 7, line: 17]
        [pc: 10, line: 14]
        [pc: 19, line: 19]
      Local variable table:
        [pc: 0, pc: 20] local: this index: 0 type: my.test.ByteCodeTest
        [pc: 2, pc: 19] local: i index: 1 type: int
        [pc: 7, pc: 10] local: j index: 2 type: int
      Stack map table: number of frames 2
        [pc: 5, append: {int}]
        [pc: 13, same]
  
  // Method descriptor #6 ()V
  // Stack: 2, Locals: 3
  public void test2();
     0  iconst_0
     1  istore_2 [i]
     2  goto 13
     5  iload_2 [i]
     6  istore_1 [j]
     7  iinc 1 1 [j]
    10  iinc 2 1 [i]
    13  iload_2 [i]
    14  bipush 20
    16  if_icmplt 5
    19  return
      Line numbers:
        [pc: 0, line: 24]
        [pc: 5, line: 26]
        [pc: 7, line: 27]
        [pc: 10, line: 24]
        [pc: 19, line: 29]
      Local variable table:
        [pc: 0, pc: 20] local: this index: 0 type: my.test.ByteCodeTest
        [pc: 7, pc: 13] local: j index: 1 type: int
        [pc: 2, pc: 19] local: i index: 2 type: int
      Stack map table: number of frames 2
        [pc: 5, full, stack: {}, locals: {my.test.ByteCodeTest, _, int}]
        [pc: 13, same]
Как мы видим, память выделяется под 2 переменные.

понедельник, 23 ноября 2009 г.

Подсчет временной разницы между двумя датами

Возникла такая задача: подсчитать разницу, прошедшую между двумя датами и уметь выводить её в годах, месяцах, днях и т.д., а также в такой форме "1 день 2 месяца 3 года".

При решении данной задачи нужно учитывать, что нельзя просто взять и вычесть количество миллисекунд одной даты из другой. Таким образом мы не можем в точности посчитать ничего, т.к. существует переход на летнее/зимнее время. Например, мы вычтем кол-во мс из даты, созданной на 30 марта 2009 года(установим также 0 часов, минут, секунд и миллисекунд) и 29 марта 2009 года(также обнулим лишние поля). Если перевести получившееся количество миллисекунд в часы, то мы получим 23 часа, а должно быть 24. Почему? Потому что именно в это время у нас переводят на зимнее время. Соответственно, необходимо учитывать данный факт.

То есть для вычисления мс, прошедших между двумя датами нужно использовать следующий метод:
public static long getDiffInMillis(Calendar c1, Calendar c2)
    {
        // учитываем перевод времени
        long time1 = c1.getTimeInMillis() + c1.getTimeZone().getOffset(c1.getTimeInMillis());
        long time2 = c2.getTimeInMillis() + c2.getTimeZone().getOffset(c2.getTimeInMillis());
        return Math.abs(time1 - time2);
    }

Таким образом мы легко получим кол-во прошедших часов, минут и секунд. Но если мы хотим получить кол-во прошедших дней, не учитывая часы, минуты и прочее, то нам необходим следующий метод:
public static int getDiffInDays(Calendar c1, Calendar c2)
    {
        Calendar gc1 = (Calendar)c1.clone();
        Calendar gc2 = (Calendar)c2.clone();

        // обнуляем часы, минуты и секунды
        gc1.set(Calendar.HOUR, 0);
        gc2.set(Calendar.HOUR, 0);
        gc1.set(Calendar.SECOND, 0);
        gc2.set(Calendar.SECOND, 0);
        gc1.set(Calendar.MILLISECOND, 0);
        gc2.set(Calendar.MILLISECOND, 0);

        return (int)(getDiffInMillis(c1, c2) / MILLISECONDS_IN_A_DAY);
    }

Но используя данную методику нельзя сказать сколько прошло месяцев и лет. Для этого мы будем использовать следующий метод:
/**
     * 

Высчитывает разницу между двумя временами, в зависимости от значения field. * field - это константное значение из java.util.Calendar, обозначающее * конкретную временную метрику(год, месяц и т.д.).

* *

Устанавливает поля, заданные в массиве clearFields равными 0.

* * @param field * константное значение из java.util.Calendar(YEAR, MONTH...) * @param c1 * первая дата * @param c2 * вторая дата * @param clearFields * поля даты, которые не учитываются при сравнении. Задаются массивом констант из из java.util.Calendar. * Например, можно не учитывать миллисекунды, секунды и часы. Данные поля будут обнулены. * @return * разницу между c1 и c2 в метрике, заданной field */ public static int getTimeDifference(int field, Calendar c1, Calendar c2, int... clearFields) { Calendar gc1, gc2; if (c2.after(c1)) { gc1 = (Calendar)c1.clone(); gc2 = (Calendar)c2.clone(); } else { gc1 = (Calendar)c2.clone(); gc2 = (Calendar)c1.clone(); } if (clearFields != null) { // очищаем поля, которые мы не будем учитывать при сравнении дат for (int clearField : clearFields) { gc1.clear(clearField); gc2.clear(clearField); } } int count = 0; for (gc1.add(field, 1); gc1.compareTo(gc2) <= 0; gc1.add(field, 1)) { count++; } return count; }
Как мы видим, данный метод просто пытается прибавить 1 к заданному временному промежутку. В clearFields мы задаем поля, которые необходимо обнулить. Таким образом мы можем найти разницу не только в годах и месяцах, но и в чем угодно, но это будет неэффективно. Следующей задачей является вывод нужного слова для временной единицы. Например, мы говорим прошло "5 лет", "2 месяца", "1 год", "11 секунд" и т.д. Оказывается, что все названия временных промежутков подчиняются единому закону. Достаточно всего лишь трех слов :). Этот закон выражен данным кодом:
/**
     * Возвращает слово, добавляющееся к временной единице, когда указывается сколько прошло
     * временных единиц.
     * В русском языке для временных единиц достаточно трех слов.
     * Значение time берется по модулю.
     * 
     * @param time
     *            количество временной единицы
     * @param timeUnitName1
     *            Название временной единицы, которое используется с 1. Например, 1 "минута".
     * @param timeUnitName2
     *            Название временной единицы, которое используется с 2. Например, 2 "минуты".
     * @param timeUnitName5
     *            Название временной единицы, которое используется с 5. Например, 5 "минут".
     * @return
     *         слово для единицы времени
     */
    public static String getTimeUnitPeriodName(long time, String timeUnitName1, String timeUnitName2, String timeUnitName5)
    {
        String result;
        time = Math.abs(time); // для отрицательных чисел
        int small = (int)(time % 10);
        int middle = (int)(time % 100);

        // если заканчивается на 11, то всегда оформляется словом timeUnitName5
        if (small == 1 && middle != 11)
        {
            result = timeUnitName1;
        }
        // если оканчиваются на 2, 3, 4, за исключением 12, 13 и 14
        else if (small >= 2 && small <= 4 && (middle < 12 || middle > 14))
        {
            result = timeUnitName2;
        }
        else
        {
            result = timeUnitName5;
        }

        return result;
    }
Оказалось, что так устроен наш язык, что особыми временными единицами являются 11, 12, 13 и 14(точнее оканчивающимся на эти числа). Остальное подчиняется общим правилам. То ли дело в английском языке. Теперь мы можем считать промежуток между двумя датами в любой временной единице и находить для данной единицы нужное слово. Осталась ещё одна нерешенная задача: как выводить временной промежуток в формате "X дней Y месяцев Z лет", но делать это так, чтобы у нас не было ситуации "12 дней 13 месяцев 1 год", а было например так "12 дней 1 месяц 2 года". Т.е. у нас не может быть больше 12 месяцев или больше 31 дня. Данную задачу мы решим следующим образом. Будем находить разницу в годах и затем вычитать данную разницу из конца промежутка. Дальше поступим также для месяца. Оставшиеся дни же просто выведем. Вот кусок кода(from и to - экземпляры Calendar):
// если конечная дата больше, то меняем их местами
        if (from.after(to))
        {
            to = from;
        }

        int diffInYears = DateUtils.getDiffInYears(from, to);
        to.add(Calendar.YEAR, -diffInYears); // вычитаем учтенные года

        int diffInMonths = DateUtils.getDiffInMonths(from, to);
        to.add(Calendar.MONTH, -diffInMonths); // вычитаем учтенные месяцы

        int diffInDays = DateUtils.getDiffInDays(from, to);
Не забудьте, что вы вычитаете из даты, поэтому нужно клонировать переданный экземпляр Calendar. Ну а далее форматируем полученные числа по своему желанию.

p.s. также не забывайте указывать первый день недели на понедельник(в России) - calendar.setFirstDayOfWeek(Calendar.MONDAY)

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

Успех

Есть люди, которые работают быстро и качественно. Результаты таких людей просто потрясают. Фактически пара людей может написать достаточно большую и сложную систему. Пара! Причем сделать это, например, за полгода или меньше. Пример перед глазами. Это круто. Конечно же речь идет не о написании бизнес-лапши. Ибо бизнес лапша на моих весах находится где-то в соотношении 100:1. 100 классов бизнес-лапши равны одному утилитарному(системному) классу.
Вообще рекомендую ввести метрики для подсчета своей эффективности. А также для интереса подсчитать коллег. Соответственно следовать своей метрике и улучшать её.
Мне очень кажется, что увольняться с работы стоит тогда, когда ты в принципе достиг на ней хороших результатов. Дальше уже или скучно или некуда. То есть пока не уволился, достигай результатов. Ставь цель и достигай её. Иначе будешь отвечать на тупые вопросы на знание Java вместо того, чтобы рассказывать о своих успехах на предыдущей работе. Например, одной из метрик может быть кол-во комментариев до и после тебя. Или ты сделал так, что делать некоторую рутинную задачу стало в 2 раза быстрее. Либо ты увеличил скорость некоторого участка в 3 раза. Разница должна выражаться в разах. Возможно ты так отрефакторил класс, что он стал в 2 раза короче, а его использование стало в 4 раза проще. Может быть ты наладил систему тестирования и обеспечил этим в 3 раза меньшее кол-во багов.

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

Старая ошибка в бинарном поиске в Java

Случайно наткнулся на статью Joshua Bloch о любопытной ошибке в реализации бинарного поиска в старой версии Java. Вот ссылка http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html. Суть проблемы в том, что на массивах из очень большого количества элементов(примерно миллиард элементов) данный поиск бы ломался. Причина этого, неочевидна, но проста. А именно при сложении двух чисел типа int можно получить число, больше максимально возможного для int. А, как мы знаем, в бинарном поиске позиция текущего элемента вычисляется, как среднее между двумя числами:

int mid =(low + high) / 2;

Джош переписывает эту строчку кода на:

int mid = low + ((high - low) / 2);

или что лучше по производительности:
int mid = (low + high) >>> 1;

Последняя строка сейчас и используется в Arrays и в Collections. Вот такая забавная история.

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

666 евро в месяц

Предлагают нам учиться во Франции и получить единоразовую стипендию в размере 8000 евро на год. А это получается по 666,6(6) евро в месяц. Неплохо. У нас в ЮУрГУ стипендия равна примерно 50$ в месяц. А зарплата сотрудников института что-то около 120-150$.
Как же тут учиться и одновременно не работать?

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

Интересные заметки по Java

Пишет некто  Dr. Heinz M. Kabutz. Довольно интересные заметки http://www.javaspecialists.eu/archive/archive.jsp

вторник, 13 октября 2009 г.

Пара вопрос к программистам-практикам на C/C++

Подскажите пожалуйста хорошие книги, статьи на тему "дизайна", конструирования программ на языке Си. Интересует, как пишутся "средней руки" системные программы на Си и желательно под Unix системы.
Общие принципы видятся такими:

  1. Выделяются относительно обособленные куски. Они раскидываются по разным файлам
  2. Для них пишутся свои структуры("замены классам") и функции.
  3. В файле(ах) "main" это все собирается воедино
Слишком уж абстрактно...
Также интересно, какими инструментами пользуются для статического анализа кода на предмет ошибок и возможных утечек памяти. В качестве IDE думаю взять Eclipse CDT. 

суббота, 10 октября 2009 г.

Программа для быстрого перевода

Использую сам уже более двух месяцев и рекомендую другим попробовать программу-обертку для перевода от Google Translate http://translateclient.com/. Выделяете любой текст и появляется иконка с кнопкой. Нажимаете и получаете перевод. Можно настроить перевод с любого на любой язык. Также, можно легко отключить работу программы в некоторых приложениях, например в Eclipse, чтобы она не выводила подсказку при выделении текста. Программа стабильна и не глючит.
Автору данной программы большое спасибо!
p.s.: забыл добавить, что работает это только для windows :(

среда, 7 октября 2009 г.

Как сделать в svn уведомления о коммитах

Теперь я понимаю *nix'ые HOW-TO и пошаговые инструкции, где и что нужно вводить :). Вот моё HOW-TO. Сам я устанавливал это на Linux сервере.
1) http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/mailer/mailer.py- сам скрипт для рассылки
http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/mailer/mailer.conf.example настройки для этого скрипта
2) Кладете mailer.py в вашу любимую директорию со скриптами. Не забудьте сделать chmod 755.
3) Переименуйте mailer.conf.example в mailer.conf. Положите его в директорию /home/username/svn/yourproject/conf/. Сделайте chmod 755.
4) Правьте mailer.conf. Тут все зависит от ваших настроек сервера. В моем случае я раскомментировал строчку с mail_command и smtp_hostname.
Запишите в to_addr адреса членов комманды, которые будут получать уведомления. Адреса разделяйте через пробел.
Добавьте в reply_to почтовый адрес сервера(для красоты и фильтрации:)).
Там ещё много всяких настроек. Они уже на усмотрение остаются.
5) Заключительный шаг.
Идем в svn директорию проекта и находим там папку hooks. Переименовываем файл post-commit.tmpl в post-commit. Делаем chmod 755.
Там комментируем лишние линии, кроме REPOS и REV.
Добавляем следующую линию, описывающую путь до скрипта:
/home/username/script/mailer.py commit "$REPOS" "$REV"

Слайды к статье про фунциональное программирование в Java

Многие, наверное, читали следующую статью http://antilamer.livejournal.com/245962.html. Но я думаю, что далеко не все видели слайды к данной статье http://spbhug.folding-maps.org/wiki/EugeneKirpichov?action=AttachFile&do=get&target=JavaFP_ru.ppt. В презентации больше информации, примеров и вообще лучше усваивается.

p.s.: хотя в Google на запрос "Функциональное программирование Java" данные слайды вылезают первыми.

Проглядел комментарии

Извиняюсь перед всеми, что не выводил комментарии. Надеялся, что мне по почте будут выводиться уведомления и попросту не смотрел.
p.s.: Там есть комментарии, оставленные несколько месяцев назад...

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

Наука
Выбрал темой для научных исследований тему №3:"координатор параллельной СУБД на базе СУБД PostgreSQL". Буду постргеc ковырять, а также MPI. Все это дело, между прочим, пишется на чистых Сях. Прощай JVM и сборка мусора! Привет, Access Denied и free!

ФП
Стало доходить "промышленное понимание" основ функционального программирования(ФП). К сожалению, не читал ни одной книги по ФП, поэтому называю это понимание промышленным. Оно ничем фундаментальным не подкреплено. Однако, теперь лень писать циклы, а однотипные действия очень хочется запихать в некие "объектики-функции"(скажем так:)). В Java для этого очень могут помочь доп. библиотеки. Но увлекаться всем этим не стоит. Нужно держать code-style. Однако, приходит смутное понимание, что 1000 строк кода можно уместить в 500 и даже меньше и все будет читабельно. Хочется иметь не 100 методов, а 10 объектов-функций и просто комбинировать их :)

Универ
Очень хочется сюда попасть. Посмотрим, насколько это окажется реальностью.

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

Ссылки на подкасты и научная работа

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

Подкасты, которые я регулярно слушаю:
http://ruby.rpod.ru/
http://maxim.podfm.ru/runetologia/
http://taop.rpod.ru/


Также предложили следующие темы для научной работы:
- оптимизация обменов данными в параллельной СУБД для многопроцессорных
иерархий.
- динамическая реконфигурация параллельной СУБД.
- координатор параллельной СУБД на базе СУБД PostgreSQL


Проект на RoR развивается и скоро уже будет в продакшн :-) Ждите...

вторник, 22 сентября 2009 г.

TDD подход

Занимаюсь задачей по построению отчета. Было пару интересных моментов в этой задаче, связанных с оптимизацией и самим построением. Удалось время построения отчета довести примерно до 1,5 минут, что очень даже приемлемо. В остальном же задача унылая, преунылая.
Почему же она такая преунылая и я занимаюсь ей уже 78 часов..? Потому что отчет строится не совсем правильно. Проблема в том, что автоматизированных тестов нет вообще. Соответсвенно протестировать мне же свою работу почти что невозможно. Вот тут я сразу вспомнил TDD и разработку через тестирование. По сути надо было в начале написать тесты, моделирующие различные состояния бизнес-объектов, а после этого браться за сам отчет. Теперь же ни я ни кто-либо другой не уверен, что данный отчет будет работать корректно. Никогда ранее я не ощущал такой острой необходимости в TDD. Мне кажется, что эта задача будет вечно теребить мой мозг. Самое же страшное - это когда ты не уверен в работоспособности собственного кода и все понимают, что протестировать это вручную крайне утомительно(а значит оно так и остается на твоей совести).

воскресенье, 2 августа 2009 г.

Ruby Design

Понравилось недавнее высказывание на Ruby Talk: "Ruby, thankfully, is designed to be nice to humans, not to parsers."

четверг, 30 июля 2009 г.

Narrowing proxy

Если у вас Hibernate вдруг стал писать в логгере следующие строчки:
"WARN org.hibernate.engine.StatefulPersistenceContext.ProxyWarnLog - Narrowing proxy to class имя_класса - this operation breaks == "
То есть хорошее объяснение вот здесь https://forum.hibernate.org/viewtopic.php?p=2404391

Ужасный маппинг (Crazy Mapping)

Вчера отрефакторил одну "мега-фичу". Она заключалась в том, что маппинг для сущностей создавался автоматически и подключался при каждом старте системы. Это было очень плохо по следующим причинам:
  1. Нет зафиксированного состояния БД. Непонятно, какие сейчас названия у колонок, внешних ключей, индексов и даже таблиц. Для нас это очень важно т.к. мы мигрируем БД из одного состояния в другое и нам обязательно нужно знать, как сейчас называются объекты в БД.
  2. Смена логики формирования влечет смену всех имен объектов в БД.
  3. У нас так не принято делать. Можете не считать это аргументом, но для меня это аргумент.
Я уже обрадовался, что больше такой "фичи" у нас нет, но не тут-то было. Начала вылезать ошибка, что сущности "ru.naumen.unids.archive.StudentArchive" нет в hibernate. Оказалось, что автор данной "фичи" маппил два класса в одну таблицу, а я вынес только один маппинг. То есть у него это выглядело вот так:
1) <class name="ru.naumen.unids.archive.StudentArchive" table="AR_STUDENT" lazy="false">
2) <class name="ru.naumen.unistudent.bobjects.student.StudentArchive" table="AR_STUDENT" lazy="false">
В остальном же весь маппинг полностью аналогичен. Вот так... Сейчас разгребаем.

пятница, 24 июля 2009 г.

О новом, старом и общем

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

Понравилось недавнее высказывание Google, о том, что их система масштабируется горизонтально. Вся их система держится на нескольких основных технологиях, таких как Map-Reduce, Big Table, GFS и т.д. И оптимизация каждой из них ведет к оптимизации всей системы. Это замечательно.
Как такая идея может пригодиться нам, обывателям? Например, когда мы делаем систему кэширования, то она должна быть универсальной, единой для системы. Вообще же надо стремиться к универсализации и использовании open-source решений. Ресурсов не хватает все это поддерживать, не то что развивать.

четверг, 23 июля 2009 г.

Изобретатели велосипедов хуже идиотов

Настроение сегодня ни к черту из-за того, что целый день ковыряюсь в идиотском модуле, написанным одним "программистом". Этот хренов модуль, как и его автор ничего не слышали о MVC. Также они считают, что когда все понапихано в одном месте - это признак хорошего кода. Как же надо было хотеть изобретать велосипеды, чтобы заменить все стандартные компоненты на компоненты собственного производства ("@author FullIdiot"). Его уже давно не волнует, что теперь штук 300 выписок создано на основе его "наработок". И теперь от этого идиотизма никуда уже не деться, не помогут никакие книги про рефакторинг.
Да и ещё этот горе-программер решил похвастаться знанием шаблонов разработки. Применил "паттерны" Visitor, Adapter, AdapterProvider. Тошнит уже... Кому разгребать эту конюшню..?

p.s.: Изобретатели велосипедов хуже идиотов. идиоты хотя бы пользуются готовыми наработками.

среда, 22 июля 2009 г.

Проблемы с кодировкой в патчах Eclipse

Проблема была с русскими символами при накате Eclipse патча (Apply Patch). Собственно выводилась "абракадабра". Наконец-то потратил немного времени, чтобы побороть эту проблему . У нас почти все файлы кодируются в UTF8. Я думал, что возможно патчи кодируются в ANSI и при перекодировке из UTF8 с ними что-то случается. Но оказалось, что патчи сохраняются в UTF8 (видимо тут учитываются настройки кодировки проекта), а вот накатываются, как ANSI. Решением здесь будет перекодировать патч в ANSI. Например, в NotePad++ это делается в один клик "Кодировки -> Преобразовать в ANSI". После этого патч накатывается без проблем.

update(добавлено позже): оказывается данная проблема очень легко решается установкой параметра "-Dfile.encoding=UTF-8" в настройках Eclipse (добавить эту строчку в файл eclipse.ini).

(Eclipse patch encoding problem, проблемы с кодировкой патча в Eclipse)