Programación confiable por idioma: revisión novata. Parte 1

Una vez más, después de haber tardado dos días en escribir y depurar solo cuatrocientas líneas de código de la biblioteca del sistema, surgió la idea: "como si fuera bueno si los programas se escribieran de una manera menos dolorosa".

Y en primer lugar, dado que la depuración lleva mucho más tiempo que escribir código, necesita protección contra el tonto (incluido usted mismo) en la etapa de escritura. Y me gustaría recibirlo del lenguaje de programación utilizado (YP).

Por supuesto, tenemos que inventar un nuevo y mejor YaP!
No, primero intentaremos expresar nuestros deseos y ver lo que ya hemos inventado.

Entonces, lo que me gustaría recibir:

  • Resistencia a errores humanos, eliminación de ambigüedades durante la compilación.
  • Resistencia de entrada
  • Resistencia al daño del programa o de los datos: falla de los medios, piratería
  • Al mismo tiempo, todos tienen una sintaxis y funcionalidad más o menos tolerables.

Las áreas de aplicación son maquinaria, transporte, sistemas de control industrial, IoT, embebido, incluidos teléfonos.

Apenas es necesario para la Web, se basa (por ahora) en el principio de "salir y reiniciar" (disparar y olvidar).

Rápidamente, puede llegar a la conclusión de que el lenguaje debe compilarse (al menos compilado por Pi) para que todas las comprobaciones se ejecuten al máximo en la etapa de compilación sin VS (Versus, en adelante la oposición negativa) "oh, este objeto no tiene esa propiedad" en tiempo de ejecución. Incluso la secuencia de comandos de las descripciones de la interfaz ya hace obligatorio cubrir completamente dichas secuencias de comandos con pruebas.

Personalmente, no quiero pagar con mis recursos (incluido dinero para hardware más rápido y más costoso) por interpretación, por lo tanto, es recomendable tener una compilación JIT mínima.

Entonces, los requisitos.

Tolerancia a errores humanos


Después de hojear diligentemente los Talmuds de PVS-Studio, descubrí que los errores más comunes son errores tipográficos y copiar y pegar incompletos.

También agregaré un poco de incidentes de mi experiencia y me reuní en varias publicaciones, como ejemplos negativos. Además se actualizaron las reglas de MISRA C en la memoria.

Después de pensarlo un poco, llegué a la conclusión de que los linters aplicados después del hecho sufren el "error del sobreviviente", ya que en proyectos antiguos ya se han solucionado errores graves.

a) Deshágase de nombres similares

- Se debe hacer una verificación estricta de la visibilidad de las variables y funciones. Una vez sellado, puede usar un identificador de un alcance más general, en lugar del deseado
- Utilice nombres que no distingan entre mayúsculas y minúsculas. (VS) "Llamemos a la función como una variable, solo en Camelcase" y luego compare con algo; en C esto se puede hacer (obtenemos la dirección de la función, que es un número considerable)
- Los nombres con una diferencia de 1 letra deberían causar una advertencia (posiblemente, puede resaltar en el IDE) pero un error muy común de copiar y pegar .x, .y, .w, .h.
- no permitimos nombrar diferentes entidades de la misma manera - si hay una constante con ese nombre, entonces no debería haber una variable con el mismo nombre o un nombre de tipo
- es altamente deseable verificar el nombre de todos los módulos del proyecto - es fácil de confundir, especialmente si diferentes personas escriben módulos diferentes

b) Una vez mencionado, debe haber modularidad y preferiblemente jerarquía, un proyecto VS de 12,000 archivos en un directorio es un infierno de búsqueda.
Todavía se requiere modularidad para describir las estructuras de intercambio de datos entre diferentes partes (módulos, programas) de un proyecto. VS Me encontré con un error debido a la diferente alineación de números en la estructura de intercambio en el receptor y el transmisor.

- Para excluir la posibilidad de enlaces duplicados (diseño).

c) Ambigüedades
- Debe haber un orden específico de llamadas a funciones. Al escribir X = funcA () + fB () o Fn (funcA (), fB (), callC ()), debe comprender que la persona espera recibir los cálculos en el orden escrito, (VS) y no como pensó el optimizador.
- Excluir operadores similares. Y no como en C: + ++, <<<, | ||, & &&, = ==
- Es aconsejable tener algunos operadores comprensibles y con una prioridad obvia. Hola del operador ternario.
- Anular operadores es bastante dañino. Escribe i: = 2, pero (VS) en realidad causa una creación implícita de un objeto para el cual no hay suficiente memoria, y el disco se bloquea al intercambiar y su satélite se estrella a Marte :-(

De hecho, por experiencia personal observé un bloqueo en la línea ConnectionString = "DSN", resultó ser un instalador que abrió la base de datos (y el servidor no era visible en la red).

- Necesitamos inicializar todas las variables con valores predeterminados.
- Además, el enfoque OOP salva del olvido la reasignación de todos los campos en el objeto en alguna nueva centésima función.
- El sistema de tipos debe ser seguro - necesita control sobre las dimensiones de los objetos asignados - protección contra sobrescritura de memoria, desbordamiento aritmético de tipo 65535 + 1, pérdida de precisión e importancia al emitir, excluyendo comparaciones incomparables - porque el entero 2 no es igual a 2.0 en el caso general.

E incluso una división típica por 0 puede dar un + INF muy definido, en lugar de un error, se necesita una definición exacta del resultado.

Resistencia de entrada


- El programa debería funcionar en cualquier dato de entrada y preferiblemente, aproximadamente al mismo tiempo. (VS) Hola a Android con una reacción al botón del teléfono de 0.2s a 5s; es bueno que no Android esté manejando ABS automotriz.

Por ejemplo, un programa debe procesar correctamente 1Kb de datos y 1TB sin haber agotado los recursos del sistema.

- Es muy deseable tener un manejo de errores confiable e inequívoco en RAII que no conduzca a efectos secundarios (fugas de recursos, por ejemplo). (VS) Una cosa muy divertida es la fuga de manijas, que puede manifestarse después de muchos meses.
- Sería bueno protegerse del desbordamiento de la pila: deshabilite la recursividad.
- El problema de exceder el volumen disponible con la memoria requerida, el crecimiento incontrolado del consumo debido a la fragmentación durante la asignación dinámica / desasignación. Si el lenguaje tiene un tiempo de ejecución dependiente del montón, lo más probable es que sea malo: hola STL y Phobos. (VS) Hubo una historia con un viejo C-time de Microsoft, que devolvió la memoria de forma inadecuada al sistema, debido a que msbackup se bloqueó en grandes volúmenes (para ese momento).
- Necesitamos un trabajo bueno y seguro con cadenas, no limitado por los recursos. Es altamente dependiente de la implementación (arreglos inmutables, COW, R / W)
- Exceder el tiempo de reacción del sistema, independiente del programador. Este es un problema típico del recolector de basura. Aunque salvan de algunos errores de programación, otros traen, mal diagnosticados.
- En una determinada clase de tareas, resulta que puede prescindir de la memoria dinámica, o seleccionándola una vez al inicio.
- Para controlar la salida más allá de los límites de la matriz, y es perfectamente aceptable escribir una advertencia de tiempo de ejecución e ignorarla. Muy a menudo estos son errores no críticos.
- Tenga protección contra accesos a una sección de memoria que no fue inicializada por el programa, incluida la región nula y el espacio de direcciones de otra persona.
- Intérpretes, JIT: las capas adicionales reducen la confiabilidad, hay problemas con la recolección de basura (un subsistema muy complejo, cometerá errores) y con un tiempo de respuesta garantizado. Lo excluimos, pero en principio existe la Edición Micro de Java (donde está tan aislado de Java que solo yo me quedo, hubo un artículo interesante de dernasherbrezon (perdón, disparo) y .NET Micro Framework con C #.

Sin embargo, por consideración, estas opciones han desaparecido:

  • .NET Micro resultó ser un intérprete ordinario (tachado por la velocidad);
  • Java Micro es adecuado solo para aplicaciones integradas, ya que está demasiado castrado por API, y tendrá que cambiar al menos a SE Embedded para el desarrollo, que ya ha sido cerrado o Java normal, que es demasiado monstruoso e impredecible en respuesta.
    Sin embargo, todavía hay opciones , y aunque esto no parece un espacio en blanco para una base viable, se puede comparar con otros idiomas, incluso obsoletos o con ciertas desventajas.


- Resistencia al trabajo de subprocesos múltiples: protección de datos privados de una secuencia y mecanismos para garantizar el intercambio entre secuencias. Un programa con 200 hilos puede no funcionar en absoluto como en dos.
- La programación por contrato más las pruebas unitarias incorporadas también ayudan a dormir tranquilo.

Resistencia al daño del programa o de los datos: falla de los medios, piratería


- El programa debe estar completamente cargado en la memoria, sin cargar módulos, especialmente de forma remota.
- Borrar memoria al liberar (y no solo la asignación)
- Supervisión de desbordamiento de pila, áreas variables, especialmente cadenas.
- Reiniciar después de una falla.

Por cierto, el enfoque cuando el tiempo de ejecución tiene su propio registro, y no solo muestra que el zorro del norte y el stackrace, estoy muy impresionado.

Idiomas - y tabla de conformidad


A primera vista, para el análisis, tomamos PL seguros especialmente diseñados:

  1. Oberon activo
  2. Ada
  3. BetterC (subconjunto de dlang)
  4. IEC 61131-3 ST
  5. Safe-c

Y revíselos en términos de los criterios anteriores.

Pero este ya es el volumen del artículo de continuación, si el karma lo permite.

Con los factores antes mencionados resaltados en la tabla, bueno, tal vez se deduzca algo más sensato de los comentarios.

En cuanto a otros lenguajes interesantes: C ++, Crystal, Go, Delphi, Nim, Red, Rust, Zig (agregar al gusto), luego lo dejaré a aquellos que deseen completar la tabla de correspondencia.

Descargos de responsabilidad:

  • En principio, si un programa, por ejemplo en Python, consume 30 MB, y los requisitos de reacción son segundos, y el microordenador tiene 600 MB de memoria libre y 600 MHz por ciento, entonces ¿por qué no? Solo dicho programa será confiable con alguna probabilidad (aunque 96%), no más.
  • Además, el lenguaje debería ser conveniente para el programador; de lo contrario, nadie lo usará. Tales artículos "Se me ocurrió el lenguaje de programación ideal para que solo fuera conveniente para mí escribir" tampoco son infrecuentes en Habré, pero se trata de otra cosa.

Source: https://habr.com/ru/post/437830/


All Articles