Программирование

Недостатки Java: Threads

апр 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 блок не поддерживает таймауту по умолчнанию). И так далее. О других вещах и как обойти эти ограничения – в следующий раз.

Комментарии

---

REST: Основы

апр 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. На самом деле я хотел набросать себе небольшую подсказку, как распределить реальные данные по ресурсам, какие шаги нужно выполнить и так далее, и так далее, но об этом позже.

Комментарии

---

Недостатки Java (как языка и платформы)

апр 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, профайлеры и прочее, прочее, прочее), богатая библиотека, сообщество и так далее. Но это тема для другого разговора.

Комментарии

---

CSS specificity - то, что нужно знать каждому кто сталкивается с CSS

апр 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, которая имеет приоритет над твоими изменениями.

Комментарии

---

tcpick, как средство для дебага

апр 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 брокеры). Кому-нибудь, надеюсь, это поможет облегчить нелегкую жизнь веб-разработчика.

Комментарии

---

Читай код, cука

июл 29, 21:06

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

Создать свою структуру и пришлепать ее сбоку может любой дурак. Квалифицированный инженер-программист (с упором на первом слове, не путать с “программером”) умеет проводить анализ “чужой” подсистемы, восстановит мысль и идею автора, сможет мысль автора развить, продолжить ее, и эффективно решить свою задачу в рамках чужого подхода к проблеме. Все это – работая с кодом. Это отличительная компетенция архитектора, высший уровень инженерного мастерства. И это имеет весьма отдаленное отношение к “рефакторингу”.

Тогда у меня возникла идея провести эксперемент:

  1. Взять некоторый известный паттерн проектирования.
  2. C одной стороны показать, как выглядит код до применения паттерна.
  3. Рядом же, с другой стороны, показать как выглядит этот же код с применением паттерна.
  4. Попытаться понять, что этот код делает и как применяется паттерн.
  5. Сравнить такой вариант описания паттернов с традиционным подходом “UML-диаграмма, словесное объяснение, пример использования”.

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

Итак, сам эксперимент. Для описания я выбрал паттерн Декоратор.

Традиционный подход

Декоратор, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.

Задача

Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до или после основной функциональности объекта.

Реализация

Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.

При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.

UML-диаграмма

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

Подход «Читай код, сука»

Итак, Вопрос

Что лучше? Что легче для понимания? Подход «Читай код, сука» сработал или нет?

Комментарии [11]

---

MIT's Introduction to Algorithms

июл 25, 22:01

Недавно начал просматривать лекции по алгоритма, которые читают в MIT. Решил для большей полезности делать это в некотором процессе, который подсмотрел у других умных людей.

Прежде всего, нужно определится зачем эти лекции смотреть, какова цель. Тут все просто. Если вспомнить Programmer Competency Matrix, то выяснится, что алгоритмы – это один из тех скиллов, которые востребованы. Интересная цитата оттуда:

Working with someone who has a good topcoder ranking would be an unbelievable piece of luck!

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

Итак, что касается процесса как смотреть эти лекции с большой для себе пользы, то тут дела обстоят так:

  1. Качаем лекцию. Я ходил по ссылкам отсюда. Начинаем ее смотреть.
  2. С этого же сайта парень предлагает ввести параллельный конспект по ходу просмотра так как будто ты находишься на лекции. Кстати, полезно также после просмотра лекции сравнить свой конспект с тем, что выложен на этом сайте. Такая себе проверка.
  3. Кроме конспекта, можно еще записывать интересные мысли или высказывания профессора, которые могут касаться или не касаться изучаемого материала.
  4. Самое полезное часть изучение происходит после просмотра лекции. Либо в этот же день, либо на следующий просто необходимо открыть книгу, по которой идет этот курс.

Книга переведена на русский язык и ее даже можно скачать с торрентов или натахауса. Называется «Алгоритмы. Построение и анализ» Томаса Кормена, Чарльза Лейзерсона и других. Выглядит она следующим образом:

Замечательно в этой книге то, что профессор Чарльз Лейзерсон, который читает лекции, есть один из ее соавторов. Да и сама книга – одна из самых популярный по алгоритмам. Кстати, второй профессор Eric Damaine – живой гений, самый молодой профессор MIT, – стал им в 20 лет.

Без лекций книгу читать сложновато и немного скучно, но после просмотра – идет на ура. В общем, советую.

Комментарии [1]

---

How to Design a Good API по мотивам презентации Джошуа Блоха (продолжение)

июл 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 программистам.

Комментарии

---

Java Diff Utills: первый релиз

июл 17, 16:19

Сделал релиз библиотеки, разработку которой недавно описывал (вторая часть, кстати, скоро будет). Пока не хватает документации и примеров. Наверняка есть ошибки и недочеты, но надеюсь все это исправить в ближайшее время.

Screenshot from Google Code

Лицензия GNU. Cкачать можно отсюда.

Понравился Google Code. Особенно понравилась возможность добавить счетчик Google Analytics и тут же следить за посещаемостью. Приятно удивило, что уже и без первого релиза народ начал приходить.

В общем, сделал доброе дело. Дальше – больше :)

Комментарии

---

Презентация: Про Dependency Injection и Inversion Of Control

июл 17, 11:22

Решил выложить слайды с доклада, которою я делал для студенческой конференции. Старался сделать веселую и интересную презентацию. Тема – управление зависимостями, dependency injection и inversion of control. Доклад обзорный.

View more presentations from iobit.

Комментарии [1]

---

How to Design a Good API по мотивам презентации Джошуа Блоха

июл 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 зайцев.

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

Ну и это еще не все. Где-то в дали промаячил и третий заяц – юнит-тесты. Мы уже имеем некоторый интерфейс и можем попытаться его использовать в тестах. Возможно возникнут проблемы и сюрпризы, и нам нужно будет изменить прототипы – не беда, но то они и прототипы.

Пока все

Дальше – больше

Комментарии [2]

---

Загадка по Java № 1 (boxing и unboxing)

фев 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, а потому уже сравнивать их значения. Так кошернее.

Комментарии

---

Небольшое введение в Ant

фев 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. Но время покажет как все получится.

Комментарии

---

Android - Работа с базой данных (часть 2)

ноя 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() и тому прочее. Постараюсь этому как-нибудь выделить время. Пока всем.

Комментарии [9]

---

Android - Работа с базой данных (часть 1)

ноя 7, 18:22

В начале было слово

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

У меня нет никакой четкой и выверенной программы обучения. Не никакого плана: сначала это, потом это, потом то. Единственный мой план — это собственная интуиция. В общем-то, как бог на душу положит одним словом.

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

Цели

  1. Понять, как устроена работа с базой данных в самом широком смысле
  2. Какие эффективные практики следует применять при работе?
  3. Какие классы и объекты чаще всего используются? Какие методы следует знать, как свои пять пальцев?
  4. Как база данных связана со остальными частями приложения? Где создается, где вызывается?

Начало

Базы данных имеют больше значение в программировании хотя бы по тому, что каждый из нас с ними сталкивался (или столкнется). В Андроиде реализована поддержка баз данных на уровне каждого приложения. Т.е. создать и работать с базой данных внутри приложения настолько естественно в Андроиде, как дышать воздухом. По умолчанию доступен интерфейс для работы с базами данных SQLite. Поддержки никаких других СУБД мною замечено не было, значит за основу берем единственно доступный SQLite.

Ради справедливости стоит отметить, что сторонние разработчики предлагают свои продукты, как альтернативу SQLite. Это db4object и mcobject.

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

Небольшое введение (SQLite)

SQLite — библиотека, которая позволяет создавать база данных, который обладают рядом приятных и уникальных особенностей. Во-первых – это независимость. Под независимостью я понимаю минимальный объем сторонних библиотек и функций, которые ей нужны. Создатели говорят, что SQLite можно скомпилировать любым С компилятором и с использованием только таких библиотечных функций, как:

  • memset()
  • memcpy()
  • memcmp()
  • strcmp()
  • malloc()
  • free()
  • realloc()

Это позволяет использовать эту библиотеку в разнообразных мобильных устройствах, mp3-плеерах, да и просто в любом месте, где требуется компактная база данных.

Во-вторых, в SQLite нет никакого серверного процесса. База данных хранится в файле. И весь доступ к ней осуществляется, как к файлу. Следовательно ничего не надо устанавливать. Нет никакого процесса установки. Ты просто подключил и используешь.

В-третьих, все запросы являются транзакциями, что обеспечивает такие свойства, как Атомарность, Непротиворечивость, Изоляцию и Долговечность (ACID)

Вывод из этой все прелести очень простой. В Андроиде можно работать с базой данных, как с настоящей базой данных со всеми запросами, insert’ами и update’ами не задумываясь о том, как все это храниться в чистоте и порядке.

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

Продолжение введения (паттерн Адаптер и Шаблон