# Padrões de Complexidade em Android/Kotlin ## 1. Complexidade Ciclomática (McCabe) ### Definição ``` CC(G) = E - N + 2P onde: E = número de arestas do grafo de fluxo de controle N = número de nós P = número de componentes conectados (geralmente 1) Equivalente prático: CC = 1 + número de pontos de decisão (if, when, for, while, &&, ||, catch) Limites recomendados: CC ≤ 5: simples, fácil de testar CC 6-10: moderado, testável CC 11-20: complexo, difícil de testar — refatorar CC > 20: muito complexo — dividir obrigatoriamente ``` ### Padrões Android com Alta CC #### LlmClientFactory (estimado CC ≈ 18) ```kotlin fun create(provider: LlmProvider, context: Context?): LlmClient { return when (provider) { // +10 (11 cases) OPENAI -> { val key = store.get("openai") if (key != null) { // +1 OpenAiClient(key) } else { throw ConfigError() } } CLAUDE -> { val key = store.get("claude") if (key != null) { // +1 ClaudeClient(key) } else { throw ConfigError() } } // ... mais 9 cases similares RPA_CHATGPT -> { if (context != null) { // +1 RpaClient(context, CHATGPT) } else { throw ContextRequiredError() } } } } // CC ≈ 1 + 10 + 3 = 14 — deve ser refatorado ``` **Refatoração com Strategy + Registry (CC ≈ 2):** ```kotlin typealias ClientFactory = (config: ProviderConfig) -> LlmClient val registry: Map = mapOf( OPENAI to { config -> OpenAiClient(config.requireKey()) }, CLAUDE to { config -> ClaudeClient(config.requireKey()) }, // ... ) fun create(provider: LlmProvider, config: ProviderConfig): LlmClient { return registry[provider]?.invoke(config) // CC = 1 ?: throw UnsupportedProviderError(provider) // CC + 1 = 2 } ``` --- ## 2. Complexidade Cognitiva (Sonar) ### Diferença de McCabe ``` Complexidade ciclomática conta decisões. Complexidade cognitiva mede o esforço humano de leitura. Penalidades extras: - Estruturas aninhadas: cada nível de nesting adiciona +1 - Breaks de fluxo (break, continue, goto): +1 - Sequências de expressões booleanas: +1 por operador diferente ``` ### Exemplo: HomeScreen.kt ```kotlin // Potencial complexidade cognitiva alta em Compose: @Composable fun HomeScreen(viewModel: MainViewModel) { val state by viewModel.state.collectAsStateWithLifecycle() when (state.pipelineState) { // +1 IDLE -> { ... } RECORDING -> { if (state.isBluetoothConnected) { // +2 (nesting) if (state.audioSource == SCO) { // +3 (nesting) ScoRecordingUI() } else { GenericRecordingUI() } } else { PhoneMicUI() } } // ... } } // Cognitiva estimada: ~15-25 dependendo da implementação completa ``` --- ## 3. Análise de Acoplamento ### Métricas de Acoplamento ``` Ca (Afferent Coupling): quantos módulos dependem de X Alto Ca → X é muito usado → difícil de mudar core-logging: Ca = 6 (todos os módulos) → MUITO ACOPLADO Ce (Efferent Coupling): quantos módulos X depende Alto Ce → X depende de muita coisa → frágil app: Ce = 6 → alto, mas esperado para orchestrator Instabilidade I = Ce / (Ca + Ce) I → 0: módulo estável (difícil de mudar) I → 1: módulo instável (fácil de mudar) Para módulos Auri: core-logging: Ca=6, Ce=0 → I = 0 (ESTÁVEL) app: Ca=0, Ce=6 → I = 1 (INSTÁVEL — esperado: é a camada mais volátil) llm: Ca=1(app), Ce=1(core-logging) → I = 0.5 (EQUILIBRADO) ``` ### Lei de Dependência Estável (Martin) ``` Regra: módulos devem depender apenas de módulos mais estáveis que eles I(dependente) > I(dependência) para cada aresta Verificação Auri: app(I=1) → bluetooth(I≈0.5) ✅ (1 > 0.5) app(I=1) → core-logging(I=0) ✅ (1 > 0) voice(I≈0.5) → audio(I≈0.3) ✅ (0.5 > 0.3) voice(I≈0.5) → core-logging(I=0) ✅ (0.5 > 0) ``` --- ## 4. Complexidade de Interfaces Android ### Activity/Fragment Lifecycle Complexity ``` Android Activity lifecycle tem 7 estados principais: CREATED → STARTED → RESUMED → PAUSED → STOPPED → DESTROYED (+ RESTARTED) Transições válidas formalmente: T = { CREATED → STARTED (onStart), STARTED → RESUMED (onResume), RESUMED → PAUSED (onPause), PAUSED → STOPPED (onStop) ou PAUSED → RESUMED (onResume), STOPPED → DESTROYED (onDestroy) ou STOPPED → CREATED (onRestart), CREATED → DESTROYED (onDestroy — sem start, raro) } Armadilha: código em onResume assume estado "limpo" mas pode ser chamado após onPause sem passar por onCreate → estado parcialmente inicializado ``` ### Jetpack Compose Recomposition ``` Complexidade de recomposição: - Toda chamada @Composable pode ser recomposta a qualquer momento - Leitura de State dentro de @Composable cria subscrição automática - Recomposição é inteligente: só recompõe o subárvore mínimo necessário Problemas comuns: 1. Lambda capture de variáveis mutáveis → recomposição inesperada 2. remember { } sem key → não recomputa quando dependências mudam 3. derivedStateOf { } ausente → recalcula em toda recomposição Métrica: número de reads de State por @Composable > 5 reads por composable → considerar dividir em menores ``` --- ## 5. Análise de Complexidade de Algoritmos Específicos ### Tap Detection (HeadsetButtonController) ``` Problema: detectar single-tap, double-tap, long-press Input: sequência de eventos key_down, key_up com timestamps Algoritmo atual (estimado): - Janela de 350ms para double-tap detection - Threshold de 600ms para long-press - Implementação: coroutine com delay + cancel Complexidade: - Tempo: O(1) por evento (delay é assíncrono) - Espaço: O(1) estado (apenas timestamps) - Latência: 350ms para confirmar single-tap (inevitável) Alternativa: máquina de estados explícita Estado = (tapCount: Int, lastTapTime: Long, isLongPressing: Boolean) Mais testável e mais formal que delays aninhados ``` ### Audio Priority Selection (AudioRouteController) ``` Problema: dado conjunto de fontes disponíveis, selecionar melhor Entrada: Set (tamanho tipicamente 1-4) Algoritmo: max(availableSources, key=priority) Complexidade: O(n) onde n = |availableSources| ≤ 5 Otimização: O(1) possível com ordenação antecipada (Set ordenado) Invariante de corretude: ∀ s ∈ availableSources: priority(selectedSource) ≥ priority(s) ``` ### LLM Response Processing ``` Problema: processar streaming response de LLM Entrada: Stream de tokens Algoritmos possíveis: 1. Buffer completo: acumula tudo, processa de uma vez - Latência: O(total_tokens / bandwidth) — alta - Memória: O(total_tokens) — linear 2. Streaming parcial (implementar): acumula até sentença completa - Detectar fim de sentença: regex \.|\!|\? - Latência percebida: O(primeira_sentença / bandwidth) — baixa - Complexidade: O(1) memória por sentença processada Recomendação: streaming parcial para melhor UX Threshold de sentença: ~15-20 palavras ou primeiro ., !, ? ``` --- ## 6. Big-O das Operações Principais ``` Operação | Complexidade | Notas ──────────────────────────────────────┼──────────────┼───────────────────── Bluetooth scan | O(1) t-médio | Timeout-bounded SCO connect | O(1) | Fixed protocol Audio route selection | O(n) | n=sources (~5) STT (SpeechRecognizer) | O(w²) pior | w=palavras (HMM) LLM inference (local Ollama) | O(t·d²) | t=tokens, d=dimensão LLM inference (API) | O(t) perceb. | Latência de rede TTS synthesis | O(c) | c=caracteres Tool execution (e.g., set alarm) | O(1) | Android API call Gmail search | O(n log n) | n=emails (server-side) StateFlow update (CAS) | O(1) amort. | Lock-free Coroutine launch | O(1) | ~1μs overhead ``` --- ## 7. Análise de Entropia de Código ### Definição de Entropia de Shannon para Sistemas de Software ``` Complexidade de Halstead: η₁ = número de operadores distintos η₂ = número de operandos distintos N₁ = total de ocorrências de operadores N₂ = total de ocorrências de operandos Volume: V = (N₁+N₂) · log₂(η₁+η₂) Dificuldade: D = (η₁/2) · (N₂/η₂) Esforço: E = D · V Interpretar: - Volume alto → arquivo grande/complexo - Dificuldade alta → muitos operadores únicos vs. repetição - Esforço alto → difícil de entender Para arquivos Kotlin médios: MainViewModel.kt: estimado V ≈ 5000-10000, D ≈ 15-25 — COMPLEXO LlmProvider.kt: estimado V ≈ 500-1000, D ≈ 5-10 — SIMPLES ```