Tema representativo de entrevista

Contratos en C++26: ¿Cómo implementar de forma segura precondiciones, poscondiciones y contract_assert?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

¿Cómo explicar pre, post y contract_assert, y migrar el uso existente de assert mientras el soporte del compilador es incompleto?

Planteamiento y alcance

Eres propietario de una biblioteca de órdenes y deseas usar Contratos de C++26 para expresar precondiciones de API, poscondiciones e invariantes en el cuerpo de las funciones. Explica los límites semánticos de pre, post y contract_assert, los modos de evaluación, el manejo de violaciones y una ruta de migración desde el uso existente de assert que no elimine la validación de entradas no confiables. Incluye una estrategia de lanzamiento mientras el soporte del compilador sea incompleto.

Qué está evaluando el entrevistador

  • Separar la documentación de contratos, los diagnósticos en tiempo de ejecución y la validación de entradas de negocio.
  • Explicar por qué los predicados pueden omitirse o evaluarse más de una vez, por lo que deben estar libres de efectos secundarios.
  • Diseñar un manejador de violaciones, telemetría y aislamiento de fallas sin asumir que toda violación lanza una excepción.
  • Usar una matriz de capacidades del compilador y un despliegue gradual para gestionar las diferencias de soporte en C++26.

Preguntas de clarificación

  1. ¿Los llamadores son código confiable de la biblioteca o manejadores directos de solicitudes de red?
  2. ¿Producción debe observar una violación, terminar el proceso o aislar la solicitud y continuar?
  3. ¿Qué compilador, versión de la biblioteca estándar y cadencia de versiones de ABI están dentro del alcance?

Estructura de respuesta en 30 segundos

Comienza con la responsabilidad: pre establece lo que el llamador debe satisfacer, post establece lo que la función llamada promete al retornar, y contract_assert establece un contrato local en el cuerpo de la función. Luego, explica que los predicados no deben tener efectos secundarios porque una implementación puede omitir o repetir la evaluación; las violaciones pasan por un manejador y una política de despliegue. Finalmente, mantén la validación explícita para entradas no confiables y usa detección de características, una matriz de compiladores y canaries para la migración.

Respuesta a profundidad

1. Establecer las capas de contratos

Las precondiciones son responsabilidad del llamador; las poscondiciones son responsabilidad de la función llamada. Expresan restricciones de API componibles, como una capacidad positiva o una garantía de ordenamiento. Un contract_assert en el cuerpo de la función expresa un invariante local o una suposición de etapa del algoritmo. Los tres deben ser legibles en la revisión de código y mantenerse separados de la política de recuperación.

2. Mantener los predicados libres de efectos secundarios

Un predicado solo debe leer el estado y calcular un booleano. No incrementes contadores, no mutes cachés, no liberes recursos ni dependas de aleatoriedad de un solo uso dentro de él. La semántica de contratos de C++26 permite diferentes modos de evaluación: algunos pueden omitir la evaluación y otros pueden evaluar más de una vez. Los efectos secundarios harían que el comportamiento correcto del programa dependa de la configuración de compilación.

cpp
int withdraw(Account& a, int amount)
  pre (amount > 0)
  pre (amount <= a.balance())
  post (a.balance() == old_balance - amount);
{
  contract_assert(a.is_open());
  return a.debit(amount);
}

old_balance en el fragmento resalta un problema de diseño; C++26 no proporciona una captura general de poscondiciones, así que no asumas una sintaxis old(...). Si se necesita el valor anterior, guárdalo explícitamente en la función y confirma que guardarlo no altere la semántica de negocio, o espera una extensión posterior del estándar.

3. Elegir la evaluación y el manejo de violaciones

Define el comportamiento para modos como observar y hacer cumplir. El desarrollo y las pruebas pueden recopilar diagnósticos; un servicio crítico puede fallar de inmediato bajo cumplimiento estricto; un límite de solicitud puede convertir una falla en un resultado observable y aislado. No prometas que una violación siempre lanza una excepción: el comportamiento depende de la implementación y de la configuración de compilación. El manejador debe registrar la ubicación del contrato, el ID de correlación de la solicitud y la versión, evitando al mismo tiempo la entrada recursiva en la misma ruta de contrato.

4. Mantener la validación de entrada en el límite

Los campos de red, los montos de usuario y los permisos no son confiables. Valídalos explícitamente y devuelve un error de negocio esperado antes de llamar a una función con contratos internos. Los contratos pueden detectar errores de programación entre llamadores confiables, pero no reemplazan la autenticación, la autorización, la limitación de tasa ni las comprobaciones de formato, y no deben ser la única defensa para el saneamiento de datos.

5. Planificar la migración y el lanzamiento

Construye una matriz de capacidades por macro de características y versión del compilador, probando por separado la sintaxis, los manejadores, la información de depuración y las compilaciones optimizadas. Habilita el modo de observación en pruebas y canaries, compara la tasa de violaciones y la sobrecarga, y luego incrementa el cumplimiento gradualmente. El assert tradicional tiene expansión de macros, NDEBUG y suposiciones de efectos secundarios que no se pueden equiparar mecánicamente; audita cada uso, preserva la semántica de fallas y usa un adaptador cuando se requiera un reporte uniforme.

Respuesta de muestra de alta calidad

Trato los Contratos como restricciones de diseño ejecutables, no como validación de entrada o un sistema de excepciones. pre restringe al llamador, post restringe a la función llamada y contract_assert restringe un invariante en el cuerpo de la función. Cada predicado permanece de solo lectura porque una implementación puede omitir o repetir la evaluación, por lo que prohíbo E/S, mutación de contadores y liberación de recursos. Las violaciones pasan por un único manejador que emite diagnósticos estructurados; la política de compilación luego elige observación, cumplimiento estricto o comportamiento fail-fast, y el código no asume una excepción. El límite de la solicitud sigue realizando autenticación, autorización y verificaciones de datos no confiables. Para la migración, creo una matriz de capacidades del compilador, comienzo con pruebas y canaries, y aumento el nivel de cumplimiento gradualmente; audito NDEBUG y los efectos secundarios de macros en lugar de reemplazar assert mecánicamente. Esto proporciona verificaciones internas estables sin hacer que la recuperación en línea dependa de comportamientos de implementación no uniformes.

Errores comunes

  • Tratar pre como una verificación de seguridad completa para cada entrada.
  • Registrar logs, incrementar métricas o mutar objetos dentro de los predicados.
  • Afirmar que una violación debe lanzar una excepción o debe terminar, independientemente del modo.
  • Reemplazar cada assert con contract_assert y pasar por alto NDEBUG, argumentos de macro o efectos secundarios.
  • Describir poscondiciones de C++26 con una sintaxis general inexistente old(...).

Preguntas de seguimiento y respuestas

¿Qué pasa si el entrevistador pregunta: “¿Por qué no usar excepciones en todas partes?”?

Los contratos establecen la responsabilidad del llamador y de la función llamada y pueden participar en revisiones y políticas de compilación; las excepciones definen el flujo de control y la recuperación. Se complementan entre sí y no son intercambiables.

¿Qué pasa si la evaluación repetida de predicados es demasiado costosa?

Mantén los predicados de solo lectura y económicos, luego mide cada modo de compilación. Coloca los diagnósticos costosos en una ruta explícita en lugar de ocultar efectos secundarios en un contrato.

¿Qué pasa si preguntan si las funciones virtuales pueden llevar contratos directamente?

C++26 tiene un límite aquí: la hoja de ruta de WG21 lista el soporte de funciones virtuales como una extensión posterior. Verifica el compilador objetivo y la versión del estándar en lugar de presentar una propuesta futura como el comportamiento existente de C++26.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta