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

По сотрудничеству писать @haroncode
Download Telegram
Принцип разделения запросов и команд (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
Фрагменты, фрагменты, фрагменты.
👍3
Скорее всего все и так уже примерно знают что такое фрагмент. Все слышали про историю с появлением планшетов, разделение UI и бла бла бла. Грубо говоря можно представить, что фрагмент это просто такая сложная View со своим состоянием и жизненным циклом очень похожим на жизненный цикл Activity.

Как и View фрагмент не может существовать сам по себе, он может существовать только в рамках Activity или другого фрагмента (который естесна живет в Activity). Жизненный цикл фрагмента сильно завязан на ЖЦ Activity, если у Activity был вызван onStop, значит и у всех фрагментов тоже был вызван onStop. Не может быть такого чтобы у Activity статус pause, а фрагмента stop все абсолютно синхронно.

У Activity может быть много фрагментов. Они могут отображаться на экране разделяя его пополам, могут быть наложены друг на друга, короче как угодно. В этом плане фрагменты гораздо гибче Activity которая может быть только одна на экране. Из-за такого удобства и возникают подходы вроде Single Activity или Ribs о которых я тоже скоро сделаю посты.

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

👉 Как правильно показать первый фрагмент?
👉 Как правильно передать данные в этот фрагмент?
👉 Почему так много методов чтобы закомитить транзакцию фрагмента?
👉 Что такое Back Stack и зачем про это знать?
👉 Что будет если показать фрагмент когда Activity в статусе onStop?
👉 Может ли быть фрагмент без View?

Все эти моменты важны в работе, на некоторых можно подорваться, про них спрашивают на собесах и про них и поговорим в следующих постах.
👍218
Как правильно показать первый фрагмент? Много текста, но я чуть шизу не поймал, пока пытался придумать как показать этот процесс😄
👍12😁51🤔1
Как я уже говорил Fragment это в некотором смысле усложненная View. Однако добавить фрагмент на Activity мы не можем просто вызвать view.addView(SomeFragment()). Fragment гораздо сложнее и для добавления фрагмент в Activity мы используем Fragment Manager.

Fragment Manager – как понятно из названия полностью отвечает за работу с фрагментами. Он умеет восстанавливать фрагменты, сохранять их стейт, передавать события ЖЦ от Activity, показывать новые, все что связано с фрагментами.

У Activity есть свой Fragment Manager, и у фрагмента тоже есть свой Fragment Manager. Фрагменту нужен свой fragment manager, потому как мы можем и внутри фрагмента показывать другие фрагменты, например DialogFragment.

Значится есть два стула основных способа как показать первый фрагмент:

👉 Через верстку
👉 Через код

С методом через верстку все довольно очевидно. Просто в наш layout через специальный тег встраиваем наш фрагмент. Плюс в том, что Activity сама полностью позаботится о том как правильно запустить фрагмент и как правильно восстановить его состояние при пересоздании. Недостаток этого способа в том, что можно показать только ограниченное количество фрагментов и вы не управляете этим процессом. Чаще всего такой способ используется для какой-нибудь статики.

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

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment())
.commit()
}
}

Как я уже упомянул, Fragment Manager умеет восстанавливать фрагменты при смерти Activity. Отсюда вытекает вот это условие с savedInstanceState == null. Потому как если Activity пересоздается, то Fragment Manager сам восстановит фрагмент и нам не нужно делать эту транзакцию.

Это важно потому как, если мы заново сами создадим фрагмент, то потерям сохранённое состояние фрагмента. Пользователь вводил какие-то данные в EditText, потом случайно перевернул телефон, а мы эти данные потеряли. Спасибо за такое он явно не скажет.
👍37🔥1
В комментариях в прошлом посту меня попросили прикладывать участки кода в виде картинок, так как на телефоне они читаются максимально всрато. К сожалению телеграм в канале не позволяет вставлять картинки в середину поста, и в целом это правильно потому что канал в телеграмме это не медиум и не про это.

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

Поэтому ловите, мини статейку про то, как правильно передавать данные во фрагмент.
👍30
Почему так много методов чтобы закоммитить транзакцию?
👍15🔥5
Вспомним что такое вообще транзакция. Само по себе понятие транзакции это некоторый набор действий который либо выполняется полностью, либо не выполняется. Это свойство назвается атомарность.

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

Далее вы могли заметить что есть два метода чтобы закоммитить транзакцию: commit и commitNow. В чем разница? В том, что когда делаем commit, мы эту транзакцию отправляем в Looper главного потока, а когда делаем commitNow транзакция происходит вот прямо сейчас синхронно.

Чтобы это понять есть простая аналогия. Когда вызываем просто commit, транзакция встает в очередь. Когда вызываем commitNow эта транзакция превращается в чела который: "Мне просто спросить".

На практике крайне редко возникает потребность показать фрагмент без очереди через commitNow. В большинстве случаев лучше просто не парится и показывать фрагменты через commit. С commitNow есть еще одно ограничение о котором поговорим в следующих постах.

Также есть такой метод как executePendingTransaction, который просто выполняет все транзакции посланные в Looper. Метод блокирующий, это значит, что при его вызове, все транзакции которые были посланы в Looper и находятся в очереди, забьют на эту очередь и выполнятся сейчас. Отсюда выходит, что если вызвать сначала commit, а потом следующей строкой executePendingTransaction, то это аналогично простому вызову commitNow.
👍285
Что такое Back Stack?
👍21
Для начала вернемся к транзакциям. Помимо атомарности у транзакций есть еще одно свойство. Грубо говоря это свойство можно назвать откатываемостью. Другими словами, допустим совершается транзакция, в которой есть набор действий меняющие состояние. Откатываемость позволяет полностью откатить эту транзакцию, что значит вернуть состояние системы в изначальное. Зная это свойство идем дальше.

Что такое Back Stack? Это просто стэк в котором лежат сохранённые транзакции, стэк позволяет их откатывать. Чтобы понять, как это работает, рассмотрим как закрываются Activity. Когда нажимаем на кнопку назад то верхняя Activity убивается и мы возвращаемся к предыдущей. Сохранение транзакций в Back Stack позволяет по нажатию на эту кнопку откатывать транзакции.

Работает это так, при нажатии на кнопку назад Activity сначала у Fragment Manager узнает если что-то в Back Stack. Если есть тогда откатывается последняя транзакция добавленная в Back Stack. Это происходит до тех пор, пока в Back Stack не будет пуст. После этого уже закрывается сама Activity.

Давайте на примере. Вы добавили первый фрагмент FirstFragment() в Activity. Затем в эту же Activity мы добавляем еще один фрагмент SecondFragment(). Однако транзакцию по запуску SecondFragment добавляем в Back Stack. Теперь когда нажимаем на кнопку назад, у нас сначала откатывается транзакция SecondFragment() т.е этот фрагмент удаляется, а после второго нажатия уже закроется вся Activity, так как первый фрагмент мы не добавляли в Back Stack.

Чтобы добавить транзакцию в Back Stack нужно использовать метод addToBackStack(). У функции есть один параметр типа String, это tag транзакции. В большинстве случаев этот tag не используется и просто передается null. Однако можно туда передать строку, чтобы потом можно было откатить все транзакции до транзакции с этим tag. Причем нужно держать в голове, что отказывается вся транзакция. Это значит что если вы в одной транзакции меняете какой-то фрагмент и одновременно показываете что-то поверх, при откате этой транзакции верхний фрагмент удалится, а замена фрагментов произойдет в обратном порядке.

Как уже упоминал выше, Activity сама откатывает транзакции по нажатию на кнопку назад. Помимо это можно откатывать транзакции через специальный метод popBackStack(). Есть два варианта этого метода обычный без параметров и второй с параметром типа String это тот tag транзакции, чтобы откатить все транзакции до этой.

Важный момент. Нельзя вызывать commitNow() с addToBackStack(), потому как транзакция сразу же упадет. Сделано это из-за очевидной проблемы. Когда вызываем commitNow(), эта транзакция минует очередь, а в очереди могут быть транзакции которые меняют Back Stack. Разруливать такое довольно сложно, поэтому очевидное решение просто падать в таком случае.
👍323🔥1
Что будет если показать фрагмент когда Activity в статусе onStop?
🤔14👍3
В предыдущих постах я упоминал что ЖЦ фрагмента полностью завязан на ЖЦ Activity. Однако интересные вещи происходят на стыке этих двух сущностей. Один из интересных вопросов который может возникнуть – что произойдет если закоммитить транзакцию фрагмента в тот момент когда у Activity уже был вызван onStop?

Fragment Manager умеет сохранять состояние фрагментов и делает он это в тот момент, когда Activity сохраняет свое состояние. Но каким образом сохранять состояние когда методы сохранения состояния Activity уже вызваны?

Для этого существует такая ошибка как State Loss Exception которая позволяет понять, что мы закомитили транзакцию после вызова onSaveInstanceState у Activity. Такая ситуация может возникнуть, когда вы показываете результирующий экран после какого-нибудь запроса к серверу.

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

Когда пользователь сворачивает приложение и возвращается назад, он ожидает увидеть экран на котором был. При использовании commitAllowingStateLoss() мы пытаемся показать новый экран когда пользователь уже не в приложении. Во-первых, это плохой UX, во-вторых, это лотерея, потому как я уже сказал выше гарантий никаких нет.

Поэтому лучше всего никогда не использовать commitAllowingStateLoss(), есть куча инструментов тот же Cicerone который позволяет навигироваться между экранами, даже если пользователь свернул приложение. commitAllowingStateLoss() лучше использовать только для экранов, который пользователю не критично пропустить вроде какого-нибудь банера и только в том случае когда вы уверены, что другого выбора нет.
👍322