DEV Community

Lucas Pereira de Souza
Lucas Pereira de Souza

Posted on

Shadcn/ui e a morte das UI Libs tradicionais1

logotech

## Componentes Proprietários: Uma Revolução na Propriedade do Código e no Desenvolvimento

No dinâmico mundo do desenvolvimento de software, frequentemente nos deparamos com um desafio comum: gerenciar dependências e garantir a manutenibilidade a longo prazo de nossos projetos. A ascensão do código aberto trouxe benefícios imensos, mas também levou a um ecossistema complexo de bibliotecas, frameworks e componentes de terceiros. Embora essas peças externas de código ofereçam conveniência e acelerem o desenvolvimento, elas também podem introduzir riscos relacionados à segurança, estabilidade e dependência de fornecedores. É aqui que o conceito de \"componentes proprietários\" ou \"possuir seu código\" emerge como uma mudança de paradigma poderosa.

O Problema com a Abordagem da \"Caixa Preta\"

Tradicionalmente, muitos projetos têm dependido fortemente de componentes externos, muitas vezes opacos. Os desenvolvedores consomem esses componentes como \"caixas pretas\", confiando que eles funcionam como pretendido sem entender completamente seu funcionamento interno. Essa abordagem, embora rápida de implementar inicialmente, pode levar a várias armadilhas:

  • Vulnerabilidades de Segurança: Uma vulnerabilidade em uma dependência de terceiros pode ter um efeito cascata, comprometendo todo o seu aplicativo. A correção desses problemas geralmente depende dos mantenedores originais, levando a atrasos e potencial exposição.
  • Falta de Personalização: Quando um componente não se encaixa perfeitamente em suas necessidades específicas, você é frequentemente limitado pelas escolhas de seus criadores. Isso pode levar a soluções alternativas, hacks ou à dolorosa decisão de refatorar partes significativas do seu aplicativo.
  • \"Inferno das Dependências\": À medida que os projetos crescem, gerenciar múltiplas dependências e suas versões pode se tornar um pesadelo. Incompatibilidades, dependências transitivas e quebras de alterações podem interromper o progresso do desenvolvimento.
  • Dependência de Fornecedores (Vendor Lock-in): A dependência excessiva de um componente ou plataforma proprietária específica pode tornar extremamente difícil e caro migrar para uma solução diferente posteriormente.
  • Custos Ocultos: Embora muitas vezes gratuitos, os componentes de código aberto podem incorrer em custos indiretos significativos em termos de depuração, auditorias de segurança e o tempo gasto para entender suas complexidades quando surgem problemas.

Adotando a Propriedade do Código: A Filosofia dos \"Componentes Proprietários\"

A abordagem de \"componentes proprietários\", neste contexto, não significa necessariamente reinventar a roda para cada peça de funcionalidade. Em vez disso, defende uma mudança estratégica em direção à construção e propriedade dos componentes centrais que são críticos para a proposta de valor única e o sucesso a longo prazo do seu aplicativo. Isso significa:

  1. Entender o Núcleo: Identifique as funcionalidades principais que diferenciam seu produto. Essas são as áreas onde você deve buscar um entendimento profundo e controle.
  2. Internalizar a Lógica Crítica: Para essas funcionalidades principais, considere construí-las internamente ou personalizar e estender significativamente soluções de código aberto existentes a ponto de você verdadeiramente \"possuir\" a lógica.
  3. Gerenciamento Estratégico de Dependências: Para funcionalidades não essenciais (por exemplo, logging, funções utilitárias básicas), utilize bibliotecas de código aberto bem avaliadas. No entanto, mesmo aqui, esteja ciente dos riscos potenciais e tenha uma estratégia para gerenciá-los.
  4. Foco na Manutenibilidade e Testabilidade: Ao construir seus próprios componentes, priorize código limpo, tipagem forte e testes abrangentes desde o início.

Demonstrando com Código: Um Exemplo TypeScript/Node.js

Vamos ilustrar isso com um exemplo simplificado. Imagine que estamos construindo uma plataforma de e-commerce e precisamos de um PricingCalculator robusto. Em vez de depender apenas de uma biblioteca externa que pode não corresponder perfeitamente às nossas complexas regras de desconto, decidimos construir a nossa.

// src/core/pricing/PricingCalculator.ts

/**
 * Representa um item em um pedido, incluindo seu preço e quantidade.
 */
interface OrderItem {
  productId: string;
  quantity: number;
  unitPrice: number;
}

/**
 * Representa um desconto que pode ser aplicado a um pedido.
 */
interface Discount {
  code: string;
  percentage: number; // e.g., 0.10 for 10%
}

/**
 * Calcula os preços para pedidos, incluindo descontos e impostos.
 * Este é um componente central de nossa plataforma de e-commerce.
 */
class PricingCalculator {
  private taxRate: number;

  /**
   * Inicializa o PricingCalculator com uma taxa de imposto específica.
   * @param taxRate - A taxa de imposto aplicável (e.g., 0.07 for 7%).
   */
  constructor(taxRate: number) {
    if (taxRate < 0 || taxRate > 1) {
      throw new Error(\"Tax rate must be between 0 and 1.\");
    }
    this.taxRate = taxRate;
  }

  /**
   * Calcula o subtotal de um pedido com base nos itens e suas quantidades.
   * @param items - Um array de objetos OrderItem.
   * @returns O subtotal calculado.
   */
  calculateSubtotal(items: OrderItem[]): number {
    // Usando reduce para soma eficiente. Nomes de variáveis claros melhoram a legibilidade.
    const subtotal = items.reduce((sum, item) => {
      // Validação básica para prevenir quantidades ou preços negativos
      if (item.quantity < 0 || item.unitPrice < 0) {
        console.warn(`Invalid item data detected for productId: ${item.productId}. Skipping.`);
        return sum;
      }
      return sum + (item.quantity * item.unitPrice);
    }, 0);

    return parseFloat(subtotal.toFixed(2)); // Garante a formatação de moeda
  }

  /**
   * Aplica descontos a um determinado valor.
   * @param amount - O valor base para aplicar os descontos.
   * @param discounts - Um array de objetos Discount.
   * @returns O valor após a aplicação de todos os descontos aplicáveis.
   */
  applyDiscounts(amount: number, discounts: Discount[]): number {
    let discountedAmount = amount;

    // Itera sobre os descontos e os aplica sequencialmente.
    for (const discount of discounts) {
      // Valida a porcentagem de desconto
      if (discount.percentage < 0 || discount.percentage > 1) {
        console.warn(`Invalid discount percentage for code: ${discount.code}. Skipping.`);
        continue;
      }
      // Aplica desconto: amount * (1 - percentage)
      discountedAmount *= (1 - discount.percentage);
    }

    return parseFloat(discountedAmount.toFixed(2));
  }

  /**
   * Calcula o preço total de um pedido, incluindo subtotal, descontos e impostos.
   * @param items - Um array de objetos OrderItem.
   * @param discounts - Um array de objetos Discount a serem aplicados.
   * @returns O preço total final calculado.
   */
  calculateTotalPrice(items: OrderItem[], discounts: Discount[]): number {
    const subtotal = this.calculateSubtotal(items);
    const amountAfterDiscounts = this.applyDiscounts(subtotal, discounts);

    // Calcula o imposto: amount * taxRate
    const taxAmount = amountAfterDiscounts * this.taxRate;

    // Total final: amount após descontos + imposto
    const totalPrice = amountAfterDiscounts + taxAmount;

    return parseFloat(totalPrice.toFixed(2));
  }
}

// --- Exemplo de Uso ---

// Instancia o calculador com uma taxa de imposto de 7%
const calculator = new PricingCalculator(0.07);

// Define os itens do pedido
const orderItems: OrderItem[] = [
  { productId: 'prod-123', quantity: 2, unitPrice: 25.50 },
  { productId: 'prod-456', quantity: 1, unitPrice: 75.00 },
  { productId: 'prod-789', quantity: 3, unitPrice: 10.00 },
];

// Define os descontos aplicáveis
const orderDiscounts: Discount[] = [
  { code: 'SUMMER10', percentage: 0.10 }, // 10% de desconto
  { code: 'LOYALTY5', percentage: 0.05 }, // 5% de desconto
];

// Calcula e exibe os resultados
const subtotal = calculator.calculateSubtotal(orderItems);
console.log(`Subtotal: $${subtotal}`); // Esperado: $176.00

const finalPrice = calculator.calculateTotalPrice(orderItems, orderDiscounts);
console.log(`Preço Final (após descontos e impostos): $${finalPrice}`);
// Cálculo esperado:
// Subtotal: 176.00
// Após 10% de desconto: 176.00 * 0.90 = 158.40
// Após 5% de desconto: 158.40 * 0.95 = 150.48
// Imposto (7%): 150.48 * 0.07 = 10.5336
// Total: 150.48 + 10.5336 = 161.0136 -> Arredondado para 161.01

export { PricingCalculator, OrderItem, Discount }; // Exportando para possível uso em outros módulos
Enter fullscreen mode Exit fullscreen mode

Neste exemplo:

  • Tipagem Forte: Usamos interfaces TypeScript (OrderItem, Discount) e tipos de classe (PricingCalculator) para definir contratos claros e garantir a integridade dos dados.
  • Princípios de Código Limpo: Nomes de métodos e variáveis são descritivos (calculateSubtotal, unitPrice). A lógica é dividida em métodos menores e testáveis.
  • Tratamento de Erros e Validação: Verificações básicas são incluídas para entradas inválidas (por exemplo, quantidades negativas, taxas de imposto inválidas), prevenindo comportamentos inesperados.
  • Comentários: Explicações são fornecidas para partes complexas ou decisões de design, como a aplicação sequencial de descontos ou o arredondamento para moeda.
  • Modularidade: O PricingCalculator é uma unidade autocontida, tornando-o mais fácil de testar, reutilizar e refatorar independentemente.

Conclusão: Construindo para o Futuro

Adotar uma filosofia de \"componentes proprietários" não é resistir a bibliotecas externas; é tomar decisões informadas sobre onde investir o esforço de desenvolvimento e obter controle. Ao possuir as peças críticas do seu software, você ganha:

  • Segurança Aprimorada: Você controla o codebase e pode responder rapidamente a vulnerabilidades.
  • Maior Flexibilidade: Você pode adaptar os componentes precisamente à lógica do seu negócio e adaptá-los à medida que suas necessidades evoluem.
  • Risco Reduzido: Você minimiza a dependência de fatores externos e mitiga o impacto de problemas de terceiros.
  • Manutenibilidade a Longo Prazo: Componentes bem projetados e de propriedade são mais fáceis de entender, depurar e evoluir ao longo do tempo.

Essa abordagem estratégica permite que as equipes construam software mais resiliente, adaptável e, em última análise, mais valioso. É um investimento na estabilidade e sucesso futuros do seu produto.

Top comments (0)