Распутываем легаси-код на Android проекте

6c7642e0a203f725c1d26a3d83b9676d

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

Приложение может тормозить, состояние определяться десятками мутабельных переменных. Фризы, утечки памяти, файлы на сотни, а то и тысячи строк кода. Год-обжекты. Знакомо?

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

1. Логирование изменений в базе данных

Все ORM поддерживают возможность логирования запросов. В Room это можно сделать так:

Room.databaseBuilder(context, AppDatabase::class.java, "mydb.db")
            .setQueryCallback(
                { sqlQuery, bindArgs ->
                    Log.wtf("my_tag", "query: $sqlQuery args: $bindArgs")
                },
                Executors.newSingleThreadExecutor()
            )
            .build()

Что это даёт? Вы увидите, кто, когда и с какой частотой пишет в базу. На одном из проектов я столкнулся с ситуацией, когда при запуске приложения заново заполнялись десятки таблиц — совершенно ненужная операция, которая «исторически сложилась» и заметно тормозила старт. Логи помогли это обнаружить и устранить.

2. Логирование изменений в SharedPreferences

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

Логирование изменений поможет выявить подобные проблемы:

val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
     Log.wtf("my_tag", "key: $key value: ${prefs.all[key]}")       
}
prefs.appPreferences.registerOnSharedPreferenceChangeListener(listener)

Не забывайте отписываться от изменений, если они больше не нужны:

prefs.appPreferences.unregisterOnSharedPreferenceChangeListener(listener)

3. Логирование стектрейса вызова


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

object AppLogger {

    private const val DEFAULT_TAG = "AppLog"

    fun logStack(message: String? = null, tag: String = DEFAULT_TAG) {
        val threadName = "Call on Thread:  ${Thread.currentThread().name}\n"
        message?.run { Log.wtf(tag, this) }
        val stack = Thread.currentThread().stackTrace
            .filter { it.className.contains(this::class.java.name).not() }
            .filter { it.className.contains(LoggingProperty::class.java.name).not() }
            .filter { it.className !in listOf("dalvik.system.VMStack", "java.lang.Thread") }
            .joinToString(prefix = threadName, separator = "\n") { element ->
                "at ${element.className}.${element.methodName} (${element.fileName}:${element.lineNumber})"
            }
        Log.wtf(tag, stack)
    }
}

Эффект усиливается, если добавить логирование в геттер и сеттер мутабельной переменной:

var currentLocation: GeoPoint? = null
        get() {
            AppLogger.logStack("Get currentLocation $field")
            return field
        }
        set(value) {
            AppLogger.logStack("Set currentLocation value: $value field: $field")
            field = value
        }

Можно сделать ещё элегантнее с помощью делегата:

class LoggingProperty(private var value: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        AppLogger.logStack()
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        AppLogger.logStack()
        value = newValue
    }
}

Тогда переменная будет выглядеть так:

var currentLocation: GeoPoint? by LoggingProperty(null)

Такой подход сильно упрощает работу с кодом, где переменные меняются хаотично, а логика запутана. Эти три совета — мой проверенный арсенал для погружения в легаси-код. Они помогают быстро найти узкие места и начать приводить проект в порядок. Но я уверен, у вас есть свои практики! Делитесь ими в комментариях — будет интересно обсудить и, возможно, дополнить этот список.

© Habrahabr.ru