
El tema del código ideal a menudo causa controversia entre los programadores experimentados. Fue aún más interesante obtener la opinión del Director de Desarrollo de Parallels RAS, Igor Marnat. Debajo del corte, la opinión de su autor sobre el tema declarado. ¡A disfrutar!

Como introducción, me gustaría detenerme en la pregunta de por qué decidí escribir este breve artículo. Antes de escribirlo, hice una pregunta del título a varios desarrolladores. Trabajé con la mayoría de los chicos durante más de cinco años, con algunos un poco menos, pero confío en su profesionalismo y experiencia incondicionalmente. Toda la experiencia en desarrollo industrial durante más de diez años, todos trabajan en empresas rusas e internacionales, fabricantes de software.
A algunos colegas les resultó difícil responder (algunas personas todavía piensan), otros llamaron uno o dos ejemplos a la vez. Para aquellos que dieron ejemplos, hice una pregunta aclaratoria: "¿Qué causó realmente esta admiración?" Las respuestas fueron consistentes con los resultados de la siguiente fase de mi pequeña investigación. Busqué en la web respuestas a esta pregunta en diferentes formulaciones cerca del título del artículo. Todos los artículos respondieron aproximadamente de la misma manera que respondieron mis camaradas.
Las respuestas de los desarrolladores, así como la redacción de los artículos encontrados, relacionadas con la legibilidad y la estructura del código, la elegancia de las construcciones lógicas, el uso de todas las características de los lenguajes de programación modernos y un cierto estilo de diseño.
Cuando hice la pregunta sobre el "código divino" para mí, la respuesta surgió de inmediato, desde el subconsciente. Inmediatamente pensé en dos ejemplos de código con los que había estado trabajando durante mucho tiempo (hace más de diez años), pero todavía siento admiración y reverencia. Después de considerar los motivos de admiración de cada uno de ellos, formulé varios criterios, que se analizarán a continuación. Me detendré en el primer ejemplo de pasada, pero me gustaría analizar el segundo con más detalle. Por cierto, en un grado variable, todos estos criterios se consideran en el manual "
Código perfecto " de cada desarrollador de Steve McConnell, pero este artículo es notablemente más corto.
Ejemplo de los 90
El primer ejemplo que mencionaré se refiere a la implementación del protocolo de módem v42bis. Este protocolo fue desarrollado a finales de los 80 y principios de los 90. Una idea interesante, encarnada por los desarrolladores del protocolo, es la implementación de la compresión de flujo de información durante la transmisión a través de una línea de comunicación inestable (telefónica). La diferencia entre la compresión de flujo y la compresión de archivos es fundamental. Al comprimir archivos, el archivador tiene la capacidad de analizar el conjunto de datos por completo, determinar el enfoque óptimo para comprimir y codificar datos, y escribir todos los datos en el archivo sin preocuparse por la posible pérdida de datos y metadatos. Al descomprimir, a su vez, el conjunto de datos es completamente accesible nuevamente, la integridad está asegurada por una suma de verificación. Con la compresión en línea, el archivador puede acceder solo a una pequeña ventana de datos, no hay garantía de que no se pierdan datos, la necesidad de reinstalar la conexión e inicializar el proceso de compresión es común.
Los autores del algoritmo encontraron una solución elegante, cuya descripción toma
literalmente varias páginas . Han pasado muchos años, pero todavía estoy impresionado por la belleza y la gracia del enfoque propuesto por los desarrolladores del algoritmo.
Este ejemplo todavía no se refiere al código como tal, sino al algoritmo, por lo que no nos detendremos en él con más detalle.
¡Linux es la cabeza de todo!
Me gustaría analizar el segundo ejemplo de un código perfecto con más detalle. Este es el código del kernel de Linux. El código que, al momento de escribir, controla el trabajo de 500 supercomputadoras de las
500 principales , el código que se ejecuta en cada segundo teléfono del mundo y que controla la mayoría de los servidores en Internet.
Por ejemplo, considere el archivo memory.c del
kernel de Linux , que pertenece al subsistema de administración de memoria.
1. Las fuentes son fáciles de leer. Están escritos usando un estilo muy simple que es fácil de seguir y difícil de confundir. Los caracteres en mayúscula se usan solo para directivas y macros de preprocesadores, todo lo demás está escrito en letras pequeñas, las palabras en los nombres están separadas por guiones bajos. Este es quizás el estilo de codificación más fácil posible, excepto por la falta de un estilo. Al mismo tiempo, el código es perfectamente legible. El enfoque de sangría y comentarios es visible desde cualquier parte de cualquier archivo del núcleo, por ejemplo:
static void tlb_remove_table_one(void *table) { smp_call_function(tlb_remove_table_smp_sync, NULL, 1); __tlb_remove_table(table); }
2. No hay demasiados comentarios en el código, pero los que sí suelen ser útiles. Ellos, por regla general, no describen una acción que ya es obvia en el código (un ejemplo clásico de un comentario inútil es "cnt ++; // contador de incremento"), sino el contexto de esta acción: por qué aquí se hace lo que se hace, por qué se hace así, por qué aquí, con qué supuestos se usa, con qué otros lugares del código está conectado. Por ejemplo:
void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end)
Otro uso de los comentarios en el núcleo es describir el historial de cambios, generalmente al comienzo de un archivo. La historia del núcleo ha existido durante casi treinta años, y es interesante leer algunos lugares, te sientes parte de la historia:
3. El código del núcleo utiliza macros especiales para verificar los datos. También se utilizan para verificar el contexto en el que funciona el código. La funcionalidad de estas macros es similar a la afirmación estándar, con la diferencia de que el desarrollador puede anular la acción que se realiza si la condición es verdadera. Un enfoque general para el procesamiento de datos en el núcleo: se verifica todo lo que proviene del espacio del usuario; en caso de datos erróneos, se devuelve el valor correspondiente. En este caso, WARN_ON puede usarse para emitir un registro en el registro del kernel. BUG_ON suele ser muy útil al depurar un nuevo código e iniciar el núcleo en nuevas arquitecturas.
La macro BUG_ON generalmente hace que se imprima el contenido de los registros y la pila y detiene todo el sistema o el proceso en el contexto del cual se produjo la llamada correspondiente. La macro WARN_ON simplemente emite un mensaje al registro del kernel si la condición es verdadera. También hay macros WARN_ON_ONCE y una serie de otras, cuya funcionalidad está clara por el nombre.
void unmap_page_range(struct mmu_gather *tlb, …. unsigned long next; BUG_ON(addr >= end); tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, … unsigned long end = addr + size; int err; if (WARN_ON(addr >= end)) return -EINVAL;
El enfoque mediante el cual los datos obtenidos de fuentes poco confiables se verifican antes de su uso, y se prevé y determina la respuesta del sistema a situaciones "imposibles", simplifica significativamente la depuración y operación del sistema. Puede considerar este enfoque como una implementación del principio de falla temprana y en voz alta.
4. Todos los componentes principales del núcleo proporcionan a los usuarios información sobre su estado a través de una interfaz simple, el sistema de archivos virtual / proc /.Por ejemplo, la información del estado de la memoria está disponible en el archivo / proc / meminfo
user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal: 2041480 kB MemFree: 65508 kB MemAvailable: 187600 kB Buffers: 14040 kB Cached: 246260 kB SwapCached: 19688 kB Active: 1348656 kB Inactive: 477244 kB Active(anon): 1201124 kB Inactive(anon): 387600 kB Active(file): 147532 kB Inactive(file): 89644 kB ….
La información anterior se recopila y procesa en varios archivos fuente del subsistema de administración de memoria. Entonces, el primer campo MemTotal es el valor del campo totalram de la estructura sysinfo, que se llena con la función
si_meminfo del archivo page_alloc.c .
Obviamente, organizar la recopilación, el almacenamiento y proporcionar al usuario acceso a dicha información requiere un esfuerzo por parte del desarrollador y una sobrecarga del sistema. Al mismo tiempo, los beneficios de tener un acceso conveniente y fácil a dichos datos son invaluables tanto en el proceso de desarrollo como en la operación del código.
El desarrollo de casi cualquier sistema debe comenzar con un sistema para recopilar y proporcionar información sobre el estado interno de su código y datos. Esto será de gran ayuda en el proceso de desarrollo y prueba y, más tarde, en la operación.
Como
dijo Linus , "los programadores malos se preocupan por el código. Los buenos programadores se preocupan por las estructuras de datos y sus relaciones ".
5. Todo el código es leído y discutido por varios desarrolladores antes de comprometerse. Se registra un historial de cambios en el código fuente y está disponible. Los cambios en cualquier línea pueden rastrearse hasta su aparición: qué cambió, quién, cuándo, por qué, qué problemas fueron discutidos por los desarrolladores. Por ejemplo, el cambio en https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef en el código de memoria.c está inspirado por el httpsbbggg.shgbbbgg.shgbbbgg Se ha realizado una pequeña optimización del código (no se produce una llamada para habilitar la protección contra escritura de la memoria si la memoria ya está protegida contra escritura).
Siempre es importante para un desarrollador que trabaja con el código comprender el contexto en torno a este código, con qué supuestos se creó el código, qué y cuándo cambió, para comprender qué escenarios podrían verse afectados por los cambios que va a realizar.
6. Todos los elementos importantes del ciclo de vida del código del kernel están documentados y accesibles , comenzando
con el estilo de codificación y terminando con el
contenido y la programación para el lanzamiento de versiones estables del kernel . Cada desarrollador y usuario que quiera trabajar con el código del kernel de una forma u otra tiene toda la información necesaria para esto.
Estos momentos me parecieron importantes, básicamente, determinaron mi entusiasmo por el código central. Obviamente, la lista es muy corta y se puede ampliar. Pero los puntos enumerados anteriormente, en mi opinión, se relacionan con aspectos clave del ciclo de vida de cualquier código fuente desde el punto de vista del desarrollador que trabaja con este código.
Lo que me gustaría decir en conclusión. Los desarrolladores principales son inteligentes y experimentados, han tenido éxito. Probado por miles de millones de dispositivos Linux
Sea como desarrollador de kernel, use las mejores prácticas y lea Code Complete!
Z.Y. Por cierto, ¿qué criterio de un código ideal tiene usted personalmente? Comparte tus pensamientos en los comentarios.