Перейти к содержанию

Shorts

Computer Science

Назовите принципы ООП

Инкапсуляция, наследование, полиморфизм и абстракция. Они помогают разделять ответственность, скрывать детали реализации и работать с объектами через общие контракты.

Инкапсуляция - это сокрытие внутреннего состояния и деталей реализации за публичным API. Например, поле делают private, а доступ к нему контролируют через методы или свойства.

Полиморфизм позволяет работать с разными реализациями через общий тип. Например, ViewModel может зависеть от интерфейса Repository, а конкретная реализация подставляется через DI.

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

Абстракция выделяет значимые свойства и поведение, скрывая несущественные детали реализации. В Kotlin и Java её обычно выражают через interfaces, abstract classes и публичные контракты.

Что такое мутабельность и иммутабельность?

Мутабельный объект можно изменить после создания, иммутабельный - нет. Иммутабельность упрощает работу с состоянием, многопоточностью и декларативным UI.

Что такое SOLID?

SOLID - это пять принципов объектно-ориентированного дизайна: SRP, OCP, LSP, ISP и DIP. Их цель - уменьшить связанность и сделать код проще для расширения, тестирования и поддержки.

Single Responsibility Principle - у класса должна быть одна основная ответственность и одна причина для изменения. Например, ViewModel не должна одновременно работать с сетью, базой, форматированием и навигацией.

Open/Closed Principle - программные сущности должны быть открыты для расширения, но закрыты для постоянной модификации. Новое поведение лучше добавлять через композицию, стратегии и новые реализации, а не разрастающиеся if или when.

Liskov Substitution Principle - реализация подтипа должна корректно заменять базовый тип, не нарушая его контракт и ожидаемое поведение.

Interface Segregation Principle - несколько небольших специализированных интерфейсов обычно лучше одного большого. Клиент не должен зависеть от методов, которые ему не нужны.

Dependency Inversion Principle - высокоуровневая логика не должна напрямую зависеть от низкоуровневых деталей. Оба уровня должны зависеть от абстракций, например ViewModel - от интерфейса Repository.

Какие практики существуют кроме SOLID?

Например: DRY, KISS, YAGNI, separation of concerns, composition over inheritance, immutability, dependency injection, code review и automated testing. Это ориентиры, а не абсолютные правила: их применяют по контексту, чтобы уменьшать сложность и стоимость изменений.

Что такое Code Smell?

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

Какие главные правила проектирования API в коде?

API должен быть минимальным, понятным, последовательным и трудным для неправильного использования. Важно скрывать детали реализации, выбирать ясные имена и типы, сохранять backward compatibility и не раскрывать mutable state без необходимости.

Что такое Big O?

Big O описывает, как растут время выполнения или потребление памяти алгоритма при увеличении входных данных. Это оценка масштабируемости, а не точное время в миллисекундах.

Какова сложность бинарного поиска?

Бинарный поиск занимает O(log n) на отсортированных данных с доступом к среднему элементу за O(1), на каждом шаге отбрасывая половину диапазона поиска.

Какова средняя сложность QuickSort?

В среднем QuickSort работает за O(n log n), а в худшем случае - за O(n²). На практике хорошие реализации используют более безопасный выбор pivot и гибридные подходы.

ArrayList или LinkedList?

ArrayList даёт быстрый доступ по индексу O(1) и обычно лучше работает на практике благодаря cache locality. LinkedList полезен редко: поиск по индексу стоит O(n), а преимущество вставки есть только когда нужный node уже найден.

Что нужно переопределить для ключа HashMap?

Переопределяйте equals() и hashCode() вместе, если нужно собственное равенство по значению; унаследованное сравнение по идентичности тоже корректно. Если два объекта равны по equals(), их hash code обязательно должен совпадать.

Какие структуры данных ты знаешь?

Основные структуры: массив, динамический массив, связный список, stack, queue, deque, hash table, set, tree, heap, trie и graph.

Какова худшая сложность вставки в бинарное дерево поиска?

В несбалансированном binary search tree худшая сложность вставки - O(n), если дерево вырождается в связный список. В сбалансированном дереве, например AVL или red-black tree, вставка занимает O(log n).

Какова сложность поиска в HashMap?

В среднем поиск занимает O(1) при хорошем распределении hash codes. В худшем случае при большом количестве коллизий возможна деградация до O(n), а в современных Java-реализациях treeified bucket может дать O(log n).

Что произойдёт при одновременном изменении обычного HashMap из нескольких потоков?

HashMap не thread-safe: возможны lost updates, проблемы видимости и неконсистентное внутреннее состояние. Решение - ограничить доступ одним потоком, синхронизировать операции общей блокировкой или использовать concurrent collection.

Как работает ConcurrentHashMap?

ConcurrentHashMap обеспечивает thread-safe доступ без одной глобальной блокировки на всю map. Чтения в основном неблокирующие, а обновления используют atomic operations и локальную синхронизацию отдельных buckets; для составных операций нужны putIfAbsent(), compute() или merge().

Что такое stack и heap?

Stack хранит call frames, локальные переменные и ссылки, а heap - объекты. У каждого потока свой stack, а heap общий и очищается Garbage Collector.

Что такое memory leak?

Memory leak возникает, когда объект уже не нужен, но всё ещё достижим через цепочку ссылок и поэтому не может быть очищен GC. В Android частые причины - ссылки на Activity, listeners, callbacks и долгоживущие singleton-объекты.

Kotlin

Чем val отличается от var?

val нельзя переназначить после инициализации, а var можно. При этом val не делает сам объект иммутабельным: содержимое mutable-коллекции всё ещё можно менять.

Чем lateinit отличается от lazy?

lateinit var инициализируется вручную позже, а слишком раннее чтение приводит к UninitializedPropertyAccessException. val by lazy инициализируется автоматически при первом обращении, а результат кэшируется.

Что такое nullable types?

В Kotlin nullability является частью системы типов: String не может быть null, а String? может. Это снижает риск NullPointerException и заставляет явно обрабатывать отсутствие значения.

Чем == отличается от ===?

== проверяет structural equality и вызывает equals(). === проверяет referential equality, то есть указывают ли ссылки на один и тот же объект.

Чем as отличается от as??

as выполняет обычное приведение и бросает ClassCastException, если тип несовместим. as? возвращает null вместо исключения.

Что такое data class?

data class предназначен для хранения данных. Kotlin автоматически генерирует equals(), hashCode(), toString(), copy() и componentN() для свойств primary constructor.

Чем sealed class отличается от enum class?

enum class описывает фиксированный набор констант одного типа. sealed class или sealed interface описывает ограниченную иерархию, где каждый вариант может содержать собственные данные.

Чем object отличается от companion object?

object объявляет singleton. companion object - объект, связанный с классом и используемый для static-like членов, фабрик и констант.

Что вернёт emptyList()?

emptyList() возвращает пустой read-only список типа List<T>. Добавлять элементы в него нельзя; для изменяемого списка нужен mutableListOf().

Чем List отличается от MutableList?

List предоставляет только операции чтения, а MutableList позволяет изменять коллекцию. При этом List не гарантирует настоящую иммутабельность объекта, а только ограничивает доступный API.

Что такое extension function?

Extension function добавляет удобный синтаксис вызова функции для существующего типа без наследования и изменения исходного класса. Под капотом это обычная функция, которая не получает доступ к private-членам класса.

Что такое higher-order function?

Это функция, которая принимает другую функцию как параметр или возвращает функцию. В Kotlin такие функции широко используются в коллекциях, Flow и DSL.

Для чего нужен inline?

inline позволяет встроить тело функции и лямбд в место вызова, уменьшая накладные расходы на создание объектов для лямбд. Особенно полезен для небольших higher-order functions и reified generics.

Для чего нужен reified?

reified сохраняет информацию о generic-типе внутри inline-функции. Это позволяет использовать T::class, is T и другие операции без явной передачи Class или KClass.

Kotlin Int - примитив или объект?

На уровне языка это обычный тип Kotlin. На JVM он обычно компилируется в primitive int, но nullable и generic-контексты требуют boxing в Integer.

Какой тип Kotlin не имеет экземпляров?

Nothing не имеет возможных значений и не может быть инстанцирован. Его используют как return type функций, которые никогда нормально не завершаются, например всегда бросают исключение.

Во что превращается sealed class при компиляции в JVM bytecode?

В базовый JVM-класс или interface и отдельные классы-наследники. Ограничение и exhaustive-проверки обеспечивает Kotlin compiler; на современных JVM также может использоваться metadata для sealed types.

Android basics

Назовите четыре основных компонента Android-приложения

Activity, Service, BroadcastReceiver и ContentProvider. Они могут быть точками входа приложения. Activity, Service и ContentProvider объявляются в манифесте; BroadcastReceiver также можно зарегистрировать во время выполнения.

Чем Bound Service отличается от обычного Service?

Bound Service - это Service, к которому другие компоненты подключаются через bindService() и взаимодействуют с ним через IBinder. Pure bound service существует, пока к нему привязан хотя бы один клиент. Started service может продолжать работу независимо от запустившего его компонента, но с учётом завершения процесса и ограничений Android на фоновую работу.

Какой компонент инициализируется раньше: Application или ContentProvider?

Сначала создаётся объект Application, затем инициализируются ContentProvider, и только после этого вызывается Application.onCreate(). Поэтому providers часто используются библиотеками для ранней автоинициализации.

Чем explicit Intent отличается от implicit Intent?

Explicit Intent указывает конкретный компонент, который нужно запустить. Implicit Intent описывает действие, а Android выбирает подходящее приложение или компонент через intent filters.

Что такое Bundle?

Bundle - контейнер для передачи небольших наборов разнотипных данных между Android-компонентами и для сохранения состояния. Он поддерживает primitive-типы, Parcelable, Serializable и некоторые другие типы.

Чем Parcelable отличается от Serializable?

Parcelable оптимизирован для Android IPC и обычно быстрее, но требует специальной реализации или @Parcelize. Serializable проще, но использует reflection и создаёт больше накладных расходов.

Что происходит при повороте экрана?

Поворот - это configuration change, и по умолчанию Activity уничтожается и создаётся заново. ViewModel переживает это пересоздание, а небольшой UI state можно сохранять через SavedStateHandle или onSaveInstanceState().

Может ли onDestroy() не вызваться?

Да. Система может завершить процесс без вызова onDestroy(), поэтому на него нельзя полагаться для сохранения критичных данных.

Как ViewModel переживает поворот экрана?

ViewModel хранится в ViewModelStore, связанном с логическим владельцем экрана, а не с конкретным экземпляром Activity. После пересоздания новая Activity получает тот же ViewModel, пока процесс не был убит.

Сколько Looper может быть у одного потока?

У одного потока может быть не более одного Looper, при этом поток может вообще его не иметь. Главный поток получает Looper автоматически, а для фонового потока можно использовать Looper.prepare() или HandlerThread.

Какие lifecycle-методы Activity вызываются при повороте устройства?

При стандартном configuration change старая Activity проходит onPause() → onStop() → onDestroy(), а новая - onCreate() → onStart() → onResume(). Перед уничтожением также обычно вызывается onSaveInstanceState().

Чем invalidate() отличается от requestLayout()?

invalidate() запрашивает перерисовку View и приводит к новому draw pass. requestLayout() запускает повторные measure и layout, когда изменились размер или позиционирование.

Какие методы переопределяют в Custom ViewGroup?

Главные методы - onMeasure() для расчёта размеров и onLayout() для размещения дочерних View. onDraw() нужен только если контейнер рисует собственный контент.

Как работает RecyclerView?

RecyclerView координирует работу LayoutManager, Recycler, Adapter и ViewHolder. LayoutManager определяет, какие позиции сейчас нужны и где разместить их View. Он запрашивает элементы через Recycler, который по возможности переиспользует существующий ViewHolder, а при необходимости просит Adapter создать и привязать его: RecyclerView -> LayoutManager -> Recycler -> Adapter -> ViewHolder.

Чем ViewBinding отличается от DataBinding?

ViewBinding даёт типобезопасный доступ к View и почти не добавляет runtime-сложности. DataBinding дополнительно поддерживает выражения в XML, two-way binding и BindingAdapters, но усложняет сборку.

Activity Context или Application Context?

Activity Context используют для UI, которому нужны тема или окно экрана, например для диалогов и создания View с темой экрана. Application Context подходит для зависимостей со временем жизни процесса, которым эта UI-конфигурация не нужна.

Что такое ANR?

ANR означает, что компонент приложения не ответил за отведённое системой время, часто из-за блокировки главного потока. Частые причины - блокирующие операции, тяжёлые вычисления и неправильная синхронизация на главном потоке.

Что такое jank?

Jank - это визуальные рывки, когда кадр не успевает подготовиться к frame deadline. Для 60 FPS бюджет одного кадра составляет примерно 16,6 мс.

Что такое overdraw?

Overdraw возникает, когда одни и те же пиксели рисуются несколько раз поверх друг друга. Он не всегда является проблемой, но лишние backgrounds и слои могут создавать ненужную GPU-нагрузку.

Jetpack Compose

Что такое Jetpack Compose?

Jetpack Compose - декларативный UI toolkit для Android. UI описывается как функция от state, а Compose обновляет затронутые части интерфейса при изменении состояния.

Какие базовые Compose-контейнеры используются для расположения элементов?

Row располагает элементы горизонтально, Column - вертикально, а Box позволяет накладывать их друг на друга. Для больших одномерных коллекций используют LazyColumn и LazyRow, а для lazy grids - LazyVerticalGrid и LazyHorizontalGrid. FlowRow и FlowColumn переносят небольшие наборы, но не являются lazy; BoxWithConstraints адаптируется к constraints родителя, а Scaffold организует стандартные slots Material-экрана.

Какие аннотации стабильности есть в Compose?

Основные аннотации - @Immutable и @Stable. Они являются контрактом с Compose compiler: @Immutable обещает неизменяемое публичное состояние, а @Stable - предсказуемое и наблюдаемое изменение свойств.

Стабильны ли лямбды без захваченных переменных в Compose?

Да. Compose считает типы лямбд стабильными, включая лямбды с захваченными значениями. При включённом strong skipping лямбды внутри composable автоматически мемоизируются с захваченными значениями в качестве ключей. Изменение ключей может создать новый экземпляр лямбды, поэтому стабильность не гарантирует неизменную identity или пропуск recomposition.

Какие фазы отрисовки есть в Compose?

Основные фазы - composition, layout и drawing. Composition определяет структуру UI, layout измеряет и размещает элементы, а drawing рисует их содержимое.

Какая структура хранит composition tree и состояние Compose?

Внутренне Compose использует Slot Table. В ней Composer хранит группы composition, значения remember и данные, необходимые для последующих recomposition.

Что такое recomposition?

Recomposition - повторный вызов composable-функций, которые зависят от изменившегося state. Это нормальный механизм Compose; проблема возникает только если recomposition слишком частая или дорогая.

Чем remember отличается от rememberSaveable?

remember сохраняет значение между recomposition, но не переживает recreation. rememberSaveable дополнительно сохраняет поддерживаемый state через saved state mechanism.

Что такое mutableStateOf?

mutableStateOf создаёт observable state, за изменениями которого следит Compose. Когда значение меняется, зависимые composable-функции могут быть запланированы на recomposition.

Что такое state hoisting?

State hoisting - перенос состояния выше по дереву composable. Компонент получает value и callbacks, становится более stateless, переиспользуемым и тестируемым.

Когда использовать LaunchedEffect?

LaunchedEffect используют для coroutine side effects, привязанных к lifecycle composable и его key. При изменении key предыдущая coroutine отменяется и запускается новая.

Когда использовать rememberCoroutineScope?

Он нужен для запуска coroutine по UI-событию, например по нажатию кнопки. Screen-level business logic обычно лучше запускать во ViewModel.

Как уменьшать лишние recomposition?

Нужно правильно разделять state, не передавать нестабильные объекты без необходимости, использовать stable keys и выносить тяжёлые вычисления из composable. Оптимизацию стоит начинать с измерения и поиска реального bottleneck.

Coroutines и Flow

Как ты используешь coroutines и Flow в Android-приложении?

Coroutines использую для one-shot асинхронных операций: сети, базы данных и параллельной загрузки. Flow - для потоков обновлений: UI state, Room observation, поиска, realtime-данных и объединения нескольких источников.

Что такое coroutine и чем она отличается от Thread?

Coroutine - лёгкая задача, которая выполняется поверх потоков и может приостанавливаться без их блокировки. Один поток может последовательно обслуживать множество coroutines.

Что делает suspend?

suspend означает, что функция может приостановиться и продолжить выполнение позже без блокировки потока. Это не означает автоматический переход на background thread.

Чем launch отличается от async?

launch возвращает Job и используется, когда отдельный результат не нужен. async возвращает Deferred<T>, результат которого получают через await().

Что такое structured concurrency?

Structured concurrency связывает coroutines с конкретным scope и его lifecycle. Родитель ожидает завершения детей и может отменить их как единую группу.

Чем coroutineScope отличается от supervisorScope?

В coroutineScope ошибка одного child обычно отменяет остальных. В supervisorScope дочерние задачи изолированы и могут обрабатывать ошибки независимо.

Как работает cancellation?

Отмена кооперативная: coroutine реагирует в отменяемых точках приостановки, через ensureActive() либо проверяя isActive и прекращая работу при отмене. CancellationException обычно не нужно перехватывать как обычную ошибку.

Какие основные Dispatchers существуют?

Dispatchers.Main используется для UI, IO - для блокирующего ввода-вывода, Default - для CPU-intensive работы. withContext() позволяет сменить контекст внутри suspend-функции.

Какие средства синхронизации используются в Kotlin Coroutines?

Чаще всего - Mutex для защиты suspend-кода, atomic operations для простого общего состояния и state confinement, когда состоянием владеет одна coroutine. synchronized тоже возможен, но внутри него нельзя вызывать suspend-функции.

Что такое Flow?

Flow - асинхронный поток значений из coroutines ecosystem. Обычный Flow cold: каждый collector заново запускает upstream.

Что такое Channel в Kotlin Coroutines?

Channel - hot coroutine primitive для передачи значений между coroutines. Он похож на очередь с suspending operations send и receive, а каждый элемент обычно получает один receiver.

Чем Channel отличается от Flow?

Обычный Flow чаще всего является cold declarative stream и запускается отдельно для каждого collector-а. Channel - hot primitive для point-to-point передачи: он существует независимо от receivers, а каждый отправленный элемент получает только один из них.

Чем Channel отличается от SharedFlow?

Channel обычно передаёт каждый элемент одному из конкурирующих receivers. SharedFlow рассылает каждое значение всем активным collectors и может повторять его в соответствии с настройкой replay.

Когда использовать Channel вместо SharedFlow?

Channel подходит, когда важна семантика очереди или одного consumer-а: для producer-consumer pipelines, распределения работы и actor-like коммуникации. SharedFlow выбирают, когда одно значение должны увидеть все активные collectors; ни один из этих механизмов не гарантирует надёжную доставку UI events после process death.

Чем Flow отличается от suspend-функции?

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

Чем cold Flow отличается от hot Flow?

Cold Flow начинает работу для каждого collector отдельно. Hot Flow существует независимо от подписчиков; StateFlow и SharedFlow являются hot flows.

Чем collect отличается от collectLatest?

collect обрабатывает каждое значение полностью. collectLatest отменяет обработку предыдущего значения, если приходит новое.

Когда использовать flatMapLatest?

Когда новое входное значение должно переключить приложение на новый inner Flow и отменить старый. Типичный пример - поиск по меняющемуся query или наблюдение данных по выбранному id.

Чем combine отличается от zip?

После того как каждый источник эмитил хотя бы одно значение, combine объединяет последние значения источников при поступлении новых значений. zip объединяет значения попарно и ждёт новый элемент от каждого потока.

Что такое StateFlow?

StateFlow - hot flow для хранения состояния. У него всегда есть текущее value, а новый collector сразу получает последнее значение.

Что такое SharedFlow?

SharedFlow - настраиваемый hot flow для широковещательной доставки значений. Его часто используют для событий и effects, настраивая replay и buffer.

StateFlow или SharedFlow?

StateFlow подходит для состояния, которое должно иметь текущее значение и восстанавливаться новым подписчиком. SharedFlow чаще используют для событий, сигналов и one-off effects.

Что такое lifecycle-aware collection?

Это сбор Flow только в подходящем lifecycle state экрана. В Compose для state обычно используют collectAsStateWithLifecycle(), а во View system - repeatOnLifecycle().

Архитектура

Зачем нужен ViewModel?

ViewModel хранит и координирует state экрана, переживает configuration change и отделяет UI от data orchestration. Он не должен напрямую зависеть от View, Fragment или Activity.

Как хранить UI state?

Обычно как StateFlow<UiState>, где UiState - immutable data class или sealed hierarchy. UI подписывается на state и просто отображает его.

Как разделять state и effects?

State описывает, что экран отображает сейчас, а effects - одноразовые действия вроде navigation или snackbar. State обычно хранится в StateFlow; для временных effects можно использовать SharedFlow или Channel с явными правилами lifecycle и доставки. Критичные результаты операций должны отражаться в state и восстанавливаться после пересоздания UI; для переживания process death их нужно сохранять или восстанавливать из долговременного источника. Сам по себе StateFlow не обеспечивает сохранение данных.

Что такое single source of truth?

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

Нужен ли domain layer всегда?

Нет. Он полезен при сложной бизнес-логике, переиспользуемых use cases и необходимости отделить правила от UI и data layers, но для простого CRUD может быть лишним.

Зачем нужен Repository?

Repository скрывает конкретные источники данных и предоставляет единый контракт для UI или domain layer. Он может объединять сеть, базу, кэш и mapping.

Как спроектировать загрузку данных из API с сохранением в локальную БД?

Repository координирует remote и local data sources: загружает DTO, преобразует их в entities и сохраняет в Room в транзакции. UI наблюдает за Room через Flow как за single source of truth; отдельно определяются refresh policy, stale data и поведение при ошибках.

MVVM или MVI?

MVVM обычно проще и подходит большинству экранов. MVI полезен для сложного state-driven UI, где нужны явные actions, immutable state и предсказуемые переходы.

Нужен ли отдельный framework для MVI?

Нет. MVI-style подход можно реализовать на обычном ViewModel, StateFlow, immutable UiState и sealed actions/effects.

Что такое reducer?

Reducer вычисляет новое состояние на основе предыдущего state и action или result: State + Action -> New State. В строгом Redux-style подходе это чистая функция, поэтому переходы состояния остаются предсказуемыми и удобными для тестирования.

Reducer относится только к Redux или MVI?

Нет. Термин исторически связан с Flux и Redux и часто встречается в MVI, но MVVM тоже может использовать immutable UiState, actions и переходы состояния в стиле reducer. Формальный reducer нужен не каждому экрану.

Что означает reducer-like управление состоянием в современном Android?

Это обновление immutable state через явные переходы, часто прямо во ViewModel с помощью _state.update { it.copy(...) }. Такой код получает преимущества reducer-подхода без отдельного класса Reducer и Redux-style framework.

Что такое Clean Architecture?

Это подход, который разделяет UI, business logic и data access и направляет зависимости к более стабильным абстракциям. Важно применять его прагматично, а не создавать слои без реальной пользы.

По каким критериям разделять приложение на модули?

Модули выделяют по функциональной ответственности, направлению зависимостей, независимости разработки и переиспользованию. Типичные границы - features, design system, networking, storage и общие contracts; каждый модуль должен иметь понятный публичный API.

В чём разница между api и implementation в Gradle?

implementation скрывает зависимость внутри текущего модуля и не добавляет её в compile classpath потребителей. api делает зависимость транзитивно доступной, поэтому её используют, когда её типы входят в публичный API модуля.

Зачем нужна multi-module architecture?

Она помогает разделять features, уменьшать связанность, улучшать ownership и может ускорять инкрементальные сборки в зависимости от графа зависимостей и конфигурации сборки. Но слишком раннее дробление проекта увеличивает Gradle-сложность и boilerplate.

Как работать с legacy-кодом?

Сначала нужно зафиксировать текущее поведение, затем делать небольшие безопасные изменения. Incremental refactoring обычно надёжнее полного rewrite, потому что сохраняет скрытые бизнес-правила и снижает риск.

Dependency Injection

Зачем нужен Dependency Injection?

DI передаёт зависимости в класс извне, уменьшая связанность и упрощая тестирование. Он также помогает централизованно управлять созданием и lifecycle объектов.

Чем DI отличается от Service Locator?

При DI зависимости явно передаются в constructor или method. При Service Locator класс сам запрашивает зависимости у глобального контейнера, из-за чего они становятся менее заметными.

Что такое Hilt?

Hilt - DI framework поверх Dagger с готовой интеграцией в Android lifecycle. Он упрощает создание graph, injection в Activity, Fragment и ViewModel, а также управление scopes.

Чем @Binds отличается от @Provides?

@Binds связывает интерфейс с уже создаваемой реализацией. @Provides используют, когда объект нужно создать вручную, например для Retrofit, Room или внешнего SDK.

Что такое scope в DI?

Scope определяет, как долго один instance переиспользуется внутри dependency graph. Scope должен соответствовать реальному lifecycle объекта, а не назначаться как Singleton по умолчанию.

Почему не стоит делать всё singleton?

Singleton увеличивает lifetime объекта, может удерживать лишнее состояние и усложнять тесты. Долгоживущими стоит делать только действительно общие stateless-сервисы или ресурсы уровня приложения.

Networking

Что такое HTTP?

HTTP - протокол прикладного уровня для обмена сообщениями между клиентом и сервером. Запрос содержит method, URL, headers и иногда body, а ответ - status code, headers и необязательное тело.

Что такое REST?

REST расшифровывается как Representational State Transfer - передача состояния представления. Это архитектурный стиль клиент-серверного взаимодействия вокруг ресурсов и стандартных HTTP-операций; REST API обычно stateless, а представление ресурса передаётся в JSON или другом формате.

Чем GET отличается от POST?

GET используют для получения ресурса и обычно не изменяет состояние сервера. POST отправляет данные в body и часто создаёт ресурс или запускает операцию.

Чем 401 отличается от 403?

401 Unauthorized обычно означает отсутствие или невалидность аутентификации. 403 Forbidden означает, что сервер понял запрос, но отказывается его выполнять; сам код не доказывает успешную аутентификацию.

Как выполнить сетевой запрос, не заморозив UI?

Нужно использовать неблокирующий suspend API из coroutine. Retrofit suspend-функции не выполняют сетевой I/O на main thread, а для блокирующего API следует явно использовать withContext(Dispatchers.IO).

Что такое Retrofit?

Retrofit - type-safe HTTP client для Android и JVM. Он преобразует интерфейс с аннотациями в реализацию API-вызовов и обычно работает поверх OkHttp.

Что такое OkHttp interceptor?

Interceptor перехватывает request и response и может добавлять headers, логировать, обновлять токены или повторять запросы. Важно не помещать туда тяжёлую бизнес-логику.

Как обрабатывать network errors?

Технические ошибки нужно маппить в понятные domain или UI-состояния: no internet, unauthorized, validation error, server error. Пользователю не следует показывать raw exception или HTTP code без контекста.

Что такое GraphQL?

GraphQL - язык запросов и runtime для API, где клиент явно указывает нужные поля. Он уменьшает overfetching, но требует аккуратной работы со схемой, кэшем и ошибками.

Что такое gRPC?

gRPC - RPC framework, который обычно использует контракты сервисов в .proto и бинарную сериализацию Protocol Buffers. Он поддерживает unary и streaming-вызовы и полезен для производительных или realtime-сценариев.

Libraries and Build

Как и куда публикуют Android-библиотеки?

Android-библиотеку собирают как AAR и публикуют в Maven-репозиторий: Maven Central, GitHub Packages, Nexus или Artifactory. Обычно используется Gradle-плагин maven-publish, который загружает artifact, POM, sources и при необходимости documentation.

Как версионировать библиотеку?

Часто используют Semantic Versioning: MAJOR для несовместимых изменений, MINOR для обратно совместимой функциональности и PATCH для исправлений. Для published API полезны API dumps и binary compatibility validation.

Testing

Какие основные типы тестов бывают?

Unit tests проверяют отдельную логику в изоляции, integration tests - совместную работу нескольких компонентов, UI tests - интерфейс и пользовательские действия на устройстве или эмуляторе. End-to-end tests проходят критический сценарий через всё приложение и реальные или близкие к production внешние системы, поэтому используются выборочно.

Как убедиться, что новая feature реализована корректно и снизить количество багов?

Нужно уточнить acceptance criteria и edge cases, спроектировать state и contracts, покрыть критичную логику тестами и пройти code review. Дополнительно проверяют ошибки, retry, повторные действия, lifecycle, offline-сценарии и безопасный rollout.

Как ты проходишь путь от идеи feature до реализации?

Сначала уточняю пользовательскую цель, требования и ограничения, затем проектирую data flow, UI state, actions и errors. После этого выбираю достаточную архитектуру, разбиваю работу на проверяемые шаги, пишу код и тесты, проверяю сценарии, провожу review и наблюдаю результат после релиза.

Что тестировать в первую очередь?

В первую очередь - бизнес-логику, mapping, use cases, reducers и ViewModel state transitions. Приоритет определяется риском и ценностью, а не стремлением покрыть всё одинаково.

Mocks или fakes?

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

Как тестировать ViewModel?

Нужно подавать входные actions и проверять observable state transitions и effects. Лучше тестировать поведение, а не private-методы и внутренние детали реализации.

Как тестировать coroutines и Flow?

Используют runTest, test dispatchers и виртуальное время. Это делает тесты быстрыми и детерминированными без Thread.sleep().

Зачем нужны UI tests?

UI tests проверяют критические пользовательские сценарии и интеграцию компонентов. Они дороже и хрупче unit tests, поэтому обычно используются выборочно.