En gran medida, la programación es la ciencia de resolver problemas por computadora. Debido a que los problemas suelen ser difíciles, las soluciones y los programas que implementan esas soluciones también pueden ser difíciles. Para facilitar el desarrollo de esas soluciones, debe adoptar una metodología y disciplina que reduzca el nivel de esa complejidad a una escala manejable.
En los primeros años de la programación, el concepto de la computación como ciencia era más o menos un experimento de ilusiones. Nadie sabía mucho acerca de la programación en esos días, y pocos pensaban en ello como una disciplina de ingeniería en el sentido convencional. Sin embargo, a medida que la programación maduraba, tal disciplina comenzó a emerger. La piedra angular de esa disciplina es comprender que la programación se realiza en un entorno social en el que los programadores deben trabajar juntos. Si ingresa en la industria, seguramente será uno de los muchos programadores que trabajan para desarrollar un gran programa. Además, es casi seguro que ese programa viva y requiera mantenimiento más allá de su aplicación original. Alguien querrá que el programa incluya alguna nueva característica o trabajo de alguna manera diferente. Cuando eso ocurre, un nuevo equipo de programadores debe ingresar y hacer los cambios necesarios en los programas. Si los programas están escritos en un estilo individual con poca o ninguna coincidencia, lograr que todos trabajen juntos de manera productiva es extremadamente difícil.
Para combatir este problema, los programadores comenzaron a desarrollar un conjunto de metodologías de programación que se denominan colectivamente. Ingeniería de software . El uso de buenas habilidades de ingeniería de software no solo facilita la lectura y comprensión de sus programas por parte de otros programadores, sino que también facilita la escritura de dichos programas en primer lugar. Uno de los avances metodológicos más importantes de la ingeniería de software es la estrategia de diseño de arriba hacia abajo o refinamiento por etapas , que consiste en resolver problemas comenzando con el problema en su conjunto. Rompe todo el problema en partes y luego resuelve cada pieza, desglosándolas aún más si es necesario. Esta estrategia de arriba abajo se complementa con prueba iterativa donde se asegura de que las piezas más pequeñas de la solución estén funcionando antes de continuar.
Para ilustrar el concepto de refinamiento por pasos, enseñemos Karel para resolver un problema nuevo. Imagina que Karel ahora vive en un mundo que se parece a esto:
En cada una de las columnas, hay una torre de conos de una altura desconocida, aunque algunas columnas (como la 7 y la 9 en el mundo de la muestra) pueden estar vacías. El trabajo de Karel es recolectar todos los conos en cada una de estas torres, colocarlas nuevamente en la esquina más oriental de la primera fila y luego regresar a su posición inicial. Por lo tanto, cuando Karel termina su trabajo en el ejemplo anterior, todos los 25 conos actualmente en las torres deben apilarse en la esquina de la 9ª columna y la 1ª fila, de la siguiente manera:
Es importante destacar que usted puede suponer que Karel inicialempiezaCon cero conos en su bolsa. Cada cono recogidos se añaden a su bolsa. Al colocar conos en la esquina, karel puede usar el bolsaConConos() prueba.
La clave para resolver este problema es descomponer el programa de la manera correcta, al mismo tiempo que se puede probar a medida que avanza. Esta tarea es más compleja que las otras que ha visto, lo que hace que la elección de los subproblemas apropiados sea más importante para obtener una solución exitosa.
La idea clave en el refinamiento paso a paso es que debe comenzar el diseño de su programa desde la parte superior, que se refiere al nivel del programa que es conceptualmente más alto y más abstracto. En este nivel, el problema de la torre cono se divide claramente en tres fases independientes. Primero, Karel tiene que recoger todos los conos . Segundo, Karel tiene que depositarlos en la última intersección. En tercer lugar, Karel tiene que volver a su posición Karel . Esta descomposición conceptual del problema sugiere que el método run para este programa tendrá la siguiente estructura:
public void run() {
recogerTodosLosConos();
soltarTodoConos();
volverACasa();
}
En este nivel, el problema es fácil de entender. Por supuesto, hay algunos detalles que quedan en la forma de métodos que aún no ha escrito. Aun así, es importante mirar cada nivel de la descomposición y convencerse de que, mientras crea que los métodos que está a punto de escribir resolverán los subproblemas correctamente, tendrá una solución al problema en su totalidad. .
Ahora que ha definido la estructura del programa como un todo, es hora de moverse en el primer subproblema, que consiste en recopilar todos los conos . Esta tarea es en sí misma más complicada que los problemas simples de los capítulos anteriores. Recolectar todos los conos significa que debes recoger los conos en cada torre hasta llegar a la esquina final. El hecho de que necesite repetir una operación para cada torre sugiere que necesita un ciclo while aquí. El ciclo while repetirá el proceso de recogerUnaTorre y luego en movimiento.
Precaución: Es peligroso tratar de escribir todo el programa sin pruebas a medida que avanza Si comete un error será difícil encontrar el error. Sabemos que vamos a repetir el proceso de recogida de una torre. Escribamos y prueba recogiendo una sola torre antes de que pongamos el RecogerUnaTorre ciclo for en un ciclo for . AsítemporalmentePodemos comenzar con la siguiente definición de recogerTodosLosConos:
private void recogerTodosLosConos() {
/* Implementación temporal para propósitos de prueba. */
recogerUnaTorre();
moverse();
}
Como principio guía, si tiene un complejo ciclo , pruebe elcuerpode la ciclo antes de escribir la ciclo completa.
Cuando se llama Karel , Karel se encuentra en la base de una torre de conos o en una esquina vacía. En el primer caso, debe recoger el conos en la torre. En este último, simplemente puede moverse en. Esta situación suena como una aplicación para la sentencia if, en la que escribirías algo como esto:
if(conosPresentes()){
recogerLaTorreReal();
}
Antes de agregar dicha declaración al código, debe pensar si necesita realizar esta prueba. A menudo, los programas pueden hacerse mucho más simples observando que los casos que al principio parecen ser especiales pueden tratarse exactamente de la misma manera que la situación más general. En el problema actual, ¿qué sucede si decide que hay una torre de conos en cada avenida pero que algunas de esas torres tienen una altura de conos ? Hacer uso de esta información simplifica el programa porque ya no tiene que probar si hay una torre en una avenida particular.
El método recogerUnaTorre todavía es lo suficientemente complejo como para que un nivel adicional de descomposición esté en orden. Para recolectar todos los conos en una torre, Karel debe realizar los siguientes pasos:
Una vez más, este esquema proporciona un modelo para el método recogerUnaTorre, que se ve así:
private void recogerUnaTorre(){
girarIzquierda();
líneaDeConosDeConos();
mediaVeulta();
moverseAPared();
girarIzquierda();
}
Los comandos girarIzquierda al principio y al final del método recogerUnaTorre son críticos para la corrección de este programa. Cuando se llama Karel , Karel siempre está en algún lugar de la 1ª fila mirando hacia el este. Cuando complete su operación, el programa en su totalidad funcionará correctamente solo si Karel está nuevamente orientado al este en esa misma esquina. Las condiciones que deben cumplirse antes de llamar a un método se conocen como condiciones previas ; Las condiciones que deben aplicarse una vez finalizado el método se conocen como postcondiciones .
Cuando defina un método, tendrá muchos menos problemas si anota exactamente cuáles son las condiciones previas y posteriores. Una vez que lo haya hecho, debe asegurarse de que el código que escribe siempre deje las condiciones posteriores satisfechas, asumiendo que las condiciones previas se cumplieron para comenzar. Por ejemplo, piense en lo que sucede si llama a recogerUnaTorre cuando Karel está en la primera fila mirando hacia el este. El primer comando girarIzquierda deja Karel mirando hacia el norte, lo que significa que Karel está correctamente alineado con la columna de conos representa la torre. El método líneaDeConosDeConos, que aún no se ha escrito, pero aún así realiza una tarea que usted comprende conceptualmente, simplemente moverse s sin girar. Por lo tanto, al final de la llamada a líneaDeConosDeConos, Karel todavía estará orientada al norte. La convocatoria mediaVeulta por lo tanto, deja Karel hacia el sur. Al igual que la líneaDeConosDeConos, el método moverse moverse no implica giros, sino simplemente moverse s hasta que llega al muro del límite. Debido a que Karel está orientado hacia el sur, este muro delimitador será el que se encuentra en la parte inferior de la pantalla, justo debajo de la primera fila. El comando final girarIzquierda por lo tanto, deja Karel en la 1ª fila mirando hacia el este, lo que satisface la postcondición.
Usted run su programa y borra con éxito una torre y deja Karel en el postcondition prometido. ¡Wahoo! ¡Acabas de alcanzar un hito en la resolución de esta difícil tarea! Ahora tenemos que repetir el proceso de limpiar una torre con un ciclo while .
Pero, ¿ ciclo while aspecto tiene este ciclo while ? En primer lugar, debes pensar en la prueba condicional. Desea que Karel detenga cuando toque la pared al final de la fila. Por lo tanto, usted desea que Karel siga funcionando mientras el espacio en el frente esté libre. Por lo tanto, sabes que el método recogerTodosLosConos incluirá un ciclo while que usa la prueba frenteDespejado . En cada posición, quieres que Karel recolecte todos los conos en la torre que comienza en esa esquina. Si le da a esa operación un nombre, que podría ser algo como recogerUnaTorre, puede seguir adelante y escribir una definición para el método recogerTodosLosConos aunque todavía no haya completado los detalles.
Usted, sin embargo, tiene que tener cuidado. El código para recogerTodosLosConos no se ve así:
private void recogerTodosLosConos(){
/* buggy ciclo ! */
while(frenteDespejado()) {
recogerUnaTorre();
moverse();
}
}
Esta implementación está defectuosa por la misma razón por la que la primera versión del PlaceConoLinea general del capítulo 6 no pudo hacer su trabajo. Hay un error de poste en esta versión del código, porque Karel necesita probar la presencia de una torre cono en la última avenida. La implementación correcta es:
private void recogerTodosLosConos(){
while(frenteDespejado()) {
recogerUnaTorre();
moverse();
}
recogerUnaTorre();
}
Tenga en cuenta que este método tiene precisamente la misma estructura que el programa principal del programa PlaceConoLinea presentado en el capítulo 6. La única diferencia es que este programa llama a recogerUnaTorre donde el otro llamado ponerCono . Estos dos programas son ejemplos de una estrategia general que se ve así:
private void recogerTodosLosConos(){
while(frenteDespejado()) {
realizar alguna operacion
moverse();
}
Realiza la misma operación para la esquina final.
}
Puede usar esta estrategia siempre que necesite realizar una operación en cada esquina a medida que moverse por un camino que termina en una pared. Si recuerda la estructura general de esta estrategia, puede usarla siempre que encuentre un problema que requiera dicha operación. Las estrategias reutilizables de este tipo aparecen frecuentemente en la programación y se conocen como lenguajes de programación o patrones . Cuantos más patrones sepa, más fácil le resultará encontrar uno que se adapte a un tipo particular de problema.
A pesar de que el trabajo duro se ha realizado, todavía hay varios cabos sueltos que deben resolverse. El programa principal llama a dos métodos, soltarTodoConos y volverACasa, que aún no están escritos. Del mismo modo, recogerUnaTorre llama a líneaDeConosDeConos y moverse rseAPared. Afortunadamente, los cuatro de estos métodos son lo suficientemente simples como para codificarlos sin más descomposición, especialmente si utiliza moverse rseAPared en la definición de volverACasa. Aquí está la implementación completa: