1. Por Que as Funções Precisam Devolver Valores? A Máquina de Processamento

Nas aulas anteriores, aprendemos que subprogramas podem receber dados do mundo exterior através de seus parâmetros. No entanto, se uma sub-rotina apenas receber dados e não devolver nenhuma resposta para quem a invocou, ela fica restrita a imprimir coisas na tela ou a alterar variáveis por referência.

Considere uma analogia com a engenharia mecânica: uma função é como uma máquina de moer café. Você insere os grãos inteiros na abertura superior (os parâmetros de entrada), a máquina aciona suas lâminas e motores internos (o processamento algorítmico) e entrega o pó de café refinado na bandeja inferior (o valor de retorno).

Sem a capacidade de retornar valores, você não conseguiria calcular o preço final de um carrinho de compras, não conseguiria verificar se um usuário digitou uma senha correta para tomar uma decisão em um se/senao e não conseguiria calcular senos, cossenos e raízes quadradas. O comando retorno (ou return) é o tubo de saída por onde o resultado computado é devolvido às mãos do programa principal.

Fluxograma da instrução retorne ilustrando o cálculo, a saída imediata e a geração de código morto inalcançável
Figura 1: A mecânica do comando retorne. Assim que a linha de retorno é alcançada, o valor é despachado para a variável receptora e o frame da função é destruído.

2. Anatomia do Comando Retorne: O Ponto Final e a Desalocação

O comando retorno realiza duas ações simultâneas e inseparáveis a nível de processador:

  1. Avaliação e Entrega do Valor: A expressão à direita da palavra retorno é resolvida e seus bits são copiados para um registrador especial da CPU (ou área de transferência da pilha);
  2. Interrupção Abrupta do Fluxo (Desalocação): A execução da função é interrompida imediatamente. O Stack Frame da função é desempilhado da memória RAM e o Program Counter salta de volta para a linha exata que fez a chamada no bloco chamador.
logicabr.dev Retorno Simples
funcao calcularAreaRetangulo(numero base, numero altura) {
    // A expressão (base * altura) é calculada e devolvida imediatamente:
    retorno base * altura;

    // [ATENCAO] Qualquer instrução após retorno é inalcançável (dead code):
    saida("Isto é código morto e inalcançável!");
}

Observe com atenção o aviso de código morto: ao atingir retorno, a sub-rotina fecha suas portas. Nenhuma linha escrita abaixo dela terá a menor chance de ser processada.

3. Tipagem Estrita do Retorno: Inteiro, Real, Lógico e Caractere

Em linguagens fortemente tipadas (como Portugol estruturado, C, Java, C#, Rust e TypeScript), o cabeçalho da função exige um Contrato de Tipo de Retorno. Esse tipo informa ao compilador exatamente qual categoria de dado será devolvida após a execução dos dois pontos (: tipo):

Tipo de Retorno Exemplo de Assinatura Valor Tipicamente Devolvido Uso Típico
numero (inteiro) funcao contarVogais(texto txt) 0, 42, -15 Contagens, índices, posições e códigos de erro numéricos.
numero / moeda (decimal) funcao calcularMedia(numero n1, numero n2) 7.5, 3.14159, 0.0 Cálculos financeiros, medidas físicas, porcentagens e médias.
logico funcao ehMaiorDeIdade(numero idade) VERDADEIRO ou FALSO Predicados de validação, checagens de regras e permissões.
texto funcao formatarMoeda(moeda val) "R$ 150,00", "APROVADO" Textos formatados, nomes compostos e siglas de status.

Regra Inviolável de Tipagem: Em linguagens com contrato de retorno (C, Java), uma função que devolve int não pode executar return "Sucesso". No logicabr.dev o tipo não vai no cabeçalho, mas a regra continua valendo na ponta de quem recebe: se a função faz retorno "Sucesso" e você guarda o resultado em numero x = minhaFuncao(), a execução é interrompida por valor inválido para numero (Type Mismatch).

4. A Mágica da Composição: Funções Diretamente Dentro de Expressões

Uma das maiores vantagens das funções com retorno sobre procedimentos é que a chamada da função se comporta como se fosse um valor concreto. Isso permite que você encadeie chamadas dentro de fórmulas matemáticas ou até as passe como argumentos para outras funções, sem a necessidade de criar dezenas de variáveis temporárias descartáveis:

Diagrama de composição de funções sendo resolvidas e substituídas em uma fórmula matemática
Figura 2: Composição Funcional. Cada chamada de função é avaliada de forma independente, e seu valor resultante é conectado diretamente na expressão exterior.

Compare a elegância das duas abordagens para calcular a hipotenusa de um triângulo retângulo \(c = \sqrt{a^2 + b^2}\):

Comparação de Estilos de Código Variáveis Excessivas vs Composição Limpa
// ABORDAGEM VERBOSA (poluição com variáveis intermediárias temporárias):
quadA = quadrado(catetoA)
quadB = quadrado(catetoB)
soma = quadA + quadB
hipotenusa = raiz(soma)

// ABORDAGEM ELEGANTE COM COMPOSIÇÃO FUNCIONAL (uma única linha expressiva):
hipotenusa = raiz(quadrado(catetoA) + quadrado(catetoB))

A composição funcional reduz o número de variáveis que você precisa gerenciar mentalmente, diminui o risco de nomes colidirem e deixa o código incrivelmente próximo da notação matemática universal.

5. O Padrão Clean Code: Early Return (Guarda de Saída Antecipada)

Na programação estruturada clássica dos anos 1970, recomendava-se que cada função tivesse rigorosamente um único comando retorno na última linha. Essa regra, se seguida cegamente em problemas do mundo real, costuma gerar uma anomalia visual terrível conhecida como a Pirâmide da Ruína (Pyramid of Doom) ou código flecha:

Comparativo entre o anti-padrão da Pirâmide da Ruína e o padrão Clean Code Early Return com guard clauses
Figura 3: Pirâmide da Ruína vs Early Return. Cláusulas de guarda eliminam a indentação profunda, deixando o caminho de sucesso (happy path) completamente plano e linear.

Com o padrão Early Return (Retorno Precoce), você inverte o pensamento tradicional:

  1. Você testa primeiro os casos de erro, exceção ou dados inválidos com Cláusulas de Guarda (Guard Clauses);
  2. Se alguma regra for violada, você executa um retorno imediatamente, encerrando a função na largada;
  3. O restante da função fica livre de qualquer senao, permitindo que o Caminho Feliz (Happy Path) seja executado em linha reta no nível principal de indentação.
logicabr.dev Early Return na Prática
funcao aprovarEmprestimo(moeda salario, moeda valorParcela, numero scoreCredito) {
    // GUARDA 1: Verifica se a renda é compatível
    se (salario < 1500.00) {
        retorno "REJEITADO: Renda mínima não atingida";
    }

    // GUARDA 2: Verifica comprometimento de renda (máx 30%)
    se (valorParcela > (salario * 0.30)) {
        retorno "REJEITADO: Parcela excede 30% da renda";
    }

    // GUARDA 3: Verifica pontuação de risco
    se (scoreCredito < 600) {
        retorno "REJEITADO: Score de crédito insuficiente";
    }

    // CAMINHO FELIZ (HAPPY PATH): Todas as guardas passaram!
    retorno "APROVADO: Crédito liberado para depósito!";
}

Perceba a facilidade de leitura deste código: qualquer novo desenvolvedor entende em 5 segundos cada uma das regras de corte do banco, sem ter que caçar onde cada bloco se/senao fecha.

6. Funções Predicativas: O Poder de Nomes Semânticos

Uma função predicativa (ou predicado) é uma função cujo tipo de retorno é estritamente logico (booleano). A convenção internacional de Clean Code dita que essas funções devem sempre ter nomes formulados como uma pergunta sim/não:

  • ehPar(numero): Devolve VERDADEIRO se o número for divisível por 2;
  • temSaldoSuficiente(conta, valor): Devolve VERDADEIRO se o saldo for maior ou igual ao saque;
  • ehBissexto(ano): Devolve VERDADEIRO se o ano atende às regras do calendário gregoriano;
  • estaVazio(lista): Devolve VERDADEIRO se o tamanho for zero.

Veja como o seu código ganha a fluidez de uma frase lida em um livro:

logicabr.dev Uso de Funções Predicativas
funcao ehPar(numero n) {
    retorno (resto(n, 2) == 0);  // A própria expressão relacional já é booleana!
}

numero numeroDigitado = 14;

// O comando se lê com clareza em linguagem natural:
se (ehPar(numeroDigitado)) {
    saida("O número informado é par!");
}

7. O Desafio de Retornar Múltiplos Valores

Por definição matemática formal, o comando retorno devolve um único valor por vez. E se você precisar de uma função que calcule simultaneamente o Quociente e o Resto de uma divisão inteira? Ou que devolva as coordenadas cartesianas \((X, Y)\) de um ponto?

As linguagens de programação resolveram essa limitação histórica de quatro formas genéricas:

  1. Parâmetros por Referência (Portugol e C/C++): Os dados extras são devolvidos preenchendo variáveis passadas com var (ou ponteiros). No logicabr.dev, o equivalente é passar um vetor ou registro para a função preencher;
  2. Empacotamento em Vetores ou Registros: A função cria uma estrutura composta contendo múltiplos campos e retorna essa estrutura única (estudaremos isso nas Trilhas 5 e 6);
  3. Tuplas Nativas (Python): Em Python você pode escrever return a, b, que na verdade devolve uma tupla (a, b) que pode ser desempacotada na chamada;
  4. Retorno Múltiplo de Primeira Classe (Linguagem Go): O compilador de Go permite declarar formalmente func dividir(a, b int) (int, int) e retornar return quociente, resto diretamente.

8. Panorama Multi-Linguagem: Como Cada Linguagem Trata o 'return'

Python 3 (Múltiplos Retornos com Tupla) Python moderno
# Python permite múltiplos retornos empacotados automaticamente:
def calcular_extremos(lista):
    return min(lista), max(lista)

# Desempacotamento elegante na chamada:
menor, maior = calcular_extremos([10, 5, 80, 2])
JavaScript (ES6+) Desestruturação de Objeto
// Retornando múltiplos valores através de objeto literal:
function obterDimensoes() {
    return { largura: 1920, altura: 1080 };
}
const { largura, altura } = obterDimensoes();
Linguagem Go (Golang) Retorno Múltiplo Nativo
// Em Go, retorno múltiplo é suportado nativamente na gramática:
func dividir(a, b int) (int, int) {
    return a / b, a % b
}
quociente, resto := dividir(10, 3)

9. 5 Armadilhas Críticas com Retornos de Funções

Fique atento aos deslizes conceituais mais comuns envolvendo retornos:

  1. O Retorno Faltante em Ramos Condicionais: Se você colocar comandos retorno dentro de um se e esquecer de cobrir o senao, o programa poderá alcançar o final da função sem devolver nenhum valor, gerando falha de execução ou retorno indefinido;
  2. Confundir 'saida' (print) com 'retorno' (return): Um dos erros mais clássicos de quem está começando é imprimir o resultado na tela com saida(resultado) achando que isso equivale a devolver o valor. Imprimir na tela apenas pinta pixels no monitor; a variável chamadora não recebe absolutamente nada!
  3. Código Morto Após o Retorno: Lembrar sempre que qualquer linha posicionada imediatamente após um retorno é sumariamente descartada pelo processador;
  4. Retornar o Tipo Incorreto: Em linguagens fortemente tipadas, tentar retornar um valor de tipo diferente do declarado no cabeçalho provocará rejeição imediata do compilador;
  5. Ignorar o Valor Retornado: Chamar uma função com retorno como se fosse um procedimento (por exemplo, escrever apenas calcularImposto(valor); solto na linha sem atribuir a nenhuma variável nem usar em expressão) faz com que o trabalho de cálculo seja simplesmente jogado fora no lixo!

10. Projeto Prático Resolvido: Motor de Cálculo Tributário de E-Commerce

Vamos integrar todos os tópicos aprendidos — tipagem estrita de retorno, funções predicativas, composição em expressões matemáticas e o padrão Early Return — em um motor completo de precificação de loja virtual com cálculo de impostos, frete grátis e cupons de desconto:

logicabr.dev logicabr.dev (Motor Tributário Completo - 100% Funcional)
// ========================================================
// FUNÇÃO 1: Predicado booleano para validar cupom VIP
// ========================================================
funcao ehCupomValido(texto cupom) {
    retorno (cupom == "PROMO10" ou cupom == "VIP2026");
}

// ========================================================
// FUNÇÃO 2: Alíquota de ICMS com base na categoria
// ========================================================
funcao obterAliquotaICMS(texto categoria) {
    se (categoria == "LIVROS") {
        retorno 0.00;  // Isenção tributária cultural
    }
    se (categoria == "ALIMENTOS") {
        retorno 0.07;  // Cesta básica: 7%
    }
    retorno 0.18;  // Alíquota padrão de 18%
}

// ========================================================
// FUNÇÃO 3: Cálculo de Frete com Padrão Early Return
// ========================================================
funcao calcularFrete(moeda valorPedido, numero distanciaKm) {
    // GUARDA: Pedidos a partir de R$ 250,00 ganham FRETE GRÁTIS!
    se (valorPedido >= 250.00) {
        retorno 0.00;  // Early return antecipado
    }
    retorno 15.00 + (distanciaKm * 0.50);
}

// ========================================================
// FUNÇÃO 4: Aplicador de Cupom
// ========================================================
funcao aplicarDescontoCupom(moeda subtotal, texto cupom) {
    se (ehCupomValido(cupom)) {
        retorno subtotal * 0.90;  // 10% de desconto
    }
    retorno subtotal;
}

// ========================================================
// FLUXO PRINCIPAL (Coordenação e Composição)
// ========================================================
moeda precoItem = 280.00;
texto categoriaProduto = "ALIMENTOS";
texto cupomPromocional = "PROMO10";

saida("=== SIMULADOR DE CHECKOUT E-COMMERCE ===");
saida("Preço de tabela do produto: R$ " + precoItem);

// 1. Cálculo de impostos com composição:
moeda precoComImposto = precoItem + (precoItem * obterAliquotaICMS(categoriaProduto));
saida("Preço com ICMS (" + categoriaProduto + "): R$ " + precoComImposto);

// 2. Cálculo do frete (valorPedido >= 250 ativa early return):
moeda freteCalculado = calcularFrete(precoComImposto, 45.0);
saida("Valor do Frete (45 km): R$ " + freteCalculado);

// 3. Aplicação do cupom promocional em composição final:
moeda precoFinal = aplicarDescontoCupom(precoComImposto + freteCalculado, cupomPromocional);

saida("========================================");
saida("TOTAL FINAL A PAGAR: R$ " + precoFinal);
saida("========================================");

11. Perguntas Frequentes (FAQ)

É possível usar a instrução 'retorno' sem nenhum valor após ela?

Sim, mas apenas dentro de Procedimentos (ou funções do tipo void em C/Java). Nesses casos, o comando retorno; vazio (sem valor) serve exclusivamente como um mecanismo de encerramento prematuro antecipado para abortar a execução da sub-rotina sem produzir valor.

Qual o impacto do Early Return na legibilidade de funções complexas?

O Early Return reduz drasticamente a Complexidade Ciclomática da função. Ao invés de o leitor humano ter que rastrear mentalmente quais condições levam a quais blocos aninhados, ele lê as regras de descarte uma a uma e, ao final, sabe que o caminho principal está completamente garantido.

Posso colocar o comando 'retorno' dentro de um laço de repetição?

Com certeza! Se você colocar um retorno dentro de um para ou enquanto, no instante em que a condição for satisfeita, o laço e a própria função são encerrados simultaneamente, devolvendo o valor para o chamador. É a forma mais comum de implementar buscas lineares encapsuladas em funções.

Por que algumas pessoas evitam colocar retornos no meio da função?

Essa restrição vem do paradigma clássico da programação estruturada dos anos 1960/70 para evitar que desenvolvedores dispersassem múltiplos pontos de saída caóticos. Na engenharia moderna, contudo, é consenso que usar guard clauses logo no início para abortar exceções é incomparavelmente mais limpo do que aninhar dezenas de se/senao.

O que aprenderemos na próxima aula (Aula 19)?

Na Aula 19, desvendaremos o mistério do Escopo de Variáveis (Local vs Global): entenderemos por que variáveis declaradas dentro de uma função desaparecem quando ela termina, como evitar colisões de identificadores e os graves riscos de poluir o escopo global.

Pratique Retornos de Funções na IDE Online!

Abra o editor online do logicabr.dev, crie suas próprias funções predicativas e componha fórmulas matemáticas interativas no console.

Abrir IDE Online e Experimentar →