El principio de Separación Comando-Consulta (Command-Query Separation / CQS) es una metodología de programación propuesta por Bertrand Meyer, creador del lenguaje Eiffel.
Este principio establece:
Cada función debe ser o un comando, que ejecuta una acción, o una consulta (query), que extrae datos, pero nunca ambas al mismo tiempo.
- Un comando siempre produce efectos secundarios.
- Una consulta es una función pura y no modifica el estado del sistema.
Comando
# Devuelve True o False como resultado de su ejecución
save(user)
Según el principio CQS, la función save() es un comando. Lo único que puede devolver es la confirmación de su ejecución (True o False), o incluso None, como en el caso de print().
Romper CQS sería devolver cualquier otro dato significativo desde save(). Sin embargo, hay excepciones.
Por ejemplo, cuando se abre un archivo para escritura, la operación devuelve un descriptor de archivo en lugar de None:
file = open('/etc/hosts', 'r')
La separación entre comandos y consultas está estrechamente relacionada con el concepto de funciones puras.
- Los comandos ejecutan código no determinista y con efectos secundarios.
- Las consultas pueden ejecutarse múltiples veces sin alterar el estado del sistema.
Separar los comandos de las consultas mejora la claridad y previene errores lógicos.
Consulta
# Devuelve True o False
is_admin(user)
La función is_admin() es un predicado y un claro ejemplo de una consulta.
Una consulta nunca debe modificar el estado del sistema. Por lo tanto, is_admin() no debería modificar al usuario ni convertirlo en administrador, ya que eso rompería CQS y sería ilógico.
A diferencia de un comando, el valor True o False en una consulta no indica éxito o fracaso, sino simplemente la respuesta a una pregunta.
❌ Problema con funciones que parecen consultas pero no lo son.
⏬ Ejemplo con una función que modifica los datos originales:
users = {
{'name': 'Stan', 'kids': ['John', 'Mary']},
{'name': 'Donald', 'kids': ['James']},
{'name': 'Lily', 'kids': []},
{'name': 'Julian', 'kids': []}
}
# Al principio parece que take_kids() devuelve una lista
# con los hijos de todos los usuarios
take_kids(users) # ['John', 'Mary', 'James']
# Pero en realidad, modifica el diccionario users y lo regresa
print(users) # => ['John', 'Mary', 'James']
Si llamamos take_kids(users) de nuevo, el código probablemente fallará porque la estructura de users ya cambió. Esto viola CQS porque una función que aparenta ser una consulta no debería modificar los datos originales.
Una forma alternativa de expresar CQS es:
Si haces una pregunta, no cambies la respuesta.
⏬ Ejemplo de una consulta bien diseñada:
max_number = max([1, 30, 4])
✔️ No produce efectos secundarios.
✔️ Es determinista: puedes llamarlo infinitas veces sin alterar el estado del sistema.
Cualquier modificación en una consulta rompe la intuición natural del programador. Nadie espera que is_admin() o max() causen un efecto destructivo en el sistema.
Situaciones complicadas
En la práctica, no siempre es posible separar perfectamente comandos y consultas.
Por ejemplo, supongamos que al iniciar sesión en un sitio web, se genera automáticamente un nombre de usuario si el usuario aún no tiene uno.
# Verificamos si el usuario tiene un nombre
username = get_name()
if not username:
# Si no tiene, generamos uno y lo guardamos
username = generate_username()
set_name(username)
Si encapsulamos toda esta lógica en una función, ¿cómo debería llamarse?
username = get_username()
Esto es confuso, porque get_username() parece ser una consulta, pero en realidad está modificando el estado del sistema.
✅ Solución: usar un nombre de comando
username = set_username()
O incluso más explícito:
username = set_username_if_empty()
✔️ Aunque sigue violando CQS, al menos deja claro que lo está haciendo.
✔️ Un buen nombre ayuda a evitar confusiones y errores.
Resumen
- Comandos modifican el estado del sistema y pueden tener efectos secundarios.
- Consultas solo devuelven datos y nunca deben modificar nada.
- Si una función parece una consulta pero modifica datos, está mal diseñada.
- En casos inevitables, elige un nombre claro que refleje el propósito de la función.
Materiales adicionales
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.