Pular para o conteúdo principal

Capítulo 5: Depuração & Revisão de Código

Objetivo

Estes exercícios simulam perguntas de entrevista onde você recebe um código bugado ou mal escrito e deve encontrar os problemas e corrigi-los. Esse formato é muito popular em grandes empresas (estilo Sonar/Codility).


Exercício 1 — Encontrar e corrigir 4 bugs​

"Este código tem 4 bugs. Encontre-os e corrija-os."

O código bugado​

public class DiscountCalculator {

public double calculateDiscount(List<Product> products, String coupon) {
double total = 0;
for (int i = 0; i <= products.size(); i++) { // BUG 1
Product p = products.get(i);
if (p.getPrice() > 0) {
total =+ p.getPrice(); // BUG 2
}
}
double discount = 0;
if (coupon == "SAVE20") { // BUG 3
discount = total * 0.20;
}
return total - discount; // BUG 4: pas de null check
}
}

O código corrigido​

public class DiscountCalculator {

public double calculateDiscount(List<Product> products, String coupon) {
if (products == null || products.isEmpty()) return 0; // FIX 4

double total = 0;
for (int i = 0; i < products.size(); i++) { // FIX 1: < ao invés de <=
Product p = products.get(i);
if (p.getPrice() > 0) {
total += p.getPrice(); // FIX 2: += ao invés de =+
}
}
double discount = 0;
if ("SAVE20".equals(coupon)) { // FIX 3: .equals() ao invés de ==
discount = total * 0.20;
}
return total - discount;
}
}

Explicação dos bugs​

BugProblemaConsequênciaCorreção
1<= ao invés de <ArrayIndexOutOfBoundsExceptionTrocar para <
2=+ ao invés de +==+ atribui +valor (positivo), não acumulaTrocar para +=
3== compara referênciasDuas String idênticas podem ter referências diferentesUsar .equals()
4Sem verificação de nullNullPointerException se products for nullAdicionar guard clause
Pegadinhas favoritas dos entrevistadores
  • =+ vs +=: Um atribui, o outro soma. Muito sutil visualmente.
  • == vs .equals(): Em Java, == compara endereços de memória, não o conteúdo.
  • Off-by-one (<= vs <): O erro mais clássico em programação.
Frase de entrevista

"Encontrei quatro bugs: erro off-by-one no loop, atribuição ao invés de adição, comparação de referência ao invés de .equals(), e ausência de verificação de null. Esses são clássicos pitfalls de Java."


Exercício 2 — Revisão completa de código​

"Faça uma revisão deste código. O que você melhoraria?"

O código para examinar​

public class DataProcessor {
public static ArrayList<String> process(ArrayList<String> data) {
ArrayList<String> result = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
String s = data.get(i);
if (s != null) {
if (s.length() > 0) {
if (!s.equals("SKIP")) {
result.add(s.toUpperCase());
}
}
}
}
return result;
}
}

O código melhorado​

public class DataProcessor {

public List<String> process(List<String> data) {
if (data == null) return List.of();

return data.stream()
.filter(Objects::nonNull)
.filter(s -> !s.isEmpty())
.filter(s -> !"SKIP".equals(s))
.map(String::toUpperCase)
.toList();
}
}

Todos os problemas identificados​

#ProblemaCorreçãoPrincípio
1ArrayList na assinaturaUsar List (interface)Programar contra interface
2Método staticRemover staticTestabilidade (mocking)
3Sem guard clause para nullAdicionar if (data == null) return List.of()Null safety
4if aninhados (3 níveis)Stream pipelineLegibilidade
5s.equals("SKIP")"SKIP".equals(s)Evitar NPE
6s.length() > 0s.isEmpty()Código idiomático
7ArrayList mutável retornadatoList() retorna lista imutávelImutabilidade
Frase de entrevista

"Eu refatoraria para usar streams pela legibilidade, programaria para interfaces e adicionaria null safety. Esse é exatamente o tipo de código que o Sonar apontaria — condicionais aninhadas e estado mutável."


Exercício 3 — Encontrar o vazamento de memória​

"Este código causa um vazamento de memória. Encontre-o."

O código problemático​

@Service
public class CacheService {
private final Map<String, Object> cache = new HashMap<>();

public void put(String key, Object value) {
cache.put(key, value);
}

public Object get(String key) {
return cache.get(key);
}

// Pas de méthode pour vider le cache !
// Le Map grandit indéfiniment → OutOfMemoryError
}

O código corrigido​

@Service
public class CacheService {
private final Map<String, Object> cache = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > 1000; // Limite a 1000 entradas
}
};

// Ou melhor, usar Caffeine/Redis
}

Ainda melhor: usar Spring Cache​

@Service
public class ProductService {

@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
return productRepository.findById(id).orElseThrow();
}

@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
productRepository.deleteById(id);
}
}
Frase de entrevista

"Um cache HashMap sem política de expiração é um vazamento de memória esperando para acontecer. Eu usaria Spring Cache com Caffeine ou Redis, que gerenciam TTL e tamanho máximo automaticamente."


Exercício 4 — Encontrar o problema de concorrência​

"Este código é thread-safe? Encontre o problema."

O código problemático​

@Service
public class CounterService {
private int count = 0;

public void increment() {
count++; // NÃO é thread-safe!
}

public int getCount() {
return count;
}
}

Problema: count++ não é atômico. Na verdade, são 3 operações:

  1. Ler count
  2. Somar 1
  3. Escrever o novo valor

Se dois threads fizerem count++ ao mesmo tempo, um incremento pode ser perdido.

As soluções​

// Solução 1: AtomicInteger
@Service
public class CounterService {
private final AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet();
}

public int getCount() {
return count.get();
}
}

// Solução 2: synchronized
@Service
public class CounterService {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}
Frase de entrevista

"count++ não é atômico — é read-modify-write. Eu usaria AtomicInteger para thread safety sem lock, ou synchronized se precisar proteger uma seção crítica maior."


Exercício 5 — Corrigir má gestão de exceções​

"O que está errado com este tratamento de exceções?"

O código ruim​

@Service
public class PaymentService {

public void processPayment(PaymentRequest req) {
try {
gateway.charge(req.getAmount());
orderService.updateStatus(req.getOrderId(), "PAID");
} catch (Exception e) {
// PROBLEMA 1: catch muito amplo
// PROBLEMA 2: engole a exceção silenciosamente
// PROBLEMA 3: log inútil
System.out.println("Error: " + e.getMessage());
}
}
}

O código corrigido​

@Service
@Slf4j
public class PaymentService {

@Transactional
public void processPayment(PaymentRequest req) {
try {
gateway.charge(req.getAmount());
} catch (PaymentDeclinedException e) {
log.warn("Pagamento recusado para o pedido {}: {}", req.getOrderId(), e.getMessage());
throw new BusinessException("Pagamento foi recusado", e);
} catch (GatewayTimeoutException e) {
log.error("Timeout do gateway para o pedido {}", req.getOrderId(), e);
throw new ServiceUnavailableException("Gateway de pagamento está fora do ar", e);
}

orderService.updateStatus(req.getOrderId(), "PAID");
}
}

Problemas identificados​

#ProblemaPor que é grave
1catch (Exception e)Captura tudo, inclusive NullPointerException, OutOfMemoryError
2Exceção engolidaO chamador nunca sabe que o pagamento falhou
3System.out.printlnSem log estruturado, sem stack trace, perdido em produção
4Sem rollbackSe o charge der certo mas o updateStatus falhar, dados inconsistentes
Frase de entrevista

"Nunca capture Exception genérico. Capture exceções específicas, faça log com o nível adequado (warn vs error), relance como exceções de negócio e use @Transactional para consistência dos dados."


Exercício 6 — Identificar code smells​

"Liste todos os problemas neste código."

O código para analisar​

public class Utils {

public static String x(String s) {
String r = "";
for (int i = s.length() - 1; i >= 0; i--) {
r = r + s.charAt(i);
}
return r;
}

public static boolean check(String email) {
if (email.contains("@") == true) {
if (email.contains(".") == true) {
return true;
} else {
return false;
}
} else {
return false;
}
}

public static int calc(int a, int b, String op) {
if (op == "+") return a + b;
if (op == "-") return a - b;
if (op == "*") return a * b;
if (op == "/") return a / b;
return 0;
}
}

Os problemas​

MétodoProblemas
x()Nome incompreensível, concatenação em loop (O(n²)), sem verificação de null
check()== true redundante, if/else return true/false pode ser expressão
calc()== ao invés de .equals(), divisão por zero, retorna 0 silenciosamente

O código corrigido​

public class StringUtils {

public static String reverse(String input) {
if (input == null) return null;
return new StringBuilder(input).reverse().toString();
}
}

public class EmailValidator {

public static boolean isValid(String email) {
return email != null && email.contains("@") && email.contains(".");
}
}

public class Calculator {

public static int calculate(int a, int b, String operator) {
return switch (operator) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> {
if (b == 0) throw new ArithmeticException("Divisão por zero");
yield a / b;
}
default -> throw new IllegalArgumentException("Operador desconhecido: " + operator);
};
}
}

Checklist de revisão de código​

Quando pedirem para você "revisar" um código em entrevista, verifique sistematicamente:

CategoriaPontos a verificar
Null safetyVerificações de null nas entradas? Optional usado?
NomenclaturaNomes explícitos? Convenções Java (camelCase)?
ExceçõesCatch específico? Nenhuma exceção engolida?
Strings.equals() e não ==? StringBuilder para concatenação em loop?
ColeçõesInterfaces (List, não ArrayList)? Imutáveis quando possível?
SOLIDUma única responsabilidade? Depende de abstrações?
Thread safetyVariáveis compartilhadas protegidas?
PerformanceSem N+1? Sem loop O(n²) escondido?
SegurançaSem SQL injection? Entradas validadas?

⬅️ Capítulo 4: REST & Padrões | ⬅️ Voltar ao Módulo 18