Esta postagem é um exercício. Aqui, vou desenvolver um código para o cálculo de aprovação de alunos em uma disciplina. Para isso, vou adotar uma premissa muito rígida: resolver exclusivamente o problema que está sendo apresentado da maneira mais direta possível. Não vou usar conceitos de OOP, abstrações, boas práticas, ou recursos mais avançados da linguagem Java. O negócio vai ser mover o CARD para DONE.

Atenção! A leitura deste texto faz muito mais sentido se você ler também a parte 2, com o mesmo problema, mas implementando usando OOP e SOLID. Provas, Recuperação, Final… não OOP & não SOLID # Parte 1. Mais ainda, seria legal se você se permitisse comparar a solução proposta aqui, com a solução final da outra postagem.

Primeiro problema

O sistema deve receber três notas de um aluno de três avaliações aplicadas durante um semestre letivo em uma disciplina de curso superior. O sistema deve informar se o aluno está aprovado ou reprovado, considerando esse conjunto de notas. As notas são valores numéricos que podem ir de 0 até 10. O critério para aprovar é ter média simples maior ou igual a 7.0.

A implementação aqui é fácil. É só tirar a média e colocar um if.

public class GradingCalculator {
    public boolean isApproved(
            double exam1,
            double exam2,
            double exam3
    ) {
        if ((exam1 < 0.0 || exam1 > 10.0)
                || (exam2 < 0.0 || exam2 > 10.0)
                || (exam3 < 0.0 || exam3 > 10.0)) {
            throw new IllegalArgumentException("Wrong exam values");
        }
        if ((exam1 + exam2 + exam3) / 3.0 < 7.0) {
            return false;
        }
        return true;
    }
}

Primeiro acréscimo

O sistema agora deve considerar a nota da reposição. A reposição é uma prova extra que o aluno faz caso sua média das três primeiras provas não seja suficiente para aprovação. Nesse caso, a prova da reposição deve substituir a menor nota entre as três avaliações. Se a nota da reposição for ainda menor, ela não deve ser considerada.

Boa idea. Dar a chance pro aluno refazer uma das provas é justo. Nem todo mundo está bem todo tempo e a ideia de avaliação contínua me fazer muito sentido. Considerar a nota da reposição complicou um pouco, mas eu conheço uma técnica de implementação que vai ferrar o próximo programador a dar manutenção nisso.

public class GradingCalculator {
    public boolean isApproved(
            double exam1,
            double exam2,
            double exam3,
            double replacement
    ) {
        if ((exam1 < 0.0 || exam1 > 10.0)
                || (exam2 < 0.0 || exam2 > 10.0)
                || (exam3 < 0.0 || exam3 > 10.0)
                || (replacement < 0.0 || replacement > 10.0)) {
            throw new IllegalArgumentException("Wrong exam values");
        }
        if ((exam1 + exam2 + exam3) / 3.0 < 7.0) {
            double sum = exam1 + exam2 + exam3
                    - Math.min(exam1, Math.min(exam2, exam3))
                    + replacement;
            if (sum / 3.0 < 7.0) {
                return false;
            }
        }
        return true;
    }
}

Um pouco mais de complexidade

O sistema deve permitir que cada avaliação, incluindo a reposição, seja composta por um conjunto de atividades. A nota de avaliação é a média simples das notas das atividades, com a nota das atividades também indo de 0 até 10.

O negócio aqui é mover a tarefa para DONE. O Go Horse canta! Sem firula. Se cada avaliação é composta por várias atividades, nada melhor que um array para cada. Tira a média de tudo e vida que segue. Que merda é essa com Math.min que colocaram aqui? Próxima!

public class GradingCalculator {
    public boolean isApproved(
            double exam1[],
            double exam2[],
            double exam3[],
            double replacement[]
    ) {
        validadeValues(exam1);
        validadeValues(exam2);
        validadeValues(exam3);
        validadeValues(replacement);
        double exam1Avg = calculateAverage(exam1);
        double exam2Avg = calculateAverage(exam2);
        double exam3Avg = calculateAverage(exam3);
        double replacementAvg = calculateAverage(replacement);
        if ((exam1Avg + exam2Avg + exam3Avg) / 3.0 < 7.0) {
            double sum = exam1Avg + exam2Avg + exam3Avg
                    - Math.min(exam1Avg, Math.min(exam2Avg, exam3Avg))
                    + replacementAvg;
            if (sum / 3.0 < 7.0) {
                return false;
            }
        }
        return true;
    }

    private double calculateAverage(double values[]) {
        double sum = 0.0;
        for (double x : values) {
            sum += x;
        }
        return sum / values.length;
    }

    private void validadeValues(double values[]) {
        for (double x : values) {
            if (x < 0.0 || x > 10.0) {
                throw new IllegalArgumentException("Wrong exam values");
            }
        }
    }
}

Dava para ter pensando nisso antes?

As atividades realizadas dentro de uma avaliação podem ter um peso variável na composição da nota da avaliação. O peso de uma atividade é um valor que pode variar de 1 a 10. A nota da avaliação deve ser a média ponderada das notas de todas as atividades.

Qual é a maneira mais rápida de fazer isso? Tem um monte de opção, mas nenhuma delas envolve criar uma classe para representar uma tarefa e seu peso. O negócio é mandar dois arrays cada avaliação, um com as notas da satividades, e outro com os pesos. A pessoa que fez isso recebeu a tarefa numa segunda-feira de manhã e terminou antes do meio dia, mas levou o assunto até sexta-feira, passando status que faltava só testar. Ambos líder técnico e gestor de tecnologia da equipe pensaram consigo que a tarefa parecia estar levando mais tempo que o necessário.

public class GradingCalculator {
    public boolean isApproved(
            double exam1[],
            double exam1Weights[],
            double exam2[],
            double exam2Weights[],
            double exam3[],
            double exam3Weights[],
            double replacement[],
            double replacementWeights[]
    ) {
        validadeValues(exam1);
        validadeValues(exam1Weights);
        validadeValues(exam2);
        validadeValues(exam2Weights);
        validadeValues(exam3);
        validadeValues(exam3Weights);
        validadeValues(replacement);
        validadeValues(replacementWeights);
        double exam1Avg = calculateAverage(exam1, exam1Weights);
        double exam2Avg = calculateAverage(exam2, exam2Weights);
        double exam3Avg = calculateAverage(exam3, exam3Weights);
        double replacementAvg = calculateAverage(replacement, replacementWeights);
        if ((exam1Avg + exam2Avg + exam3Avg) / 3.0 < 7.0) {
            double sum = exam1Avg + exam2Avg + exam3Avg
                    - Math.min(exam1Avg, Math.min(exam2Avg, exam3Avg))
                    + replacementAvg;
            if (sum / 3.0 < 7.0) {
                return false;
            }
        }
        return true;
    }

    private double calculateAverage(
            double values[],
            double wights[]
    ) {
        double sum = 0.0;
        double sum2 = 0.0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i] * wights[i];
            sum2 += wights[i];
        }
        return sum / sum2;
    }

    private void validadeValues(double values[]) {
        for (double x : values) {
            if (x < 0.0 || x > 10.0) {
                throw new IllegalArgumentException("Wrong exam values");
            }
        }
    }
}

A última chance

Caso o aluno não seja aprovado após a reposição, ele pode fazer uma prova final, com nota variando de 0 a 10. A prova final é única, não sendo composta por múltiplas atividades. Suponhamos que N seja a média das três primeiras avaliações (possivelmente considerando também a prova de reposição), e vamos supor que F seja a nota da prova final, o critério para aprovação neste caso é a média simples entre N e F, cujo valor deve ser maior ou igual a 6.0.

Recebi essa tarefa desdém, porque tenho certeza que do ponto de vista pedagógico é muito difícil alguém ser aprovado em uma avaliação com todo conteúdo do semestre, mesmo depois de ter feito quatro provas e obter notas ruins. Com meus botões, penso que esta funcionalidade vale mais para o professor descobrir aqueles alunos que, por alguma razão, não conseguiram média, mas estão se dedicando de alguma forma. Talvez o professor ajude de alguma forma, afinal de contas, em muitos casos, é prerrogativa dele a forma de avaliação.

public class GradingCalculator {
    public boolean isApproved(
            double exam1[],
            double exam1Weights[],
            double exam2[],
            double exam2Weights[],
            double exam3[],
            double exam3Weights[],
            double replacement[],
            double replacementWeights[],
            double finalExam
    ) {
        validadeValues(exam1);
        validadeValues(exam1Weights);
        validadeValues(exam2);
        validadeValues(exam2Weights);
        validadeValues(exam3);
        validadeValues(exam3Weights);
        validadeValues(replacement);
        validadeValues(replacementWeights);
        if (finalExam < 0.0 || finalExam > 10.0) {
            throw new IllegalArgumentException("Wrong exam values");
        }
        double exam1Avg = calculateAverage(exam1, exam1Weights);
        double exam2Avg = calculateAverage(exam2, exam2Weights);
        double exam3Avg = calculateAverage(exam3, exam3Weights);
        double replacementAvg = calculateAverage(replacement, replacementWeights);
        if ((exam1Avg + exam2Avg + exam3Avg) / 3.0 < 7.0) {
            double sum = exam1Avg + exam2Avg + exam3Avg
                    - Math.min(exam1Avg, Math.min(exam2Avg, exam3Avg))
                    + replacementAvg;
            if (sum / 3.0 < 7.0) {
                if (((sum / 3.0) + finalExam) / 2.0 < 6.0) {
                    return false;
                }
            }
        }
        return true;
    }

    private double calculateAverage(
            double values[],
            double wights[]
    ) {
        double sum = 0.0;
        double sum2 = 0.0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i] * wights[i];
            sum2 += wights[i];
        }
        return sum / sum2;
    }

    private void validadeValues(double values[]) {
        for (double x : values) {
            if (x < 0.0 || x > 10.0) {
                throw new IllegalArgumentException("Wrong exam values");
            }
        }
    }
}

Isso é bom para relatórios

O sistema deve ser incrementado para dizer em quais condições o aluno foi aprovado, ou reprovado, isto é, se foi aprovado por média sem reposição, se foi aprovado com prova de reposição, se foi aprovado com a prova final, ou foi reprovado.

Desde a primeira linha de código muitos meses atrás, a pessoa que começou esse sistema deveria ter pensando nessa situação. Era melhor usar números como resultado, mas quem usou o boolean deve ter pensado no cenário da época, já que função deveria descobrir apenas se o aluno será aprovado ou reprovado.

public class GradingCalculator {
    public int isApproved(
            double exam1[],
            double exam1Weights[],
            double exam2[],
            double exam2Weights[],
            double exam3[],
            double exam3Weights[],
            double replacement[],
            double replacementWeights[],
            double finalExam
    ) {
        validadeValues(exam1);
        validadeValues(exam1Weights);
        validadeValues(exam2);
        validadeValues(exam2Weights);
        validadeValues(exam3);
        validadeValues(exam3Weights);
        validadeValues(replacement);
        validadeValues(replacementWeights);
        if (finalExam < 0.0 || finalExam > 10.0) {
            throw new IllegalArgumentException("Wrong exam values");
        }
        double exam1Avg = calculateAverage(exam1, exam1Weights);
        double exam2Avg = calculateAverage(exam2, exam2Weights);
        double exam3Avg = calculateAverage(exam3, exam3Weights);
        double replacementAvg = calculateAverage(replacement, replacementWeights);
        if ((exam1Avg + exam2Avg + exam3Avg) / 3.0 < 7.0) {
            double sum = exam1Avg + exam2Avg + exam3Avg
                    - Math.min(exam1Avg, Math.min(exam2Avg, exam3Avg))
                    + replacementAvg;
            if (sum / 3.0 < 7.0) {
                if (((sum / 3.0) + finalExam) / 2.0 < 6.0) {
                    return 4;
                } else {
                    return 2;
                }
            } else {
                return 1;
            }
        }
        return 0;
    }

    private double calculateAverage(
            double values[],
            double wights[]
    ) {
        double sum = 0.0;
        double sum2 = 0.0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i] * wights[i];
            sum2 += wights[i];
        }
        return sum / sum2;
    }

    private void validadeValues(double values[]) {
        for (double x : values) {
            if (x < 0.0 || x > 10.0) {
                throw new IllegalArgumentException("Wrong exam values");
            }
        }
    }
}

Não é só nota, é envolvimento

O sistema deve considerar a frequência do aluno. Um aluno não será reprovado por faltas quando tiver uma frequência igual ou superior a 75% das aulas. A reprovação por faltas deve acontecer mesmo se o aluno obtiver média para aprovação. Nestes casos, o sistema deve indicar também se a reprovação foi por falta, no caso dele alcançar média para aprovação, ou por falta e média, nos caso dele não conseguir média para aprovação, e também não apresentar a frequência necessária.

Implementei essa muito descontente. Não é justo que o aluno seja reprovado mesmo tirando nota 10 em todas as avaliações. E se o aluno trabalha e estuda, se ele tem família e filhos?

public class GradingCalculator {
    public int isApproved(
            double exam1[],
            double exam1Weights[],
            double exam2[],
            double exam2Weights[],
            double exam3[],
            double exam3Weights[],
            double replacement[],
            double replacementWeights[],
            double finalExam,
            int totalClasses,
            int frequency
    ) {
        validadeValues(exam1);
        validadeValues(exam1Weights);
        validadeValues(exam2);
        validadeValues(exam2Weights);
        validadeValues(exam3);
        validadeValues(exam3Weights);
        validadeValues(replacement);
        validadeValues(replacementWeights);
        if (finalExam < 0.0 || finalExam > 10.0) {
            throw new IllegalArgumentException("Wrong exam values");
        }
        if (totalClasses <= 0) {
            throw new IllegalArgumentException("Wrong value");
        }
        if (frequency < 0 || frequency > totalClasses) {
            throw new IllegalArgumentException("Wrong value");
        }
        double exam1Avg = calculateAverage(exam1, exam1Weights);
        double exam2Avg = calculateAverage(exam2, exam2Weights);
        double exam3Avg = calculateAverage(exam3, exam3Weights);
        double replacementAvg = calculateAverage(replacement, replacementWeights);
        boolean frequencyApproved = ((double) frequency / totalClasses) >= 0.75;
        if ((exam1Avg + exam2Avg + exam3Avg) / 3.0 < 7.0) {
            double sum = exam1Avg + exam2Avg + exam3Avg
                    - Math.min(exam1Avg, Math.min(exam2Avg, exam3Avg))
                    + replacementAvg;
            if (sum / 3.0 < 7.0) {
                if (((sum / 3.0) + finalExam) / 2.0 < 6.0) {
                    return frequencyApproved ? 4 : 6;
                } else {
                    return frequencyApproved ? 2 : 5;
                }
            } else {
                return frequencyApproved ? 1 : 5;
            }
        }
        return frequencyApproved ? 0 : 5;
    }

    private double calculateAverage(
            double values[],
            double wights[]
    ) {
        double sum = 0.0;
        double sum2 = 0.0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i] * wights[i];
            sum2 += wights[i];
        }
        return sum / sum2;
    }

    private void validadeValues(double values[]) {
        for (double x : values) {
            if (x < 0.0 || x > 10.0) {
                throw new IllegalArgumentException("Wrong exam values");
            }
        }
    }
}

Conclusão

Sem dúvida, a solução final atende a todos os requisitos apresentados. A questão é: qual foi o custo dessa escolha? O sistema foi construído considerando um curso superior com três avaliações, uma prova de reposição e uma prova final. Mas e se amanhã ele precisar atender a um modelo diferente, como o ensino básico, com avaliações bimestrais, segunda chamada, recuperação paralela e regras distintas de frequência? A cada nova necessidade, vamos ampliar ainda mais a complexidade do mesmo método? Responder tudo isso é impossível, sobretudo quando estamos codificando para resolver um problema em específico, adicionar uma funcionadade bem definida. Mas seguir boas práticas garante que essas alterações sejam mais fáceis de fazer, e também permitem que a manutenção do próprio sistema seja mais simples.

A função central concentra toda a lógica e retorna um número inteiro. O valor zero representa aprovação. Isso é evidente para quem escreveu o código ou acompanhou sua evolução. Porém, para alguém que não conhece esse contexto, o número zero não comunica absolutamente nada. Ele é apenas um número. O significado está implícito, escondido na implementação. Um valor isolado não carrega intenção, não expressa domínio e não revela regra de negócio.

Esse problema se agrava quando o sistema evolui. Em determinado momento, o retorno deixa de ser um boolean e passa a ser um int. Todas as partes que dependiam apenas de uma informação simples, aprovado ou reprovado, agora precisam interpretar códigos numéricos. O que antes era um simples if (aprovado) passa a ser uma verificação como if (resultado == 0 || resultado == 1) ou if (resultado != 3). Componentes que não deveriam conhecer detalhes internos da regra passam a depender deles. O conhecimento da regra se espalha pelo sistema de forma frágil e implícita.

Nada disso é artificial ou exagerado. Pelo contrário, esse tipo de solução é comum no mercado. Pressão por prazo, foco exclusivo na entrega imediata e ausência de modelagem explícita levam a decisões pragmáticas que resolvem o problema do momento. O sistema funciona hoje. O requisito é entregue. O CARD é movido para DONE. No entanto, cada atalho gera um custo acumulado que será pago no futuro.

O método responsável pelo cálculo começou simples: três notas e uma média. Com o tempo, foram sendo adicionadas novas regras, substituição da menor nota, prova final, controle de frequência, médias ponderadas, códigos de retorno. Todas essas responsabilidades foram incorporadas ao mesmo ponto do sistema. O resultado é um método extenso, repleto de condicionais aninhadas e fluxos de decisão difíceis de acompanhar. Ler o código exige esforço. Alterá-lo exige cautela. Entendê-lo exige contexto prévio.

O problema central não é apenas técnico; é semântico. O retorno inteiro representa estados importantes do negócio, mas não comunica significado por si só. O valor 0 só significa “aprovado por média” porque alguém sabe disso. Fora desse conhecimento tácito, ele é apenas um número mágico. O mesmo vale para os demais códigos. As regras não estão modeladas de forma explícita; estão escondidas em decisões condicionais. Isso cria acoplamento implícito e dependência de memória humana.

Além disso, a capacidade de evolução fica comprometida. Se surgirem novas regras, como média mínima por avaliação, pesos diferentes por período, dependência automática, critérios diferenciados por curso, a tendência será continuar expandindo o mesmo método. Mais parâmetros. Mais condicionais. Mais ramificações. O código cresce até se tornar rígido e arriscado de modificar. Cada alteração aumenta a chance de falha.

A reflexão principal é que elegância não é um luxo acadêmico. Não se trata de aplicar padrões por formalidade ou seguir princípios por dogma. Trata-se de estruturar o sistema de forma que ele possa evoluir sem colapsar. É possível evoluir incrementalmente e, ainda assim, organizar responsabilidades, explicitar regras de negócio e comunicar intenção com clareza.

Este exercício demonstra que é possível construir uma solução direta, pragmática e funcional, mesmo que estruturalmente frágil. Ela atende aos requisitos e resolve o problema atual. A pergunta relevante não é se funciona, porque funciona, sim. A pergunta é outra: você gostaria de manter esse código pelos próximos cinco anos?

Categorized in:

Uncategorized,

Last Update: 12/02/2026