← Назад к вопросам

Какие знаешь варианты обработки ошибок?

1.0 Junior🔥 151 комментариев
#Опыт и софт-скиллы

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Подходы к обработке ошибок в Android-разработке

Обработка ошибок — системный подход к управлению исключительными ситуациями. В Android мы используем комбинацию парадигм.

1. Исключения (Exceptions)

Проверяемые (checked) и непроверяемые (unchecked) исключения. Первые обязательны для обработки, вторые (RuntimeException) обычно указывают на ошибки программирования.

// Проверяемое исключение
try {
    val file = FileInputStream("file.txt")
} catch (e: FileNotFoundException) {
    Log.e("App", "Файл не найден", e)
    showErrorToast()
}

// Непроверяемое исключение
fun calculate(value: String): Int {
    return try {
        value.toInt() * 2 // Может выбросить NumberFormatException
    } catch (e: NumberFormatException) {
        Log.w("App", "Некорректный формат числа")
        defaultValue
    }
}

2. Возврат кодов ошибок

Традиционный подход, особенно в нативных библиотеках и низкоуровневых операциях.

fun saveData(data: String): ResultCode {
    return if (writeToStorage(data)) {
        ResultCode.SUCCESS
    } else {
        ResultCode.WRITE_ERROR
    }
}

enum class ResultCode {
    SUCCESS,
    WRITE_ERROR,
    NETWORK_ERROR,
    VALIDATION_ERROR
}

3. Функциональный подход с Result<T>

Современный паттерн, популярный в Kotlin, который инкапсулирует результат или ошибку.

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

fun fetchUserData(userId: String): Result<User> {
    return try {
        val user = apiService.getUser(userId)
        Result.Success(user)
    } catch (e: IOException) {
        Result.Error(e)
    }
}

// Использование
when (val result = fetchUserData("123")) {
    is Result.Success -> updateUI(result.data)
    is Result.Error -> showErrorDialog(result.exception)
}

4. LiveData/StateFlow с состояниями загрузки

Для UI-слоя в архитектурах типа MVVM, MVI.

sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<out T>(val data: T) : UiState<T>()
    data class Error(val message: String, val retryAction: () -> Unit) : UiState<Nothing>()
}

class UserViewModel : ViewModel() {
    private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val userState: StateFlow<UiState<User>> = _userState.asStateFlow()
    
    fun loadUser() {
        viewModelScope.launch {
            _userState.value = UiState.Loading
            try {
                val user = repository.getUser()
                _userState.value = UiState.Success(user)
            } catch (e: Exception) {
                _userState.value = UiState.Error(
                    message = "Не удалось загрузить пользователя",
                    retryAction = { loadUser() }
                )
            }
        }
    }
}

5. Callback-функции с ошибками

Для асинхронных операций, особенно в legacy-коде.

interface DataCallback<T> {
    fun onSuccess(data: T)
    fun onError(error: Throwable)
}

fun loadData(callback: DataCallback<String>) {
    thread {
        try {
            val data = performNetworkRequest()
            callback.onSuccess(data)
        } catch (e: Exception) {
            callback.onError(e)
        }
    }
}

6. Корутины с исключениями

Structured Concurrency в Kotlin корутинах предоставляет мощные механизмы.

viewModelScope.launch(
    CoroutineExceptionHandler { _, throwable ->
        // Централизованная обработка
        handleError(throwable)
    }
) {
    try {
        val data = withContext(Dispatchers.IO) {
            repository.fetchData() // Может выбрасывать исключения
        }
        processData(data)
    } catch (e: NetworkException) {
        // Специфичная обработка
        showNetworkError(e)
    } catch (e: ValidationException) {
        showValidationError(e)
    }
}

7. Контракты и валидация

Предотвращение ошибок через проверку предусловий.

fun updateProfile(name: String, age: Int) {
    require(name.isNotBlank()) { "Имя не может быть пустым" }
    require(age in 1..120) { "Некорректный возраст" }
    
    // Основная логика
    repository.saveProfile(name, age)
}

Ключевые принципы

  • Fail-fast: Быстрое выявление ошибок
  • Graceful degradation: Плавная деградация функционала
  • User-friendly messages: Понятные сообщения пользователю
  • Logging и мониторинг: Логирование для диагностики
  • Локализация ошибок: Обработка близко к источнику проблемы
  • Retry-логика: Стратегии повторных попыток для временных сбоев

Выбор подхода зависит от контекста: для UI удобны StateFlow/LiveData с sealed-классами, для бизнес-логики — Result-типы, для асинхронных операций — корутины с обработчиками исключений. Важно сочетать защитное программирование с четкой коммуникацией ошибок пользователю. В современных приложениях часто комбинируют несколько подходов на разных уровнях приложения.