Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
64 - Telegram Web
Telegram Web
Я давно ничего не постил в канал, сейчас решил прервать молчание, так как сейчас я менторю джунов и тех кто только начинает вливаться в профессию и у меня появились мысли которыми могу поделится. Когда только начинаешь вливаться в разработку часто источником информации являются книги или различные статьи. Беда книг по Android в том, что они быстро устаревают. Устаревают после каждого google IO, а статьи порой пишутся просто, чтобы написать.

Поэтому я и создал этот канал где пишу про фундаментальные штуки, которые меняются оочень редко и стараюсь не писать про то, что можно и так легко найти в документации.
👍3
Я решил написать пост про вещи на которых подрываются начинающие, и о которых забывают написать в книгах. Это 4 всадника апокалипсиса которые делают больно старшим разработчикам.🔽
👍10🔥2
1️⃣ Хранение переменных в Activity
Это касается всего что связано с UI. Почему это проблема? Да потому что Activity – компонент приложения Android фреймворка. Это значит, что мы вообще её не контролируем. Activity контролирует система, в любой момент Activity может просто перестать существовать и вы ничего с этим не сделаете. Всегда думайте о том, что будет если Activity пересоздастся!

Это же касается и списков. Не нужно во ViewHolder делать какие-либо переменные, потому как Adapter это тоже фреймворк, который мы не контролируем.

Да можно сохранять переменные в bundle, но не все туда можно сохранить, не говоря уже о том, что он ограничен по размеру (1kb это кстати популярный вопрос на собесах). Если можно избежать переменных в Activity, Fragment... лучше не делать. Хотите сохранить переменные сохраняйте их во ViewModel или что-то другое что не связано с компонентами приложения. Только не сохраняйте данные в статику, об этом я напишу в других постах.

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

2️⃣ Обработка ошибок
Часто можно заметить, что на исключение которые прилетают при запросе в сеть либо забивают, либо делают что-то вроде printStackTrace. 🙅‍♂️ Не делайте так, хотя бы в лог отправляйте исключение. Вот тут закон Мерфи работает вообще на максимум, все что может упасть упадет, но приложение должно продолжать работу. Продолжать работу это значит что не просто проглотить ошибку, а сообщить об этом пользователю и сказать что ему делать дальше.

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

3️⃣ Переусложнение
Вот это вообще боль. Кто-то однажды сказал, что лучший джун, это джун который выгорел, и это блин действительно так. Это уже больше относиться с soft скиллам, а не hard, но это супер важно. У всех у нас есть амбиции стать крутыми разрабами, показать что мы можем решить любую задачу и написать сложный код. Я понимаю, сам таким был, охото написать весь проект на вечер, прикрутить крутую анимацию даже если она не нужна, написать более оптимизированный код принеся в жертву читабельность.

Совет один НЕ НУЖНО ЭТО ДЕЛАТЬ. Поверьте у вас в карьере еще будет возможность показать что вы крутые, не бегите впереди паровоза. Очень большая вероятность того, что вы наломаете дров и придется переделывать.

Возникает вопрос "А как понять что переусложню?". Лучше всего руководствоваться логикой. Если для решения простой задачи, типа изменения элемента в списке или перекрашивания кнопки у вас получается прям дофига кода стоит задуматься. До вас уже решили все задачи, погуглите, смотрите код других проектов, чтение кода других очень сильно прокачивает.

Суть – не нужно писать сложный код, нужно писать настолько простой код насколько это вообще возможно. Чем меньше кода нужно для решения задачи тем лучше. Есть даже такой принцип KISS.

4️⃣ Преждевременная оптимизация
Это сильно связано с прошлым пунктом. Суть в том, что не нужно писать супер быстрый код. Если вы не пишете игру или операционную систему, то вам не нужно выжимать все из железа. Да конечно не нужно использовать заведомо дурацкие решения типа сортировки пузырьком, или ходить в базу данных на UI потоке.

При этом не нужно на начально этапе париться о том, как сделать мега быструю отрисовку или как сделать параллельный алгоритм где достаточно однопоточного. Страуструп сказал "Преждевременная оптимизация корень всех зол", поэтому лучше парьтесь над читабельностью кода и его краткостью, оптимизацию будете делать позже, или никогда 😄.
👍192
В чем разница м/у смертью Activity и смертью процесса?

Все мы знаем, что у Activity есть некоторый ЖЦ. Однако порой в статьях забывают сделать акцент на том, что умереть может не только Activity. Есть два понятия смерть Activity и смерть процесса.
👇
👍7
Сначала разберем как может умереть 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
2025/07/13 17:27:59
Back to Top
HTML Embed Code: