Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.
Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support
Last updated 2 weeks, 2 days ago
Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 1 month ago
Автоформатирование кода или как испортить свой код.
Чаще всего разработчику приходиться читать код своих коллег и зачастую этот код сильно отличается от собственных представлений "правильного" кода, по крайней мере у меня часто возникают вопросы в стиле "зачем здесь var?", "почему нельзя было сделать конкретный тип, а не Any?", а ещё чаще я задаю себе вопрос "что вообще за хйня тут написана?"*, как говорится все люди разные и все видят по разному решение одной и той же проблемы, но что ещё важнее написать решение таким образом, чтобы остальные разработчики смогли понять что за черную магию вы натворили.
Для этого как раз и существуют всякие кодстайлы, например официальный от Kotlin, статические анализаторы или подмножество линтеров, например detekt и другие похожие штуки, которые нужны чтобы упорядочить написание кода разными разработчиками / командами / компаниями и даже отдельными OpenSource перцами, в итоге формируется некоторый общий стиль написания кода для целого сообщества языка программирования, это очень круто, я всеми нейронами за такие формирования, но есть такой сомнительный инструмент из разряда линтеров и кодстайлов как автоформатирование кода, ярким примером такой штуки является Gradle таска ktlintFormat из Gradle плагина ktlint'а.
В последующих абзацах я ни в коем случае не хочу сказать что автоформатирование кода это полностью ненужная вещь и что я не вижу в этом инструменте ничего хорошего, это не совсем так, я считаю что автоформатирование кода это хороший инструмент, но только для небольших и рутинных задач - удалить ненужные отступы, отсортировать список зависимостей по алфавиту и всякое такое, но когда дело доходит до форматирования исходников, я выхожу из игры и делаю это не только потому что сильно люблю свой код (и это тоже конечно), самое главное что можно потерять при автоформатировании кода это возможность прокачать умение писать понятный и логичный код, приведу два примера, в первом будет использован статический анализатор detekt, который не пропустит ваш код если с ним что-то не так, во втором будет использоваться автоформатирование кода через специальную Gradle таску, в ktlint'е как я уже упоминал она называется ktlintFormat:
1) Пишем какой-то код, пропускаем через статический анализатор detekt, видим что проект не собирается, исправляем, снова пропускаем через detekt, опять ничего не собирается, повторяем, и после десятка / сотни таких итераций разработчик начинает привыкать к правильному кодстайлу и вырабатывает автоматизм написания такого кода
2) Пишем какой-то код, запускаем автоформатирование через Gradle таску ktintFormat, всё ок, пишем другой код, также запускаем автоформатирование, опять всё ок, при таком решении проблемы нам со временем становится всё равно на то, что мы пишем, ведь Gradle таска сама отформатирует код, зачем напрягать жвачку, когда есть волшебная кнопка?
Вдобавок, автоформатирование кода - это нисколько не магическая утилита, способная превратить запутанный и нелогичный код во что-то более менее понятное, что ещё хуже эта штука формирует иллюзорное восприятие: можно написать что угодно, все равно отформатируется, более логичным решением будет способ заставить разработчика задуматься когда он пишет код, например используя какой-нибудь статический анализатор, кричащий "чувак, здесь ерунда какая-то, исправляй", в итоге, когда начинаешь задумываться о стиле написания своего кода выстраиваются правильные ориентиры и появляется стремление к более осознанному проектированию классов / функций и т.д.
Пишите в комментах ваше мнение и всем хорошего кода!
Пару слов о value классах.
В объектно-ориентированных языках нам приходиться оперировать классами, это очень удобно в прикладных задачах, например: нужно реализовать логику корзины с продуктами, создаём новый класс для корзины и продукта, кладём в корзину нужные товары и отправляем на оплату, всё очень просто и логично, но предположим, что нам нужно создать класс для прогресса, где корректными значениями считаются числа от 0 до 100, у нас появляется компромисс использовать класс, в котором инкапсулированы проверки на корректность, но при этом есть дополнительный оверхед вследствие работы сборщика мусора или использовать...
продолжение в Telegraph'е
Пишите в комментах ваше мнение и всем хорошего кода!
Kotlin Coroutines internals.
Закончил наконец-то статью на Хабре по корутинам, получилась просто бомба! Никогда ещё не писал настолько объёмный и сложный материал, ушло около 2-х недель из которых половину времени я прокрастинировал и в какой-то момент даже думал что брошу это дело, но благо всё обошлось и статья, отредактированная практически до мелочей уже опубликована!
Теперь по материалу статьи, я долго пытался разобраться в исходниках корутин, на это ушло буквально десятки вечеров занимательного чтива под названием "исходный код", мне ни раз рекомендовали книжку Kotlin Coroutines: Deep Dive, но удивительно устроен мозг человека, вопреки лени порой выбор падает на более сложный путь и вместо книжки я выбрал дебаг с покраснением глаз, ладно это всё лирика, в статье описан принцип работы корутин под капотом и базовые кирпичики такие как диспатчеры, контекст, EventLoop, Continuation и другие важные штуки, в общем не пожалеете если потратите часик на статью)
Ну и конечно же убедительная просьба, делитесь статьёй со своими коллегами, друзьями, знакомыми, ссылка на статью:
Kotlin Coroutines под капотом на Хабре
Пишите в комментах ваше мнение и всем хорошего кода!
Пару слов о Kotlin Compiler'е.
Сейчас уже не новость что Kotlin может компилироваться не только в Java байт-код, но и в JavaScript, Objective-C и даже в нативный код под разные архитектуры, всё это возможно благодаря архитектуре компилятора, построенной на простой идеи:
1) есть Frontend часть, которая компилирует исходной код программы на Kotlin в некоторое промежуточное представление или IR (intermediate representation), удобное для дальнейшей обработки
2) есть Backend часть, которая берёт промежуточное представление или IR и превращает его в код конкретной платформы: для JVM - это байт-код, для iOS - Objective-C, для какой-нибудь нативной утилиты под Linux - ассемблер или машинный код
В такой схеме IR независим от конкретной платформы, а следовательно есть пространство для создания всяких штук, которые могут его менять при компиляции кода, например компиляторные плагины, такие как Compose Compiler.
Хотелось бы добавить, что идея Kotlin компилятора не новая и давно реализуется в таких штуках как:
1) LLVM - набор компиляторов для генерации нативного кода под определённую архитектуру, имеет своё промежуточное представление LLVM IR, используется Kotlin'ом для Native таргета.
2) Rust - один из самых высоко оптимизированных компиляторов и языков соответственно, имеет несколько уровней промежуточного представления, компилится в нативный код и WebAssembly, язык низкого уровня того же назначения что и JavaScript
3) Java - если вы не задумывались, то байт-код это тоже своего рода промежуточное представление, которое исполняется на разных виртуальных машинах, заточенных под свои платформы.
Пишите в комментах ваше мнение и всем хорошего кода!
Пару слов о Backend Driven UI.
Backend Driven UI или как часто сокращают BDUI - это библиотека, фреймворк или проще говоря штука для создания динамического интерфейса на основе ответа бэкенда без изменения кода Android / iOS приложения.
Небольшой пример: в приложении есть экран с детальной информацией об игре, вам сказали что нужно добавить карусельку "похожие игры", вы идёте на экран об игре, добавляете карусельку, пересобираете apk / aab, публикуете в магазине приложений и так каждый раз, когда появляются новые изменения в дизайне деталки, в целом это нормально, обычно такие изменения заранее планируются и заливаются в пределах релиза.
Проблема тут только в частоте этих самых изменений, если... продолжение в Telegraph'е
Пишите в комментах ваше мнение и всем хорошего кода!
Dispatchers.Main под капотом.
CoroutineDispatcher - один из базовых компонентов Kotlin корутин, предназначенный для переключения потоков, чтобы разобраться как он работает под капотом, немного погрузимся в общий принцип работы корутин.
Любая корутина создаёт Continuation объект и передаёт его suspend функциям, те в свою очередь вызывают метод continuation.resumeWith()
когда нужно вернуть результат или ошибку, это можно сравнить с обычными callback'ами, только с тем отличием, что в Kotlin Coroutines всё реализовано под капотом + есть дополнительные механизмы в стиле Structured Concurrency.
Но чтобы переключать потоки знания общего принципа достаточно, давайте задумаемся на минутку, что надо сделать, чтобы результат callback'а или Continuation'а в нашем случае был на другом потоке?
Да конечно вызвать Continuation.resumeWith()
на другом потоке:
// IO thread
val ctx = coroutineContext
// метод isDispatchNeeded проверяет есть ли смысл переключать корутину на главный поток, чтобы не делать лишних переключений, если корутина и так находится на главном потоке
if (mainDispatcher.isDispatchNeeded(ctx)) {
// метод dispatch выполняет блок кода на главном потоке
mainDispatcher.dispatch(ctx) {
// Main thread
continuation.resumeWith(...)
}
} else {
// уже находимся на главном потоке, продолжаем выполнение
continuation.resumeWith(...)
}
Если у вас появится желание написать свой CoroutineDispatcher, то вы должны обязательно реализовать метод dispatch() и было бы неплохо переопределить isDispatchNeeded() чтобы исключить лишние переключения.
Вернёмся к главному потоку, в Android такой Dispatcher или как он назван в исходниках HandlerContext
реализован с помощью Handler'а:
```
// реализация для Dispatchers.Main
override fun isDispatchNeeded(...) = true
// реализация для Dispatchers.Main.immediate
override fun isDispatchNeeded(...): Boolean {
// сравнивает Looper текущего потока с главным
// handler.looper это Looper.getMainLooper()
return Looper.myLooper() != handler.looper
}
override fun dispatch(
context: CoroutineContext,
block: Runnable
) {
// handler.post() выполняет код на главном потоке
if (!handler.post(block)) { ... }
}
```
Dispatchers.Main не делает проверок на главный поток, поэтому если написать несколько вложенных viewModelScope.launch()
вызовов всегда будет происходить переключение текущего потока на главный через Handler.post()
, даже если код уже выполняется на главном потоке.
Чтобы избежать ненужных переключений используйте Dispatchers.Main.immediate, в таком случае если код уже выполняется на главном потоке, метод isDispatchNeeded()
вернёт false и dispatch()
не будет вызван.
P.S. Хочу порекомендовать крутой канал с авторским контентом и полезными материалами в области Android разработки @dolgo_polo_dev
Пишите в комментах ваше мнение и всем хорошего кода!
Пару фактов о StateFlow и SharedFlow.
1) В отличии от простой реализации паттерна Observer, как это было в LiveData например, в StateFlow / SharedFlow механика основана на корутинах.
Всё начинается с подписки данных через collect, где запускается бесконечный цикл, который при отсутствии новых значений переводит корутину в SUSPEND состояние и что очень важно сохраняет ссылку на Continuation.
Думаю не секрет, что Continuation это обычный callback, который неявно передаётся в каждую suspend функцию и нужен для выхода из SUSPEND состояния.
При добавлении новых значений в StateFlow или SharedFlow извлекается сохранённый Continuation объект, у которого вызывается метод resume(), корутина выходит из SUSPEND состояния и подписчик получает новое значение или значения если это SharedFlow.
2) Состояния подписчиков, такие как ссылки на Continuation объекты, хранятся в массиве слотов AbstractSharedFlowSlot, по большей части это сделано для переиспользования общей логики, так как у StateFlow и SharedFlow общий родитель - AbstractSharedFlow.
3) Все возможные операции изменения состояния сделаны через атомарные конструкции (StateFlow) или synchronized блоки (SharedFlow), поэтому это потокобезопасные штуки.
4) StateFlow при изменении своего единственного значения сравнивает его с новым equals() методом, если значения равны, ничего не делает.
5) SharedFlow хранит значения в буфере, размер которого настраивается двумя параметрами: replay и extraBufferCapacity, первый отвечает за основную часть буфера, значения из этой части будут прилетать новым подписчикам, например если replay = 3, то при добавлении нового подписчика, в него придут 3 значения из буфера, второй параметр только добавляет дополнительное место для буферизации значений.
6) SharedFlow нельзя превратить в StateFlow если указать размер буфера нулевым, так как эта штука работает немного по другому, предположим у нас есть два подписчика и какой-нибудь источник в котором происходит генерация новых значений, все хорошо до тех пор пока один из подписчиков вдруг не окажется очень медленным и в случае нулевого размера буфера новое значение будет добавлено только когда все подписчики получат предыдущее, вызов SharedFlow.emit() в таком случае переходит в SUSPEND состояние.
Если буфер ненулевой то значения будут добавляться до тех пор пока он не заполнится.
7) Помимо перехода источников SharedFlow в SUSPEND состояние при переполнении буфера, есть ещё две интересные стратегии:
BufferOverflow.DROP_OLDEST - удаление из буфера самых старых значений
BufferOverflow.DROP_LATEST - пропуск новых значений, важно что здесь не происходит никаких операций с буфером
В обеих случаях SharedFlow.emit() успешно завершается и не переходит в SUSPEND состояние.
Пишите в комментах ваше мнение и всем хорошего кода!
Многомодульность в Android приложениях.
Недавно я привёл в порядок мой небольшой Pet проект и поделил его на несколько модулей, получилось очень даже неплохо, что мне захотелось черкануть парочку мыслей на этот счёт:
1) В проекте должен быть как минимум один модуль, являющийся точкой входа в приложение, если вы не пишите библиотеку конечно, чаще всего его называют app модулем, в таком модуле обычно происходит настройка параметров для сборки aab / apk артефактов, подключение всех остальных модулей, построение DI графа и графа навигации, а также тут живёт наследник Application класса с главной Activity.
2) Построение DI графа или графа навигации происходит в app модуле, который собирает всё воедино, например если для DI используется Dagger, то в дочерних модулях будут свои Dagger компоненты, app модуль в данном случае будет иметь общий Dagger компонент, включающий в себя остальные, таким образом получится целостный граф зависимостей, где без проблем можно пробросить Application Context или другие корневые штуки в дочерние модули.
3) Для уменьшения времени сборки проекта и повышения инкапсуляции модуля можно поделить классы на реализацию и интерфейс: реализацию ограничить internal модификатором, а интерфейс сделать публичным, в таком случае при изменении кода реализации будет пересобран только один модуль, содержащий эту реализацию, если конечно не был изменён интерфейс.
4) Gradle позволяет переиспользовать настройки скриптов с помощью Convention Plugins, например можно создать шаблон с уже прописанными настройками для Android: compileSdk, minSdk, targetSdk, compileOptions и тд, а потом просто подключать его в модулях, как обычный Gradle плагин, это позволит уменьшить количество шаблонного кода.
Всё что я описал здесь можете глянуть на Github'е, если знаете как сделать правильнее, жду PR в репозиторий)
P.S. На прошлой неделе появилось сообщество Mobile Broadcast в Барнауле, пока не было встреч, но если в группе есть люди из моего города, вступайте, если вы не знаете прикола сообщества, то это лайтовые встречи с обсуждением тем из Android / iOS разработки, а также смежных областях, таких как дизайн например.
Пишите в комментах ваше мнение и всех с первыми деньками лета!
Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.
Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support
Last updated 2 weeks, 2 days ago
Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 1 month ago