апр 23, 15:21
Когда я говорил о недостатках Java, то упустил одну вещь, а именно – многопоточность.
Стандартные реализация потоков в Яве имеет некоторые проблемы, некоторые из которых я попробую описать. На полноту я не претендую, поэтому расскажу только то, что знаю, самые очевидные из них.
Небольшой ликбез
В яве центральное понятие для мнопоточности – это понятие “монитора”. Монитор можно представить себе как объект специального назначения, который представляют собой набор некоторого кода, процедур, доступ к которым гарантирован по принципу взаимного исключения (mutal-exclusion), или взаимо-исключающему семафору (mutal-exclusion semaphore или просто mutex, мьютекс). Мониторы существуют только на уровне работы JVM, они не доступны как объекты Java.
Основная идея мьютекса – это владение. Только один поток может завладеть мьютексом в одно время. Если другой поток попытается захватить мьютекс, это заблокирует его пока первый поток не отпустит мьютекс.
Если несколько потоков ждут освобождения мьютекса, то только один получит мьютекс, когда тот освободится. Остальные продолжат ожидание.
Т.е другими словами, имея код:
synchronized (this) {
doSomething();
}
мы можем представить его себе, как:
this.mutex.acquire();
try {
doSomething();
} finally {
this.mutex.release();
}
Проблема 1: Каждый объект в Java имеет только один монитор
Чтобы объяснить это, приведу аналогию, которую я прочел в одной из книг по потокам и она мне показалась очень удачной.
Представим себе, что монитор – это туалет на борту самолета. Только один человек (поток, thread) может воспользоваться туалетов в одно время. Все остальные вынуждены ждать пока туалет не освободится, создавая очереди и так далее, и так далее. Пока дверь закрыта – все ждут.
В этом примере, объект – это самолет. Пассажиры – потоки. Туалет – монитор. Замок на двери – мьютекс.
В яве, у каждого объекта есть один и только один монитор. В тоже время, один монитор (туалет) может иметь несколько дверей в него. Каждая дверь представляет собой synchronized блок. Когда пассажир заходит в туалет, то все двери закрываются. Это и есть проблема и ограничение.
С другой стороны, если в туалет ведут двери, на которые не установлен замок (нет synchronized блока), то пассажир может войти в туалет, не закрыв замки и вполне может ожидать, что его потревожит кто-то другой (другой поток).
Можно проиллюстрировать с помощью следующего кода:
public class EveryObjectIsMutex {
private int count;
private int anotherCount;
static public void main(String[] args) {
final EveryObjectIsMutex mutex = new EveryObjectIsMutex();
new Thread(new Runnable() {
public void run() {
mutex.setCount(20);
}
}).start();
System.out.println(mutex.getCount());
System.out.println(mutex.getAnotherCount());
}
synchronized public int getCount() {
return count;
}
synchronized public void setCount(int count) {
// some long calculations goes here
this.count = count;
}
synchronized public void setAnotherCount(int anotherCount) {
this.anotherCount = anotherCount;
}
synchronized public int getAnotherCount() {
return anotherCount;
}
}
Здесь пока выполняется метод setCount(20) в отдельном потоке, никакой другой методов над объектом mutex выполнить нельзя. Даже getAnotherCount(), хотя тот и использует совсем другие данные, никак не пересекаясь с данными метода setCount().
Выход из этой ситуации делать synchronized над разными объектами внутри setCount() и setAnotherCount() с помощью:
…
private Object lock1;
pirvate Object lock2;
…
public void setCount(int count) {
synchronized (lock1) {
// some long calculations goes here
this.count = count;
}
}
synchronized public void setAnotherCount(int anotherCount) {
synchronized (lock2) {
this.anotherCount = anotherCount;
}
}
wait() и notify()
Wait() и notify() – это еще способ синхронизации. Первый – взаимное исключение с помощью мьютексов – позволяет осуществлять последовательный доступ к общим данным.
Второй – это кооперация с помощью wait() и notify(). Это необходимо, когда один поток зависит от другого потока (например, ждет данных, которые еще не поступили).
С этими методами связано несколько проблем.
Первая – wait() принимает как параметр таймаут, максимальное время, которые нужно ждать, но при возврате из метода wait() нет никакой возможности узнать был ли возврат вызван таймаутом или тем, что другой поток вызвал notify().
Вторая проблема – nested-monitor lockout (не знаю, как перевести по-хорошему на русский). С wait() и notify(), как и с любыми блокирующими операциями, очень легко нарваться на это вид deadlock’a. Проблема заключается в том, что блокирующая функция может быть вызвана из синхронизированного кода. И единственный способ разблокировать ее – вызвать другой синхронизированный метод.
Пример:
public class BlockingObject {
public synchronized void sleep() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void wakeUp() {
notify();
}
};
public class NestedMonitorLockout {
private BlockingObject object = new BlockingObject();
public static void main(String[] args) {
final NestedMonitorLockout nestedLock = new NestedMonitorLockout();
new Thread(new Runnable() {
public void run() {
nestedLock.sleep();
}
}).start();
nestedLock.wakeUp();
}
private synchronized void sleep() {
object.sleep();
}
private synchronized void wakeUp() {
object.wakeUp();
}
}
Основное правило, как избежать этого – не делать блокирующих вызовов внутри синхронизированных методов. А если делать, то должен быть не синхронизированный метод, который может достучаться к блокируемому объекту.
Пока все.
На этом пока можно остановится. Уже из этих примеров видно, что многопоточное программирование в яве – нетривиальная задача. Стандартные средства есть, но они несовершенны (wait() мог бы возвращать boolean, индикатор, что возврат был по таймауту или по notify(), synchronized блок не поддерживает таймауту по умолчнанию). И так далее. О других вещах и как обойти эти ограничения – в следующий раз.
Программирование
апр 21, 20:48
Введение
REST – термин такого же рода как “объектно-ориентированный”. Под этим терминов понимается подход к архитектуре. Это если в общих словах.
Точно также как и в ООП есть некоторых набор концепций (инкапсуляция, наследование, полиморфизм), так и REST определяет некоторый набор принципов (statelessness, addressability, connectedness, единый интерфейс и т.д.).
В ООП также есть свои понятия:
- класс
- объект
- методы
- передача сообщений
В REST тоже есть свои понятия:
- Resource – ресурс (например, список ссылок)
- Representation – виды ресурса (например, html – страница со ссылками в браузере, rss – таже страница, но в для рсс-фида)
- Адрес ресурса (например, URL страницы)
- Единый интерфейс (GET, POST, PUT, DELETE и другие методы для HTTP-протокола)
Точно также как можно написать программу на объектно-ориентированном языке не используя никаких из принципов ООП, так и сайты, что хоть и доступны с помощью отличной реализации REST-принципов – протокола HTTP, сами эти REST принципы могут и не использовать.
Понятия REST (поподробнее)
Ресурс
Ресурс – под ресурсом понимается все что угодно достаточно важное для того, чтобы иметь ссылку на себя. Ресурсом может быть ссылка, карта города, статья в энциклопедии об осьминоге, случайное число больше 100 и меньше 323.
То, что делает, ресурс ресурсом – это наличие хотя бы одного адреса. Если у ресурса нет адреса и к нему нельзя обратиться, значит это не ресурс, а просто набор данных, битов, который описывает какой-то другой ресурс.
Адрес
Адрес – это имя ресурса, его местоположение. Ресурс может иметь несколько адресов, от одного и больше.
В вебе, у нас есть своей название для адреса – URL, universal resource locator.
Представление ресурса (representation)
Стоит различать ресурс и его представление. Ресурс – это некоторая идея то, что находится.
Когда пользователь заходит на страницу /shop, он получает не идею ресурса, а определенный набор байтов, данные – это и есть представление ресурса. Сервер может по запросу /shop вернуть набор товаров в этом магазине, а может вернуть изображение этого магазина на карте.
Представление ресурса – это информация о текущем состоянии ресурса.
Информация о том какое представление следует выбрать, если их доступно несколько, может находится как и в адресе ресурса (например, если это страница в журнале, /article.en указывает на английскую версию, /article.ru – на русскую), так и зависеть от контекста запроса (в этом примере, клиент посылает http-заголовок о том, где он находится и какой язык предпочитает)
Принципы REST (поподробнее)
Addressability
Наличие адреса, который может сохранить, обратится по нему к ресурсу – это самое большое благо. Это может показаться тривиально, но даже такие монстры как Гугл не всегда следует этому правило.
Возьмем к пример, Gmail. Так весь интерфейс построен на ajax’e, нет очевидной возможности обратится к некоторому письму напрямую по адресу. Поэтому для доступа к отдельному письму приходится заходить на Gmail и вручную выбирать его из списка. Если тебе позже понадобится это письмо, то придется проделывать все эти действия еще раз. Добавить ссылку на письмо в текстовый файл, в букмарки не выйдет, т.к. адреса нет.
Statelessness
Отсутствие состояния. Это означает, что любой запрос к ресурсу происходит в полной изоляции, т.к. как будто этот запрос был выполнен впервые. Запрос не зависит ни от каких данных из прошлых запросов. Все необходимая информация находится в теле запроса.
Connectedness
Наличие связей между ресурсами – огромный плюс. Если бы у ресурса не было бы связей, нам пришлось запомнить адреса ко всем ресурсам, правила по которым эти адреса создаются и так далее.
Приведу пример. Представим, что было бы если Google Maps не возвращал кнопок “вверх-вниз”, “влево-вправо”, “увеличить-уменьшить”. Единственный способ навигации для пользователя – это запоминать текущую широту и долготу и вручную править градусы левее или правее, чтобы увидеть следующий участок на карте. Вряд ли бы таким сервисом кто-то пользовался.
Но если рассматривать не пользовательский веб, а веб-сервисы, которые созданы для компьютера, а не человека, то такая проблема будет довольно распространена.
Единый интерфейс
Единый интерфейс означает лишь то, что каждый ресурс использует одни и те же методы для доступа к своим данным. Причем важно не то, как эти методы называются, а то что они выполняют схожую функции независимо от ресурса.
В вебе, метод GET – означает получить представление об ресурсе. Было бы странно увидеть сайт, на которым с помощью GET’a удалялись статьи, а с помощью POST можно было увидеть ее представление. Со стандартным браузером таким сайтом было бы невозможно пользоваться, т.к. браузер рассчитывает получить представление с помощью метода GET, а с помощью POST – отправить команду на обновление или удаление.
Это все.
Все, что нужно знать про REST. Четыре понятия и четыре принципа. Конечно, еще много вопросов остается открытыми, но об этом поговорим потом.
P.S. На самом деле я хотел набросать себе небольшую подсказку, как распределить реальные данные по ресурсам, какие шаги нужно выполнить и так далее, и так далее, но об этом позже.
Программирование
апр 18, 20:26
Попробовал трезво порассуждать чего же в Java действительно не хватает, а если хватает, то работает не так. А то всякие выдуманные проблемы, как у Николая Палиенко не выдерживают критики.
Итак, порядок тут значения не имеет. Проблемы языка и платформы совмещены.
Java – это клей для связи одного xml с другим
Возможно так исторически сложилось, но xml’а в яве много, очень много. Это сейчас уже можно задеплоить простой сервлет без использования и строчки xml’a. До прошлого года это сделать было невозможно.
Проблема с xml – это то, что он плохо расширяется. Spring, Hibernate, сервлеты, JMS, JAXB, зависимости через Maven или через что-то другое – все это тянет за собой кучу xml’a, в котором трудно ориентироваться как только система начинает розрастаться.
Для борьбы с этим придумали java annotations и это действительно помогает, но не всегда. В Spring некоторые вещи можно делать пока только через xml.
Опять же в какой-то мере это проблема общая для всех платформ. На память приходит популярный веб-фреймворк на PHP – Symfony. Конфигов и там достаточно (пусть там и не xml, а yaml).
Модульность
Во-первых, программы/библиотеки на яве распространяются в виде JAR-файлов. Формат JAR был изобретен еще в середине 90-х и, по правде говоря, он не очень хорош ни как средство для дистрибуции библиотек, ни как средство для запуска программ (студентота, наверное, до сих пор спрашивает своих преподавателей: “WTF? Где мой .exe?”).
Основная проблема – jar-файлы не поддерживают версий и поэтому на них трудно ссылаться. Ссылаться же на них приходится, когда в дело вступают зависимости между jar’ами. И как следствие – необходимо ссылаться из classpath, т.е. вручную прописывая путь ко всем необходимым библиотеками при старте программы. Это pain in ass.
Для борьбы с этим были изобретены разные монструозные системы: OSGI, Maven, Ivy + Ant и так далее. Пока это помогает, но требует дополнительных усилий.
Verbosity – Многословие
Java не поддерживает first-class функций. Как следствие, не так такого понятия как функции высшего порядка, т.е. нельзя передать функции как один из аргументов или возвратить ее в качестве результата. Внутренние классы (анонимные) позволяют как обойти это ограничение, но это ведет к ненужной многословности.
Метапрограммирование
Проиллюстрирую на одной проблеме. В яве принято писать гетеры и сетеры. По стандарту они пишутся по одним и тем же правилам из года в год. Юнит-тесты к ним естественно никто не пишет, но ошибки все равно возникают. Кроме того это банально надоедает.
Если бы яве можно было расширить возможности языка и определить по умолчанию поведение для всяких get- и set-методов, это бы сохранило множество нервов.
К сожалению, сделать это без аннотаций, java reflection и тому подобных вещах – нельзя. Хотя такое дополнение к языку не помешало. Добавлять или вызывать методы динамично, в режиме runtime, в яве очень сложно и порой не хватает.
Возможно из-за этого недостатка фреймворки и любой другой generic код в яве выглядит громоздким и плодит ненужные иерархии классов.
Разные мелочи и досадные ошибки
Тут я просто пройдусь небольшим списком:
- массивы и примитивные типы – убрать, сделать только объекты
- checked exceptions – хорошо в теории, но на практике не работает
- generics – что с ними не так заслуживает отдельного поста
- многие базовые вещи занимают много времени – java.io, java.reflect
Заключение
Думаю, я вернусь еще к этому списку и буду время от времени дополнять его. В любом случае, у явы есть и свои преимущества. Статическая типизация, огромный набор тулз (поддержка IDE, профайлеры и прочее, прочее, прочее), богатая библиотека, сообщество и так далее. Но это тема для другого разговора.
Программирование
апр 17, 15:42
Specificity – слово, которое я затрудняюсь первести на русский язык в контексте CSS. Словарь переводит его как “специфичность”, “особенность”, “своеобразие”, “специфика”, но мне все эти определения не нравятся и поэтому оставим так как есть. Можно, конечно, говорить “порядок применения css-правил к элементам”, но это слишком сложно.
О specificity, что меня удивило, даже бывалые фронд-енд девелоперы могут и не знать. Т.е. как-то догадываются, но четкие правила в глаза не видели. В тоже время правило эти простые и избавлюят иногда от головной боли.
Что такое specificity на примере
Допустим у нас есть два css-правила:
li {background: red}
li {background: green}
Какой цвета будет использоваться: красный или зеленый? Правильный ответ — зеленый, но почему? Скорее всего ответ будет: потому что он объявлен последним. А если мы изменим наш пример таким образом:
body li {background: red}
li {background: green}
Какой цвет будет тогда? Правильный ответ – красный. И опять вопрос – почему? Ответ как раз и дает нам css specificity, т.е. правила в каком порядке мы должны применять стили к нашим элементам.
Как считается specificity
В пример выше для каждого из правил высчитывается его specificity, специфичность (вот оно!):
- для
li, специфичность = 0, 0, 0, 1 (используется один элемент)
- для
body li, специфичность = 0, 0, 0, 2 (используется два элемента)
Если к элементу добавить класс, то мы получим:
- для
li.first – 0, 0, 1, 1 (один элемент, один класс). В этом случае элемент с классом .first будет брать вверх над прочими вышенаписанными правилами, т.к. специфичность класса имеет приоритет над элементами. Можно представить себе это как десятичные цифры. “0, 0, 0, 2” – это 2, “0, 0, 1, 1” – это 11. 11 > 2, значит выигрывает li.first.
- для
body ul li.first, специфичность 0, 0, 1, 3. 13 > 11, и такое правило имеет приоритет перед правилом li.first со специфичностью “0, 0, 1, 1”.
Теперь попробуем ввести элементы с id.
- для
#navlinks li, специфичность = 0, 1, 0, 1 (один ID, один элемент). Если перевести в десятичную систему, то получим 101 > 11 > 1, что перетрет все остальные определения.
Кстати, тут стоит помнить, что мы должны волноваться о специфичности, только когда имеем дело с определениями, которые конфликтуют между собой. То есть:
#navlinks {background: red}
li {background: green; font-style: italic}
перетрет определение цвета, но стиль шрифта будет использоваться такой какой прописан у следующего правила с наибольшей специфичностью.
Теперь осталось тольку узнать за что отвечает лидирующий ноль, когда я описывал специфичность. За него отвечает inline-стили. Т.е. если у нас есть:
<style>
#navlinks {background: red}
</style>
<ul>
<li style="background: blue">Some text goes here!</li>
</ul>
то специфичность для style="..." = 1, 0, 0, 0, что больше любых других правил.
Заключение
Вот и все правила. По себе помню, как бывало в молодые годы обновляешь какой-нибудь css, а эффекта от этого – ноль. Еще раз обновляешь, еще раз – не помогает. А это как раз и есть та самая specificity, специфичность в действии. Все это лишь следствие того, что где-то объявлено правило с большим specificity, которая имеет приоритет над твоими изменениями.
Программирование
апр 15, 21:03
Иногда бывает полезно посмотреть что же происходит в сети, какие пакеты отправляются, как закрываются и открываются соединения между, например, базой данных и нашим приложениям.
Команда tcpick прекрасно подходит для этой цели. Есть еще tcpdump, но она сложнее и не столь необходима для того уровня анализа пакетов, который нам нужен.
Расскажу, как я ей пользуюсь.
Синтаксис
sudo tcpick -C -i eth1 "port 80"
Так выглядит стандартная команад. Главное тут:
- флаг -i — интерфейс для прослушивания. Чаще всего либо eth1, либо lo, если интересует локальный траффик.
- “port 80” – это фильтр. В данном случае “port” — это тип фильтра, который указывает, что ловить нужно пакеты только те, которые приходят c некоторого порта (80 в этом примере). Все возможные опции можно посмотреть с помощью man pcap-filter.
- -флаг C – это штука только для удобства. Раскрашивает вывод в удобные для чтения цвета.
Применения
Задача 1. Глянуть, какие mysql запросы отправляет клиент
Бывает очень необходимо, когда нет возможности включить лог запросов на сервере.
sudo tcpick -C -yP -i lo "dst port 3306"
Здесь нам пригодился флаг -yP, который выводит содержания tcp-пакетов в наиболее читабельном виде (невыводимые символы заменяются точками). “dst port 3306” указывает, что нас интересуют только исходящие запросы на 3306 порт (3306 – mysql порт по умолчанию). Если использовать просто “port 3306”, то выводится будут также и ответ. Хотя понять, что там выводится бывает трудно.
Задача 2. Проверить, что мы используем http connection pool и не открываем соединения при каждом запросе.
Открытие любоого соединения – это ресурсоемкая операция и поэтому стараются использовать какой-либо пул из уже заготовленных соединений, чтобы сэкономить драгоценные ресурсы. Чтобы отследить закрытия и открытия http-соединений можно использовать следующую команду:
sudo tcpick -i eth0 -C | grep www
Так, например, если открыть какой-нибудь Github в браузере, то можно увидеть как первый раз создаются новые соединения, а все последующие перезагрузки страницы их используют. Все это благодаря keep-alive. В версии протокола HTTP 1.1 такое поведение принято по умолчанию.
Задача 3. Посмотреть какие http-запросы шлются на 80 порту вместе с хедерами
Полезно, когда лень открывать Charles Proxy/Firebug (а если это IE, то его там банально нет) или нужно посмотреть как между собой общаются разные remote сервисы.
sudo tcpick -C -yP -i eth1 "port 80" | egrep "(HTTP|^[A-Za-z-]+:\ .*|^$)"
Заключение
В общем, применений в дебаге можно найти еще много (memcached, redis, jms брокеры). Кому-нибудь, надеюсь, это поможет облегчить нелегкую жизнь веб-разработчика.
Программирование
июл 29, 21:06
Как-то недавно я наткнулся на статью под названием Читай код, где автор доказывал важность такого скилла, как умение читать чужой код:
Создать свою структуру и пришлепать ее сбоку может любой дурак. Квалифицированный инженер-программист (с упором на первом слове, не путать с “программером”) умеет проводить анализ “чужой” подсистемы, восстановит мысль и идею автора, сможет мысль автора развить, продолжить ее, и эффективно решить свою задачу в рамках чужого подхода к проблеме. Все это – работая с кодом. Это отличительная компетенция архитектора, высший уровень инженерного мастерства. И это имеет весьма отдаленное отношение к “рефакторингу”.
Тогда у меня возникла идея провести эксперемент:
- Взять некоторый известный паттерн проектирования.
- C одной стороны показать, как выглядит код до применения паттерна.
- Рядом же, с другой стороны, показать как выглядит этот же код с применением паттерна.
- Попытаться понять, что этот код делает и как применяется паттерн.
- Сравнить такой вариант описания паттернов с традиционным подходом “UML-диаграмма, словесное объяснение, пример использования”.
Я заметил, что мне легче понять, что делает программа, увидев ее код, а не услышав или прочитав ее описание. С паттернами это чувствуется очень сильно. От такой шпаргалки у меня в глазах рябит:

Итак, сам эксперимент. Для описания я выбрал паттерн Декоратор.
Традиционный подход
Декоратор, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Задача
Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до или после основной функциональности объекта.
Реализация
Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.
UML-диаграмма

Описание паттерна взято из Википедии. А теперь перейдем ко второму способу объяснения.
Подход «Читай код, сука»

Итак, Вопрос
Что лучше? Что легче для понимания? Подход «Читай код, сука» сработал или нет?
Программирование
июл 25, 22:01

Недавно начал просматривать лекции по алгоритма, которые читают в MIT. Решил для большей полезности делать это в некотором процессе, который подсмотрел у других умных людей.
Прежде всего, нужно определится зачем эти лекции смотреть, какова цель. Тут все просто. Если вспомнить Programmer Competency Matrix, то выяснится, что алгоритмы – это один из тех скиллов, которые востребованы. Интересная цитата оттуда:
Working with someone who has a good topcoder ranking would be an unbelievable piece of luck!
Как мне кажется, дело здесь даже не совсем в том, что хорошее знание алгоритмов дает тебе возможность писать интересный, быстрый и хитроумный код или что-либо еще в этом роде. На себе почувствовал, что порешав несколько алгоритмов, разобравшись в них, начинаешь чувствовать себе увереннее, когда сталкиваешься с обычными программистскими задачами и пишешь обычный код. Возможно, это только у одного меня такое чувство, не знаю.
Итак, что касается процесса как смотреть эти лекции с большой для себе пользы, то тут дела обстоят так:
- Качаем лекцию. Я ходил по ссылкам отсюда. Начинаем ее смотреть.
- С этого же сайта парень предлагает ввести параллельный конспект по ходу просмотра так как будто ты находишься на лекции. Кстати, полезно также после просмотра лекции сравнить свой конспект с тем, что выложен на этом сайте. Такая себе проверка.
- Кроме конспекта, можно еще записывать интересные мысли или высказывания профессора, которые могут касаться или не касаться изучаемого материала.
- Самое полезное часть изучение происходит после просмотра лекции. Либо в этот же день, либо на следующий просто необходимо открыть книгу, по которой идет этот курс.
Книга переведена на русский язык и ее даже можно скачать с торрентов или натахауса. Называется «Алгоритмы. Построение и анализ» Томаса Кормена, Чарльза Лейзерсона и других. Выглядит она следующим образом:

Замечательно в этой книге то, что профессор Чарльз Лейзерсон, который читает лекции, есть один из ее соавторов. Да и сама книга – одна из самых популярный по алгоритмам. Кстати, второй профессор Eric Damaine – живой гений, самый молодой профессор MIT, – стал им в 20 лет.
Без лекций книгу читать сложновато и немного скучно, но после просмотра – идет на ура. В общем, советую.
Программирование
июл 19, 15:15
В предыдущей части был описан общий процесс разработки API. Эта часть состоит из более общих советов или принципов проектирования, которые я выделил по собственному усмотрению и ориентируюсь на вышеупомянутую презентацию.
Для начала вспомним, как выглядит процесс разработки API по Блоху. Он состоит условно из трех этапов: сбор сведений, краткая спецификация и программирование. На последнем этапе должен получится прототип API, некоторый каркас будущей программы, который состоит из интерфейсов, прототипов классов и методов с комментариями и юнит-тестами.
При написании прототипа, как уже говорилось, могут возникнуть проблемы или недостатки разработанной спецификации, который тут же можно исправить, внеся нужные изменения.
Следующий шаг, он же последний, – реализация. Здесь мы создаем реальный код. В оставшейся части презентации Джошуа Блох дает набор общих принципов по проектированию в целом. Ниже я привел список наиболее существенных, по-моему мнению. Все они сформулированы кратко, но очень точно.
Если это трудно назвать, то это плохой знак
Или, другими словами, Good Names Drive Development. Под этим понимается, прежде всего, что API должно выполнять одну функцию и делать это хорошо. Хороший признак такой модульности – это понятные и простые имена для классов и методов. Если возникли проблемы с именами, возможно следует каким-либо образом применить композицию или декомпозицию для того, чтобы функции, которые выполняет разрабатываемый модуль, были легко понятны и их можно было бы дать подходящее имена.
Если сомневаешься, оставь
Интересный совет, который понравится ленивым программистам. Основывается он на том, что API должно настолько минимально, насколько это возможно. Естественно, что программа должно соответствовать предъявляемым требованиям. Но стоит перегружать его функциями, по-тому что не всегда большое количество является признаком качества.
Хороший совет в случае, когда вы решаете, стоит ли добавить новый параметр в метод или стоит выделить новый метод, или сделать еще один класс – если сомневаешься, бросай. Не стоит делать этого сейчас. Лучше сделать это потом, когда реальность сама рассеет все сомнении. Добавлять легче, чем изменять.
Документируй дотошно
Одна из прописных истин, но все же часта про нее забывают. Любой самый совершенный дизайн, который, кстати, не так часто и увидишь, не может обойтись без документации. Следует документировать каждый класс, интерфейс, метод, конструктор, параметр и исключение.
Не заставляй клиента делать то, что может сделать твоя программа
Хороший пример того, что под понимается, приводит Джошуа Блох, описывая дизайн DOM API:
import org.w3c.dom.*;
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
// DOM code to write an XML document to a specified output stream.
private static final void writeDoc(Document doc, OutputStream out)throws IOException{
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
t.transform(new DOMSource(doc), new StreamResult(out));
} catch(TransformerException e) {
throw new AssertionError(e); // Can’t happen!
}
}
Метод writeDoc() пишет XML документ в заданный поток вывода. Такая простоя вещь как запись документа в поток заняла 15 строк кода, большая часть из которых вообще непонятного предназначения: исключения, которые не могут возникнуть, создание дополнительного объекта Transformer и так далее.
Общее правило в этом случае – уменьшайте код, который нужно будет писать клиенту для использования вашего API. Клиенту не нужно знать, какие дополнительные действия нужно совершить прежде, чем вызвать тот или иной метод, какие исключения нужно отлавливать, какие параметры установить. Проблемы такого рода решается, в основном, с помощью copy-paste, что само по себе есть неприятно, скучно и приводит к ошибкам.
Совершенства не достичь, но можно попытаться
Как и везде, будут ошибки и недостатки. Не стоит их пугаться. Продолжайте учиться и будет счастья. На этом все.
Конечно тут представлена только малая часть из того, о чем говорил Джошуа Блох в своей презентации. Надеюсь хотя бы в таком малом объеме, такая информация все равно будет полезна. Саму презентацию очень рекомендую посмотреть не только Java программистам.
Программирование
июл 17, 16:19
Сделал релиз библиотеки, разработку которой недавно описывал (вторая часть, кстати, скоро будет). Пока не хватает документации и примеров. Наверняка есть ошибки и недочеты, но надеюсь все это исправить в ближайшее время.

Лицензия GNU. Cкачать можно отсюда.
Понравился Google Code. Особенно понравилась возможность добавить счетчик Google Analytics и тут же следить за посещаемостью. Приятно удивило, что уже и без первого релиза народ начал приходить.
В общем, сделал доброе дело. Дальше – больше :)
Программирование
июл 17, 11:22
Решил выложить слайды с доклада, которою я делал для студенческой конференции. Старался сделать веселую и интересную презентацию. Тема – управление зависимостями, dependency injection и inversion of control. Доклад обзорный.
Программирование
июл 12, 19:49
Оригинальная презентация находится здесь. В своей презентации Джошуа Блох, один из дизайнеров Java libraries, дает советы о том, как создать хороший API, приводя большое количество примеров удачного и неудачного дизайна.
Мое вольное трактование этой презентации находится ниже. Здесь я попытался в удобной форме показать и рассказать, как эти советы воплощаются в жизнь. Такой себе reald-world example from scratch.
Для примера я выбрал разработку java-библиотеки – аналога diff-утилиты с возможностью применять патчи. Главная причина такого выбора – отсутствие удобного API для решения подобных задач на Java. Существующие программы (diff-match-patch от Гугла, java-diff, jrcs-diff) хотя и обладают некоторыми достоинствами, но чаще всего неудобны в использовании. Говорю это, опираясь на свой личный опыт.
В своей презентации Джошуа Блох выделяет некоторый процесс, по которому создавались хорошие API. Итак, начнем с самых первых шагов этого процесса.
Первый шаг: Сбор сведений
На этом этапе главная задача разработчика – собрать реальные требования к разрабатываемой системе. Не стоит углубляться в технические детали и фокусироваться на том, как должна работать система. Нас не интересует как она работает. Нам интересно, что именно она делает, какие задачи выполняет и для чего эта система нужна в принципе. It’s no about an activity, it’s about the result.
Джошуа Блох советует выделить такие требования в формате use cases (примеров использования) и наиболее общем виде. Такие use cases представляют из себя краткие описание задач, которые необходимо выполнять с помощью API.
Вернемся к примеру diff-библиотеки. Прежде всего у меня есть собственные требование на основе тех задач, которыми я планирую выполнять с помощью этой бибилотеки. Также уже есть и существующие библиотеки, со своим API и примерами использования. Подумав и подключив фантазию я остановился на следующем списке:
- Вычисления diff между двумя текстами
- Применения патча, который содержит diff, к оригинальном тексту
- Операция обратная к патчу, когда по пропатченному тексту и патчу вычисляется оригинальный текст (я этим не пользовался, но вдруг такая возможность будет необходима)
- Вывод diffов в разные формы для дальнейшего, более удобного просмотра (двух-панельный вид, отображение изменений внутристрочно).
- Возможность использовать разные форматы для diff (unified diff, normal format и т.д.)
На данном этапе мы также может попробовать провести некоторое обобщение и подумать над выделением компонентов. Хороший признак того, что программный код должен быть представлен в виде некоторого компонента – это большая вероятность того, что этот код будет меняться или работать по разному в будущем.
Для примера, мы можем предположить, что алгоритм вычисления diff возможно будет меняться в зависимости от многих факторов : обращать внимания на пробелы или нет, игнорировать пустые строки или считать их изменением, какую именно реализацию алгоритма стоит использовать (некоторые алгоритмы подходят для некоторых текстов лучше, чем другие) и так далее. Другой пример – это обработки разных форматов для диффа (unified diff, normal format и т.д.). В большинстве случаев будет использовать unified diff, как наиболее распространенный, но возможность других форматов нужно также брать во внимание.
Второй шаг: Написание краткой спецификации
Здесь главное не испугаться слова “спецификация”. Никаких 50 страниц и 2-недельного корпения. Все что необходимо – это одна страничка формата А4. Краткость является огромным плюсом, потому что спецификацию в одну страницу намного легче исправлять и даже переделывать заново.
А это возможно и необходимо будет сделать после совещаний, на которых вы эту спецификацию должны презентовать. Чем большей людей выскажет свое мнения по поводу вашего API, тем лучше для вас. Вы тут же можете найти проблемы, тут же их исправить и продолжать дальше. Все это может занять меньше, чем пол часа.
Итак, мой пример спецификация для diff-библиотеки, написанный на некотором псевдо-коде, который я придумал сам. Надеюсь, будет понятно:
DiffRow:
String tag;
String oldLine;
String newLine;
---------------------
Chunk:
int position;
int size;
List<String> changedLines;
---------------------
Delta:
Chunk original;
Chunk revised;
applyTo(target)
---------------------
AddDelta extends Delta:
applyTo(target):
go to line with position original.position
insert lines from revised.changedLines
---------------------
DeleteDelta extends Delta:
applyTo(target):
go to line with position original.position
delete n lines, where n = original.size
---------------------
ChangedDelta extends Delta:
applyTo(target):
go to line with position == original.position
delete n lines, where n = original.size
insert n lines from revised.changedLines
---------------------
Patch:
List<Delta> deltas;
Patch(String unifiedDiff);
Patch(String rcsDiff);
Patch()
addDelta()
getDeltas()
applyTo(target);
---------------------
DiffLib:
diff(String original, String revised, DiffAlgorytm algorytm): Patch
patch(String original, Patch patch): String revised
original(String revised, Patch patch): String original
getDiffRows(String original, String revised)
Основным кирпичиком diff файла является Delta. Дельта описывает какие именно изменения произошли (Insert, Add или Change). Дельта состоит из 2 Сhunk – один Chunk описывает оригинальный файл, другой – измененный. Chunk содержит описание того, начиная с какой строки произошли изменения и какие именно. Патч состоит из набора дельт. На выходе операции diff() получается патч. Патч можно применить к тексту, точно также как и любую дельту.
Третий шаг: Начинаем программировать API
На этом шаге Блох советует начинать писать API, не обращая внимания на реализацию. Здесь создаются прототипы классов и методов. Кстати, начать написание прототипа API можно даже не пройдя до конца шаг 2, т.е. не определив спецификацию до конца. В любом случае если что-то и изменится, прототип можно всегда легко изменить.
При написании прототипов, кстати, можно убить 2 зайцев.
В одном из своих подкастов Будам дал совет о том, как писать комментарии. Основная идея состоит в том, чтобы писать комментарии до того, как написан код. Что же на данном мы код и не пишем, только прототипы, по-этому никаких противоречий с тем, чтобы сразу написать комментарии не возникает.
Ну и это еще не все. Где-то в дали промаячил и третий заяц – юнит-тесты. Мы уже имеем некоторый интерфейс и можем попытаться его использовать в тестах. Возможно возникнут проблемы и сюрпризы, и нам нужно будет изменить прототипы – не беда, но то они и прототипы.
Пока все
Дальше – больше
Программирование
фев 28, 21:43
В каждом языке есть свои маленькие секреты и хитрости, о которых иногда полезно знать. Решил завести себе небольшой цикл для таких хитростей касательно Явы. Ну что ж, поехали!
Прелюдия
Есть код. Нужно ответить, что он выведет.
Integer a = 200;
Integer b = 200;
System.out.println(a == b);
Здесь все просто. Выведет False. Для тех, кто в танке и читают книжки невнимательно, объясню.
Ява сравнивает примитивные типы (byte, short, int, long и т.д.) и объектные (Byte, Short, Integer и Long, соответственно) по разному. У примитивных типов проверяется их значение. У объектных типов проверяется указывают ли они на один и тот же объект. А так как мы создали два разных объекта (пусть даже и с одинаковыми значениями), проверка возвращает False.
Завязка
Теперь немного изменим наш код. Всего лишь уберем по паре нулей у целых чисел. Что выведет?
Integer a = 2;
Integer b = 2;
System.out.println(a == b);
Не буду томить с ответом, выведет — True. Опять же — почему? Чтобы ответить на этот вопрос нужно знать как устроена Ява, а точнее JVM.
Устроена JVM так, как ни странно, что любит оптимизировать и кешировать. По-этому для нужд производительности виртуальная машина кеширует объекты для часто встречающихся примитивов (чисел от -127 до 127, некоторых символов, true и false). Другими словами, когда мы пишем:
Integer a = 2;
новый объект не создается, а берется уже готовый. Откуда? Из кеша. Это естественно повышает производительность наших программ и дает повод для гордости программистам, которые причастились к этой великой тайне.
Итог
Мораль этой истории проста — избегать сравнения объектных типов, соответствующим стандартным примитивам. Лучше приводить их вручную через type casting, а потому уже сравнивать их значения. Так кошернее.
Программирование
фев 9, 16:47
Твиттер твиттером, но и про блог забывать не стоит. Вот решил сюда кое-что написать. Решил написать про Ant. Такой себе небольшой урок, которого будет достаточно, чтобы понимать и самому пользоваться этой небольшой, но очень полезной программой.
Что мы узнаем?
- Что такое Ant и для чего он нужен.
- Как написать простой build.xml файл.
- Как скомпилировать с помощью Ant проект.
- Как подключить библиотеки при компиляции.
Про Ant двумя словами
Ant – это утилита для сборки java-программ. Это ее основное назначение. Она использует инструкции, определенными в файле build.xml, для того, чтобы собирать программу (компилировать, копировать куда надо и так далее). В Unix аналогом такой программы является команда make и написанный для нее makefile. В общем-то, здесь можно провести такую аналогию:

Естественно знать, что такое make и makefile необязательно. Но с этого с этого можно сделать вывод, что понимая суть ant и build.xml вам уже не нужно объяснять, что такое make и makefile. И в обратную сторону, зная, что такой make и makefile, вам не нужно объяснять, для чего нужен ant и build.xml. Синтаксис у них совершенно разный, и по-этому такое понимание практических навыков вам никаких не даст, но все-таки это лучше, чем ничего. Но сейчас не об этом.
Для тех, кто не сталкивался ни с make, ни с ant мы уже сказали, что Ant – собирает программу. А как именно, определенно в файле build.xml. И этого вполне достаточно, чтобы начать.
h2 Написание первого, простого build.xml и компиляция нашей программы
Синтаксис build.xml вполне прост. Вот так выглядит обычный build файл:
<project name="My Sample Application" default="compile" basedir=".">
<target name="target" description="Some text here">
</target>
</project>
Один проект — один build.xml. При объявлении проекта мы должны указать его имя (name), команду, выполняемую по умолчанию (default) и папку, относительно которой высчитываются относительные пути (basedir). Проект может состоять из нескольких команд (Target). Каждый Target может состоять из нескольких действий (Actions).
Действием может быть копирование файлов из одной папку в другую, компилирование файлов, удаление, извлечение javadoc и так далее. Разработчики могут также создавать и свои собственные действия, и таким образом расширять Ant.
Напишем Target для компиляции. Взяли клавиатуру, и написали. И вот, что получилось:
<project name="My Sample Application" default="compile" basedir=".">
<target name="compile">
<javac destdir="./build/classes" srcdir="./src">
</javac>
</target>
</project>
Здесь мы объявили Target с именем compile. Внутри него находится действие javac, которое принимает как параметры папку с исходными текстами, и папку в которую будет записан результат в виде .class файлов.
Для того, чтобы вызвать наше действие следует выполнить ant в командой строке. При вызове ant мы можем передать какой build файл использовать, и какую команду следует выполнить. Если этого не сделать, то по умолчанию ant будет использовать build.xml, который находится в текущей папке и Target по умолчанию, указанный в нем.
Т.е. нам достаточно написать:
:/path/to/project$ ant
или
:/path/to/project$ ant compile
или
:/path/to/project$ ant -buildfile build.xml compile
Подключаем дополнительные библиотеки
Сегодня уже вряд ли найдешь сколько-нибудь сложное приложение, которое бы не использовало сторонние библиотеки. Мы можем указать Ant какие библиотеки стоит использовать следующим образом:
<path id="libs.dir">
<fileset dir="lib">
<include name="*.jar" />
</fileset>
</path>
<target name="compile">
<javac destdir="./build/classes" srcdir="${src.dir}">
<classpath refid="libs.dir" />
</javac>
</target>
Строка <classpath refid=“libs.dir” /> указывает, что необходимо подключить дополнительные библиотеки и что находятся они по адресу libs.dir. Чуть выше мы определили, что такое это самое libs.dir.
Здесь следует сделать отступление и сказать, что в Ant присутствует некоторая концепция типов данных. Подробнее о них стоит рассказать в отдельной статье, но пока стоит запомнить, что такие вещи как путь, списки файлов, упорядоченные или неупорядоченные, – все есть некоторый объект со своим типом, который можно использовать в действиях. Одним из примеров таких типов есть path, внутри которого используется другой тип fileset для представления списка файлов.
Делаем красиво. Выносим свойства за скобки
В прошлых примерах мы использовали путь ./build/classes для указания папки, в которую мы хотим скомпилировать наши классы. В больших build.xml файлах, в которых содержаться намного больше разных Targets велика вероятность, что эта же строка понадобиться нам еще где-либо.
По-этому хорошей практикой считается определение таких параметров отдельно от самих Targets. Приведу весь build.xml для примера:
<project name="My Sample Application" default="compile" basedir=".">
<property name="build.classes.dir" location="./build/classes" />
<property name="src.dir" location="./src" />
<path id="libs.dir">
<fileset dir="lib">
<include name="*.jar" />
</fileset>
</path>
<target name="compile">
<javac destdir="${build.classes.dir}" srcdir="${src.dir}">
<classpath refid="libs.dir" />
</javac>
</target>
</project>
Здесь есть еще один тонкий момент. Значения свойства могут быть присвоены как с помощью location атрибута, так и с помощью value. Разница между ними в том, что если location содержит путь к папке или файлу, разделитель между папками (/ или \) будет подставляться правильно в зависимости от того, под Виндовс или под Линуксом запущен Ant. Это просто удобно. Не придется при случае переименовывать все пути, как однажды пришлось мне на своей работе. Хотя вроде бы умные люди писали, а такой мелочи, наверное, не знали.
Заключение
То, что я тут рассказал хватит, чтобы делать 60-70% основной работы. Конечно, лучше знать больше. Возможно, в следующей статье расскажу подробнее на тему типов данных (path, fileset и так далее) и все, что с ними связано. Еще хотелось бы рассказать и про автоматизацию тестов с помощью Ant. Но время покажет как все получится.
Программирование
ноя 8, 21:01

Продолжаем продолжать
Итак, в последний раз мы прошли такую себе мини-лекцию по базам. Сегодня время пришло заняться практикой.
Начнем с первого пункта:
Создание базы данных
В Андроиде я насчитал 3 способа создания базы данных. Начнем с самого неправильно, и закончим самым православным.
1 способ – использовать класс SQLiteDatabase. Для начала сделаем небольшой обзор этого класса, так как в будущем он нам ой как пригодится.
Иерархия:
java.lang.Object
android.database.sqlite.SQLiteClosable
android.database.sqlite.SQLiteDatabase
Предков всего ничего: стандартный явовский Object и интерфейс SQLiteClosable. Последний мы вряд ли мы когда-нибудь будем использовать — это всего лишь внутренний интерфейс, которой исходя из названия содержит в себе методы, необходимые для закрытие (удаления) SQLite классов. В данном случае его наследует наш SQLiteDatabase, который следовательно представляет из себя ресурс, который можно прикрыть, если что)
Перейдем к рассмотрению наиболее популярных методов. Для начала просто опишем их, не вдаваясь в подробности, как с ними работать:
- void execSQL(String sql) — выполнение sql-запроса, который не является выборкой (т.е. без select). Это всякие CREATE TABLE, INSERT, DELETE и так далее.
- Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) — большой и важный метод с кучей параметров. Используем для выборки. Параметры сами за себя говорят. Подробнее обсудим позже
- Cursor rawQuery(String sql, String[] selectionArgs) — сырой, обычный, дикий sql-запрос. Очень полезно иметь возможность обратиться к базе, используя всякие хитрые Join’ы и так далее.
Дальше я не буду сильно описывать следующие методы. Скажу лишь, что они представляют цивильный способ совершения часто встречаемых запросов:
- insert() – возвращает id вставленной строки
- update() – возвращает количество обновленных строк
- replace() – не часто встречал такой метод в других API. Нужно поковыряться еще. Замена.
- delete() – возвращает количество удаленных строк
Дальше пойдут методы, которые возвращают ответ «Да» или «Нет»:
- isOpen() – true, если база была не закрыта
- isReadOnly() – true, если база была открыта только для чтения
Методы для работы с транзакциями:
- beginTransaction() – начало
- endTransaction() – конец
- inTransaction() – true, если открыта транзакция
- setTransactionSuccessful() – только после вызова этого метода транзакция будет закомичена.
Остальные вряд ли будут часто использоваться. С вышеперечисленными пока не будем сильно заморачиваться, а остановимся над тем, с чего собственно начали — создание базы.
Как я и говорил, существует 3 способа это сделать. И одним из этим способов является метод openDatabase(). Вот его полное описание:
static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
где:
- path — путь к файлу базы данных. Путь для любой базы данных имеет вид: «/data/data/<package_name>/databases. <package_name> – имя пакета, которое вы указывали при создании проекта. Что-то вроде com.google.android.maps. Ну, или com.example.mysmallapp
- factory — хз, что такое. По умолчанию стоит null — пусть так и стоит, пока не будет трогать.
- flags — флаги или константы, определенные внутри класса SQLiteDatabase. Их всего четыре: CREATE_IF_NECESSARY (создавать базу, если это нужно), OPEN_READONLY (открыть только для чтения), OPEN_READWRITE (читать и писать), NO_LOCALIZED_COLLATORS (открыть без поддержки локализации)
Т.е. для того, чтобы создать базу с нуля нам нужно вызвать метод openDatabase() примерно таким образом:
SQLiteDatabase mDatabase = SQLiteDatabase.openDatabase("/data/data/com.example.app/databases/my.db", null, SQLiteDatabase.CREATE_IF_NECESSARY);
Все. Таким образом, мы получили новую, чистую базу данных для дальнейшей работы. Так как флаг SQLiteDatabase.CREATE_IF_NECESSARY пишут в конце очень часто при открытии, разработчики Андроида решили предложить метод openOrCreateDatabase() для краткости:
SQLiteDatabase mDatabase = SQLiteDatabase.openOrCreateDatabase("/data/data/com.example.app/databases/my.db", null);
Так даже удобнее. Но это только первый способ.
2 способ — это метод openOrCreateDatabase() в классе Context. О классе Context особо поговорим потом. Сейчас остановимся но том, что он представляет объект для общения с ОС Андроида.
Если вам доступен объект типа Context, то очень рекомендуется использовать его метод openOrCreateDatabase(). Почему? Для начала разберем его поподробнее:
SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory)
где
- name — имя базы данных, уникальное в рамках приложения
- mode — константа, для указания режима (MODE_PRIVATE или 0 – по умолчанию, MODE_WORLD_READABLE для чтения, MODE_WORLD_WRITEABLE для записи)
- factory — пока не трогаем, ставим null
Возвращает этот метод объект типа SQLiteDatabase.
Какие преимущества у такого способа? Оно одно — нам не нужно указывать полный путь к файлу базы данных. Достаточно только ее имени. Так как путь к приложению мы уже знаем исходя из данных, которые предоставляет Context.
Для полноты картины разберем еще несколько методов класса Context для работы с базами данных:
- boolean deleteDatabase(String name) — удаляем базу по имени. True, если завершилось успешно.
- File getDatabasePath(String name) — возвращает абсолютный путь по имени базы
Со вторым способом мы разобрались. Перейдем к следующему, самому православному способу, который советуют использовать разработчики Гугла.
3 способ — использовать SQLiteOpenHelper.
Как становится ясно из названия, SQLiteOpenHelper помогает нам открывать/создавать базы данных. Начнем с описания класса:
Иерархия:
java.lang.Object
android.database.sqlite.SQLiteOpenHelper
Здесь все. Также все просто и с методами. Их всего 6. 2 из них — абстрактные, и их следует переопределять в потомках. Перечислим первые 4:
- void close() – закрыть все открытые соединения
- SQLiteDatabase getReadableDatabase() – возвращает базу для чтения
- SQLiteDatabase getWritableDatabase() – база для чтения и записи
- onOpen(SQLiteDatabase db) — вызывается после того, как база будет открыта
И 2 абстрактных метода:
- onCreate(SQLiteDatabase db) — вызывается после того, как база создана в первый раз. Здесь мы создаем необходимые таблицы. И если нужно, заносим в них какие-либо начальные данные.
- onUpgrade(SQLiteDatabase db) — вызывается, когда базе нужен апгрейд. Здесь мы удаляем таблицы, создаем новые, добавляем поля и так далее.
Ну, и конструктор:
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
где
- context — контекст приложения. Именно через его метод openOrCreateDatabase() и работает SQLiteOpenHelper
- name — имя базы
- factory — говорили уже
- version — версия базы. Используется, когда схему базы данных нужно обновить.
Приведем пример наследования и использования SQLiteOpenHelper:
class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "data";
private static final String DATABASE_CREATE = "create table test_table (_id integer primary key autoincrement, some_test text not null);";
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
Здесь мы использовали паттерн Шаблон для инкапсуляции внутренних данных. Для использования базы теперь нужно лишь вызвать getReadableDatabase() или getWriteableDatabase() метод. Т.е.:
DatabaseHelper mDbHelper = new DatabaseHelper(mCtx);
SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
И мы получим нашу любимую базу. Все очень просто и лаконично смотриться.
Конец второй части
На этом пока остановимся. Дальше разберем, как же все таки работать со всеми этими методами — insert(), update(), replace(), delete() и тому прочее. Постараюсь этому как-нибудь выделить время. Пока всем.
Программирование
ноя 7, 18:22

В начале было слово
Я уже говорил в недавнем времени о выборе платформы Android в качестве хобби-изучения на ближайшее время. И так, чтобы провести учиться с пользой буду рассказывать о том, как проходит этот самый процесс.
У меня нет никакой четкой и выверенной программы обучения. Не никакого плана: сначала это, потом это, потом то. Единственный мой план — это собственная интуиция. В общем-то, как бог на душу положит одним словом.
По-этому не удивляйтесь, что на роль первого погружения в Андроид я выбрал трепанацию SQLite, как базы данных для хранения данных андроид-приложении.
Цели
- Понять, как устроена работа с базой данных в самом широком смысле
- Какие эффективные практики следует применять при работе?
- Какие классы и объекты чаще всего используются? Какие методы следует знать, как свои пять пальцев?
- Как база данных связана со остальными частями приложения? Где создается, где вызывается?
Начало
Базы данных имеют больше значение в программировании хотя бы по тому, что каждый из нас с ними сталкивался (или столкнется). В Андроиде реализована поддержка баз данных на уровне каждого приложения. Т.е. создать и работать с базой данных внутри приложения настолько естественно в Андроиде, как дышать воздухом. По умолчанию доступен интерфейс для работы с базами данных SQLite. Поддержки никаких других СУБД мною замечено не было, значит за основу берем единственно доступный SQLite.
Ради справедливости стоит отметить, что сторонние разработчики предлагают свои продукты, как альтернативу SQLite. Это db4object и mcobject.
Но о них мы поговорим, как-нибудь потом. Сейчас для этого не время.
Небольшое введение (SQLite)
SQLite — библиотека, которая позволяет создавать база данных, который обладают рядом приятных и уникальных особенностей. Во-первых – это независимость. Под независимостью я понимаю минимальный объем сторонних библиотек и функций, которые ей нужны. Создатели говорят, что SQLite можно скомпилировать любым С компилятором и с использованием только таких библиотечных функций, как:
- memset()
- memcpy()
- memcmp()
- strcmp()
- malloc()
- free()
- realloc()
Это позволяет использовать эту библиотеку в разнообразных мобильных устройствах, mp3-плеерах, да и просто в любом месте, где требуется компактная база данных.
Во-вторых, в SQLite нет никакого серверного процесса. База данных хранится в файле. И весь доступ к ней осуществляется, как к файлу. Следовательно ничего не надо устанавливать. Нет никакого процесса установки. Ты просто подключил и используешь.
В-третьих, все запросы являются транзакциями, что обеспечивает такие свойства, как Атомарность, Непротиворечивость, Изоляцию и Долговечность (ACID)
Вывод из этой все прелести очень простой. В Андроиде можно работать с базой данных, как с настоящей базой данных со всеми запросами, insert’ами и update’ами не задумываясь о том, как все это храниться в чистоте и порядке.
Конечно, свойства SQLite откладывают свой отпечаток на работу с ней. Так, например, открытие доступа к базе может потребовать создание нового файла и так далее. Но API Андроида делает большинство работы за нас. И многом мы можем и не заботиться, как убедимся позже.
Продолжение введения (паттерн Адаптер и Шаблон )
Одно из любимых слов в Android API — это Адаптер. Встречаются они повсюду, и по-этому следует уяснить, что же это такое. Хорошо, что это совсем не трудно.
Выжимка из книги Design Patterns:
Паттерн Адаптер (известен также как Wrapper, «Обертка»)
Назначение: Преобразует интерфейс одного класса к интерфейсу другого.
Мотивация: Иногда класс из инструментальной библиотеки, спроектированный для повторного использования, не удается использовать только потому, что его интерфейс не соответствует тому, который нужен конкретному приложению.
Если быть понятнее, то Адаптер используют для предоставления доступа к объекту через специальный интерфейс. Одной из причин этому может быть несоответствие исходного интерфейса. Другая причина — просто создание более удобного интерфейса, который скрывает ненужные подробности работы с исходным объектом, и предоставляет удобную альтернативу.
С последней причиной очень тесно паттерн Шаблон. Цель этого паттерна — создание класса, который модифицирует логику своего подкласса таким образом, чтобы сделать его завершенным — например, скрыть конфигурационные параметры, методы для проверки прав, да и вообще все , что угодно.
Для удобства приведу пример некоторого псевдо-класса для работы с базой данных. Пусть у нас есть класс для доступа к базе. Назовем его Database. Для создания соединения мы передаем в конструкторе необходимые реквизиты: имя базы данных, пользователя, пароль:
class Database {
private Connection mConnection;
Database(String database_name, String username, String password) {
connection = getDbConnection( database_name, username, password);
}
....
}
Это утомительно — каждый раз помнить и передавать параметры к базе, когда нам понадобится к ней доступ. Как выход, можно создать класс MyDatabase, который дополнял бы класс Database нужным нам образом. Выглядеть это будет примерно так:
class MyDatabase extends Database {
private static final String DATABASE_NAME = "sample_database";
private static final String USERNAME = "user";
private static final String PASSWORD = "password";
MyDatabase() {
super(DATABASE_NAME, USERNAME, PASSWORD);
}
...
}
Все. Теперь достаточно вызвать new MyDatabase() и все будет сделано за нас.
Для чего я все это рассказываю? Для того, что приведенный выше прием станет одним из наших любимых при написании классов для работы с базой данных, да и вообще в большинстве аспектов работы над приложениями в Андроиде.
Работа с базой данных (общая картина)
Начнем с простого. Любой процесс работы с базой данных включает в себе четко определенный набор методов.
- Создание базы данных. Или ее открытие базы.
- Работа с базой данных:
- Заполнение базы данными (вставка)
- Изменение существующих данных (обновление)
- Выбор данных по различным критерия (выборка)
- Удаление данных
Конец первой части
На сегодня все. Завтра будем самое основное – увидим Андроид в действии, разберем классы и там уже пойдем дальше.
Программирование
окт 25, 20:46

Скачал не так давно Android SDK. Совсем для себя неожиданно.
Захотелось просто изучить что-то новое, абсолютно не связанное с вебом. Как говорил Дмитрий Честных изучать новые языки, технологии полезно и интересно. И я с ним полностью согласен.
Что касается языков и технологий, если они уже упомянули, то не так давно (месяца 2-3 назад) я уже успел переквалифицироваться с программиста на PHP на программиста J2EE. Переквалифицировался скорее номинально, чем реально. Потому что с явой до текущего своего места работы сталкивался очень туманно и давно (первый курс университета => прочтение книги Дейтеля по Си => прочтение несколько глав из книги Дейтеля по Java, последнее только из уважения к Дейтелю).
Из этого всего вытекает, что осваивать Андроид вдвойне полезнее.
- Что-то новое
- Java, java
Третьим пунктом можно было бы приписать Андроид Маркет, но я об этом совсем не думаю.
Что касается самого Андроида, то не так давно вышла новая версия sdk – 1.0 по счету. Прям как знак с выше – вот тебе, парень, новая версия. Бери, специально для тебя сделали.
Я, конечно, спасибо. Посмотрю, что там под коробкой и как оно работает. Но об этом поподробнее в следующий раз. Сначала попробую рассказать о первых впечатлениях. Потом уже о впечатлениях в работе.
Программирование
сен 5, 23:39
Программирование
фев 22, 14:53
Итак, в прошлый раз разговор был о Symfony. Сегодня речь пойдет о Akelos. Кто не в курсе Akelos, скажу лишь то, что этот фреймворк является полным клоном RoR с единственным отличием – написан он на PHP. Правда ли это для вопросов интернационализации я не знаю. Но как бы то ни было все равно посмотрим, что Akelos нам приготовил.
Пару слов о документации
Как таковой для Akelos ее нет. Есть пару отписок, небольшой Readme, плюс сгенерированная с помощью phpxref дока и демонстрационный скринкаст. Все. Может быть в этом плане разработчик понадеялся на прошлый опыт с Ruby On Rails. Не знаю, но факт, как говорится, на лицо и с этим следует смириться. В оправдание могу сказать, что фреймворк достаточно само документированный и разобраться как и что происходит не составляет особого труда. Исходники благо открыты для любого применения.
Интернационализация интерфейса, сообщений и тд
Здесь Akelos ничем примечательным от Symfony не отличается. Если помните в Symfony для проблемы поддержки нескольких языков и стран использовался параметр Culture ( язык_СТРАНА в двухбуквенных кодах). Akelos используют более простую модель. В файле config задаются разрешенные локали, которые можно увидеть в банальном HTTP-заголовке «Accept-Language» (например, «ru, en_us»). После того как настроены необходимые языки Akelos будет самостоятельно создавать и следить за словарями для этих языков с помощью специального метода t() или специальных тегов _{} в виде.
Каждый раз когда вызываются эти методы, фреймворк, во-первых, обновит словарь, если переданного слова или фразы не будет в словаре, и, во-вторых, при выводе возьмет перевод слова или фразы из словаре, соответствующему выбранной локали.
Для того, чтобы сменить локаль достаточно в URL добавить код языка. Например, вот так будет выглядить классическая строка с доступными языками:
<div align="right">
<?php echo $url_helper->link_to($controller->t('en'), array('lang' => 'en')) ?> |
<?php echo $url_helper->link_to($controller->t('de'), array('lang' => 'de')) ?> |
<?php echo $url_helper->link_to($controller->t('ru'), array('lang' => 'ru')) ?> |
</div>
Это все. Akelos по щелчку на ссылку прибавит к URL выбранный язык. И даже более того, во-всех ссылках, созданных с помощью link_to(), отобразится изменения, связанные с выбором языка. Ни о чем разработчику беспокоится не приходится.
Что касается словарей, то никого XML подобия вы не встретите. Обыкновенные ассоциативные массивы:
$dictionary = array();
$dictionary['Pages'] = 'Страницы';
$dictionary['OK'] = 'OK';
$dictionary['Cancel'] = 'Отмена';
Локализация содержимого
Если с интернационализацией интерфейса все очень просто и понятно, то почему же должно быть по-другом с содержимым подумали разработчики и упростили работу нам до безобразия.
Для начала следует обьявить что твоя любимая модель имеет многоязыковую структуру и что даже несколько столбцов в соответствующей ей таблицы из базы данных имеет перевод на другие языки мира. Как это делается? А вот так:
function up_1()
{
$this->createTable('pages', "
id,
en_title,
de_title,
ru_title,
en_body text,
de_body text,
ru_body text
");
}
Все это естественно должно находится в инсталлере приложения. Как видите, в одной таблице мы имеет сразу же и английский, и немецкий, и русский вариант заголовка и текста страницы. Ну что же, разработчики доупрощались настолько, что нарушили святые нормальные формы старика Кодда, так ревностно оберегаемые математиками и профессорам ВУЗов. Мне если честно, такой поворот событий тоже не очень нравится. Не правильно это по единственной простой причине – если не все страницы могут быть переведены на все три языка, то на что будет похожа наша таблица? Пустое пространство – это не есть хорошо и все-таки следует беречь ресурсы данные нам Богом.
Ну оставим и пойдем дальше. Дальше никаких сюрпризов не ожидается. Все те же get- и set-методы автоматом подхватываются фрейморков. И мы просто лениво наслаждаться тем, что мир прекрасен и чуден, и все так хорошо и просто. На всякий случай приведу пример как же это делается:
Контроллер:
$this->Page->setAttribute('title', $this->params['title']);
$this->Page->setAttribute('body', $this->params['body']);
$this->Page->save();
Вид:
<h1>En</h1>
<p>
<label for=“page_title”>_{Title}</label><br />
<?php echo $form_helper->text_field(‘title’, ‘en’)?>
</p>
<p>
<label for=“page_body”>_{Body}</label><br />
<?php echo $form_helper->text_area(‘body’, “en”) ?>
</p>
<h1>Ru</h1>
<p>
<label for=“page_title”>_{Title}</label><br />
<?php echo $form_helper->text_field(‘title’, ‘ru’)?>
</p>
<p>
<label for=“page_body”>_{Body}</label><br />
<?php echo $form_helper->text_area(‘body’, ‘ru’) ?>
</p>
Модель (инсталлер):
$this->createTable('pages', "
id,
en_title,
ru_title,
en_body text,
ru_body text
");
И все. Красота да и только.
Выводы
Akelos прост на уровня интуитивного понимания. Конечно хранение в одной таблицы сразу всех версий контента не очень красиво. Гораздо приятным в этом отношении выглядит Symfony, которые как заядлый отличник старается все время вытянуть на пятерку.
Но и у Symfony есть свои недостатки, которые, как мне кажется, все-таки сглаживаются на больших проектах. Взять хотя бы тот же XML для словарей. Его неудобно писать вручную. Но кто знает, возможно на большом сайте такой необходимости уже и не будет, так как можно будет использовать генерирующие XLIFF программы.
По большому счету, что Akelos, что Symfony — без разницы. Все они используют ту же модель – хеллпер, словарь, ORM, автоматом подхватывающие get- и set-методы.
Надеюсь, что это небольшая обзорная статья в чем-то поможет в реализации многоязычных приложения самостоятельно. Курс и направление движения есть. Существуют ли какие-либо другие варианты? Наверняка существуют. Если мне что-нибудь интересного в этом плане попадется, обязательно напишу.
В следующей статье я планирую немножко развить эту тему и немножко отойти к другой. Меня интересует ORM-фреймворк Doctrine. Что за вкусности там есть, опять же поддержка интернационализации, удобно ли пользоваться, насколько эффективен для применения «один разработчик – небольшой приложение». Словом, не буду загадывать – все равно сами все увидите.
Программирование
фев 16, 15:16
Предисловие
Вообще говоря, слово «интернацинализация» для веб-разработчика очень часто означает кошмар. Чаще всего то, как это делается, понимается смутно.
Я решил провести небольшое исследование на эту тему. Причем рассмотреть этот вопрос на примере того, как это делает два PHP-фреймворка — Symfony и не шибко известный Akelos.
Почему Akelos? Во-первых, потому что в недавнем времени мне пришлось писать под него приложение с поддержкой 3 человеческих языков. Akelos, как мне сообщили некие авторитетные люди, есть полный клон RoR. Не могу сказать правда это или нет, так не имею опыта работы с последним.
Теория (свободный перевод из мануала Symfony)
Слово «интернационализация» (internationalization) довольно длинное. Поэтому разработчики привыкли сокращать его до простого «i18n» (понимается как i, 18 английских букв и n в конце. Локализация (localization) точно таким же образом сводится к лаконичному «l10n». Вот эти два понятия и покрывают 2 разных аспекта разработки мультиязычных веб-приложений.
Под интернационализацией понимают несколько версий одного и того же контента на разных языках или в разных форматах. Например, интерфейс gmail может предлагать один и тот же сервис на разных языках. Меняются только надписи на кнопках.
Локализация же предполагает определенный контент, зависящий от страны, из который ты пришел. Возьмем для примера новостной портал. Если ты находишься в Украине, то тебе показывают последние заголовки об Украине. Если же ты находишься в России, то тебе показывают последние заголовки об Путине. Все просто. Стоит сказать, что локализация может включать в себя не только перевод контента, но и сам контент может отличаться в разных локализированных версиях.
Если все обобщить, то получится что i18n и l10n означает, что приложение умеет справляется с:
- Переводом текста на разные языки (как в интерфейсе, так и в самом содержании)
- Стандартами и форматами (даты, суммы, числа и тому подобное)
- Локализацией контента (множество версий заданного объекта в зависимости от страны)
Как же это делает Symfony?
Здесь я не собираюсь переводить целую главу из учебника по Symfony. Все там достаточно толково и понятно описано. Просто остановимся вкратце на некоторых вещах.
В Symfony есть такой замечательный объект как sfUser, который содержит все рутину по работе с пользовательской сессией. По идее, про $_SESSION вы должны забыть совсем. Так что имейте это ввиду.
Итак, в этом объекте sfUser есть замечательный параметр “Culture”. В настройках для приложения вы можете установить его по умолчанию и переопределить, если надо, в контроллере.
Интересно, что Culture, кодируется двумя кодами – двухбуквенным кодом для языка в нижнем регистре (по ISO 639-1) и и двухбуквенным кодом для страны в верхнем регистре (по ISO 3166-1) примерно так – «fr_FR», «ru_UA». Это сделано для таких случаев, когда нам нужен один контент на одном языке, но для разных стран. Например, один контент на испанском языке для Мексики, и совершенно другой для Испании.
Что еще интересного? Поддерживается автоматическое добавление параметра Culture в URL. Для этого необходимо настроить параметры роутинга для нужных страниц и все.
Info Symfony, как и большинство приложений в таких случаях, при первом запросе определяет язык по HTTP-заголовку «Accept-Language».
Поддерживается форматирование дат, денежных сумм, чисел. с помощью хелперов. Плюс поддерживается ввод и перевод во внутренний формат всех данных, зависящих от культурных особенностей (См. класс sfI18N).
Теперь об локализации приложений. Рассмотрим случай с существованием одно и того же контента на разных языках и хранение этой информации в базе данных.
В Symfony каждая таблица, которая содержит данные для разных языков, должна быть разделена на 2 таблицы. Первая – это та, которая не содержит никакой связанной с локализацией информации. И вторая – это та, которая содержит только локализированние данные. Эти две таблицы связаны с друг другом связью «один ко многим».
Пример задание схемы базы данных:
my_product:
_attributes: { phpName: Product, isI18N: true, i18nTable: my_product_i18n }
id: { type: integer, required: true, primaryKey: true, autoincrement: true }
price: { type: float }
my_product_i18n:
_attributes: { phpName: ProductI18n }
id: { type: integer, required: true, primaryKey: true, foreignTable: my_product, foreignReference: id }
culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
name: { type: varchar, size: 50 }
Как видим, первая таблица содержит только первичный ключ «id» и цену товара. Во второй таблице, объявлен внешний ключ на «id» из первой таблицы. К тому же присутствует дополнительно поле «culture» для задание параметра Culture, и естественно само поле?содержащее необходимый контент, «name». Symfony позволяет не описывать все так подробно. Все будет сделано само собой, если придерживаться соглашения об именах (см. мануал).
Теперь о сохранении данных. Для необходимо перед сохранением устанавливать Culture c помощью метода setCulture() класса модели. Можно автоматизировать этот процесс и делать все эти необходмые дела внутри метода hydrate(), перопределив его примерно таким образом в своей модели:
public function hydrate(ResultSet $rs, $startcol = 1)
{
parent::hydrate($rs, $startcol);
$this->setCulture(sfContext::getInstance()->getUser()->getCulture());
}
Выборка осуществляется с помощью метода doSelectWithI18n() в классах Peer’ах.
Info Для справедливости стоит сказать, что все эти выборки и set’ы справедливы для ORM-фреймворка Propel, который по умолчанию использует Symfony. Есть еще и Doctrine. Но его мы рассмотрим как-нибудь потом.
Перевод интерфейса (всяких там сообщений, лейбочек и тому подобное) сделан просто и со вкусом. Для этого всего лишь необходимо использовать хелпер __($phrase) – два символа подчеркивания, если кто не понял. Каждый раз, когда вызывается этот хелпер, Symfony смотрит перевод $phrase в словаре в зависимости от выбранного языка.
Словари написаны в c использованием XML Localization Interchange File Format (XLIFF). Первый раз встретил этот формат именно в Symfony. Вид имеет простецкий. К сожалению чего не скажешь о самом XML, который читается, честно говоря, с трудом.
Пример:
<?xml version="1.0" ?>
<xliff version="1.0">
<file orginal="global" source-language="en_US" datatype="plaintext">
<body>
<trans-unit id="1">
<source>Welcome to our website.</source>
<target>Bienvenue sur notre site web.</target>
</trans-unit>
<trans-unit id="2">
<source>Today's date is </source>
<target>La date d'aujourd'hui est </target>
</trans-unit>
</body>
</file>
</xliff>
Есть еще мелкие приятности, которые имеет Symfony. Это и перевод в зависимости от параметров (полезно для сообщений типа «1 комментарий», «2 комментария». Это и перевод предложений, содержащих переменные. И тому подобное.
Конец первой части
Хватит наверно для начала. Писалось это все в субботу утром за чашечкой чая, когда 2 чувака в квартире, где я проживаю, меняли двери, сверлили там вовсю, хлопали и топали. Пока все.
Программирование