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

Расскажи про свою самую сложную задачу

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

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

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

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

Одна из самых сложных задач: Архитектурный рефакторинг живого проекта с 500k+ пользователей

Моей самой сложной и комплексной задачей был полный архитектурный рефакторинг крупного банковского приложения для Android с аудиторией более 500 000 активных пользователей. Приложение находилось в активной разработке более 5 лет и представляло собой классический пример "большой кучи кода" (Big Ball of Mud).

Суть проблемы и вызовы

Основные проблемы, которые нужно было решить:

  1. Монолитный GodObject-класс: Ключевая бизнес-логика, навигация, работа с UI и сетевыми запросами была сосредоточена в одном Activity на 5000+ строк кода.
  2. Тесная связанность (Tight Coupling): Классы знали друг о друге напрямую, что делало любой код хрупким. Изменение одной кнопки могло сломать процесс оплаты.
  3. Отсутствие слоистой архитектуры: Не было четкого разделения на Data Layer, Domain Layer и UI Layer. Сетевые модели использовались напрямую во ViewModel и даже во View.
  4. Нулевая тестируемость: Из-за сильной связанности и зависимостей от Android SDK (Context, Activity) написание юнит-тестов было физически невозможно.
  5. Высокая стоимость изменений: Любая новая фича или исправление бага занимали в 3-4 раза больше времени, чем должно было, и часто вносили регрессии.

Стратегия и реализация

Рефакторинг нельзя было провести "рывком", так как приложение должно было выходить еженедельно. Мы выбрали стратегию постепенного, инкрементального рефакторинга.

Шаг 1: Внедрение Dependency Injection (DI)

Первым делом мы внедрили Hilt (до этого использовался ручной DI или Singleton-ы). Это позволило начать управлять зависимостями и разорвать прямые связи между классами.

// Было (проблема):
class OldPaymentActivity {
    private val apiClient = ApiClient() // Прямое создание
    private val repository = PaymentRepository(apiClient)
}

// Стало (решение):
@HiltAndroidApp
class MyApplication : Application()

@Module
@InstallIn(ViewModelComponent::class)
object AppModule {
    @Provides
    fun provideApiClient(): ApiClient = ApiClient()
}

@HiltViewModel
class NewPaymentViewModel @Inject constructor(
    private val repository: PaymentRepository // Зависимость внедряется
) : ViewModel()

Шаг 2: Разделение на слои (Clean Architecture)

Мы начали выделять логику из монолита в отдельные, четко разделенные слои:

  • Data Layer: Репозитории, Data Sources (Remote, Local), Data Mappers.
  • Domain Layer: Use Cases (Интеракторы), содержащие чистую бизнес-логику без Android-зависимостей.
  • UI Layer: ViewModel (c StateFlow/SharedFlow), Compose/Fragment-экран.
// Domain Layer - Независим от Android
class GetUserAccountUseCase @Inject constructor(
    private val repository: AccountRepository
) {
    suspend operator fun invoke(userId: String): Result<Account> {
        return repository.getAccount(userId)
    }
}

// UI Layer (ViewModel)
@HiltViewModel
class AccountViewModel @Inject constructor(
    private val getAccountUseCase: GetUserAccountUseCase
) : ViewModel() {
    private val _state = MutableStateFlow<AccountState>(AccountState.Loading)
    val state: StateFlow<AccountState> = _state.asStateFlow()

    fun loadAccount() {
        viewModelScope.launch {
            getAccountUseCase("userId").collect { result ->
                _state.value = result.toState() // Маппинг в UI State
            }
        }
    }
}

Шаг 3: Рефакторинг навигации

Мы заменили хаотичные startActivity() и FragmentTransaction на однонаправленную навигацию с помощью NavComponent и собственного NavigationManager, который обрабатывал события из ViewModel.

Ключевые сложности

  • Параллельная разработка: Команда продолжала добавлять новые функции в старый код, пока мы постепенно переписывали модули. Требовалась отличная коммуникация.
  • Обучение команды: Нужно было обучить 7 разработчиков новым принципам, паттернам и библиотекам, проводя код-ревью и воркшопы.
  • Поиск точки входа: Выбор первого модуля для рефакторинга был критичен. Мы начали с относительно изолированного, но важного модуля "Карты лояльности".
  • Состояние гонки (Race Conditions): В старом коде были скрытые баги, связанные с асинхронностью, которые проявились только при декомпозиции.

Результаты

Процесс занял около 9 месяцев, но принес фундаментальные улучшения:

  1. Снижение количества критических багов на 60% благодаря изоляции слоев и тестируемости.
  2. Рост покрытия кода юнит-тестами с ~2% до 65+% на новых модулях. Use Cases и ViewModel стали легко тестируемыми.
  3. Ускорение разработки новых функций на 40% за счет переиспользования готовых слоев и четких контрактов.
  4. Повышение стабильности приложения (ANR-ы и краши снизились) за счет правильного управления жизненными циклами и фоновыми задачами в Domain Layer.

Этот опыт глубоко закрепил понимание, что архитектура — это не роскошь, а необходимость для долгосрочной поддержки проекта. Главный вывод: масштабный рефакторинг живого проекта возможен, но требует тщательного планирования, инкрементального подхода, терпения и полной поддержки команды.

Расскажи про свою самую сложную задачу | PrepBro