Как пользоваться кодом android

Редактор кода на Android: часть 1

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

Вступление

Привет всем! Судя из названия вполне понятно о чем будет идти речь, но всё же я должен вставить свои пару слов перед тем как перейти к коду.

Я решил разделить статью на 2 части, в первой мы поэтапно напишем оптимизированную подсветку синтаксиса и нумерацию строк, а во второй добавим автодополнение кода и подсветку ошибок.

Для начала составим список того, что наш редактор должен уметь:

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

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

MVP — простой текстовый редактор

На данном этапе проблем возникнуть не должно — растягиваем EditText на весь экран, указываем gravity , прозрачный background чтобы убрать полосу снизу, размер шрифта, цвет текста и т.д. Я люблю начинать с визуальной части, так мне становится проще понять чего не хватает в приложении, и над какими деталями ещё стоит поработать.

На этом этапе я так же сделал загрузку/сохранение файлов в память. Код приводить не буду, в интернете переизбыток примеров работы с файлами.

Подсветка синтаксиса

Как только мы ознакомились с требованиями к редактору, пора переходить к самому интересному.

Очевидно, чтобы контролировать весь процесс — реагировать на ввод, отрисовывать номера строк, нам придется писать CustomView наследуясь от EditText . Накидываем TextWatcher чтобы слушать изменения в тексте и переопределяем метод afterTextChanged , в котором и будем вызывать метод отвечающий за подсветку:

Q: Почему мы используем TextWatcher как переменную, ведь можно реализовать интерфейс прямо в классе?
A: Так уж получилось, что у TextWatcher есть метод который конфликтует c уже существующим методом у TextView :

Оба этих метода имеют одинаковое название и одинаковые аргументы, да и смысл вроде у них тот же, но проблема в том что метод onTextChanged у TextView вызовется вместе с onTextChanged у TextWatcher . Если проставить логи в тело метода, то увидим что onTextChanged вызовется дважды:

Это очень критично если мы планируем добавлять функционал Undo/Redo. Также нам может понадобится момент, в котором не будут работать слушатели, в котором мы сможем очищать стэк с изменениями текста. Мы ведь не хотим, чтобы после открытия нового файла можно было нажать Undo и получить совершенно другой текст. Хоть об Undo/Redo в этой статье говориться не будет, важно учитывать этот момент.

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

Но вернёмся к подсветке.

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

Сейчас нам важно знать только две вещи:

  1. Pattern определяет что конкретно нам нужно найти в тексте
  2. Matcher будет пробегать по всему тексту в попытках найти то, что мы указали в Pattern

Может не совсем корректно описал, но принцип работы такой.

Т.к я пишу редактор для JavaScript, вот небольшой паттерн с ключевыми словами языка:

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

Далее с помощью Matcher мы пройдёмся по всему тексту и установим спаны:

Поясню: мы получаем объект Matcher у Pattern, и указываем ему область для поиска в символах (Соответственно с 0 по text.length это весь текст). Далее вызов matcher.find() вернёт true если в тексте было найдено совпадение, а с помощью вызовов matcher.start() и matcher.end() мы получим позиции начала и конца совпадения в тексте. Зная эти данные, мы можем использовать метод setSpan для раскраски определённых участков текста.

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

Итак, запускаем!

Дело в том что метод setSpan работает медленно, сильно нагружая UI Thread, а учитывая что метод afterTextChanged вызывается после каждого введенного символа, писать код становится одним мучением.

Поиск решения

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

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

Точно! Так и сделаем! Вот только… как?

Оптимизация

Хоть я и упомянул что нас заботит только производительность метода setSpan , всё же рекомендую выносить работу RegEx в фоновой поток чтобы добиться максимальной плавности.

Нам нужен класс, который будет в фоне обрабатывать весь текст и возвращать список спанов.
Конкретной реализации приводить не буду, но если кому интересно то я использую AsyncTask работающий на ThreadPoolExecutor . (Да-да, AsyncTask в 2020)

Нам главное, чтобы выполнялась такая логика:

  1. В beforeTextChanged останавливаем Task который парсит текст
  2. В afterTextChanged запускаем Task который парсит текст
  3. По окончанию своей работы, Task должен вернуть список спанов в TextProcessor , который в свою очередь подсветит только видимую часть

И да, спаны тоже будем писать свои собственные:

Таким образом, код редактора превращается в нечто подобное:

Читайте также:  Native methods in android

Т.к конкретной реализации обработки в фоне я не показал, представим что мы написали некий JavaScriptStyler , который в фоне будет делать всё тоже самое что мы делали до этого в UI Thread — пробегать по всему тексту в поисках совпадений и заполнять список спанов, а в конце своей работы вернёт результат в setSpansCallback . В этот момент запустится метод updateSyntaxHighlighting , который пройдётся по списку спанов и отобразит только те, что видны в данный момент на экране.

Как понять, какой текст попадает в видимую область?

Буду ссылаться на эту статью, там автор предлагает использовать примерно такой способ:

И он работает! Теперь вынесем topVisibleLine и bottomVisibleLine в отдельные методы и добавим пару дополнительных проверок, на случай если что-то пойдёт не так:

Последнее что остаётся сделать — пройтись по полученному списку спанов и раскрасить текст:

Не пугайтесь страшного if ‘а, он всего лишь проверяет попадает ли спан из списка в видимую область.

Ну что, работает?

Работает, вот только при редактировании текста спаны не обновляются, исправить ситуацию можно очистив текст от всех спанов перед наложением новых:

Ещё один косяк — после закрытия клавиатуры кусок текста остаётся неподсвеченным, исправляем:

Главное не забыть указать adjustResize в манифесте.

Скроллинг

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

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

Достаточно вызывать метод отвечающий за обновление подсветки:

Нумерация строк

Если мы добавим в разметку ещё один TextView то будет проблематично их между собой связать (например, синхронно обновлять размер текста), да и если у нас большой файл то придется полностью обновлять текст с номерами после каждой введенной буквы, что не очень круто. Поэтому будем использовать стандартные средства любой CustomView — рисование на Canvas в onDraw , это и быстро, и не сложно.

Для начала определим что будем рисовать:

  • Номера строк
  • Вертикальную линию, отделяющую поле ввода от номеров строк

Предварительно необходимо вычислить и установить padding слева от редактора, чтобы не было конфликтов с напечатанным текстом.

Для этого напишем функцию, которая будет обновлять отступ перед отрисовкой:

Для начала мы узнаем кол-во строк в EditText (не путать с кол-вом » \n » в тексте), и берем кол-во символов от этого числа. Например, если у нас 100 строк, то переменная gutterDigitCount будет равна 3, потому что в числе 100 ровно 3 символа. Но допустим, у нас всего 1 строка — а значит отступ в 1 символ будет визуально казаться маленьким, и для этого мы используем переменную count , чтобы задать минимально отображаемый отступ в 3 символа, даже если у нас меньше 100 строк кода.

Эта часть была самая запутанная из всех, но если вдумчиво прочитать несколько раз (поглядывая на код), то всё станет понятно.

Далее устанавливаем отступ предварительно вычислив widestNumber и widestWidth .

Приступим к рисованию

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

Ну а теперь можно приступать к рисованию, объявим переменные с типом Paint :

Где-нибудь в init блоке установим цвет текста и цвет разделителя. Важно помнить, что если вы поменяйте шрифт текста, то шрифт Paint ‘а придется применять вручную, для этого советую переопределить метод setTypeface . Аналогично и с размером текста.

После чего переопределяем метод onDraw :

Смотрим на результат

Выглядит круто.

Что же мы сделали в onDraw ? Перед вызовом super -метода мы обновили отступ, после чего отрисовали номера только в видимой области, ну и под конец провели вертикальную линию, визуально отделяющую нумерацию строк от редактора кода.

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

Заключение

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

Также оставлю ссылку на исходники моего редактора кода на GitHub, там вы найдёте не только те фичи о которых я рассказал в этой статье, но и много других которые остались без внимания.

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

Источник

Советы по чистому коду новичкам в Java/Android

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

За основу статьи взяты советы из классики “Роберт К. Мартин: Чистый код”. Отобрал из них те, которые наиболее часто встречались в виде проблем у студентов. Приведенные советы написаны с учетом моего опыта разработки коммерческих Android приложений. Поэтому не ко всем Android-проектам приведенные ниже советы подойдут, я уже не говорю про другие системы.

Советы в основном приводил с примерами кода как НЕ НУЖНО делать. Как ни странно, у большинства студентов были одни и те же ошибки. Все примеры кода были придуманы, любые совпадения с реально существующим кодом случайны.

Общие советы

1. Код должен быть легко читаемым, понятным и очевидным

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

Читайте также:  Android studio debugger не работает

2. При написании кода нужно придерживаться Java Code Conventions либо других спецификаций, принятых на проекте командой

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

Наименование

1. Имена классов, функций, переменных и параметров должны передавать закладываемый в них смысл.

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

Наименования классов MyActivity, CustomView, MyAdapter говорит только об одном, что это не часть Android SDK и все. SecondActivity говорит, что это не часть Android SDK и о наличии в проекте еще одного Activity, но не факт =)

Правильно именовать классы: MainActivity, FoodsActivity, FoodCartActivity, PhoneInputView . MainActivity — очевидно, что основной разводящий экран приложения. FoodsActivity — экран с перечнем продуктов. PhoneInputView — компонент ввода номера телефона.

Следующие названия переменных бесполезны и нарушают правила наименования:

как и параметры конструктора:

2. Название публичного класса и файла должно совпадать.

Есть следующий класс

Название файла при этом оказывается “products_Activity.java”.
Это не правильно. Название публичного класса и java-файла должны совпадать.

3. В наименованиях нужно использовать только буквы латинского алфавита, никаких цифр, символов подчеркивания и дефисов. Исключения составляют наименования из стандартов (ГОСТ, ISO), символы подчеркивания для разделения слов в наименованиях констант.

Примеры выше: m_textview_1 . Часто вместо lastName пишут userName2 , что не правильно.

4. Не нужно использовать строчные “L” и “O” в качестве имен локальных переменных, т.к. их трудно отличить от “1” и “0”.

Надуманный пример, но что-то подобное я встречал в своей практике

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

5. Не нужно указывать тип в суффиксе имен.

Вместо accountList нужно писать просто accounts . Это позволит в любое время изменить тип переменной без переименования самой переменной.
А еще ужаснее выглядит nameString, ageFloat .

Исключение составляют наследники классов Android SDK: Activity, Fragment, View, Uri и т.д. По названию NewsSynsService сразу понятно, что класс является «сервисом» и ответственен за синхронизацию новостей. Использование суффикса view в nameView, photoView позволяет легко отличить переменные, относящиеся к верстки, от остальных. Имена view обычно начинают с существительного. Но имена кнопок лучше начинать с глагола: buyButton

6. Имена могут и должны содержать термины из математики, названия алгоритмов, паттернов проектирования и т.д.

Увидев имя BitmapFactory , не автор кода сразу поймет смысл этого класса.

7. Не нужно указывать никакие префиксы при именовании.

Вместо m_user, mUser просто пишется user . Указывать префикс s для статических полей в современных IDE излишне.

Исходники Android SDK не являются здесь показателем в силу давности создания первых версий и наследования кодовой базы до наших дней.

s_ ни к чему в начале названия статического поля. К тому же название констант должно писаться прописными буквами:

8. В наименование классов нужно использовать существительные.

Классы это как объекты реального мира. Поэтому нужно использовать существительные для их названия: AccountsFragment, User, Car, CarModel .

Не нужно называть классы Manager, Processor, Data, Info , т.к. они имеют слишком общее значение. Лучше название класса длиной в два-четыре слова, чем просто Data .

9. Названия классов должны начинаться с прописной буквы.

Слова НЕ должны отделяться символом подчеркивания. Нужно следовать нотации CamelCase: GoodsFragment, BaseFragment

10. Используйте одно слово для каждой концепции.

Использование fetch, retrieve, get в одном классе сбивает с толку. Если класс назвали Customer , то имена переменных класса и параметров функций этого типа лучше называть customer , а не user .

Функции

1. Функция должна выполнять только одну “операцию”. Она должна выполнять ее хорошо. И ничего другого она делать не должна.

Под “операцией” следует понимать не одну математическую операцию или вызов другой функции, а действия на одном уровне абстракции. Чтобы определить, что функция выполняет более одной операции, следует попробовать извлечь из нее другую функцию. Если извлечение функции не дает ничего кроме простой перестановки кода, то значит разбивать дальше функцию не имеет смысла

К примеру есть функция setProducts класса ProductsAdapter :

Внутри функции происходит три основных операции: 1) фильтрация newProducts , 2) очистка и вставка новых значений в products , 3) обновление адаптера notifyDataSetChanged .

Фильтрация элементов newProducts должна происходить в другом методе и даже в другом в класса (например в presenter-е). В ProductsAdapter должны прийти уже отфильтрованные данные.

Лучше переделать код следующим образом:

Параметр newProducts содержит уже отфильтрованный список продуктов.
Можно еще строчки java products.clear(); products.addAll(newProducts);
вынести в отдельную функцию, но особого смысла я в этом не вижу. Лучше заменить notifyDataSetChanged на DiffUtil, это позволит обновлять ячейки в списке эффективнее

2. Размер функции должен быть 8-10 строк.

Функция размером в 3 экрана это не функция, это *****. У меня тоже не всегда получается ограничить размер функции 8-10 строками кода, но нужно стремится к этому. Пример по понятным причинам приводить не буду.

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

3. В теле функции все должно быть на одном уровне абстракции.

Вычисление значения локальной переменной errorMessage имеет более низкий уровень абстракции, чем остальной код внутри функции. Поэтому код java «Article » + article.getName() + » is incorrect» лучше вынести в отдельную функцию.

Читайте также:  Half life карты для андроид

4. Наименовании функции и передаваемых параметров должно сообщать о том, что делает функция

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

Непонятно по какому полю будет происходить поиск, что передается на вход функции.

Лучше переделать в следующий вид:

Название функции говорит, что происходит поиск Product по полю id . На вход функция принимает не “null” значение. Если Product не найдется, то вернется “null”.

Роберт К. Мартин советует использовать параметры в качестве части названия функции:

Вызов функции может выглядеть так:

На проектах я такой способ не встречал. Лучше данный способ не использовать и писать полностью название:

5. Имена функций должны начинаться с глагола. Лучше длинное имя, чем не содержательное короткое.

Название start не сообщает, что именно стартует функция. Только заглянув в тело становится понятно, что функция открывает Activity.

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

6. Вместо передачи в аргументы функции флага (boolean) лучше разбить функцию на две функции

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

7. Дублирующий код следует выносить в отдельную функцию.

Код внутри setOnClickListener отличается только стилем. Этот код стоит вынести в отдельный метод:

8. Не передавайте и не возвращайте из функции “null” когда можно вернуть пустой объект.

Это позволит избежать ошибок NullPointerexception и не нужно будет в местах вызова писать проверки на null.

Kotlin поможет отучиться от вредной привычки передавать и возвращать null.)

Форматирование

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

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

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

Данный совет может противоречить предыдущему. Выбрав приоритетный совет для команды, стоит придерживаться его на всем проекте.

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

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

4. Переменные экземпляра класса, статические константы должны быть все в начале класса в одном месте.

В начале класса объявляются публичные константы, затем приватные константы и после них идут приватные переменные.

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

  • в математических операциях для отделения символа операции от операндов: int x = a + b;
  • отделения параметров функций: private Article createNewArticle(String title, String description)
  • передаваемых параметров функции: createNewArticle(title, description)

6. «Магические цифры, строки» должны быть вынесены в константы .
Пожалуй, самый популярный совет, но все равно продолжают его нарушать.

Лишь прочитав постановку задачи я понял, для чего в коде использовалось число “10”. Лучше это число вынести в константу и дать осмысленное имя.

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

Классы

1. Класс должен иметь одну “ответственность”, одну причину для изменения.

К примеру, наследники класса RecyclerView.Adapter должны отвечать за создание и связывание View с данным. В нем не должен находится код сортировки/фильтрации списка элементов.
Часто в файл с Activity добавляют класс RecyclerView.Adapter, что является не правильным.

2. Не нужны пустые методы или просто вызывающий метод из родительского класса

3. Если переопределяете какой-то метод без вызова метода родительского, то проверьте, что так можно делать.

Загляните в исходники родительских классов, документации. Переопределяемые методы жизненного цикла Activity, Fragment, View должны обязательно должны вызывать методы родительского класса.

Есть аннотация @CallSuper , предупреждающая о необходимости вызывать родительский метод при переопределении.

Советы о разном

1. Используйте средства Android Studio для улучшение качества Вашего кода.

Если она подчеркивает или выделяет код, значит что-то может быть не так. В Android Studio есть средства Rearrange, Reformat, Extract Method, Inline Method, анализаторы кода, горячие клавиши и т.д.

2. Не нужно комментировать каждый метод, код должен быть самодокументированным.

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

3. Строки, цвета, размеры должны быть в ресурсах (xml)

Не нужно писать:

Исключения составляют файлы из папок test, mock. Такие файлы не должны попадать в релизную версию

4. Не нужно передавать context в RecyclerView.Adapter. context можно получить из любой view. Элемент по клику в RecyclerView.Adapter должен меняться через notifyItemChanged, а не непосредственным изменением вьюшек в методе обработки нажатия

В методе onClick непосредственно происходит вызов bindViewHolder , что НЕ правильно.

5. В RecyclerView.ViewHolder вместо определения позиции объекта в списке нужно вызывать getAdapterPosition, а не передавать position из метода onBindViewHolder

Правильно писать:

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

6. Используйте Quantity Strings (Plurals) для множественного числа. Вместо написания в коде логики по подбору правильного окончания слов.

7. Не сохраняйте большие данные Bitmap в bundle. Размер bundle ограничен

8. Если прописать “id” в xml, то Android сам восстановит состояние стандартных View при повороте.

Источник

Оцените статью