O tipo folly::Expected<T, E> estabelece uma disciplina rigorosa para gestão de falhas, combinando verificação estática com salvaguardas em tempo de execução. A estrutura obriga o desenvolvedor a distinguir explicitamente entre condições operacionais previsíveis e violações de contrato no código, impedindo que erros sejam silenciosamente ignorados.
Mecanismo de Contrato e Verificação Interna
A implementação baseia-se em um guardião de acesso que valida o estado antes de liberar o payload. O trecho abaixo ilustra a lógica de proteção aplicada internamente:
template<typename Payload, typename Fault>
class Expected {
void enforceContract() const {
if (FOLLY_UNLIKELY(!this->hasValue())) {
if (FOLLY_LIKELY(this->hasError())) {
// Propaga exceção tipada com a falha original
throw_exception<BadExpectedAccess<Fault>>(this->stored_fault);
}
throw_exception<BadExpectedAccess<void>>();
}
}
public:
const Payload& value() const& {
enforceContract(); // Barreira de segurança
return this->data_;
}
bool hasValue() const noexcept;
bool hasError() const noexcept;
const Fault& error() const&;
Payload value_or(U&& fallback) const&;
};
Modelo de Dupla Camada de Falhas
- Erros de Domínio: Cenários antecipados pela regra de negócio (ex: saldo insuficiente, gateway indisponível, formato inválido).
- Erros de Contrato: Equívocos de prograamção (ex: invocar
value()sem validar o estado previamente).
Restrições de API e Padrões de Consumo
A interface bloqueia acesssos inseguros através de restrições de tipo e checagens ativas. O compilador e o runtime trabalham em conjunto para impor fluxos seguros:
// ✅ Fluxo validado
Expected<Transaction, LedgerFault> response = processPayment(req);
// 1. Verificação explícita
if (response.hasValue()) {
commit(response.value());
} else {
rollback(response.error());
}
// 2. Fallback estruturado
Transaction safeTx = response.value_or(createVoidTransaction());
// 3. Encadeamento funcional
auto receipt = response
.then([](Transaction tx) { return generateReceipt(tx); })
.orElse([](LedgerFault f) { return handleFault(f); });
// ❌ Acesso inseguro (interrompido em runtime)
Transaction raw = response.value(); // 💥 Lança BadExpectedAccess se houver falha
Estratégias de Implementação por Camada
1. Regra de Negócio - Validação Explícita
void handleCheckout(const std::string& couponCode) {
auto validation = DiscountEngine::validate(couponCode);
if (validation.hasValue()) {
applyDiscount(validation.value());
} else {
auto fault = validation.error();
switch (fault) {
case CouponFault::EXPIRED:
notifyUser("Cupom expirado.");
break;
case CouponFault::INVALID_REGION:
notifyUser("Cupom não aplicável à sua região.");
break;
}
}
}
2. Configuração Interna - Acesso Assertivo
void bootstrapSystem() {
// Dados internos devem ser íntegros. Se falharem, o estado é corrompido.
// A chamada direta a .value() age como asserção em tempo de execução.
auto dbConfig = ConfigParser::loadDatabaseSettings().value();
initializePool(dbConfig);
}
3. Programação Defensiva - Valor de Reserva
void setupCache() {
auto ttlResult = CachePolicy::parseTTL(rawInput);
// Utiliza padrão seguro caso a análise falhe
auto policy = ttlResult.value_or(getDefaultTTL());
applyCachePolicy(policy);
}
4. Pipeline Funcional
Expected<AuditLog, PipelineFault> runAuditPipeline(const std::string& rawEvent) {
return EventParser::decode(rawEvent)
.then([](ParsedEvent evt) { return enrichMetadata(evt); })
.then([](EnrichedEvent data) { return persistToAuditDB(data); })
.orElse([](auto fault) {
logSystemFailure("Pipeline interrompido", fault);
return generateFallbackLog();
});
}
Gestão de Exceções e Limites de Captura
A exceção BadExpectedAccess não deve ser tratada como mecanismo de fluxo de negócio. Ela sinaliza um defeito no código.
| Camada | Deve Capturar? | Justificativa |
|---|---|---|
| Regra de Negócio | Não | Mascara violações de contrato e quebra a previsibilidade |
| Suites de Teste | Sim | Valida que acessos inseguros são corretamente interceptados |
| Framework/Loop Principle | Sim | Garante encerramento controlado e geração de core dumps |
| Telemetria/APM | Sim | Registra violações para correção em ciclos futuros |
Fluxos Corretos vs. Antipadrões
// ❌ Antipadrão: Usar exceção para recuperar erro de domínio
void flawedFlow() {
try {
auto res = fetchInventory();
auto stock = res.value(); // 💥 Esqueceu de verificar hasValue()
updateUI(stock);
} catch (const BadExpectedAccess<InventoryFault>& ex) {
// 💀 Trata falha de programação como se fosse regra de negócio
handleInventoryFault(ex.error());
}
}
// ✅ Padrão: Separação clara entre domínio e contrato
void robustFlow() {
auto res = fetchInventory();
if (res.hasValue()) {
updateUI(res.value()); // Seguro: contrato validado
} else {
// Tratamento direto da falha operacional
handleInventoryFault(res.error());
}
}
void handleInventoryFault(InventoryFault fault) {
switch (fault) {
case InventoryFault::SYNC_TIMEOUT:
scheduleRetry();
break;
case InventoryFault::WAREHOUSE_OFFLINE:
switchToBackupWarehouse();
break;
case InventoryFault::DATA_MISMATCH:
triggerReconciliation();
break;
}
}
Captura no Nível da Aplicação
int main() {
try {
runApplicationLoop();
} catch (const BadExpectedAccess<void>& violation) {
LOG(FATAL) << "Violação de contrato detectada: " << violation.what();
return EXIT_FAILURE; // Encerramento imediato para investigação
}
}
Integração em Serviços Reais
class PaymentGateway {
public:
Expected<Authorization, GatewayFault> authorize(const std::string& token) {
auto parsed = TokenValidator::parse(token);
if (parsed.hasError()) {
return makeUnexpected(GatewayFault::MALFORMED_TOKEN);
}
// Após validação, o acesso é seguro dentro do escopo interno
return submitToProcessor(parsed.value());
}
private:
Expected<Authorization, GatewayFault> submitToProcessor(TokenData data) {
// Lógica de comunicação com adquirente...
}
};
// Consumo no cliente
void processOrder(const std::string& userToken) {
PaymentGateway gateway;
auto auth = gateway.authorize(userToken);
if (auth.hasValue()) {
finalizeOrder(auth.value());
} else {
displayPaymentError(auth.error());
}
}
Princípios Fundamentais da Abordagem
- Inevitabilidade: O caminho feliz e o caminho de erro são explicitamente tipados, impossibilitando ignorar falhas acidentalmente.
- Transparência: A assinatura da função documenta todas as falhas operacionais possíveis sem depender de documentação externa.
- Segurança Determinística: O uso correto garante ausência de crashes inesperados; o uso incorreto falha imediatamente com rastreamento preciso.
- Otimização para Depuração:
BadExpectedAccessage como um circuito de proteção, expondo violações de contrato no exato momento em que ocorrem.
A arquitetura força a adoção do ciclo verificar-acessar, transformando a gestão de erros em uma prática estruturada e verificável pelo compilador e pelo runtime.