¿Cómo se empieza a abordar la comprensión de una gran base de código de código abierto?

Cuando me acerco a una base de código grande, usualmente utilizo un enfoque combinado de subsistemas principales / inmersión profunda.

Paso 1: Entendiendo los principales subsistemas
La primera etapa en la comprensión de cualquier pieza de software es la comprensión de los subsistemas principales. Por lo general, puede consultar la jerarquía de directorios del código fuente como guía. Por ejemplo, aquí hay un navegador de código fuente para Linux:

http://lxr.free-electrons.com/

Así que podemos ver a partir de esto que hay algunos subsistemas, como drivers, crypto, kernel, mm, net, fs, ipc. La primera etapa es entender lo que hacen, por ejemplo, mm es la gestión de la memoria: la forma en que se asignan las páginas, cómo funciona el intercambio, etc. fs es sistemas de archivos, ipc: comunicaciones entre procesos.

Es posible que deba ir algunos niveles más profundo que solo el nivel superior. Por ejemplo, es posible que deba buscar en el núcleo para ver cuáles son las subpartes del núcleo.

De manera similar, si observa JDK, encontrará algunas piezas diferentes: debe comprender qué son esas piezas: el analizador, la máquina virtual, las bibliotecas centrales, la JNI, los controladores para cosas como gráficos, etc.

Paso 2: buceo profundo
Nota: Solo debe intentarse después de comprender los subsistemas principales.

Por lo general, cuando está leyendo el código fuente, es por una razón: desea realizar una modificación, por lo que desea comprender un componente en particular. Así que puedes rastrear el funcionamiento de ese componente en particular.

Alternativamente, si no tiene un componente en particular en mente, elija algunas actividades de ejemplo: por ejemplo, para el kernel de Linux, podría estar interesado en: ¿cómo iniciar un proceso?

Por lo tanto, buscaría dónde empezar, que es la llamada exec () en unistd.h (http://lxr.free-electrons.com/so…).

Aquí es donde se pone interesante: por el agujero del conejo, vaya. Ahí es cuando necesita comprender los sistemas de compilación, los archivos make, etc., para comprender cómo llegamos desde esa única llamada, hasta el cable metálico.

Un poco más de buceo te lleva a kernel / fork.c (http://lxr.free-electrons.com/so…). Un poco más de buceo te lleva al método do_fork (http://lxr.free-electrons.com/so…), que hace cosas como clonar el espacio de memoria, pasa los parámetros.

Luego entra en esta función, y comprende que se trata de un grupo de macros, y el siguiente método central es copy_process (http://lxr.free-electrons.com/so…). Repita hasta que llegue al metal desnudo, tocando fondo en los detalles de un chip en particular.

Tienes la idea Repita esto varias veces para otras tareas (¿qué sucede cuando se realiza una llamada de ipc? ¿Qué significa realmente abrir () y leer () un archivo? ¿Qué sucede cuando llamo a malloc ()?) Y comienza a obtener un sentir el sistema de compilación, los supuestos incorporados a las cosas, los estándares de codificación, los diseños, etc.

Estos dos pasos deberían al menos ayudarte a comenzar.

Para cualquier corpus de código bien establecido y grande, debería haber algún tipo de guía para desarrolladores que lo ayude a comenzar. Busca eso y léelo.

Sin embargo, incluso en ausencia de eso, puedes intentar esto:

Comience por formular hipótesis (adivinar) sobre qué cosas tendrá que encontrar * en algún lugar * en esa masa de código. Según lo que sepa sobre la funcionalidad del código (cómo los usuarios acceden a estos sistemas), puede deducir algunas de las cosas que tienen que estar allí * en algún lugar *.

Por ejemplo, usted (debería) saber que el kernel de Linux comienza haciendo un montón de detección / inicialización de hardware / dispositivo (un proceso de arranque), que admite un conjunto de argumentos de línea de comando que LILO, GRUB, pueden haberle pasado. Syslinux, o algún otro gestor de arranque (lo que significa que debe haber algún código para analizar esas opciones de la línea de comandos y almacenar o usar esas configuraciones), en las tablas de partición por lo general deben detectarse y analizarse y al menos un sistema de archivos debe ubicarse y montarse en / (el sistema de archivos raíz) y que, en última instancia, el kernel inicia un proceso en el espacio de usuario (init) y desde allí actúa como un servicio que responde a las llamadas del sistema (desde el espacio del usuario) y los eventos de hardware (principalmente a través de interrupciones, pero posiblemente también más exóticos). Hotplug o eventos de energía.

Según este conocimiento, puede esperar ver algún tipo de proceso mediante el cual encuentre y ejecute el programa init, algún código que analice las opciones de la línea de comandos y haga algo con ellas (principalmente almacenándolas en algunas estructuras de datos internas), y algún tipo de la tabla de despacho que enumera las llamadas al sistema. También esperaría encontrar algún tipo de sistema de inicialización de interrupciones o eventos y esperaría que haya alguna forma de “conectar” estos eventos para que varios manejadores puedan asociarse con ciertos tipos de eventos.

Elige cualquiera de esos y ve a leer ese código. A medida que lo lees, puedes obtener información sobre de qué depende.

Mire la estructura del directorio y vea si puede adivinar dónde ponen diferentes cosas. Busque README y API, y otros archivos .TXT o .DOC que puedan ofrecer un resumen de alto nivel de cómo está organizado el código. Adivine qué términos deben usarse para implementar algunas de estas cosas que sabe que deben estar allí, en algún lugar, y busque esos términos.

(En el camino, para el kernel de Linux, encontrará que hay muchos detalles que no previó. Por ejemplo, la forma en que Linux admite los initramfs (las versiones anteriores tenían un disco de RAM inicial initrd) que era También está disponible simplemente como un blob opaco de datos si no se estaban usando las opciones initrd. Hay un gancho, anteriormente llamado linuxrc para el subsistema initrd, ahora name / init en initramfs. Initramfs es un tipo específico de archivo cpio, así que hay un descompresor de cpio muy especializado y especializado integrado en el núcleo para ese propósito.

El kernel configura un entorno para el proceso de inicio y busca un ejecutable en varios lugares … volviendo a / bin / sh si no se encuentra ninguno de los otros. La funcionalidad relacionada con execve se basa en un sistema de procesamiento modular “binfmt” que puede ejecutar el antiguo a.out, ELF y cualquier tipo de #! (shebang) guiones, entre otros. A medida que avanza en el código, aprende sobre todos estos rincones que los desarrolladores han tenido que acomodar durante la historia del proyecto. (En particular, dado que estos se pueden implementar como módulos cargables y se pueden cargar dinámicamente con ideas como kmod (¿o el antiguo kerneld?).

Obviamente, el kernel de Linux (o cualquier kernel de sistema operativo) es un caso especial dado que no está vinculado a las bibliotecas C estándar y no implementa la función “main ()” del espacio de usuario convencional. Por lo tanto, encontrar el punto de entrada es diferente al de cualquier programa C normal.

Para la mayoría de estos programas, puede comenzar en “main ()” o el equivalente de “main ()” en cualquier entorno en el que esté escrito su cuerpo de código. (Obviamente, bucear en una masa de Clojure o Scala, o Node.js, para ejemplo, sería diferente).

De manera similar, para algo como MySQL, usted sabe que este código se ejecuta como un demonio, tiene que leer las opciones de la línea de comandos, leer y analizar los archivos de configuración, y en función de ellos, debe encontrar y abrir varias bases de datos subyacentes (esquema) y sus tablas asociadas. Usted sabe que admite varios motores de almacenamiento y que algunos de ellos pueden estar listados y vinculados dinámicamente a través de los datos de configuración.

Una vez que se carga un daemon MySQL y se realizan las inicializaciones de la base de datos (o se manejan los errores relacionados con los datos faltantes o dañados), se sabe que, en última instancia, abre sockets (Unix o dominio de Internet … o ambos) y acepta conexiones. Esas conexiones que interactúan con el servidor a través de una API (por ejemplo, varias API diferentes, dependiendo de cómo implementan la replicación). Usted sabe que, en última instancia, las declaraciones SQL deben pasarse a través de esta API, deben analizarse, debe generarse un “plan” para ejecutar el SQL analizado (consultas o DML, manipulaciones de datos).

Entonces, comienza con algunas deducciones. Sabes que ciertas cosas tienen que estar ahí; así que busca esos. Cuando los encuentres, se sentirán familiares porque esperabas que estuvieran allí y ya tenías algunas ideas preconcebidas de cómo deben ser.

También puede explorar un sistema y su espacio de nombres interno mediante el trazado de algunas de las rutas de código que lo llaman. Por ejemplo, ejecute strace o ltrace en algo simple como el comando wc. Luego, busque esas llamadas al sistema en el código fuente del kernel. Ejecute ltrace en un demonio de MySQL, observe la secuencia de inicio y busque cada una de las funciones de MySQL que aparecen en la lista. (Tenga en cuenta que querrá compilar su propia copia del binario y ejecutarlo sin realizar una copia para realizar este ejercicio. Considere que es un requisito previo bastante importante para obtener y compilar las fuentes de cualquier corpus de código que desee estudiar. ¡Duh!) Puede intentar ejecutar el software bajo gdb y jugar con los puntos de entrada que aprendió a través de su rastreo o la lectura directa del código fuente.

Gracias Rwik Dutta por A2A

Comprender una base de código de fuente abierta grande depende mucho de qué tan bien se mantiene la estructura del código. Es por eso que todas las grandes organizaciones hacen hincapié en un estilo de código y una estructura de software bien mantenidos. Así, la mayoría de las veces los proyectos tienen un enfoque modular.

Paso 1: Intento entender todos los módulos del proyecto. Estos son generalmente el superconjunto de varias características con una abstracción adecuada sin decir mucho sobre cómo se hace. Primero debe saber qué se hace y comprender el flujo de trabajo entre los distintos módulos.

Paso 2: Una vez que haya comprendido el flujo de trabajo y los diversos módulos, profundice en un solo módulo e intente identificar los diversos submódulos y funciones en este módulo y su flujo de trabajo.

Paso 3: repita el Paso 2 siempre que llegue a una sola función o submódulo y luego comprenda el código fuente de esa parte.

Este es un poco de un método recursivo que sigo para entender las bases de código. Pero al final del día, depende un poco de cómo entienda el código base más fácilmente y podrá adoptar su propia forma de entender con tiempo y experiencia.

En este momento, estoy usando el sistema de tamices múltiples Stanford (abreviado CoreNLP) para construir mi propia implementación. Por lo tanto, creo que puedo hablar un poco sobre esto.

Créeme, CoreNLP es un monstruo y una base de código muy grande. El sistema incluye casi todas las cosas en el campo del procesamiento del lenguaje natural, desde la tokenización, el etiquetado de POS, el reconocimiento de entidades con nombre, el análisis y la resolución de referencias. Estoy realizando una investigación sobre resolución coreferencec. Un documento que estoy replicando el experimento es del grupo de PNL de Stanford. Por lo tanto, no tengo otra opción (ya sabes, hay muchos Paquetes Java de Resolución de Coreferencia).

Creo que un enfoque es adecuado para mí. Eso es construir su propia implementación desde cero de acuerdo con su investigación o los requisitos comerciales basados ​​en ese código base.

Tome CoreNLP como ejemplo, el código proporciona un ejemplo similar a mi requisito. Por lo tanto, cada mañana, después de comenzar mi trabajo, comencé a leer el ejemplo. Fue muy duro al principio. Pasé dos semanas para leer el ejemplo. Durante las dos semanas, construí mi implementación de acuerdo con el ejemplo. Si encontrase clases nuevas, podría usar la tecla de acceso rápido de Eclipse para saltar a esa clase y leer el constructor, los campos y los métodos relacionados que se usaron en mi implementación. De esta manera, puedo aprender CoreNLP más rápidamente de acuerdo con mi requerimiento. Hay dos razones para esto: primero es que puedo implementar mi idea y desarrollar un prototipo en poco tiempo. Segundo, el CoreNLP es tan grande que no todo es relevante para mi caso. Puedo dejar solo algunas cosas triviales. Tal vez después los recojo. En este período, solo copio todo lo que está en el ejemplo en mi implementación si necesito implementar la misma función que en el ejemplo. Una vez, pensé que un segmento en el ejemplo no era útil y no agregué ese segmento a mi implementación. Luego pasé una tarde para depurar. Así que en esta fase, me limito a seguir el ejemplo.

A medida que trabajo en la implementación, comencé a familiarizar el sistema. Luego puedo salirme con el ejemplo y comenzar a implementar otras cosas no especificadas en el ejemplo. Si también encuentro algunas clases desconocidas, también acabo de leer el código relevante para mi implementación. Pero en algún momento, cuando CoreNLP no proporciona algunas funciones o algunas clases, solo implemento las mías basadas en las estructuras de datos especificadas en CoreNLP.

Es muy divertido. Una cosa me sorprende es que agregaré comentarios muy detallados al código durante mi trabajo. Es realmente útil para otros leer el código.

Agregando a la respuesta de Waleed, después de tener una idea de alto nivel del diseño de origen y la pila de llamadas, el siguiente mejor paso es ejecutar las pruebas de unidad respectivas en modo de depuración y repasar las declaraciones. Esta puede ser una experiencia esclarecedora para comprender el comportamiento en tiempo de ejecución de los módulos de código.

Con una idea bastante buena de un módulo, el siguiente paso lógico es escribir algunas pruebas unitarias nuevas y hacerlas “verdes”.

Si la intención es intentar una modificación, es útil leer el historial de revisión del archivo y los comentarios en el control de versiones y tener una idea de la intención de las modificaciones anteriores, si las hubiera.

Gracias A2A!
Muchas veces, la base de código para su proyecto OpenSource elegido no será pequeña, por lo que no sería muy fácil entender todo el código de una sola vez.

Aún desea estudiar … lea primero la documentación , lea la especificación funcional de ese software, le dará pistas sobre los módulos en los que se ha dividido el código base, ahora sería lo suficientemente inteligente como para identificar los enlaces entre varios módulos. y correlacionarlos con el código base.

La corrección de errores sería más fácil si sabe de qué componentes se construye su proyecto. Un error, de hecho, la mayoría de las veces se relacionará con uno o más componentes, pero no todos. Por lo tanto, suponiendo que haya leído el documento a fondo y que conozca la interacción entre esos componentes, podría determinar fácilmente la sección de código en la que se encuentra el error. También hay otras formas de identificar la sección de código (a través de los registros recopilados o un tipo especial de mensajes de error).
Comience con defectos menores , no se apresure a los errores de alta severidad, pueden volverse desalentadores.

¡Todo lo mejor!

Para ampliar la respuesta de Jim Dennis

Descubrí que el libro “Lectura de código: una perspectiva de código abierto” es un buen punto de partida para que las pistas “descodifiquen”, analicen y comprendan la base del código: http://www.spinellis.gr/codereading

Libros relacionados con “lectura de código”:

http://speckyboy.com/2012/07/12/…

Solución de errores y depuración por Gurudutt Mallapur en techtalkies

Información de fondo :
Normalmente navego en blogs y tutoriales, preguntas frecuentes, preguntas internas basadas en las palabras clave del código ocular o utilizando grep / find para filtrar nombres de archivos o palabras en el código.

Sin embargo, estas fuentes externas funcionan mejor para un código más genérico que el código específico de la compañía.

Si solo 2 millones de líneas de código se ajustan a su definición de grande, puede consultar este breve video sobre cómo abordar la pila de Netflix OSS:

Mencionó Linux kernel, JDK y MySQL. También los hemos analizado, así como el 40 + MLOC de Android. Y un montón de otros proyectos interesantes. Solo déjame saber si quieres acceder a ellos. Puedes chatear con nosotros en softagram.com.