Já parou para pensar quantas vezes você usou uma simples decimal para representar um preço, string para um SKU ou int para uma quantidade no seu código? 🤔
Se a resposta foi "várias vezes" (e provavelmente foi), você pode estar caindo numa armadilha muito comum: a obsessão por tipos primitivos! Essa é uma das regras mais importantes do Object Calisthenics que pode transformar completamente a qualidade do seu código.
🎯 O Que é Object Calisthenics?
Object Calisthenics é como uma "academia para o seu código"! 💪 Criado por Jeff Bay, é um conjunto de 9 regras que funcionam como exercícios para tornar seu código mais orientado a objetos e próximo dos princípios SOLID.
As 9 regras são:
- Apenas um nível de indentação por método
- Não use a palavra-chave ELSE
- Encapsule todos os tipos primitivos e Strings ⭐ (nosso foco hoje!)
- Coleções como objetos de primeira classe
- Um ponto por linha
- Não abrevie
- Mantenha todas as entidades pequenas
- Não mais que duas variáveis de instância por classe
- Evite getters/setters/properties
🚨 O Problema: Obsessão por Tipos Primitivos
Cenário Típico (que você já viu mil vezes)
public class Product
{
private string _name;
private decimal _price;
private int _stock;
private string _sku;
public Product(string name, decimal price, int stock, string sku)
{
// Validação do nome
if (string.IsNullOrWhiteSpace(name) || name.Length < 3)
throw new Exception("Nome do produto inválido");
// Validação do preço
if (price <= 0)
throw new Exception("Preço deve ser maior que zero");
// Validação do estoque
if (stock < 0)
throw new Exception("Estoque não pode ser negativo");
// Validação do SKU (aquela validação chata)
if (string.IsNullOrWhiteSpace(sku) || !Regex.IsMatch(sku, @"^[A-Z0-9\-]+$"))
throw new Exception("SKU inválido");
_name = name;
_price = price;
_stock = stock;
_sku = sku;
}
}
😱 Os Problemas Dessa Abordagem
1. Código Duplicado Everywhere
Precisa criar um OrderItem? Vai ter que copiar todas essas validações de novo!
2. Responsabilidade Confusa
Por que diabos um Product precisa saber como validar SKU? 🤯
3. Violação do Princípio Aberto-Fechado
Se a regra de validação de preço mudar, você vai ter que mexer na classe Product. E se tiver mais 10 classes usando preço? Boa sorte! 😅
4. Classe Gigante
Sua classe vai ficar enorme sem ter nenhum comportamento realmente relacionado ao Product.
✨ A Solução: Value Objects
Criando um Preço que se Valida
using System;
public class Price
{
private readonly decimal _value;
public Price(decimal value)
{
if (value <= 0)
throw new ArgumentException("Preço deve ser maior que zero");
_value = value;
}
public decimal GetValue() => _value;
public Price Add(Price other) => new Price(_value + other._value);
public Price ApplyDiscount(decimal percentage)
=> new Price(_value * (1 - percentage / 100));
public override string ToString() => _value.ToString("C2");
}
SKU que Sabe se Comportar
using System;
using System.Text.RegularExpressions;
public class SKU
{
private readonly string _value;
public SKU(string sku)
{
Validate(sku);
_value = sku;
}
private void Validate(string sku)
{
if (string.IsNullOrWhiteSpace(sku))
throw new ArgumentException("SKU não pode ser vazio");
if (!Regex.IsMatch(sku, @"^[A-Z0-9\-]{5,20}$"))
throw new ArgumentException("SKU deve ter entre 5 e 20 caracteres alfanuméricos");
}
public string GetValue() => _value;
public override string ToString() => _value;
public override bool Equals(object obj)
=> obj is SKU sku && _value == sku._value;
public override int GetHashCode() => _value.GetHashCode();
}
Estoque com Regras de Negócio
using System;
public class Stock
{
private readonly int _value;
public Stock(int quantity)
{
if (quantity < 0)
throw new ArgumentException("Estoque não pode ser negativo");
_value = quantity;
}
public int GetValue() => _value;
public bool IsAvailable() => _value > 0;
public bool IsLowStock(int threshold = 10) => _value <= threshold && _value > 0;
public bool IsOutOfStock() => _value == 0;
public Stock Remove(int quantity)
{
if (quantity > _value)
throw new InvalidOperationException("Estoque insuficiente");
return new Stock(_value - quantity);
}
public Stock Add(int quantity) => new Stock(_value + quantity);
}
Nome de Produto com Validações
using System;
public class ProductName
{
private readonly string _value;
public ProductName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Nome não pode ser vazio");
if (name.Length < 3)
throw new ArgumentException("Nome deve ter pelo menos 3 caracteres");
if (name.Length > 100)
throw new ArgumentException("Nome não pode ter mais de 100 caracteres");
_value = name.Trim();
}
public string GetValue() => _value;
public override string ToString() => _value;
public override bool Equals(object obj)
=> obj is ProductName name && _value == name._value;
public override int GetHashCode() => _value.GetHashCode();
}
🎉 O Resultado: Product Renovado
public class Product
{
private readonly ProductName _name;
private readonly Price _price;
private readonly Stock _stock;
private readonly SKU _sku;
public Product(ProductName name, Price price, Stock stock, SKU sku)
{
_name = name;
_price = price;
_stock = stock;
_sku = sku;
}
public string GetName() => _name.GetValue();
public decimal GetPrice() => _price.GetValue();
public int GetStock() => _stock.GetValue();
public string GetSKU() => _sku.GetValue();
// Agora você pode ter comportamentos reais do Product!
public bool CanBeSold() => _stock.IsAvailable();
public bool NeedsRestock() => _stock.IsLowStock();
public Price CalculateDiscountedPrice(decimal discount) => _price.ApplyDiscount(discount);
}
🚀 Como Usar na Prática
// Criação segura - qualquer valor inválido vai explodir na hora certa!
var name = new ProductName("Notebook Gamer XYZ");
var price = new Price(2999.99m);
var stock = new Stock(15);
var sku = new SKU("NGB-XYZ-2024");
var product = new Product(name, price, stock, sku);
// Comportamentos ricos!
if (product.CanBeSold())
{
var discountedPrice = product.CalculateDiscountedPrice(10);
Console.WriteLine($"Produto disponível por {discountedPrice}");
}
if (product.NeedsRestock())
{
Console.WriteLine("⚠️ Produto com estoque baixo - reabastecer!");
}
🎯 Benefícios Que Você Vai Ganhar
✅ Reutilização Total
Precisa de Price em 20 classes? Use o mesmo Price object em todas!
✅ Manutenção Centralizada
Mudou a regra de validação? Só mexe em um lugar!
✅ Testes Mais Fáceis
Teste a validação do SKU separadamente do Product. Cada coisa no seu lugar!
✅ Código Mais Expressivo
product.CanBeSold() é muito mais claro que verificar product.Stock > 0
✅ Fail-Fast
Erros são detectados no momento da criação, não quando você menos espera.
⚠️ Cuidados e Bom Senso
Nem tudo precisa virar Value Object! 🧠
Se você tem um bool isActive no seu Product, não precisa criar uma classe IsActive. Use o bom senso!
Object Calisthenics são exercícios, não leis absolutas. A ideia é treinar seu pensamento orientado a objetos, não enlouquecer criando classe para tudo.
🎊 Conclusão
A regra "Encapsule todos os tipos primitivos" do Object Calisthenics não é sobre complicar seu código - é sobre deixá-lo mais robusto, reutilizável e expressivo!
Quando você para de usar decimal para preços, string para SKUs e int para quantidades e começa a criar objetos que representam conceitos reais do seu domínio, seu código ganha vida. Ele fica mais fácil de entender, testar e manter.
🚀 Próximos Passos
- Identifique os tipos primitivos no seu código atual
- Pergunte-se: "Esse valor tem regras de negócio? Precisa de validação?"
- Crie Value Objects para os casos que fazem sentido
- Refatore gradualmente - não precisa fazer tudo de uma vez!
Lembre-se: você não escreve código apenas para a máquina, mas para pessoas (incluindo você do futuro)! 😉
Top comments (0)