Category: Переводы

Естественный Пользовательский Интерфейс

Перевод Андрея Юркова ответа на вопрос «What are the basic principles of NUI (Natural User Interface) design?»
Это был его первый технический перевод, он немного хромает, но понять суть можно.

Каковы основные принципы дизайна NUI?

Дизайн для пальцев, а не для курсоров

Сенсорные иконки должны быть гораздо больше, чем для ПК: 8-10мм для иконок, 10-14мм в области соприкосновения с пальцами.

Помните о физиологии и кинезиологии

Не заставляйте пользователей делать лишние или повторяющиеся действия.

Не для рук гориллы

Люди не должны делать много задач с поднятыми, перед их телом, руками в течение длительного периода времени. Извините, сугубо личное мнение.
Гориллы с айпадом

Расположение элементов на экране

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

Знайте технологии

Тип сенсорного экрана, датчика или камеры определяет тип жестов, которыми вы можете пользоваться.
Чем больше сложных жестов, тем меньше людей, которые смогут (или захотят) выполнять их.
Запуск действия легкой нажатием, а не давлением.

Приветливость

Используйте простой жест, чтобы дать возможность пользователям начать использовать систему.

Избегайте случайных нажатий

Различные повседневные движения со стороны пользователей могут случайно запустить систему. Избегайте их.

Жесты и командные кнопки

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

Необходимость разнообразия

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

Реклама

[Перевод] Способы сравнения объектов дат в Java

Аннотация от переводчика

Статья вкратце затрагивает трудности работы с датами в Яве на простом примере сравнения дат. Рассчитана для начинающих программистов которые уже более менее знакомых с работой с датами в Java.

Итак, давайте обрисуем проблему — сравнение объектов Date в Java известный источник ошибок.
Это появляется в стандартном коде, но также бывает и в тестирующем коде, где нам регулярно нужно создавать объекты Date которые отмечают определенный момент времени на который потом будем ссылаться в сравнении.

Старый добрый устаревший и не рекомендуемый путь

В тестирующем коде я не переживал что использую устаревшие (deprecated) методы. Поэтому я использовал старый конструктор Date чтобы инициализировать даты, после чего я сравнивал их с другим объектами дат через метод сравнения equals():

Date date = new Date(112, 5, 3);
Date userCreatedDate = user.getCreatedDate();
if (userCreatedDate.equals(date)) { // Если даты равны…
  // делаем что нибудь…
}

Преимущества: это лаконично. Недостатки: это весьма не очевидно и вам нужны хорошие знания Java API чтобы знать что первый параметр это год минус 1900 и второй это индекс месяца который начинается с ноля для Января. И это сюрприз когда вы узнаёте что последний параметр это просто… просто день месяца.

Канонический способ

Начиная с Java 1.1 в Java API был добавлен класс Calendar чтобы разделить момент во времени (т.е. дату) от её представления в специфическом справочнике (календарь). Следующий фрагмент кода (snippet) это способ получения такого же результата как выше.

Calendar calendar = Calendar.getInstance();
calendar.set(YEAR, 2012);
calendar.set(MONTH, JUNE);
calendar.set(DAY_OF_MONTH, 3);

Это не только более многословно, тут также есть ошибка: часы, минут и остальное не равно нолю (берётся от момента непосредственного создания календаря), поэтому сравнение через equals() будет возвращать false. Вот правильный код:

Calendar calendar = Calendar.getInstance();
calendar.set(YEAR, 2012);
calendar.set(MONTH, JUNE);
calendar.set(DAY_OF_MONTH, 3);
calendar.set(HOUR_OF_DAY, 0);
calendar.set(MINUTE, 0);
calendar.set(SECOND, 0);
calendar.set(MILLISECOND, 0);

По меньшей мере это ухудшает краткость 😉

Apache Commons Lang

Apache Commons изначально предоставляет различные утилитные библиотеки которые облегчают разработку в Java. Одна из таких библиотек Apache Commons Lang которая предоставляет функционал который заслуживает быть частью Java API. В нашем случае класс DateUtils позволит нам сократить код сохранив при этом его читаемость:

Calendar calendar = Calendar.getInstance();
calendar.set(YEAR, 2012);
calendar.set(MONTH, JUNE);
calendar.set(DAY_OF_MONTH, 3);
calendar = DateUtils.truncate(calendar, DAY_OF_MONTH); 

Даже лучше, DateUtils позволяет нам работать непосредственно с объектами Date в таком альтернативном виде:

Date date = new Date();
date = DateUtils.setYears(date, 2012);
date = DateUtils.setMonths(date, JUNE);
date = DateUtils.setDays(date, 3);
date = DateUtils.truncate(date, DAY_OF_MONTH);

Обратите внимание что он оставляет параметры нетронутыми, достигая неизменяемости (immutability) по принципа функционального программирования.
Преимущества: мы используем стандартное Java API.
Недостатки: да никаких. И ещё, не будет ли полностью своеобразный DSL казаться чем-то более подходящим?

Joda Time

Последний вариант это использование библиотеки Joda Time, которая нацелена стать заменой для Date и Calendar. Она также породила JSR-310 — новый и улучшенный API для манипуляции с датой и временем, который должен стать частью Java 8 (он был изначально запланирован для Java 7). Joda Time стоит посвятить отдельную статью (или даже мини-руководство). Для наших текущих нужд следующий фрагмент кода может выгодно заменить наш изначальный:

DateMidnight dm = new DateMidnight(2012, 6, 3);

Если сравнивать с первым примером, такой код кажется чище и лаконичней. И ещё, параметры самоописывающие, нет реальной нужды регулярно проверять JavaDocs чтобы узнать как инициализируется год. Кроме того, семантика имён классов ясна. И наконец, метод toDate() даёт нам мост к стандартному Java API.

Заключение

Вывод делайте сами. Лично я обычно использую Apache Commons Lang, но в последнее время склоняюсь к Joda Time. Архив с примерами кода доступен для скачивания тут в виде Maven проекта под Eclipse.

Автор: Nicolas Fränkel
Оригинал Ways of comparing Date Objects in Java.

[Перевод] Как писать сопроводительное письмо

Перевод статьи How to Write a Cover Letter.

Примечание переводчика: Сопроводительное письмо (англ. Cover Letter) — это письмо которым вы сопровождаете ваше резюме работодателю когда хотите устроится на конкретную позицию. В нём вы пишите, мол «я тут нашёл вашу вакансию на таком то сайте, я кое что умею из описанного в ней, я долго ждал такой возможности, возьмите меня пожалуйста на работу, в приложении к письму моё резюме и связаться со мной можно по этому телефону».
Например вот таким было моё сопроводительное письмо год назад когда я пытался поменять специализацию. А вот ещё один пример сопроводительного письма фрилансера.

Эту статью крупнейшая фриланс биржа oDesk рекомендует новичкам. Она будет полезна главным образом программистам и всякого рода фрилансерам работающим на западного заказчика.

Как писать сопроводительное письмо

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

Так как же его писать?

В общем, будьте честными, искренними, и профессиональными. Но детали могут стать более сложными. Следуйте этим простым правилам чтобы создать неотразимое сопроводительное письмо которому нанимателю будет сложно отказать!

Начните с приятного и профессионально приветствия.

Этот человек будет принимать решение нанимая вас — поэтому ваше начало знакомства должно убедить его думать о вас как о том с кем бы он хотел работать. «Уважаемый Евгений Николаевич», «Здравствуйте, Евгений,» будет уместным. А вот «Привет Женя,» «Как житуха, Жека?» или любая несерьёзность должна быть заменена на что нибудь более формальное. Не знаете имени человека который занимается наймом? «Уважаемый менеджер по персоналу», (“Dear Hiring Manager,”) это отличный способ задать профессиональный тон с самого начала.

Переходите к делу.

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

Меня заинтересовала ваша позиция ведущего блогера который вы недавно опубликовали. Я была профессиональным писателем на протяжении девяти лет, и я хорошо знакома с WordPress’ом и Typepad’ом. На протяжении трёх лет, как главный редактор BeingInterested, я управляла командой писателей которые публиковали по пять записей в блог за неделю. Вы можете познакомится с некоторыми моими работами в поём портфолио по адресу www.odesk.com/users/…

Подчеркните главное.

Большинство объявлений о работе дадут вам очень понятные подсказки про набор навыков которыми должен обладать желаемый кандидат.
Если у вас есть эти навыки, вы должны их упомянуть — повторяя требования нанимателя на позицию и как вы их покроете даст понять, что вы тот самый человек на эту работу:

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

Следуйте указаниям.

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

По вашему запросу, здесь предоставлены ссылки на три статьи которые я написала про местное мероприятие, садоводство или технологию…

Закрывайте продажу.

Убедитесь что вы дали им понять вашу способность занять позицию и пригласили их связаться с вами для обсуждения в будущем. Это вежливый способ «попросить работу» и закрепить ваш энтузиазм для работы с этим нанимателем:

Я могу выделять 15 часов в неделю вашей компании, и мои часы в день можно обсудить. Я очень заинтересована помочь вам сделать ваш блог успешным. Пожалуйста, чувствуйте себя свободно и обращайтесь ко напрямую чтобы обсудить детали этой работы.

Перечитайте, отредактируйте и продумайте.

Прежде чем нажать «Отправить», взгляните ещё раз на описание вакансии. Вы не упустили всех критериев в вашем сопроводительном письме? Если бы вы были человеком нанимающим на эту позицию, прошло бы это сопроводительное письмо? Поддержит ли ваш профиль и портфолио ваше принятие на эту работу? (Если нет, то не стоит! Приберегите вашу энергию для позиции которая лучше соответствует вашим навыкам и возможностям.) Если вы не уверены, попросите вашего друга просмотреть объявление вакансии и ваше сопроводительное письмо, и выслушайте внимательно его мнение, он может обнаружить подводные камни которые сыграют роль в вашем устройстве на работу.

Сопроводительные письма являются первым впечатлением для работодателя или клиента, поэтому убедитесь что вы приложили необходимые усилия. Найти больше советов по мастерству удачного сопроводительного письма вы можете найти в предыдущей статье Написание «убийственного» сопроводительного письма (ориг. Writing a Killer Cover Letter), содержащее короткий список способов добиться чтобы ваше письмо стало по настоящему «убийственным».

О авторе:

Эрика Бентон (Erica Benton) возглавляет команду блога в oDesk, и видела достаточно сопроводительных писем чтобы определить «убийственное» (“killer”) когда она его встретит. Эрика получила опыт как владелец небольшого бизнеса и фрилансера вплоть до того как она стала менджером по маркетинговым связям в oDesk.

[Перевод] Может ли программирование стать следующей массовой профессией?

Может ли программирование стать следующей массовой профессией так же как сельское хозяйство в 17-ом столетии, фабричная работа во время промышленной революции, строительство во время Великой Депрессии, и мануфактура после Второй мировой войны.
Даже лучше! Потому что программирование является творческим процессом, который может быть с или без традиционного (устаревшего?) офиса, и может создавать огромную личную и экономическую ценность.

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

Есть новая возможность, появившаяся для молодых людей чтобы получить продуктивную, предприимчивую, удовлетворяющую работу: они могут научиться программировать. Программирование это не то, чему трудно научится — любая аутсорсинговая компания берет людей без знаний и делает их Ява программистами за 3 месяца (Конечно, огромное значение имеет тут имеет талант и мастерство).
Программировать не дорого — с интернетом, облачными хостингами и открытым ПО.
До определенного момента программисты самообучаемы, и могут продолжить совершенствовать свои умения.

Есть даже великолепные сервисы: на подобии Treehouse и Codecademy, бесчисленые бесплатные онлайн курсы, Google Code University, приветливый Stack Overflow, персональные курсы вроде Dev Bootcamp, летние кружки для детей, и великолепные организации вроде CodeNow. Я уверен что перечислил далеко не все.

Ещё не так много ВУЗов обучающих программированию. Большинство их учит этому лишь как часть учебного курса.
Хотя конечно они так и должны делать — программирование это грамота, а не (только) специализированный навык. И даже дети могут начать программировать рано. Много студентов которые могут быть потрясающими в программировании, креативными, но при этом не развиваться в институтской (школьной) среде.

Существует огромный спрос на программистов, даже несмотря на общий уровень безработицы, так-что обучение программированию быстро вознаграждается.
Онлайн фриланс бирижи наподобии oDesk и Elance нанимают начинающих программистов по рейтам $15-20 за час и более.
Обучению программированию это один из лучших путей к предпринимательству. Программирование также доставляет студентам удовольствие творчества и мастерства.
Программирование однажды станет базовым профессиональным навыком — наподобие отправки электронной почты или «профессиональное владение Word-ом».
Молодые люди также готовы это изучать: программирование сейчас бренд. Парень который пишет под iPhone или Android, сегодня заполучает девчонку (ну или мальчика).

Есть даже возможность делать больше чем просто научится программировать — а стать элитным программистом, и для этого не обязательно идти в ВУЗ.
Мы сейчас на ранней стадии обучения программированию как профессии. Большинство академических курсов направлены на обучение студентов теории а не практике.
(В США только программа обучения колледжей из Лиги Плюща требует одного курса где студенты непосредственно пишут код.)
Представьте если студенты которые даже не смогли поступить в ВУЗ могут стать элитными программистами.

Для нас в США главной идеей развития является создание нового поколения учёных.
И это так и отражено в том как мы называем предмет программирования — компьютерная «наука» (computer science).
Мы можем сделать что-то другое (или в дополнение), обучая студентов быть ремесленниками, а не учёными: создать следующее поколение тех кто может хакать, творить, зарабатывать сразу, и возможно стать предпринимателями.
Обучение этому может сделать высшее образование более ценным, потому что это будет давать непосредственный результат.

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

© Roy Bahat


Не поленитесь, и почитайте ещё несколько очень важных моих статей для начинающих программистов, студентов и джуниоров.
Также не забудьте вступить в группу IT Juniors куда я пытаюсь собирать ссылки на другие полезные статьи для вас и анонсы курсов и интернатуры в компаниях.

[Перевод] Принципы хорошего программирования

Не самый лучший дизайн
Спустя многие годы я обнаружил что следующие относительно немногие фундаментальные принципы помогли мне стать более эффективным программистом.

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

DRY (Don’t repeat yourself) Не делайте одну и ту же работу дважды

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

Принцип абстракции (Abstraction Principle)

Относящийся к DRY принцип абстракции: «Каждый значительный кусок функциональности в программе должны быть реализован в одном месте исходного кода».

KISS (Keep it simple, stupid!) Не усложняй, тупица


Упрощение (и избежание сложности) всегда должно быть ключевой целью. Простой код занимает меньше времени чтобы его написать, имеет меньше ошибок, и его легче изменить.

YAGNI (You aren’t going to need it) Вам это не понадобится

Вам стоит пытаться не добавлять функциональности пока она вам не нужна. Только потратите время на разработку, отладку, поддержку и будет постоянно мешаться.

Делай сразу простейшую вещь, которая скорее всего заработает (Do the simplest thing that could possibly work)

Когда программируете сразу задайте себе вопрос: «Что является простейшей вещью, которая могла бы вот сразу заработать?». Это помогает удерживать нас на пути к простоте дизайна.

Не заставляйте меня напрягаться и думать (Don’t make me think)


Это на самом деле это название книги Стива Круга о веб-юзабилити и также имеет отношение к программированию. Дело в том, что код должен легко читаться и восприниматься с минимумом усилий. Если код вызывает затруднения чтобы его понять, то вероятно его стоит упростить.

Принцип Открытости/закрытости (Open/Closed Principle)

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

Пишите код для сопровождающего (Write Code for the Maintainer)


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

Программируй так, как если бы человек, который будет поддерживать твой код, будет брутальным психопатом и будет знать, где ты живёшь. © Martin Golding

Принцип наименьшего удивления (Principle of least astonishment) POLA


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

Принцип единственной ответственности (Single Responsibility Principle)

Компонент кода (т.е. класс или функция) должен выполнять единственную хорошо определённую задачу.

Слабая связанность (Minimize Coupling)

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

Максимальное сцепление (Maximize Cohesion)

Код который имеет похожую функциональность должен находится в том же компоненте.

Скрытие деталей реализации (Hide Implementation Details)

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

Закон Деметры (Law of Demeter)

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

Избегайте преждевременной оптимизации (Avoid Premature Optimization)

Даже не думайте об оптимизации пока ваш код работает, но медленней чем вам бы хотелось. Только потом можете начать задумываться о оптимизации, и только основываясь реальных эмпирических данных.

Мы должны забыть про небольшие улучшения эффективности, скажем на протяжении 97% времени: преждевременная оптимизация — это корень всех бед. © Дональд Кнут

От переводчика:

Преждевременная оптимизация хуже преждевременной эякуляции. © namezys

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

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

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

Также профайлеры позволяют выполнить анализ покрытия кода (Code Coverage) — процесс выявления неиспользуемых участков кода при помощи, например, многократного запуска программы.

Повторное использование это хорошо (Code Reuse is Good)

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

Разделение ответственности (Separation of Concerns, SoC)

Различные области функциональности должны управляться различными и минимально перекрывающимися модулями кода.

Обними изменения (Embrace Change)


Это заголовок книги Кента Бека, в которой рассматривается принципы экстремального программирования (XP) и методологии гибкой разработки (Agile) в целом. Многие другие принципы основаны на концепции, что вы должны ожидать и приветствовать изменения. На самом деле очень старые принципы разработки, вроде минимизации связанности, непосредственно предназначаются для более лёгкого изменения кода. Даже если вы не приверженец экстремального программирования этот подход к написанию кода не теряет смысла.

От переводчика: и ещё один очень важный принцип не упомянутый автором

В объектно-ориентированом дизайне обязателен SOLID: аббревиатура из первых букв названий приниципов, часть которых уже описана в этой статье:

Single responsibility principle Принцип единой разделения ответственности
Open/closed principle Принцип открытости/закрытости
Liskov Substitution Principle Принцип подстановки Лисков
Interface segregation principle Принцип изоляции интерфейса
Dependency Inversion Principle Принцип инверсии зависимостей

© Christopher Diggins

UPD
Посмотрите ещё на отличные мотиваторы с этими приницпами для программистов.

[Перевод| Анализ исходного кода с помощью Java 6 API

Вы когда-нибудь задумывались как инструменты вроде Checkstyle или FindBugs выполняют статический анализ кода, или как IDE такие как NetBeans или Eclipse выполняют быстрое исправление кода или находят ссылки на поле, объявленное в вашем коде? Во многих случаях, IDE имеют свои собственные API для разбора исходного кода и генерируют стандартную древовидную структуру, называемую aбстрактное синтаксическое дерево (AST) и разбирают дерево, которое может быть использовано для глубокого анализа элементов исходного кода. Хорошая новость: теперь возможно выполнять описанные задачи и многое другое с помощью трёх новых API введённых в Java как часть шестой редакции стандарта Java Standard Edition 6. Этими API, которые могут быть интересны разработчикам Java приложений которым нужно выполнять анализ исходного кода, являются Java Compiler API (JSR 199), Pluggable Annotation Processing API (JSR 269), и Compiler Tree API.

В этой статье мы рассмотрим возможности каждого из этих API и разработаем простое демо приложение, которое проверяет некие правила кодирования на Java на наборе исходных файлах переданных в него. Эта утилита также выводит сообщения о нарушения кодирования и местонахождение нарушения. Рассмотрим простой класс, который переопределяет (англ. overrides) метод equals() класса Object. Правило кодирования, которое проверяется, заключается в том, что каждый класс, который реализует метод equals() должен также переопределять метод hashCode() с правильной сигнатурой. Вы можете увидеть, что класс TestClass описанный ниже не объявляет метод hashCode(), хотя он имеет метод equals().

public class TestClass implements Serializable {
 
  int num;

  @Override
  public boolean equals(Object obj) {
        if (this == obj)
                return true;
        if ((obj == null) || (obj.getClass() != this.getClass()))
                return false;
        TestClass test = (TestClass) obj;
        return num == test.num;
  }
}

Давайте проанализируем этот класс как часть процесса компиляции с помощью этих трёх API.

Вызов компилятора программно из кода: Java Compiler API

Мы все используем утилиту командной строки javac для компиляции исходных файлов Java в class файлы. Так зачем же нам нужно API для компиляции Java файлов? Хорошо, ответ очень прост: как ясно из названия, это новое стандартное API позволяет нам вызывать компилятор наших собственных Java приложений; т.е., вы можете программировано взаимодействовать с компилятором и тем самым сделать компиляцию частью сервисов уровня приложения. Несколько типичных использований этого API перечислены ниже.

  • API компилятора помогает серверам приложений минимизировать время занимаемое развёртыванием (deploying) приложений. Например, избегая накладных расходов от использования внешнего компилятора для компилирования исходников сервлета сгенерированных из JSP страниц.
  • Инструменты разработки вроде IDE и анализаторы кода могут вызывать компилятор во время редактирования или утилиты сборки, которые значительно занимают время компиляции.

Классы Java компилятора расположены в пакете javax.tools. Класс ToolProvider из этого пакеты предоставляет метод getSystemJavaCompiler() который возвращает экземпляр определенного класса, который реализует интерфейс JavaCompiler. Этот экземпляр компилятора может быть создан для постановки компиляционных задач, которые будут выполнять саму компиляцию. Файлы исходного кода, которые нужно скомпилировать, должны быть переданы в компиляционную задачу. Для этого, API компилятора предоставляет абстракцию файлового менеджера JavaFileManager, который позволяет Java файлам быть полученными из различных источников, такие как файловая система, базы данных, память, и другие. В этом примере мы используем StandardFileManager, файл менеджер, основанный на java.io.File. Стандартный файловый менеджер может быть получен вызовом метода getStandardFileManager() из экземпляра JavaCompiler . Фрагмент кода для выполнения указанных шагов рассмотрен ниже:

  // Берём новый экземпляр Java компилятора
  JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  // Берём новый экземпляр реализации стандартного файлового менеджера
  StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
  // Получаем список файловых объектов java, в нашем случае мы имеем только один файл, TestClass.java
  Iterable<? extends JavaFileObject> compilationUnits1 = fileManager.getJavaFileObjectsFromFiles("TestClass.java");

Диагностический слушатель может быть опционально передан в метод getStandardFileManager() для выполнения диагностических отчётов о любых не фатальных проблемах. В этом фрагменте кода, мы передаём значения null, то есть мы не собираем диагностику из утилиты. Детали других параметров передаваемых в методы, вы можете узнать в Java 6 API. Метод getJavaFileObjectsfromFiles() класса StandardJavaFileManager возвращает все экземпляры StandardJavaFileManager, которые соответствуют предоставленным исходным Java файлам.

Следующим шагом будет постановка компиляционной задачи, которая может быть выполнена использованием метода getTask() класса JavaCompiler. В этом месте, компиляционная задача ещё не начинается. Задача может быть начата вызовом метода call() класса CompilationTask. Фрагмент кода для создания и запуска компиляционной задачи показан ниже.

  // Создаём задачу компиляции
  CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits1);
  // Выполняем задачу компиляции
  task.call();

Если, ошибок компиляции нет, то в папке назначения сгенерируется файл TestClass.class.

Обработка аннотаций: Подключаемое Annotation Processing API

Как мы все знаем, стандарт Java SE 5.0 внедрил поддержку для добавления и обработки метаданных или аннотаций к элементам вроде Java классов, полей, методов и прочего. Аннотации полностью обрабатываются сборочными утилитами или окружениями выполнения (runtime environments) для выполнения полезных задач наподобие управления поведением приложения, генерации кода, и.т.п.. Java 5 позволяет обрабатывать аннотированные данные, как во время компиляции, так и во время работы приложения. Обработчики аннотаций это утилиты, которые могут быть динамически подключены в компилятор для анализа исходных файлов и обработки аннотаций в них. Обработчики аннотаций могут полностью использовать метаданные для выполнения многих задач, включая и не ограничиваясь следующим:

  • Аннотации могут быть использованы для генерации дескрипторных файлов для развёртки приложений вроде persistence.xml или ejb-jar.xml, в случаях entity классов и enterprise beans соответственно.
  • Обработчики аннотаций могут использовать метаданные для генерации кода. Например, обработчик может генерировать интерфейсы Home и Remote для правильно аннотированного enterprise bean.
  • Аннотации могут быть использованы для проверки кода.

Стандарт Java 5.0 предоставил инструментарий обработки аннотаций (APT) и связанное зеркальное Reflection API (com.sun.mirror.*) для обработки аннотаций и моделирования обработанной информации. Инструмент APT запускает соответствующие обработчики аннотаций встречающихся в исходном файле. Зеркальное Reflection API предоставляет компиляционное, доступное только для чтения представление исходного файла. Главный недостаток APT в том, что он не стандартизирован; т.е. APT специфичен для Sun JDK.

Стандарт Java SE 6 ввёл новый инструментарий, называемый Pluggable Annotation Processing framework, который предоставляет стандартную поддержку для написания собственных обработчиков аннотаций. Он называется подключаемым («pluggable») потому что обработчик аннотации может быть подключен в javac динамически, и может оперировать набором аннотаций которые встречаются в исходном коде. Этот инструментарий (framework) разделён на две части: API для объявления и взаимодействия с обработчиками аннотаций — пакет javax.annotation.processing, и API для моделирования языка программирования Java — пакет javax.lang.model.

Написание собственного обработчика аннотаций

В следующей секции описано как создать собственный обработчик аннотаций и подключить его к компиляционной задаче. Обработчик аннотации наследуется от AbstractProcessor (который является реализацией интерфейса Processor) и переопределяет его метод process().

Класс обработчика аннотации будет задекорирован двумя аннотациями классового уровня, @SupportedAnnotationTypes и @SupportedSourceVersion. Аннотация SupportedSourceVersion определяет последнюю поддерживаемую версию обработчика аннотации. Аннотация @SupportedAnnotationTypes помечает в каких аннотациях этот обработчик заинтересован. Например, @SupportedAnnotationTypes ("javax.persistence.*") будет использован если обработчику нужно обрабатывать только аннотации Java Persistence API (JPA).

Стоит отметить, что обработчик аннотации вызывается даже когда не предоставлено аннотаций, если указана поддержка типов аннотаций как @SupportedAnnotationTypes("*"). Это позволяет нам использовать все возможности API моделирования вместе с Tree API для основных нужд обработки исходного кода. Используя эти API, можно получить много полезной информации касающейся модификаторов, полей, методов, и всего остального.

Фрагмент кода обработчика аннотации дан ниже:

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("*")
public class CodeAnalyzerProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    for (Element e : roundEnvironment.getRootElements()) {
      System.out.println("Element is " + e.getSimpleName());
      // Напишите здесь код для анализа каждого корневого элемента
    }
    return true;
  }
}

Обработчики аннотаций вызываются на те аннотации на которые они настроены. Обработка аннотаций может проходить в несколько раундов. Например, в первом раунде, будут обработаны первоначальные исходные Java файлы; во втором раунде, будут учтены файлы сгенерированные во время первого этапа обработки, и так далее. Обработчик должен переопределять метод process() класса AbstractProcessor. Этот метод принимает два аргумента:

  1. Набор типов/элементов/аннотаций найденных в исходном файле.
  2. RoundEnvironment который инкапсулирует информацию о текущем раунде обработки аннотации.

Если обработчик поддерживает тип аннотации, то метод process() возвращает true и другие обработчики не будут вызваны для этих аннотаций. Иначе, метод метод process() возвращает значение false и будет вызван следующий обработчик, если есть.

Подключение обработчика аннотаций

Теперь, когда наш обработчик аннотации готов к использованию, давайте посмотрим, как вызвать этот обработчик как часть процесса компиляции. Обработчик может быть вызван либо из утилиты командной строки javac либо программно через специальный Java класс. Утилита javac из Java SE 6 предоставляет опцию -processor которая принимает полное имя (fully qualified name) обработчика аннотаций который нужно подключить. Синтаксис для этого следующий:

javac -processor demo.codeanalyzer.CodeAnalyzerProcessor TestClass.java

где CodeAnalyzerProcessor это класс обработчика аннотации и TestClass это входящий для обработки Java файл. Эта утилита ищет CodeAnalyzerProcessor в classpath; т.е., нужно поместить этот класс в classpath.

Модифицированный фрагмент кода для подключения обработчика программно показан ниже. Метод setProcessors() класса CompilationTask позволяет подключить в задачу компиляции множество обработчиков аннотаций. Этот метод нужно вызвать перед методом call(). Также помните, что если обработчик аннотаций подключен к задаче компиляции, то сперва будет выполнена обработка аннотации, а только потом задача компиляции. Стоит отметить, что обработка аннотаций не произойдет если код содержит ошибки компиляции.

  CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits1);
  // Создаём список обработчиков аннотаций
  LinkedList<AbstractProcessor> processors = new LinkedList<AbstractProcessor>();
  // Добавляем обработчик аннотаций в список
  processors.add(new CodeAnalyzerProcessor());
  // Передаём список обработчиков аннотаций в компиляционную задачу
  task.setProcessors(processors);
  // Выполняем компиляционную задачу
  task.call();

Если мы выполним этот код, то обработчик аннотации во время компиляции TestClass.java будет печатать его название «TestClass».

Работа с AST: The Compiler Tree API

Aбстрактное синтаксическое дерево (AST) это доступное только для чтения представление исходного кода как дерево узлов, где каждый узел представляет языковую конструкцию Java. Например, Java класс представлен как ClassTree, объявления методов представлены как MethodTrees, объявления переменных как VariableTrees, аннотации как AnnotationTree и т.д..

Compiler Tree API предоставляет доступ к AST исходного кода Java и также предоставляет несколько утилит вроде TreeVisitors, TreeScanners, и т.д., для выполнения операций над AST. Более глубокий анализ исходного кода может быть выполнен используя TreeVisitor, который обходит все дочерние узлы дерева для извлечения необходимой информации о полях, методах, аннотациях, и других элементах класса. Посетители дерева реализованы в стиле шаблона проектирования Посетитель (Visitor). Когда Посетитель передаётся в метод accept дерева, то будет вызван метод visitXYZ который более подходит к этому дереву.

Java Compiler Tree API предоставляет три реализации TreeVisitor: SimpleTreeVisitor, TreeScanner, и TreePathScanner который и используется в нашем демо приложении. TreePathScanner это TreeVisitor который обходит все дочерние узлы дерева и предоставляет возможность для поддержки путей к родительским узлам. Для сканирования дерева нужно вызывать его метод scan(). Для посещения узлов необходимого типа, просто переопределите соответствующий visitXYZ метод. Внутри вашего посещающего метода вызовете super.visitXYZ для посещения дочерних узлов. Фрагмент кода полного класса Посетителя показан ниже:

  public class CodeAnalyzerTreeVisitor extends TreePathScanner<Object, Trees>  {

    @Override
    public Object visitClass(ClassTree classTree, Trees trees) {
      // ...какие нибудь действия
      return super.visitClass(classTree, trees);
    }

    @Override
    public Object visitMethod(MethodTree methodTree, Trees trees) {
      // ...какие нибудь действия
      return super.visitMethod(methodTree, trees);
    }

  } 

Как видите, методы-посетители принимают два аргумента: дерево отображающее узлы (ClassTree для узлов классов, MethodTree для узлов методов, и т.д.) и объект Trees. Класс Trees предоставляет утилитные методы для извлечения информации о пути элементов в дереве. Важно помнить, что объект Trees выступает мостом между JSR 269 и Compiler Tree API. В этом примере, есть только один корневой элемент, т.е. сам TestClass.

  CodeAnalyzerTreeVisitor visitor = new CodeAnalyzerTreeVisitor();

  @Override
  public void init(ProcessingEnvironment pe) {
    super.init(pe);
    trees = Trees.instance(pe);
  }
  for (Element e : roundEnvironment.getRootElements()) {
    TreePath tp = trees.getPath(e);
    // Вызов сканера
    visitor.scan(tp, trees);
  }

Следующая секция показывает получение информации исходного кода используя Tree API и вводит общую модель используемую для проверки кода далее. Метод visitClass() вызывается когда класс, интерфейс или перечисляемый тип (enum) посещается из AST с аргументом ClassTrees. Точно также метод visitMethod() вызывается для всех методов с аргументом MethodTree, visitVariable() для всех переменных с аргументом VariableTree, и т.д..

  @Override
  public Object visitClass(ClassTree classTree, Trees trees) {
    // Сохранение деталей посещяемого класса в модель
    JavaClassInfo clazzInfo = new JavaClassInfo();
    // Получаем текущий путь узла
    TreePath path = getCurrentPath();
    // Берём тип элемента соответствующего классу
    TypeElement e = (TypeElement) trees.getElement(path);
    // Устанавливаем полное определёное имя класса в модель
    clazzInfo.setName(e.getQualifiedName().toString());
    // Устанавливаем предка от которого унаследован класс 
    clazzInfo.setNameOfSuperClass(e.getSuperclass().toString());
    // Устанавливаем интерфейсы которые класс реализовывает
    for (TypeMirror mirror : e.getInterfaces()) {
      clazzInfo.addNameOfInterface(mirror.toString());
    }
    return super.visitClass(classTree, trees);
  }

Класс JavaClassInfo используемый в этом коде это наша модель для хранения информации о исходном коде. После выполнения этого кода, информация касающаяся класса вроде полного имени класса (fully qualified name), имени родительского класса, интерфейсов реализуемых TestClass, и т.д., будет извлечена и сохранена в нашей собственной модели для будущих проверочных нужд.

Установление местонахождения токенов в исходном коде

Итак, мы пока занимались получением информации о различных узлах в AST и созданию объектной модели для информации о классах, методах и полях.
С этой информацией, мы можем проверить следует ли исходный код хорошим программистским практикам, соответствует ли он спецификациям, и другое. Эта информация может быть очень полезной для проверяющих инструментов вроде Checkstyle или FindBugs, но они также требуют детали о местонахождении токенов (token) которые нарушили правила, потому что они предоставляют детали о местонахождении пользователям.

Объект SourcePositions, который является частью Compiler Tree API, предоставляет позиции всех AST узлов. Этот объект содержит полезную информацию о позициях начала и конца всех ClassTree, MethodTree, FieldTree, и т.д., в файле. Позиция определена как простая позиция символа (offset, сдвиг) с начала компилированного модуля (CompilationUnit) где первый символ занимает офсет 0. Фрагмент кода ниже показывает как можно получить офсетную позицию символа переданного объекта Tree с начала компилируемого модуля.

  public static LocationInfo getLocationInfo(Trees trees, TreePath path, Tree tree) {
    LocationInfo locationInfo = new LocationInfo();
    SourcePositions sourcePosition = trees.getSourcePositions();
    long startPosition = sourcePosition.getStartPosition(path.getCompilationUnit(), tree);
    locationInfo.setStartOffset((int)startPosition);
    return locationInfo;
  }

Однако, если нам нужно получить позицию token который даст имя самого класса или метода, тогда этого будет не достаточно. Чтобы найти нужную позицию из компилируемого модуля, одним из решений будет найти токены из символьного содержимого исходного файла. Мы можем получить символьное содержимое из JavaFileObject соответствующего компилируемого модуля, как продемонстрировано ниже.

  // Получаем делево компилируемых модулей 
  CompilationUnitTree compileTree = treePath.getCompilationUnit();
  // Берём исходный файл java который будет обработан
  JavaFileObject file = compileTree.getSourceFile();
  // Извлекаем символьное содержимое файла в строку
  String javaFile = file.getCharContent(true).toString();
  // Конвертируем содержимое java файла в символьный буфер
  CharBuffer charBuffer = CharBuffer.wrap (javaFile.toCharArray()); 

Следующий фрагмент кода находит позицию имени класса из исходного кода. Используются классы java.util.regex.Pattern и java.util.regex.Matcher для получения первого вхождения токена совпадающего с именем класса в символьном буфере, начиная с позиции начала дерева класса из дерева компилируемого модуля.

  LocationInfo clazzNameLoc = (LocationInfo) clazzInfo.getLocationInfo();
  int startIndex = clazzNameLoc.getStartOffset();
  int endIndex = -1;
  if (startIndex >= 0) {
    String strToSearch = buffer.subSequence(startIndex, 
    buffer.length()).toString();
    Pattern p = Pattern.compile(clazzName);
    Matcher matcher = p.matcher(strToSearch);
    matcher.find();
    startIndex = matcher.start() + startIndex;
    endIndex = startIndex + clazzName.length();
  } 
  clazzNameLoc.setStartOffset(startIndex);
  clazzNameLoc.setEndOffset(endIndex);
  clazzNameLoc.setLineNumber(compileTree.getLineMap().getLineNumber(startIndex));

Класс LineMap из Complier Tree API предоставляет карту символьных позиций и номеров строк из CompilationUnitTree. Мы можем получить номер строки нужного токена передав позицию начала в метод getLineMap() дерева CompilationUnitTree.

Проверка исходного кода на соответствие правилам

Теперь, когда мы полностью получили необходимую информацию из AST, следующая задача проверить что исходный код удовлетворяет определённым стандартам кодирования. Правила кодирования настроены в XML файле и управляются через наш класс называемые RuleEngine. Этот класс берёт правила из XML файла и применяет их одно за другим. Если класс не удовлетворяет правилу, RuleEngine возвращает список объектов ErrorDescription. Класс ErrorDescription инкапсулирует сообщения об ошибках и местонахождение ошибок в исходном коде.

  ClassFile clazzInfo = ClassModelMap.getInstance().getClassInfo(className);
  for (JavaCodeRule rule : getRules()) {
    // Применяем правила один за одним 
    Collection<ErrorDescription> problems = rule.execute(clazzInfo);
    if (problems != null) {
      problemsFound.addAll(problems);
    }
  }

Каждое правило реализовано как Java класс; модельная информация класса для проверки передаётся в этот класс правила. Класс правила инкапсулирует логику проверки используя модельную информацию. Реализация простого правила (OverrideEqualsHashCode) приведена ниже. Это правило проверят что класс, который переопределяет метод equal(), должен также переопределять метод hashCode(). Здесь мы итерируем по методам класса и проверяем, следуют ли они контракту по equals() и hashCode(). В TestClass, метод hashCode() отсутствует хотя есть метод equals(), в результате чего правило возвращает объект ErrorDescription содержащий соответствующее сообщение об ошибке и местонахождение ошибки.

  public class OverrideEqualsHashCode extends JavaClassRule {

    @Override
    protected Collection<ErrorDescription> apply(ClassFile clazzInfo) {
      boolean hasEquals = false;
      boolean hasHashCode = false;
      Location errorLoc = null;
      for (Method method : clazzInfo.getMethods()) {
        String methodName = method.getName();
        ArrayList paramList = (ArrayList) method.getParameters();
        if ("equals".equals(methodName) && paramList.size() == 1) {
          if ("java.lang.Object".equals(paramList.get(0))) {
            hasEquals = true;
            errorLoc = method.getLocationInfo();
          }
        } else if ("hashCode".equals(methodName) && method.getParameters().size() == 0) {
          hasHashCode = true;
        }
      }
      if (hasEquals) {
        if (hasHashCode) {
          return null;
        } else {
          StringBuffer errrMsg = new StringBuffer();
          errrMsg.append(CodeAnalyzerUtil.getSimpleNameFromQualifiedName(clazzInfo.getName()));
          errrMsg.append(" : The class that overrides equals() should ");
          errrMsg.append("override hashcode()");
          Collection<ErrorDescription> errorList = new ArrayList<ErrorDescription>();
          errorList.add(setErrorDetails(errrMsg.toString(), errorLoc));
          return errorList;
        }
      }
    return null;
  }

}

Запуск демонстрационного приложения

Вы можете скачать бинарный файл этого демо приложения из секции Ресурсы. Сохраните этот файл в любую локальную папку. Используйте следующую команду для запуска приложения из командной строки:

Заключение

Эта статья рассказывает как новое Java 6 API может быть использовано для вызова компилятора из Java кода и как разбирать и анализировать исходный код используя подключаемые обработчики аннотаций и Посетители (visitors) деревьев. Используя стандартное API вместо специфичной для IDE логики разбора/анализа делает возможным переиспользовать код в разных инструментах и окружениях. Мы только поверхностно очертили здесь эти три компиляционные API; вы можете найти много более полезных возможностей покопавшись глубже в них.

Исходный код демонстрационного приложения также расположен на GitHub

© Seema Richard.