1. El trabajo de un programador

Muy a menudo, los programadores novatos piensan en el trabajo de un programador de manera completamente diferente a como lo piensan los programadores experimentados .

Los principiantes suelen decir algo como "El programa funciona, ¿qué más necesitas?" Un programador experimentado sabe que "funciona correctamente" es solo uno de los requisitos para un programa , ¡y ni siquiera es lo más importante !

Legibilidad del código

Lo más importante es que el código del programa sea comprensible para otros programadores . Esto es más importante que un programa que funcione correctamente. Mucho más.

Si tienes un programa que no funciona correctamente, puedes arreglarlo. Pero si tienes un programa cuyo código es incomprensible, no puedes hacer nada con él.

Simplemente tome cualquier programa compilado, como el bloc de notas, y cambie su color de fondo a rojo. Tiene un programa que funciona, pero no tiene un código fuente comprensible: es imposible realizar cambios en un programa como ese.

Un ejemplo de libro de texto es cuando los desarrolladores de Microsoft eliminaron el juego Pinball de Windows porque no pudieron migrarlo a la arquitectura de 64 bits. E incluso tenían su código fuente. Simplemente no podían entender cómo funcionaba el código .

Contabilidad para cada caso de uso

El segundo requisito más importante para un programa es dar cuenta de cada escenario. Muchas veces, las cosas son un poco más complicadas de lo que parecen.

Cómo ve un programador novato el envío de mensajes SMS:

Un programa que funcione correctamente

Cómo lo ve un programador profesional:

Un programa que funcione correctamente

El escenario de "funciona correctamente" suele ser solo uno de los muchos posibles. Y es por eso que muchos novatos se quejan del validador de tareas de CodeGym: solo funciona un escenario de cada 10, y el programador novato piensa que es suficiente.


2. Situaciones anormales

Situaciones anormales

Pueden surgir situaciones anómalas en la ejecución de cualquier programa.

Por ejemplo, decide guardar un archivo pero no hay espacio en disco. O el programa está tratando de escribir datos en la memoria, pero la memoria disponible es baja. O descarga una imagen de Internet, pero la conexión se pierde durante el proceso de descarga.

Para cada situación anormal, el programador (el autor del programa) debe a) anticiparla , b) decidir cómo debe manejarla exactamente el programa , y ​​c) escribir una solución que sea lo más cercana posible a la deseada.

Es por eso que los programas tuvieron un comportamiento muy simple durante mucho tiempo: si ocurría un error en el programa, el programa finalizaba. Y ese fue un muy buen enfoque.

Supongamos que desea guardar un documento en el disco, durante el proceso de guardado descubre que no hay suficiente espacio en el disco. ¿Qué comportamiento te gustaría más?:

  • El programa termina
  • El programa continúa ejecutándose, pero no guarda el archivo.

Un programador novato puede pensar que la segunda opción es mejor, porque el programa aún se está ejecutando. Pero en realidad eso no es así.

Imagine que escribió un documento en Word durante 3 horas, pero dos minutos después de su proceso de escritura quedó claro que el programa no podría guardar el documento en el disco. ¿Es mejor perder dos minutos de trabajo o tres horas?

Si el programa no puede hacer lo que necesita, es mejor dejar que se cierre que seguir fingiendo que todo está bien. Lo mejor que puede hacer un programa cuando encuentra una falla que no puede solucionar por sí solo es informar inmediatamente del problema al usuario.


3. Antecedentes sobre las excepciones

Los programas no son los únicos que enfrentan situaciones anormales. También ocurren dentro de los programas, en los métodos. Por ejemplo:

  • Un método quiere escribir un archivo en el disco, pero no hay espacio.
  • Un método quiere llamar a una función en una variable, pero la variable es igual a nulo.
  • La división por 0 ocurre en un método.

En este caso, el método de llamada podría posiblemente corregir la situación (ejecutar un escenario alternativo) si sabe qué tipo de problema ocurrió en el método llamado.

Si estamos tratando de guardar un archivo en el disco y dicho archivo ya existe, simplemente podemos pedirle al usuario que confirme que debemos sobrescribir el archivo. Si no hay espacio disponible en el disco, podemos mostrar un mensaje al usuario y pedirle que seleccione un disco diferente. Pero si el programa se queda sin memoria, se bloqueará.

Érase una vez, los programadores reflexionaron sobre esta pregunta y llegaron a la siguiente solución: todos los métodos/funciones deben devolver un código de error que indica el resultado de su ejecución. Si una función funcionaba perfectamente, devolvía 0 . Si no, devolvía un código de error (no cero).

Con este enfoque de los errores, después de casi todas las llamadas a funciones, los programadores tenían que agregar una verificación para ver si la función finalizaba con un error. El código aumentó de tamaño y quedó así:

Código sin manejo de errores Código con manejo de errores
File file = new File("ca:\\note.txt");
file.writeLine("Text");
file.close();
File file = new File("ca:\\note.txt");
int status = file.writeLine("Text");
if (status == 1)
{
   ...
}
else if (status == 2)
{
   ...
}
status = file.close();
if (status == 3)
{
   ...
}

Es más, muy a menudo una función que descubre que se ha producido un error no sabe qué hacer con él: la persona que llama tiene que devolver el error, y la persona que llama de la persona que llama se lo devuelve a la persona que llama, y ​​así sucesivamente.

En un programa grande, una cadena de docenas de llamadas a funciones es la norma: a veces incluso puede encontrar una profundidad de llamada de cientos de funciones. Y ahora tienes que pasar el código de error de abajo hacia arriba. Y si en alguna parte del camino alguna función no maneja el código de salida, entonces el error se perderá.

Otra desventaja de este enfoque es que si las funciones devuelven un código de error, ya no pueden devolver los resultados de su propio trabajo. El resultado de los cálculos tenía que pasarse a través de parámetros de referencia. Esto hizo que el código fuera aún más engorroso y aumentó aún más el número de errores.