Escrevendo um código React melhor com a separação do princípio de comando e consulta

May 03 2023
Um dos princípios que uso constantemente em meu código é uma forma especial ou um uso concreto do Princípio de Responsabilidade Única em seu núcleo. Este princípio é o Princípio de Separação de Comando e Consulta.

Um dos princípios que uso constantemente em meu código é uma forma especial ou um uso concreto do Princípio de Responsabilidade Única em seu núcleo. Este princípio é o Princípio de Separação de Comando e Consulta .

Foto de Charles G no Unsplash

A separação de comando e princípio de consulta é um princípio de design de software que sugere que métodos ou funções devem ser comandos que modificam o estado do sistema ou consultas que retornam informações sobre o estado do sistema, mas não ambos.

Comandos (ou modificadores) são métodos que executam uma ação ou alteram o estado de um objeto sem retornar um valor. As consultas, por outro lado, são métodos que alteram o estado de um objeto. A separação de comandos e consultas pode ajudar a reduzir o acoplamento entre os componentes, facilitando o teste, a manutenção e a modificação do código. Também torna mais fácil raciocinar sobre o comportamento do código e pode melhorar o design geral de um sistema.

O código abaixo é considerado ruim porque addItemestá removeItemfazendo mais de uma coisa: alterando os dados, atualizando o preço total e retornando o valor atualizado.

class ShoppingCart {
  constructor() {
    this.items = [];
    this.totalPrice = 0;
  }

  addItem(item) {
    this.items.push(item);
    this.updateTotalPrice();
    
    return this.totalPrice;
  }

  removeItem(item) {
    const index = this.items.indexOf(item);
    if (index > -1) {
      this.items.splice(index, 1);
      this.updateTotalPrice();
    }

    return this.totalPrice;
  }

  updateTotalPrice() {
    for (let i = 0; i < items.length; i++) {
      this.totalPrice += items[i].price;
    }
  }
}

class ShoppingCart {
  constructor() {
    this.items = [];
    this.totalPrice = 0;
  }

  addItem(item) {
    this.items.push(item);
  }

  removeItem(item) {
    const index = this.items.findIndex((item) => item.id === id);
    if (index > -1) {
      this.items.splice(index, 1);
    }
  }

  get totalPrice () {
      this.items.reduce((total, item) => total + item.price, 0) 
  }
}

Em um aplicativo React, podemos separar modificadores e consultas usando uma biblioteca de gerenciamento de estado como Redux ou React Context API. Isso nos permite separar a lógica para modificar o estado do aplicativo da lógica para renderizar a interface do usuário.

Para aprender mais sobre Context e outros hooks, você pode baixar uma folha de dicas aqui contendo os hooks React mais comuns com exemplos e ilustrações.

Este princípio de Separação de Responsabilidade de Comando-Consulta pode ser aplicado em diferentes níveis. No nível da arquitetura, você já deve ter ouvido falar do CQRS nesse contexto. Essencialmente, eles são o mesmo princípio aplicado em diferentes níveis de abstração.

Exemplo: carrinho de compras

Um aplicativo de carrinho de compras pode ter uma ShoppingCartclasse que gerencia os itens do carrinho e calcula o preço total. Os métodos addIteme removeItemda ShoppingCartclasse são comandos que modificam os itens do carrinho, enquanto o getTotalPricemétodo é uma consulta que retorna o preço total dos itens do carrinho.

Antes de aplicar o Princípio de Separação de Comando e Consulta, os métodos addIteme removeItemtambém podem atualizar a propriedade de preço total diretamente. No entanto, isso pode tornar o código mais difícil de entender e manter.

Aqui está um exemplo de um componente React que lida com um carrinho de compras:

import { useState, useMemo } from 'react';

function ShoppingCart() {
  const [cart, setCart] = useState([]);

  // Command: Add item to cart
  function addItemToCart(item) {
    setCart([...cart, item]);
  }

  // Command: Remove item from cart
  function removeItemFromCart(id) {
    setCart(cart.filter((item) => item.id !== id));
  }

  // Query: Calculate total price of items in cart
  const totalPrice = useMemo(() => {
    return cart.reduce((total, item) => total + item.price, 0);
  }, [cart]);

  return (
    <div>
      <h2>Shopping Cart</h2>
      <ul>
        {cart.map((item) => (
          <li key={item.id}>
            {item.name} - {item.price}
            <button onClick={() => removeItemFromCart(item.id)}>Remove</button>
          </li>
        ))}
      </ul>
      <p>Total Price: {totalPrice}</p>
    </div>
  );
}

Benefícios da separação comando-consulta

A separação de comandos e consultas em seu código pode fornecer os seguintes benefícios:

  • Clareza: Ao separar comandos e consultas, seu código fica mais fácil de ler e entender, pois fica mais fácil determinar o que cada método faz e o que retorna.
  • Modularidade: separar comandos e consultas pode tornar seu código mais modular e reutilizável, pois comandos e consultas podem ser usados ​​de forma independente.
  • Testabilidade: separar comandos e consultas pode facilitar o teste do seu código, pois isolar e testar métodos individuais é mais fácil.
  • Complexidade reduzida: separar comandos e consultas pode ajudar a reduzir a complexidade do seu código, pois o incentiva a dividir operações complexas em partes menores e mais gerenciáveis.

Por outro lado, o princípio introduz mais uma abstração extra no código que pode tornar a depuração um pouco mais difícil. Usar um mecanismo de pub-sub ou ouvinte de eventos para separar comandos e consultas pode dificultar a depuração e o raciocínio sobre o comportamento do código, especialmente quando o site de chamada está longe dos dados que ele modifica.

Usar uma arquitetura bem definida e um estilo de codificação consistente é essencial para manter o código organizado e de fácil manutenção. Também é importante usar nomes claros e descritivos para funções e variáveis ​​para tornar seu propósito e comportamento mais compreensíveis.

Outra abordagem para separar comandos e consultas em um aplicativo React é usar uma biblioteca de gerenciamento de estado como Redux ou MobX. Essas bibliotecas fornecem um armazenamento centralizado para o estado do aplicativo e oferecem uma maneira clara e estruturada de modificar e acessar o estado de diferentes partes do aplicativo.

No Redux, por exemplo, podemos definir ações como comandos que descrevem uma mudança de estado e redutores como consultas que lidam com essas ações e retornam um novo estado:

// Define actions
const addToCart = (item) => ({
  type: 'ADD_TO_CART',
  payload: item,
});

const removeItemFromCart = (id) => ({
  type: 'REMOVE_FROM_CART',
  payload: id,
});

// Define reducer
const cartReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TO_CART':
      return [...state, action.payload];
    case 'REMOVE_FROM_CART':
      return state.filter((item) => item.id !== action.payload);
    default:
      return state;
  }
};

Conclusão

A separação de comando e princípio de consulta é um princípio importante a ser considerado ao projetar software. A separação de comandos e consultas torna seu código mais modular, testável e fácil de entender. Aderindo a esse princípio, você pode escrever um código mais fácil de manter e menos sujeito a erros e bugs.

Em resumo, ao separar o código que modifica o estado do código que retorna informações sobre o estado, você pode criar um código mais modular e testável, mais fácil de entender e manter. Com uma separação clara entre comandos e consultas, você pode obter uma arquitetura de software mais sustentável e escalável.

Se você gosta da leitura, por favor, inscreva-se na minha lista de e-mail . Compartilho técnicas de código limpo e refatoração semanalmente em blogs , livros e vídeos .