Dev Easy Notes
3.17K subscribers
123 photos
1 video
147 links
Работаю в IT уже 8 лет. Рассказываю про разработку простым языком. Полезность скрыта под тупыми шутками и слоем мата. Лучший underground канал про разработку, который вы сможете найти.

По сотрудничеству писать @haroncode
Download Telegram
Сначала разберем как может умереть Activity. Смерть Activity может произойти в двух случаях:

👉 Первый это когда что-то меняется в окружении, пользователь может заменить локаль, тему, ориентацию (телефона, а не свою), и т.д. В этом случае текущая Activity пересоздастся. Почему это сделано именно так? Так тупо проще, ведь нам нужно тянуть новые темы, ресурсы, картинки, а пересчитывать это все супер сложно.

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

Activity которые оказались в фоне просто умирают, у них даже onDestroy не вызывается, но вызываются методы onSavedInstantState. Когда мы возвращаемся назад в эти экраны, система их оживляет обратно. Все ViewModel продолжают жить, и все что мы сохранили в Bundle тоже.

Со смертью Activity все довольно просто. Каких-то движений с вашей стороны не нужно, всякие мелкие Id сохраняем в Bundle, все остальное во ViewModel и не паримся. Однако есть случай когда и ViewModel не спасет.

Есть такое понятие как процесс. Это некоторый объект системы, т.е штука которой система выдает ресурсы в виде времени процессора, памяти в оперативке и места на диске. Наше приложение работает в каком-то процессе (порой может даже в нескольких писал об этом тут). Процесс так же как и Activity может умереть.

Как это обычно происходит. Мы отправляем наше приложение в фон (сворачиваем) система через какое-то время думает ага, пользователь вот про это приложение забыл, освобожу ка я память. Берет и выгружает процесс. Что значит выгружает процесс? Просто все что было связано с этим приложением в оперативной памяти затирает. Затирает статику, затирает все ViewModel, вообще все что не сохраняется в Bundle.

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

А после у нас два путя.

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

👉 Второй, это когда пользователь успевает вернуться в приложение до того, как система прибьет его окончательно. И когда пользователь вернется в приложение, угадайте какая Activity появится первой? Правильно, та которую он видел последней. А все остальные, ну их как бы нет, типо вообще нет, ни ViewModel ничего, только то, что сохранено в Bundle. Если пользователь начнет переходить назад они будут снова возвращаться с того света, но уже без ViewModel и т.д.

Так почему нельзя сохранять что-то в статику? Причина в выгрузке процесса приложения из памяти. Представим что у вас есть Activity X и Y. В Activity X вы получаете какие-то данные с сети и сохраняете их в статику. Затем переходим в Activity Y которая уже использует эти данные.

Дальше просто наш второй кейс. Пользователь сворачивает приложение, система выгружает его их памяти. Потом пользователь решает вернуться в приложение и у нас открывается Activity Y, которая пытается пойти в статику и получить данные. А данных там нет, так как вся статика которую сохраняла Activity X была очищена, и мы в лучшем случае просто падаем и быстро узнаем о проблеме.
👍311
Одна из основных проблем индустрии это сложность. Сложность систем делает изменения дорогими для компании, а разрабов делает несчастными. Никому не хочется чинить баги в запутанном коде, а охото быстрее писать новые фичи. Естественно решением этой проблемы это сделать код проще, но как это сделать? Я думал про совет который бы давал четкие рекомендации, по тому как это сделать.

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

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

Все 3 не поместятся в один пост, поэтому я сделаю по каждой из этих концепций отдельный пост.
👍17🔥32
Чистые функции
🤔103👍1
Что такое чистая функция? По определению это функция без side effect. Side effect это когда функция делает то, что сразу не очевидно. Например функция проверки даты рождения которая еще шлет оповещение. В чистой функции результат зависит только от входных параметров и ничего больше. Чтобы понять, без заумных слов достаточно простого примера:

fun multi(x: Int, y: Int): Int = x * y

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

fun ticksElapsedFrom(time: Long): Int {
val now = System.currentTimeMillis()
return now - time
}

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

fun getCount() : Int {
count++
return count;
}

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

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

Самый простой способ это сделать – стараться всегда писать чистые функции. Разумеется всегда писать их не получится, у вас всегда будут функции которые оповещают остальные части системы, меняют состояния View, что-то логируют. Эти функции 100% не получится сделать чистыми.

Совет тут такой, что лучше разделять логику. Делайте отдельно чистые функции и отдельно функции которые меняют состояние системы. Если можно сделать вместо одной большой грязной функции несколько, то лучше сделать отдельно чистую и отдельно функцию с sife effect.
👍271
Иммутабельность
👍11
По названию понятно, что это что-то про неизменяемость. Просто запомните неизменяемость ваш лучший друг. Это значит – всегда предпочитайте неизменяемые коллекции вместо изменяемых (List вместо MutableList) и val вместо var. Изменяемость приводит к багам которые порой очень трудно отловить. Когда же объект полностью иммутабельный можно не парится что какие-то поля будут изменены без вашего ведома.

Начнем с базового примера:

data class SomeInfo(
var count: Int,
var someList: MutableList<Int>
)


Вроде бы обычный объект, что может пойти не так? Основная проблема этого объекта, что в любой момент можно заменить число, ссылку на список или сам список. Если вы передаете этот объект в какую-нибудь функцию, а потом еще куда-то, можно получить не консистентное состояние объекта. Как это может произойти: у вас было 3 числа в списке, и вы ожидаете что там будет 3 числа, а тут бац, а список пустой. Потому что какая-то функция не была чистой, она сначала сохранила ссылку на объект, а потом изменила состояние этого объекта.

Теперь рассмотрим тот же объект:


data class SomeInfo(
val count: Int,
val someList: List<Int>
)

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

Эту концепцию можно расширить не только на data class, но и в других классах бизнес логики. Если можно избавится от переменных нахер их. Все коллекции в data class делаем неизменяемыми, да и вообще все в data class лучше делать неизменяемыми. Изменяемыми коллекциями можно пользоваться только в рамках одного класса, а если отдаете список наружу, то уже отдавайте только неизменяемый.
👍211
Избегайте исключений для бизнес логики
👍13🤔51👎1
Объяснить концепцию можно через разницу в работе с исключениями м/у Java и Kotlin. В Java есть проверяемые и не проверяемые исключения. Когда используем проверяемые исключения компилятор заставляет их обрабатывать. Непроверяемые исключения как понятно из названия можно обрабатывать, можно нет компилятору пофиг. В Kotlin все исключения непроверяемые.

Творческое упражнение на подумоть. Как в Kotlin заставить пользователя функции заставить обрабатывать исключение на уровне компилятора? Вопрос кстати встречается на собесах!

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

Есть такой класс в стандартной библиотеки Kotlin – Result. Сам класс нельзя использовать как возвращаемый аргумент, т.к этот класс используется в корутинах. На сам класс пофиг, важна концепция. Если нужно заставить разработчика обработать ошибку, то вместо того, чтобы просто бросать исключение, мы можем возвращать Result. В одном случае в Result это нужный результат, во втором случае Result это класс с полем типа Thowable. Затем при помощи магии оператора when можно просто с ним работать. В таком случае у разраба не останется выбора кроме как проверить результат на success или fail.

Возможно вас заинтересовало а что не так с исключениями, поставьте пару сердец под постом и я сделаю отдельный про это.
53👍11
Что не так с исключениями?
🤔6👍1
Сперва я бы хотел уточнить что речь идет именно о бизнес логике. Кидать исключения при запросах, походах в базу данных или файловую систему это нормальная практика, потому как если упал запрос, или не оказалось нужного файла это действительно исключительная ситуация и исключения как раз созданы для этого.

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

Во-первых, они довольно дорогие по памяти и по скорости. Каждый раз когда вы создаете исключение, в момент создания собирается stack trace всех вызовов функций потока до самого метода run. Функция сбора stack trace нативная, а jni не супер быстрый механизм. Помимо этого stack trace может много занимать по памяти.

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

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

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

Суть проста, не делайте бизнес логику на исключениях. Бизнес логика должна быть по максимум состоять из чистых функций. Это то место из-за которого можно потенциально потерять деньги. Грязными могут быть ui слой, data слой, штуки связанные с DI, но бизнес логика должна быть чистой.
👍261
Принцип разделения запросов и команд (CQRS)
👍12🤔2
Есть принципы SOLID которые по своей сути сводятся к совету: "За все хорошее, против всего плохого". На самом деле SOLID это крутые советы, однако вокруг них собралось кучу мифов и фанатиков, которые уверены, что по-другому писать код запрещено. На мой взгляд основная проблема этих принципов в том, что они четко не говорят как именно писать код.

Есть принципы которые не так сильно распиарены, зато простые и четко говорят что делать. Один из них это Command-query separation principle (принцип разделения команд и запросов).

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

👉 Функции которые что-то возвращают, но при этом ничего не меняют – запросы.
👉 Функции которые что-то меняют, но ничего не возвращают – команды.

Этот принцип хорошо реализован во всех коллекциях. Вспомните List у него есть метод isEmpty который позволяет узнать пустой ли список или нет. Это по сути запрос, запрос у списка информации. Есть также метод add или set который уже меняют состояние списка, т.е является командой для списка.

Представьте если бы когда вызываешь метод isEmpty что-то менялось в состоянии списка? Жуть правда, однако в продакшен коде такое встречается довольно часто. Например, функция isValid которая при этом выводит ошибку на UI. Попробуйте сами использовать этот метод, начать просто, достаточно задать вопрос при создании функции: "А эта функция команда или запрос?".
👍40🤔1
Как эффективнее обучаться?
🔽
👍15
Формальное образование, вроде вуза и школы часто прививает установку, что можно научится чему либо только через боль, борьбу с собой и преодоление скуки. Из-за этого возникает ощущение, что начать практику можно только если прочитаешь кучу книг, выучишь все вопросы и пройдешь вон тот единственно верный курс. Спойлер – в обучении программированию это херня не работает.

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

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

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

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

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

Что же делать в этом случае? По мне так лучший совет – пообщаться с ребятами из индустрии. Стереотипных программистов социопатов почти не осталось и большая часть достаточно открытая к советам для начинающих. Общение с кем-то, кто уже работает в индустрии, может вам заменить кучу часов чтения книг.
👍26🔥61
Итак, академический мини пост. Как теорема де Моргана может помочь вам упростить код?
👍8🤔1
Довольно часто в коде приходится работать с условиями. Представьте такое условие:

if (!isEmpty && !hasProduct && !(!isFeatureEnabled || !userAuthorized)) {
doSmth()
} else {
doElse()
}

Какие проблемы у этого кода?

👉 Первое это огромное условие. Чтобы его прочитать нужно приложить довольно много усилий. В больших условиях довольно просто допустить ошибку, особенно если этот код писали не вы. Решить эту проблему можно просто вынеся это условие в отдельный метод или просто переменную:

val canWeDoSmth = !isEmpty && !hasProduct && (!isFeatureEnabled && !userAuthorized)

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

val canWeDoSmth = !(isEmpty || hasProduct || (isFeatureEnabled && userAuthorized))

Можете проверить логика осталась без изменений. Согласитесь такое условие читать намного проще, несмотря на то, что мы не избавились от всех отрицаний. Как такое получилось?

Есть такая теорема в дискретной математике, называемая теорема де Моргана. Определение у нее следующее: "Отрицание конъюнкции есть дизъюнкция отрицаний, а отрицание дизъюнкции есть конъюнкция отрицаний". Звучит страшно, однако концепция супер простая.

Конъюнкция это операция "И" (в коде &&), дизъюнкция – операция "ИЛИ" (в коде ||), ну и отрицание это операция "НЕ" (в коде !). Дальше все просто:

!(A && B) = !A || !B
!(A || B) = !A && !B

Ну а как раскрывать скобки в логических операциях, я думаю знаете. Если вдруг забыли, то операция "И" это умножение, операция "ИЛИ" это сложение, а дальше как в алгебре.

Вот и все, применить это правило на наше условие и мы получаем вместо кучи отрицаний всего одно. Простой метод который позволяет избавляться от лишних отрицаний, довольно эффективен в разработке. Если следующий раз когда будете писать условие вам покажется что оно получается сложным, вспомните про это метод.
👍401
Как система ловит ANR?
👍12
Недавно я задался вопросом, а каким образом система понимает, что приложение зависло? Для начала вспомним что такое ANR.

ANR (Application Not Responding) или до словно приложение не отвечает, вы возможно даже сами у себя на устройстве ловили такой диалог. Диалог этот системный, система его показывает в своем отдельном процессе и повлиять мы на него не можем.

В доке гугла написано, что система выкидывает ANR в двух случаях:
👉 приложение активно и UI поток или BroadcastReceiver не отвечает на события в течении 5 сек
👉 приложение в фоне то и BroadcastReceiver завис на примерно 60 сек

Каким же образом система понимает что приложение зависло и нужно показать диалог? Чтобы это понять нужно представлять как работает связка сущностей Looper + Handler + MessageQueue писал про это тут. Надеюсь вы прочитали или просто вспомнили, как эта штука работает. Дальше все просто у нас есть отдельный поток, который делает примерно следующее:

fun run() {
while(true) {
tick += ANR_TIMEOUT
uiHandler.post(specialRunnalbe)
thread.sleep(ANR_TIMEOUT)

if(tick != 0) {
showANR()
}
}
}

Бесконечный поток который постоянно отправляет в Looper главного потока specialRunnalbe. Этот Runnable обнуляет переменную tick. Если все хорошо и Looper главного потока не забит и нет блокирующих вызовов, то очередь до specialRunnalbe дойдет довольно быстро. Если же есть зависание, то Runnable не выполнится и система кинет ANR.
🔥17👍98
Всем привет!

Я планировал сделать этот пост когда на канале будет 100 подписчиков. Немного опоздал, так как это произошло быстрее, чем я рассчитывал🎉.

В этом посте ничего такого, просто хотел поблагодарить вас за подписку. Мне это безумно приятно ☺️, это мотивирует и дальше продолжать работу над каналом.

Канал понемногу развивается, я работаю над улучшением качества подачи, зарисовок и т.д.

Хотелось бы в паре слов рассказать что это за канал.

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

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

Ну и стандартное, буду очень признателен, если вы расскажете про этот канал своим друзьям/знакомым/коллегам. Всем мир🕊
👍46🔥94