Попадая на новый проект с долгой историей, вас неизбежно ждёт легаси-код. Возможно, проект прошёл через несколько команд, и теперь он в ваших руках. Бывает, что на проекте уже нет контекст-овнера, а на любой вопрос тимлид отвечает: «Так исторически сложилось.»
Приложение может тормозить, состояние определяться десятками мутабельных переменных. Фризы, утечки памяти, файлы на сотни, а то и тысячи строк кода. Год-обжекты. Знакомо?
Я хочу дать несколько советов, которые помогут разобраться в происходящем и распутать спагетти-код.
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<T>(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)
Такой подход сильно упрощает работу с кодом, где переменные меняются хаотично, а логика запутана. Эти три совета — мой проверенный арсенал для погружения в легаси-код. Они помогают быстро найти узкие места и начать приводить проект в порядок. Но я уверен, у вас есть свои практики! Делитесь ими в комментариях — будет интересно обсудить и, возможно, дополнить этот список.
ссылка на оригинал статьи https://habr.com/ru/articles/895236/
Добавить комментарий