Как стать автором
Обновить

Автоматический Code Improvement при коммите в Android Studio

Время на прочтение 10 мин
Количество просмотров 3.8K

Работать с гитом в Android Studio, как известно, можно двумя способами.


Первый способ классический — использовать консоль. Плюсом такого подхода являются в первую очередь надежность. GUI может залагать и например, зависнуть на этапе rebase так, что abort/continue/переключение на другую ветку не поможет. Консольные команды вам в помощь, они всегда безотказны.


Второй способ — использовать GUI, предоставляемый Android Studio. Плюсы очевидны — меньший порог входа, более понятно, что вообще можно делать. А также есть различные плюшки от самой студии для удобства работы с гитом. Об одном из них и пойдет речь. Кстати, используя GUI, тоже можно оставлять в покое мышку и использовать hotkey


Используемые Hotkey в статье


Shift+Shift (двойное нажатие shift) — Окно поиска. Позволяет искать как код/ресурсы, так и различные действия и настройки.


Ctrl+Alt+L (⌘+⌥+L) → Форматирование кода


Shift+Ctrl+Alt+L (⇧+⌘+⌥+L) → Форматирование кода → Форматирование кода с параметрами.


вызывает данное окно

image


О чем вообще речь и где найти?


Неактуальные импорты, пустые строки, пробелы вместо табов — эти вещи бесят, если они попадают в готовый код, предназначенный для pull request. Исправить это легко, используя hotkey Ctrl+Alt+L, однако часто мы забываем это делать.
Idea/AndroidStudio позволяет проводить эти действия автоматически, в последний момент перед коммитом


При использовании commit'а через Android Studio можно увидеть такого вида окно:


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


Чем именно мне помогут эти галочки?


✓ Reformat code


Данное улучшение приводит код в соответствии с вашим code style. Действие полностью аналогично ctrl+shift+alt+L с включенным clean up.


Настроить code style можно здесь Settings → Editor → Code Style


Tab and Indents

Применяет отступы строк в соответствии с code style (Подробно в Code Style, в одноименном разделе)


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

До коммита


class CleanTab(context: Context) {

....val date = Date()

....val button = Button(context)
....val textView = TextView(context)
....val ratingBar = RatingBar(context)
....val imageView = ImageView(context)
}

После коммита


class CleanTab(context: Context) {

-> val date = Date()

-> val button = Button(context)
-> val textView = TextView(context)
-> val ratingBar = RatingBar(context)
-> val imageView = ImageView(context)
}

Spaces

Форматирует все пробелы в коде в соответствии с code-style.


Будут удалены лишние пробелы в конструкторе Date, добавлены пробелы между объявлением переменной button и =, а также между списком аргументов args в main и {

До коммита


class CleanSpaces(context:Context) {

    val date = Date( )

    val button= Button(context)

    fun main(arg: Args){

    }
}

После коммита


class CleanSpaces(context: Context) {

    val date = Date()

    val button = Button(context)

    fun main(arg: Args) {

    }
}

Wrapping and Braces

Этот блок управляет переносом строк. Переносятся как сами строки кода, так и закрывающие/открывающие скобки.


Строчка else, а также строчка catch должны располагаться на той же линии, что и закрывающая скобка (так настроено в code style)
Общее количество символов на строке с функцией manyArguments вышло за 80 знаков(настроено в code style), поэтому аргументы будут перенесены на новую строку

До коммита


class CleanWrappingAndBraces {

    fun condition() {
        if (liveData != null) {
            <..>
        }
        else {
            <..>
        }
    }

    fun catching() {
        try {
            <..>
        }
        catch (e: Exception) {
            <..>
        }
    }

    fun manyArguments(userId: Int, issuerCountryId: String, sendingCountryId: String, receivingCountryId: String) {

    }
}

После коммита


class CleanWrappingAndBraces {

    fun condition() {
        if (liveData != null) {
            <..>
        } else {
            <..>
        }
    }

    fun catching() {
        try {
            <..>
        } catch (e: Exception) {
            <..>
        }
    }

    fun manyArguments(userId: Int, issuerCountryId: String,
                    sendingCountryId: String,
                    receivingCountryId: String) {

}

Blank Lines

Регулирует максимальное и минимальное количество пустых строчек в коде. (Подробно в Code Style, в одноименном разделе)


В данном примере удалит лишние строки от последней функции до }, а также 2 лишних пустых строчки между функциями

До коммита


class CleanBlank {

    fun foo() {

    }

    fun bar() {

    }

}

После коммита


class CleanBlank {

    fun foo() {

    }

    fun bar() {

    }

}

Rearrange code


Располагает атрибуты и элементы в том порядке, который соответствует код-стайлу. Работает в XML и HTML файлах. Настроить расположение можно в Setting → Editor → CodeStyle → XML/HTML → Arrangement


Внимание! неправильная настройка может привести к печальным последствиям. Rearrange может влиять на очередность элементов, что может привести к неправильной работе в файлах, чувствительных к расположению. Пример: в LinearLayout, при сортировке по алфавитному порядку, элемент Button может переместится выше TextView, хотя по дизайну такого быть не должно. Выход — настраивать расположение ТОЛЬКО атрибутов

Сортировка
В данном примере xmlns расположились в трех первых строчках и строго по порядку, определенному в code-style(xmlns:android на первой строчке, остальные xmlns:<...> в алфавитном порядке.
Обратите внимание, xmlns:tools и xmlns:app не были удалены даже несмотря на то, что нет случаев их использования. Это говорит о том, что rearrange влияет только на расположение элементов

До коммита


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:tools="http://schemas.android.com/tools" android:layout_height="match_parent"
    android:orientation="vertical" xmlns:app="http://schemas.android.com/apk/res-auto">

</LinearLayout>

После коммита


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

Сортировка
В данном примере блок с атрибутами android:padding изменит порядок в соответствии с code-style. Также атрибут style расположится сразу за android:id

До коммита


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="8dp"
        android:paddingBottom="8dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        style="@style/TextAppearance.MaterialComponents.Body1"
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World" />

</LinearLayout>

После коммита


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="8dp"
        android:paddingTop="8dp"
        android:paddingRight="8dp"
        android:paddingBottom="8dp"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/title"
        style="@style/TextAppearance.MaterialComponents.Body1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World" />

</LinearLayout>

Optimize import


Следит за чистотой блока импортов и приводит в правильный вид, а также проверяет на соответствие code style. Само действие полностью аналогично ctrl+shift+alt+L с включенным optimize import.


Удаление неиспользуемых импортов
В данном примере при optimize imports import java.util.* будет удален, так как нет полей или методов, нуждающихся в нем

До коммита


import android.content.Context
import android.widget.Button
import java.util.*

class RemoveUnused(context: Context) {

    val button = Button(context)
}

После коммита


import android.content.Context
import android.widget.Button

class RemoveUnused(context: Context) {

    val button = Button(context)
}

Объединение импортов

Если n или более классов импортируются из одного пакета, то такой импорт будет заменен импортом всего пакета.
Если m или более enum или java static импортируется из одного класса, то такой импорт будет заменен на импорт всего класса


Оба параметра задаются в Settings → Editor → Code Style → Kotlin → Imports


В данном примере множественные импорты из пакета android.widget будут заменены на android.widget.*

До коммита


import android.content.Context
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.RatingBar
import java.util.*

class MergeImport(context: Context) {

    <..>
}

После коммита


import android.content.Context
import android.widget.*
import java.util.*

class MergeImport(context: Context) {

    <..>
}

В том случае, если стоит настройка не объединять импорты(m = 0, n=0), объединенные импорты(импорт пакета) будут заменены импортом каждого класса/функции/поля отдельно.
В случае ниже import android.widget. и import java.util. будут упразднены

До коммита


import android.content.Context
import android.widget.*
import java.util.*

class MergeImport(context: Context) {

    <..>

После коммита


import android.content.Context
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.RatingBar
import java.util.*

class MergeImport(context: Context) {

    <..>
}

Расположение по алфавиту
После применения, все импорты отсортируются, и будут удобно располагаться в алфавитном порядке

До коммита


import android.content.Context
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import java.util.*
import android.widget.RatingBar

class SortImport(context: Context) {

    <..>
}

После коммита


import android.content.Context
import android.widget.Button
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import java.util.*

class SortImport(context: Context) {

    <..>
}

Perform code analysis


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


Пример кода с множествеными нарушениями
fun TextView.format(message: String) {
   val htmlText = Html.fromHtml(message)
}

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


При нажатии на Review покажется список найденных предупреждений:


Check TODO


Очень полезная галочка, помогает не забыть о новых TODO, которые были поставлены именно в этом коммите. Более того, студия позволяет отслеживать не только //TODO, но и //FIXME, а также любые собственные todo, которые были добавлены через Setting → Editor → TODO.


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


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


Clean up


Очень помогает держать код в чистоте. Удаляет избыточный код (см. примеры), заменяет deprecated функции. Действие полностью аналогично Actions → Code cleanup. Достаточно интересная функция, но вместе с этим может быть и опасной.


Из замеченных особенностей можно выделить две основные:


  • замена deprecated методов (подробнее ниже)
  • непонятную замену вызова методов view в презентере в одном из личных случаев

! Обязательно пересматривайте получившийся коммит, если используете данную функцию!

Redutant code
После применения удалятся элвис-операторы, т.к. были применены к non-nullable переменным

До коммита


data class User(val firstName: String, val secondName: String)

val user = User("Василий", "Пупкин")

fun getUserFullName(): String {
    val firstName = user?.firstName
    val secondName = user.secondName ?: return firstName

    return "$firstName $secondName"
}

После коммита


data class User(val firstName: String, val secondName: String)

val user = User("Василий", "Пупкин")

fun getUserFullName(): String {
    val firstName = user.firstName
    val secondName = user.secondName

    return "$firstName $secondName"
}

После применения удалится оператор public, т.к. конкретно в этом случае лишний

До коммита


data class User(val firstName: String, val secondName: String)

val user = User("Василий", "Пупкин")

public fun getUserFullName(): String {
    val firstName = user.firstName
    val secondName = user.secondName

    return "$firstName $secondName"
}

После коммита


data class User(val firstName: String, val secondName: String)

val user = User("Василий", "Пупкин")

fun getUserFullName(): String {
    val firstName = user.firstName
    val secondName = user.secondName

    return "$firstName $secondName"
}

После применения удалится вызов конструктора

До коммита


class ExecutionClass(val exec: () -> Unit)

val exec = ExecutionClass() {
    doIt()
}

После коммита


class ExecutionClass(val exec: () -> Unit)

val exec = ExecutionClass {
    doIt()
}

Deprecated код

Если в аннотации deprecated указан ReplaceWith(), то при cleanup старые методы и классы будут заменены в соответствии с этой пометкой. Надо быть очень внимательным с применением этой возможности, так как работает все очень банально — название старого метода меняется ровно на то, что было указано в ReplaceWith(). Анализатор не проверят даже существует ли такой код. Поэтому неправильно указание нового класса/метода может привести к невозможности компиляции кода. И даже если такая функция/класс существует, даже если у нее одинаковая сигнатура(что анализатор также НЕ проверяет), то это может привести к другой проблеме, необходимо было не просто заменить одну функцию на другую, но и изменить сценарий использования. Код в таком случае скомпилируется, но не будет правильно работать


Заменит старый метод и класс на новые

До коммита


@Deprecated("Будет удален", ReplaceWith("newMethod()"))
fun oldMethod() = Unit

fun newMethod() = Unit

@Deprecated("Будет удален", ReplaceWith("NewClass"))
class OldClass

class NewClass

class GetDeprecatedCodeUseCase(val clazz: OldClass) {

    init {
        val initData = oldMethod()
    }
}

После коммита


@Deprecated("Будет удален", ReplaceWith("newMethod()"))
fun oldMethod() = Unit

fun newMethod() = Unit

@Deprecated("Будет удален", ReplaceWith("NewClass"))
class OldClass

class NewClass

class GetDeprecatedCodeUseCase(val clazz: NewClass) {

    init {
        val initData = newMethod()
    }
}

Выводы и рекомендация


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


Из приведенных выше примеров можно понять несколько моментов:


Optimize import и Reformat code можно использовать безбоязненно. Они влияют только на форматирование и явные огрехи.


Rearrange и Clean up необходимо использовать аккуратно. Да, они в целом могут хорошо помочь и выловить уже что-то посерьезнее, но при неправильной настройке (Rearrange) или неоднозначном коде (Clean up) помощники могут сильно ошибиться


Check TODO и Perform code analysis также можно использовать безбоязненно. Они никаким образом не влияют на код, только дают назойливые подсказки. Да, если у вас в проекте все полностью построено на TODO и Deprecated коде, то отбоя от них не будет, и они больше будут мозолить глаза. Но если у вас в проекте достаточно чистый код и такие моменты стараетесь минимизировать, то помощники будут давать отличный шанс пересмотреть код, где вы могли допустить упущение.

Теги:
Хабы:
+3
Комментарии 0
Комментарии Комментировать

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн