Documentos de Académico
Documentos de Profesional
Documentos de Cultura
com
PUBLICADO POR
Prensa de Microsoft
Una división de Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Reservados todos los derechos. Ninguna parte del contenido de este libro puede ser reproducida o transmitida de ninguna
forma o por ningún medio sin el permiso por escrito del editor.
Incluye índice.
ISBN 0-7356-1967-0
1. Programas Informáticos--Desarrollo--Manuales, manuales, etc. I. Título.
Descargar desde Guau! Libro electrónico <www.wowebook.com>
QA76.76.D47M39 2004
005.1--dc22 2004049981
15 16 17 18 19 20 21 22 23 24 QGT 6 5 4 3 2 1
Distribuido en Canadá por HB Fenn and Company Ltd. Un registro del catálogo CIP para este libro está disponible en la
Biblioteca Británica.
Los libros de Microsoft Press están disponibles a través de libreros y distribuidores en todo el mundo. Para obtener más
información acerca de las ediciones internacionales, comuníquese con su oficina local de Microsoft Corporation o con Microsoft
Press International directamente al fax (425) 936-7329. Visite nuestro sitio Web en www.microsoft.com/mspress. Enviar
comentarios amspinput@microsoft.com.
Microsoft, Microsoft Press, PowerPoint, Visual Basic, Windows y Windows NT son marcas registradas o marcas comerciales de
Microsoft Corporation en los Estados Unidos y/o en otros países. Otros nombres de productos y empresas mencionados en este
documento pueden ser marcas comerciales de sus respectivos propietarios.
Los ejemplos de empresas, organizaciones, productos, nombres de dominio, direcciones de correo electrónico, logotipos, personas, lugares y eventos
descritos en este documento son ficticios. No se pretende ni debe inferirse ninguna asociación con ninguna empresa, organización, producto, nombre
de dominio, dirección de correo electrónico, logotipo, persona, lugar o evento reales.
Este libro expresa los puntos de vista y opiniones del autor. La información contenida en este libro se proporciona sin
ninguna garantía expresa, legal o implícita. Ni los autores, Microsoft Corporation, ni sus revendedores o distribuidores
serán responsables de los daños causados o presuntamente causados directa o indirectamente por este libro.
pero que tiene todo que ver con enriquecer el resto de mi vida
en más formas de las que podría describir
Más elogios para
Código completo
"Una excelente guía para el estilo de programación y la construcción de software".
— Martín Fowler,refactorización
“Steve McConnellCódigo completo. . .proporciona una vía rápida a la sabiduría para los programadores. . . .
Sus libros son divertidos de leer, y nunca olvidas que habla de una experiencia personal ganada con mucho
esfuerzo”. —Jon Bentley,Perlas de programación, 2ª ed.
“Este es simplemente el mejor libro sobre construcción de software que he leído. Cada desarrollador debería
tener una copia y leerla de cabo a rabo todos los años. ¡Después de leerlo anualmente durante nueve años,
“El software de hoydebersea robusto y resistente, y el código seguro comienza con la construcción disciplinada
del software. Después de diez años, todavía no hay mejor autoridad queCódigo completo.”
“Un examen exhaustivo de los problemas tácticos que intervienen en la elaboración de un programa bien
diseñado. El trabajo de McConnell cubre temas tan diversos como la arquitectura, los estándares de codificación,
“La enciclopedia definitiva para el desarrollador de software esCódigo completopor Steve McConnell.
Subtitulado 'Un manual práctico de construcción de software', este libro de 850 páginas es exactamente
eso. Su objetivo declarado es reducir la brecha entre el conocimiento de los 'profesores y gurús de la
industria' (Yourdon y Pressman, por ejemplo) y la práctica comercial común, y 'ayudarlo a escribir mejores
programas en menos tiempo y con menos dolores de cabeza'. . . . Todo desarrollador debería tener una
copia del libro de McConnell. Su estilo y contenido son completamente prácticos”.
—Chris Loosley,Cliente/servidor de alto rendimiento
“El libro seminal de Steve McConnellCódigo completoes uno de los trabajos más accesibles que discute en
detalle los métodos de desarrollo de software. . . .”
—Erik Bethke,Desarrollo y producción de juegos
“Una mina de información útil y consejos sobre cuestiones más amplias en el diseño y la producción de un
buen software”.
—John Dempster,La computadora de laboratorio: una guía práctica para fisiólogos y
neurocientíficos
“Si te tomas en serio mejorar tus habilidades de programación, deberías obtenerCódigo completo
por Steve McConnell”.
—Jean J. Labrosse,Bloques de construcción de sistemas integrados: módulos completos y listos para usar en C
“Steve McConnell ha escrito uno de los mejores libros sobre desarrollo de software independiente
del entorno informático. . .Código completo.”
—Kenneth Rosen,Unix: la referencia completa
“Cada media edad más o menos, te encuentras con un libro que hace un cortocircuito en la escuela de la
experiencia y te ahorra años del purgatorio. . . . No puedo expresar adecuadamente lo bueno que es este libro.
“Microsoft Press ha publicado lo que considero el libro definitivo sobre construcción de software. Este es
un libro que pertenece al estante de todos los desarrolladores de software”.
—Warren Keuffel,Desarrollo de software
“Todo programador debería leer este extraordinario libro”. —TL (Frank) Pappas,Computadora
“Si aspiras a ser un programador profesional, esta puede ser la inversión de $35 más sabia que jamás hayas
hecho. No te detengas a leer el resto de esta reseña: simplemente sal corriendo y cómpralo. El propósito
declarado de McConnell es reducir la brecha entre el conocimiento de los gurús de la industria y la práctica
comercial común. . . . Lo asombroso es que lo logra”.
— Ricardo Mateosian,micro IEEE
“Código completodebe ser lectura obligatoria para cualquier persona. . . en el desarrollo de software.”
“Me animan a arriesgarme un poco más de lo habitual y recomendar, sin reservas, la obra de Steve
McConnell.Código completo. . . . Mi copia ha reemplazado a mis manuales de referencia de API como
el libro más cercano a mi teclado mientras trabajo”.
—Jim Kyle,Diario de tecnología de Windows
“Este tomo masivo pero bien escrito es posiblemente el mejor volumen jamás escrito sobre los aspectos
prácticos de la implementación de software”.
—Tommy Usher,Programación de Sistemas Embebidos
“Este es el mejor libro sobre ingeniería de software que he leído hasta ahora.”
—Edward Kenworth,.Revista EXE
“Este libro merece convertirse en un clásico, y debería ser de lectura obligatoria para todos los
desarrolladores, y los responsables de gestionarlos”. —Peter Wright,programa ahora
Código completo, segunda edición
0-7356-1967-0
steve mconnell
Contenido de un vistazo
Parte IV Declaraciones
14 Organización del código de línea recta. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Uso
15 de condicionales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 Lazos de
dieciséis control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 Estructuras de Control
17 Inusuales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Métodos controlados por
18 tablas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 Problemas generales de
19 control. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
viii
viii Tabla de contenido
3 Medir dos veces, cortar una vez: requisitos previos aguas arriba. . . . . . . . . . . . . . . . . 23
¿Qué opinas de este libro? Microsoft está interesado en escuchar sus comentarios sobre esta publicación para que podamos
mejorar continuamente nuestros libros y recursos de aprendizaje para usted. Para participar en una
¡Queremos escuchar de ti! breve encuesta en línea, visite:www.microsoft.com/learning/booksurvey/
V413HAV ix
X Tabla de contenido
5 Diseño en la Construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.1 Desafíos de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.2 Conceptos clave de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.3 Elementos básicos del diseño: heurística . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.4 Prácticas de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.5 Comentarios sobre Metodologías Populares. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
PUBLICADO POR
Prensa de Microsoft
Una división de Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Reservados todos los derechos. Ninguna parte del contenido de este libro puede ser reproducida o transmitida de ninguna
forma o por ningún medio sin el permiso por escrito del editor.
Incluye índice.
ISBN 0-7356-1967-0
1. Programas Informáticos--Desarrollo--Manuales, manuales, etc. I. Título.
QA76.76.D47M39 2004
005.1--dc22 2004049981
15 16 17 18 19 20 21 22 23 24 QGT 6 5 4 3 2 1
Distribuido en Canadá por HB Fenn and Company Ltd. Un registro del catálogo CIP para este libro está disponible en la
Biblioteca Británica.
Los libros de Microsoft Press están disponibles a través de libreros y distribuidores en todo el mundo. Para obtener más
información acerca de las ediciones internacionales, comuníquese con su oficina local de Microsoft Corporation o con Microsoft
Press International directamente al fax (425) 936-7329. Visite nuestro sitio Web en www.microsoft.com/mspress. Enviar
comentarios amspinput@microsoft.com.
Microsoft, Microsoft Press, PowerPoint, Visual Basic, Windows y Windows NT son marcas registradas o marcas comerciales de
Microsoft Corporation en los Estados Unidos y/o en otros países. Otros nombres de productos y empresas mencionados en este
documento pueden ser marcas comerciales de sus respectivos propietarios.
Los ejemplos de empresas, organizaciones, productos, nombres de dominio, direcciones de correo electrónico, logotipos, personas, lugares y eventos
descritos en este documento son ficticios. No se pretende ni debe inferirse ninguna asociación con ninguna empresa, organización, producto, nombre
de dominio, dirección de correo electrónico, logotipo, persona, lugar o evento reales.
Este libro expresa los puntos de vista y opiniones del autor. La información contenida en este libro se proporciona sin
ninguna garantía expresa, legal o implícita. Ni los autores, Microsoft Corporation, ni sus revendedores o distribuidores
serán responsables de los daños causados o presuntamente causados directa o indirectamente por este libro.
23 Depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535
23.1 Descripción general de los problemas de depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535
24 Refactorización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
24.1 Tipos de Evolución del Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
24.2 Introducción a la refactorización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565
24.3 Refactorizaciones específicas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571
24.4 Refactorización segura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 579
24.5 Estrategias de refactorización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 582
29 Integración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
29.1 Importancia del Enfoque de Integración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
29.2 Frecuencia de integración: ¿por fases o incremental? . . . . . . . . . . . . . . . . . . . . . . . . . 691
29.3 Estrategias de Integración Incremental. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 694
29.4 Prueba diaria de generación y humo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702
Bibliografía. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 863
índice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 885
¿Qué opinas de este libro? Microsoft está interesado en escuchar sus comentarios sobre esta publicación para que podamos
mejorar continuamente nuestros libros y recursos de aprendizaje para usted. Para participar en una
¡Queremos escuchar de ti! breve encuesta en línea, visite:www.microsoft.com/learning/booksurvey/
Prefacio
Mi principal preocupación al escribir este libro ha sido reducir la brecha entre el conocimiento
de los gurús y profesores de la industria, por un lado, y la práctica comercial común, por el
otro. Muchas técnicas de programación poderosas se esconden en revistas y artículos
académicos durante años antes de llegar al público de programación.
Programadores experimentados
Este manual sirve a los programadores experimentados que desean una guía completa y fácil de usar para el
desarrollo de software. Debido a que este libro se enfoca en la construcción, la parte más familiar del ciclo de
vida del software, hace que las poderosas técnicas de desarrollo de software sean comprensibles tanto para
los programadores autodidactas como para los programadores con capacitación formal.
xix
XX Prefacio
Líderes técnicos
Muchos líderes técnicos han utilizadoCódigo completopara educar a los programadores menos
experimentados en sus equipos. También puede usarlo para llenar sus propios vacíos de conocimiento. Si
eres un programador experimentado, es posible que no estés de acuerdo con todas mis conclusiones (y me
sorprendería que lo hicieras), pero si lees este libro y piensas en cada problema, rara vez alguien
mencionará un problema de construcción que no ha considerado previamente.
Programadores autodidactas
Si no ha tenido mucho entrenamiento formal, está en buena compañía. Alrededor de 50 000 nuevos
desarrolladores ingresan a la profesión cada año (BLS 2004, Hecker 2004), pero solo se otorgan alrededor de
35 000 títulos relacionados con el software cada año (NCES 2002). A partir de estas cifras, hay un pequeño
salto para llegar a la conclusión de que muchos programadores no reciben una educación formal en
desarrollo de software. Los programadores autodidactas se encuentran en el grupo emergente de
profesionales (ingenieros, contadores, científicos, maestros y propietarios de pequeñas empresas) que
programan como parte de sus trabajos pero que no necesariamente se ven a sí mismos como
programadores. Independientemente del alcance de su educación en programación, este manual puede
brindarle información sobre prácticas de programación efectivas.
Estudiantes
Consecuentemente, los programadores pueden tener dificultades para encontrar una buena fuente de información
sobre programación.
Las técnicas descritas en este libro llenan el vacío después de los textos de programación
introductorios y avanzados. Después de haber leídoIntroducción a Java,Java avanzado, y Java
Avanzado Avanzado, ¿qué libro lees para aprender más sobre programación? Puede leer libros
sobre los detalles del hardware de Intel o Motorola, las funciones del sistema operativo
Microsoft Windows o Linux, u otro lenguaje de programación; no puede usar un lenguaje o
programa en un entorno sin una buena referencia a dichos detalles. Pero este es uno de los
pocos libros que trata sobre la programación en sí. Algunas de las ayudas de programación
más beneficiosas son prácticas que puede usar independientemente del entorno o lenguaje en
el que esté trabajando. Otros libros generalmente descuidan tales prácticas, razón por la cual
este libro se concentra en ellas.
Profesional
experiencia
Otro software
libros
Programación
libros de idiomas
Construcción
Revista
Tecnología artículos
referencias
Listas de verificación listas para usarEste libro incluye docenas de listas de verificación que puede usar
para evaluar su arquitectura de software, enfoque de diseño, calidad de clases y rutinas, nombres de
variables, estructuras de control, diseño, casos de prueba y mucho más.
Información de última generaciónEste manual describe algunas de las técnicas más actualizadas
disponibles, muchas de las cuales aún no se han hecho de uso común. Debido a que este libro se
basa tanto en la práctica como en la investigación, las técnicas que describe seguirán siendo útiles
durante años.
Conceptos aplicables a los lenguajes más comunesEste libro describe técnicas que puede
usar para aprovechar al máximo cualquier lenguaje que esté usando, ya sea C++, C#, Java,
Microsoft Visual Basic u otros lenguajes similares.
Numerosos ejemplos de códigoEl libro contiene casi 500 ejemplos de código bueno y malo. He
incluido tantos ejemplos porque, personalmente, aprendo mejor de los ejemplos. Creo que otros
programadores también aprenden mejor de esa manera.
Los ejemplos están en varios idiomas porque dominar más de un idioma suele ser un punto de
inflexión en la carrera de un programador profesional. Una vez que un programador se da cuenta de
que los principios de programación trascienden la sintaxis de cualquier lenguaje específico, las
puertas se abren al conocimiento que realmente marca la diferencia en calidad y productividad.
Para hacer que la carga de múltiples idiomas sea lo más ligera posible, he evitado las características
esotéricas del lenguaje, excepto donde se discuten específicamente. No necesita comprender todos
los matices de los fragmentos de código para comprender los puntos que están planteando. Si se
enfoca en el punto que se ilustra, encontrará que puede leer el código independientemente del
idioma. He tratado de facilitar aún más su trabajo anotando las partes significativas de los ejemplos.
capítulos, las secciones de "Recursos adicionales" describen otros libros y artículos que puede leer
mientras busca los temas que le resultan más interesantes.
cc2e.com/1234 sitio web del libroSe proporcionan listas de verificación actualizadas, libros, artículos de revistas, enlaces
web y otro contenido en un sitio web complementario encc2e.com. Para acceder a información
relacionada conCódigo completo, 2ª ed., entrarcc2e.com/seguido de un código de cuatro dígitos, un
ejemplo del cual se muestra aquí en el margen izquierdo. Estas referencias a sitios web aparecen a lo largo
del libro.
Las discusiones sobre la construcción también se han visto obstaculizadas por la sugerencia de
que tratar la construcción como un desarrollo de software distintoactividadimplica que la
construcción también debe ser tratada como unfase. En realidad, las actividades y las fases del
software no tienen que establecerse en una relación particular entre sí, y es útil analizar la
actividad de construcción independientemente de si otras actividades del software se realizan
en fases, iteraciones o de algún otro modo. camino.
La construcción es importante
Otra razón por la cual los investigadores y escritores han descuidado la construcción es la idea
errónea de que, en comparación con otras actividades de desarrollo de software, la
construcción es un proceso relativamente mecánico que presenta pocas oportunidades de
mejora. Nada mas lejos de la verdad.
XXIV Prefacio
La construcción de código generalmente representa alrededor del 65 por ciento del esfuerzo en proyectos
pequeños y el 50 por ciento en proyectos medianos. La construcción representa alrededor del 75 por ciento
de los errores en proyectos pequeños y del 50 al 75 por ciento en proyectos medianos y grandes. Cualquier
actividad que represente del 50 al 75 por ciento de los errores presenta una clara oportunidad de mejora. (El
Capítulo 27 contiene más detalles sobre estas estadísticas).
Algunos comentaristas han señalado que aunque los errores de construcción representan un alto porcentaje
de los errores totales, los errores de construcción tienden a ser menos costosos de corregir que los causados
por los requisitos y la arquitectura, y se sugiere que, por lo tanto, son menos importantes. La afirmación de
que los errores de construcción cuestan menos repararlos es cierta, pero engañosa porque el costo de no
corregirlos puede ser increíblemente alto. Los investigadores han descubierto que los errores de codificación
a pequeña escala representan algunos de los errores de software más costosos de todos los tiempos, con
costos que ascienden a cientos de millones de dólares (Weinberg 1983, SEN 1990). Obviamente, un costo
económico de reparación no implica que repararlos deba ser una prioridad baja.
La ironía del cambio de enfoque fuera de la construcción es que la construcción es la única actividad
que se garantiza que se realizará. Los requisitos pueden asumirse en lugar de desarrollarse; la
arquitectura se puede modificar en lugar de diseñar; y las pruebas pueden abreviarse u omitirse en
lugar de planificarse y ejecutarse por completo. Pero si va a haber un programa, tiene que haber
construcción, y eso hace que la construcción sea un área excepcionalmente fructífera en la que
mejorar las prácticas de desarrollo.
Cuando los críticos de arte se En resumen, no pude encontrar ningún libro que siquiera hubiera intentado capturar el conjunto de
reúnen hablan de Forma,
técnicas prácticas disponibles a partir de la experiencia profesional, la investigación industrial y el
Estructura y Significado.
Cuando los artistas se juntan
trabajo académico. La discusión necesitaba actualizarse para los lenguajes de programación actuales,
hablan de dónde se puede la programación orientada a objetos y las prácticas de desarrollo de vanguardia. Parecía claro que un
comprar trementina barata.
libro sobre programación debía ser escrito por alguien que tuviera conocimientos sobre el estado del
- Pablo Picasso
arte teórico pero que también estuviera creando suficiente código de producción para apreciar el
estado de la práctica. yo
Prefacio xiv
concibió este libro como una discusión completa sobre la construcción de código, de un
programador a otro.
Bellevue, Washington
Día de los Caídos, 2004
http://www.microsoft.com/learning/support/
Para conectarse directamente a Microsoft Knowledge Base e ingresar una consulta sobre una
pregunta o problema que pueda tener, vaya a:
http://www.microsoft.com/learning/support/search.asp
Apartado Postal:
Prensa de Microsoft
Correo electrónico:
mspinput@microsoft.com
Expresiones de gratitud
Un libro nunca es escrito realmente por una sola persona (al menos ninguno de mis libros lo es). Una segunda edición
es aún más una empresa colectiva.
Me gustaría agradecer a las personas que contribuyeron con los comentarios de revisión de
partes significativas del libro: Hákon Ágústsson, Scott Ambler, Will Barns, William D.
Bartholomew, Lars Bergstrom, Ian Brockbank, Bruce Butler, Jay Cincotta, Alan Cooper, Bob
Corrick , Al Corwin, Jerry Deville, Jon Eaves, Edward Estrada, Steve Gouldstone, Owain Griffiths,
Matthew Harris, Michael Howard, Andy Hunt, Kevin Hutchison, Rob Jasper, Stephen Jenkins, Ralph
Johnson y su Grupo de Arquitectura de Software en la Universidad de Illinois, Marek Konopka, Jeff
Langr, Andy Lester, Mitica Manu, Steve Mattingly, Gareth McCaughan, Robert McGovern, Scott
Meyers, Gareth Morgan, Matt Peloquin, Bryan Pflug, Jeffrey Richter, Steve Rinn, Doug Rosenberg,
Brian St. Pierre, Diomidis Spinellis, Matt Stephens, Dave Thomas, Andy Thomas-Cramer, John
Vlissides, Pavel Vozenilek, Denny Williford,Jack Woolley y Dee Zsombor.
Cientos de lectores enviaron comentarios sobre la primera edición y muchos más enviaron comentarios
individuales sobre la segunda edición. Gracias a todos los que se tomaron el tiempo para compartir sus
reacciones al libro en sus diversas formas.
Trabajar con Devon Musgrave, editor del proyecto de este libro, ha sido un placer especial. He
trabajado con numerosos editores excelentes en otros proyectos, y Devon se destaca como
especialmente concienzudo y fácil de trabajar. ¡Gracias, Devon! Gracias a Linda Engleman que
defendió la segunda edición; este libro no habría existido sin ella. Gracias también al resto del
personal de Microsoft Press, incluidos Robin Van Steenburgh, Elden Nelson, Carl Diltz, Joel
Panchot, Patricia Masserman, Bill Myers, Sandi Resnick, Barbara Norfleet, James Kramer y
Prescott Klassen.
Me gustaría recordar al personal de Microsoft Press que publicó la primera edición: Alice Smith,
Arlene Myers, Barbara Runyan, Carol Luke, Connie Little, Dean Holmes, Eric Stroo, Erin O'Connor,
Jeannie McGivern, Jeff Carey, Jennifer Harris. , Jennifer Vick, Judith Bloch, Katherine Erickson, Kim
Eggleston, Lisa Sandburg, Lisa Theobald, Margarite Hargrave, Mike Halvorson, Pat Forgette,
Peggy Herman, Ruth Pettis, Sally Brunsman, Shawn Peck, Steve Murray, Wallis Bolz y Zaafar
Hasnain.
xvii
xviii Expresiones de gratitud
Gracias a los revisores que contribuyeron tan significativamente a la primera edición: Al Corwin, Bill
Kiestler, Brian Daugherty, Dave Moore, Greg Hitchcock, Hank Meuret, Jack Woolley, Joey Wyrick,
Margot Page, Mike Klein, Mike Zevenbergen, Pat Forman, Peter Pathé, Robert L. Glass, Tammy
Forman, Tony Pisculli y Wayne Beardsley. Un agradecimiento especial a Tony Garland por su revisión
exhaustiva: con 12 años de retrospectiva, aprecio más que nunca cuán excepcionales fueron
realmente los miles de comentarios de revisión de Tony.
listas de control
Requisitos 42
Arquitectura 54
Prerrequisitos ascendentes 59
Principales Prácticas de Construcción 69
Diseño en la Construcción 122
Clase Calidad 157
Rutinas de alta calidad 185
Programación defensiva 211
El proceso de programación de pseudocódigo 233
Consideraciones generales en el uso de variables de 257
nomenclatura de datos 288
Datos Fundamentales 316
Consideraciones en el uso de tipos de datos inusuales 343
Organización de código de línea recta 353
Uso de condicionales 365
Bucles 388
Estructuras de control inusuales 410
Métodos controlados por tablas 429
Cuestiones de estructura de 459
control Un plan de garantía de 476
calidad Programación en pareja 484
491
efectiva Inspecciones efectivas
xxix
xxx listas de control
Diseño 773
Código de autodocumentación
Mesas
Tabla 3-2 Buenas prácticas típicas para tres tipos comunes de proyectos de software Efecto de 31
Tabla 3-3 omitir los requisitos previos en proyectos secuenciales e iterativos 33 Efecto de
Tabla 3-4 centrarse en los requisitos previos en proyectos secuenciales e iterativos Proporción 34
Tabla 4-1 62
de declaraciones de lenguaje de alto nivel a códigos C equivalentes Patrones de
Cuadro 23-1 distancia psicológica entre nombres de variables Tiempo relativo de 556
Tabla 25-1 ejecución de lenguajes de programación 600 Costos de operaciones
xxi
xxxii Mesas
Figura 1-2 Este libro se centra en la codificación y la depuración, el diseño detallado, la planificación de la
construcción, las pruebas unitarias, la integración, las pruebas de integración y otras actividades en
aproximadamente estas proporciones. 5
Figura 2-1 La metáfora de la escritura de cartas sugiere que el proceso de software se basa en
costosos ensayos y errores en lugar de una planificación y un diseño cuidadosos. 14
Figura 2-3 La penalización por un error en una estructura simple es solo un poco de
tiempo y tal vez algo de vergüenza. 17
Figura 2-4 Las estructuras más complicadas requieren una planificación más cuidadosa. 18
Figura 3-1 El costo de reparar un defecto aumenta dramáticamente a medida que aumenta el tiempo
desde que se presenta hasta que se detecta. Esto sigue siendo cierto ya sea que el proyecto sea
altamente secuencial (haciendo el 100 por ciento de los requisitos y el diseño por adelantado) o
altamente iterativo (haciendo el 5 por ciento de los requisitos y el diseño por adelantado). 30
Figura 3-2 Las actividades se superpondrán hasta cierto punto en la mayoría de los proyectos, incluso en aquellos que son
muy secuenciales. 35
Figura 3-3 En otros proyectos, las actividades se superpondrán durante la duración del
proyecto. Una clave para una construcción exitosa es comprender el grado en que se
han completado los requisitos previos y ajustar su enfoque en consecuencia. 35
Figura 3-4 La definición del problema sienta las bases para el resto del proceso de
programación. 37
Figura 3-7 Sin una buena arquitectura de software, es posible que tenga el problema correcto pero
la solución incorrecta. Puede ser imposible tener una construcción exitosa. 44
xxxii
xiv Cifras
Figura 5-2 Los niveles de diseño en un programa. El sistema (1) se organiza primero en
subsistemas (2). Los subsistemas se dividen además en clases (3), y las clases se
dividen en rutinas y datos (4). El interior de cada rutina también está diseñado
(5). 82
Figura 5-5 Con algunas reglas de comunicación, puede simplificar significativamente las interacciones de
los subsistemas. 84
Figura 5-6 Este sistema de facturación se compone de cuatro objetos principales. Los objetos se han
simplificado para este ejemplo. 88
Figura 5-7 La abstracción le permite tener una visión más simple de un concepto complejo. 90
Figura 5-8 La encapsulación dice que, no solo se le permite tener una visión más simple de un
concepto complejo, sino que no se le permite ver ninguno de los detalles del concepto
complejo. Lo que ves es lo que obtienes, ¡es todo lo que obtienes! 91
Figura 5-9 Una buena interfaz de clase es como la punta de un iceberg, dejando la mayor parte de la clase
sin exponer. 93
Figura 5-10 G. Polya desarrolló un enfoque para la resolución de problemas en matemáticas que
también es útil para resolver problemas en el diseño de software (Polya 1957). 109
Figura 8-1 Parte del puente flotante de la Interestatal 90 en Seattle se hundió durante una tormenta
porque los tanques de flotación quedaron descubiertos, se llenaron de agua y el puente
se volvió demasiado pesado para flotar. Durante la construcción, protegerse contra las
cosas pequeñas es más importante de lo que piensa. 189
Figura 8-2 Definir algunas partes del software que funcionan con datos sucios y otras que funcionan
con datos limpios puede ser una forma efectiva de liberar a la mayoría del código de la
responsabilidad de verificar datos incorrectos. 204
Figura 9-1 Los detalles de la construcción de clases varían, pero las actividades generalmente ocurren en el
Figura 9-2 Estas son las principales actividades que intervienen en la construcción de una rutina. Por lo
general, se realizan en el orden que se muestra. 217
Figura 9-3 Realizará todos estos pasos a medida que diseñe una rutina, pero no
necesariamente en un orden particular. 225
Figura 10-1 "Tiempo de vida prolongado" significa que una variable está activa en el transcurso de muchas
declaraciones. “Tiempo de vida corto” significa que está en vivo solo por unas pocas declaraciones.
“Span” se refiere a qué tan juntas están las referencias a una variable. 246
Figura 10-2 Los datos secuenciales son datos que se manejan en un orden definido. 254 Los datos
Figura 10-3 selectivos le permiten usar una pieza u otra, pero no ambas. 255
Cifras xxxv
Figura 13-1 La cantidad de memoria utilizada por cada tipo de datos se muestra mediante líneas
dobles. 324
Figura 13-2 Un ejemplo de una imagen que nos ayuda a pensar en los pasos necesarios para volver
a vincular punteros. 329
Figura 14-1 Si el código está bien organizado en grupos, los cuadros dibujados alrededor de las secciones
Figura 14-2 Si el código está mal organizado, los recuadros dibujados alrededor de las secciones relacionadas se
superponen. 353
Figura 17-1 La recursividad puede ser una herramienta valiosa en la batalla contra la complejidad, cuando se usa
Figura 18-1 Como sugiere el nombre, una tabla de acceso directo le permite acceder directamente al
elemento de la tabla que le interesa. 413
Figura 18-2 Los mensajes se almacenan sin ningún orden en particular y cada uno se identifica con un ID de
mensaje. 417
Figura 18-3 Además del ID del mensaje, cada tipo de mensaje tiene su propio formato. 418
Figura 18-4 En lugar de acceder directamente, se accede a una tabla de acceso indexada a través de
un índice intermedio. 425
Figura 19-1 Ejemplos del uso de la ordenación de líneas numéricas para pruebas booleanas. 440
Figura 20-1 Centrarse en una característica externa de la calidad del software puede afectar otras características
de manera positiva, negativa o no afectarlas en absoluto. 466
Figura 22-2 A medida que aumenta el tamaño del proyecto, disminuye la proporción de errores cometidos durante
la construcción. No obstante, los errores de construcción representan entre el 45% y el 75% de todos los
errores, incluso en los proyectos más grandes. 521
Figura 23-1 Intente reproducir un error de varias maneras diferentes para determinar su causa
exacta. 545
Figura 24-1 Los cambios pequeños tienden a ser más propensos a errores que los cambios más grandes
(Weinberg 1983). 581
xxxvi Cifras
Figura 24-2 Su código no tiene que ser desordenado solo porque el mundo real es desordenado.
Conciba su sistema como una combinación de código ideal, interfaces del código ideal
al desordenado mundo real y al desordenado mundo real. 583
Figura 24-3 Una estrategia para mejorar el código de producción es refactorizar el código heredado
mal escrito a medida que lo toca, para moverlo al otro lado de la "interfaz con el
desordenado mundo real". 584
Figura 27-2 A medida que aumenta el tamaño del proyecto, los errores generalmente provienen más de los
requisitos y el diseño. A veces todavía provienen principalmente de la construcción (Boehm
1981, Grady 1987, Jones 1998). 652
Figura 27-3 Las actividades de construcción dominan los proyectos pequeños. Los proyectos más
grandes requieren más arquitectura, trabajo de integración y pruebas del sistema para
tener éxito. El trabajo de requisitos no se muestra en este diagrama porque el esfuerzo de
requisitos no es una función tan directa del tamaño del programa como lo son otras
actividades (Albrecht 1979; Glass 1982; Boehm, Gray y Seewaldt 1984; Boddie 1987; Card
1987; McGarry, Waligora y McDermott 1989; Brooks 1995; Jones 1998; Jones 2000; Boehm
et al. 2000). 654
Figura 27-4 La cantidad de trabajo de construcción de software es una función casi lineal del tamaño del
proyecto. Otros tipos de trabajo aumentan de forma no lineal a medida que aumenta el tamaño
del proyecto. 655
Figura 28-1 Este capítulo cubre los temas de administración de software relacionados con la
construcción. 661
Figura 28-2 Las estimaciones creadas al principio de un proyecto son intrínsecamente inexactas. A medida
que avanza el proyecto, las estimaciones pueden volverse más precisas. Vuelva a estimar
periódicamente a lo largo de un proyecto y use lo que aprenda durante cada actividad para
mejorar su estimación para la próxima actividad. 673
Figura 29-2 ¡La integración por fases también se llama integración "big bang" por una
buena razón! 691
Figura 29-3 La integración incremental ayuda a que un proyecto genere impulso, como una bola de nieve
que cae por una colina. 692
Cifras xxxvii
Figura 29-4 En la integración por fases, integra tantos componentes a la vez que es difícil
saber dónde está el error. Puede estar en alguno de los componentes o en
alguna de sus conexiones. En la integración incremental, el error suele estar en
el nuevo componente o en la conexión entre el nuevo componente y el sistema.
693
Figura 29-5 En la integración de arriba hacia abajo, agrega clases en la parte superior primero, en la parte inferior al
final. 695
Figura 29-6 Como alternativa a proceder estrictamente de arriba hacia abajo, puede integrar de
arriba hacia abajo en cortes verticales. 696
Figura 29-7 En la integración de abajo hacia arriba, las clases se integran en la parte inferior primero y en la parte superior
al final. 697
Figura 29-8 Como alternativa a proceder puramente de abajo hacia arriba, puede integrar de abajo
hacia arriba en secciones. Esto desdibuja la línea entre la integración ascendente y la
integración orientada a funciones, que se describe más adelante en este capítulo. 698
Figura 29-9 En la integración sándwich, integra primero las clases de nivel superior y las de nivel inferior
ampliamente utilizadas y deja las clases de nivel medio para el final. 698
Figura 29-10 En la integración orientada al riesgo, primero integra las clases que espera que sean más
problemáticas; implementas clases más fáciles más tarde. 699
Figura 29-11 En la integración orientada a funciones, las clases se integran en grupos que forman
funciones identificables, por lo general, pero no siempre, varias clases a la vez. 700
Figura 29-12 En la integración en forma de T, construye e integra una porción profunda del
sistema para verificar los supuestos arquitectónicos y luego construye e integra la
amplitud del sistema para proporcionar un marco para desarrollar la funcionalidad
restante. 701
Figura 34-1 Los programas se pueden dividir en niveles de abstracción. Un buen diseño le permitirá
pasar gran parte de su tiempo enfocándose solo en las capas superiores e ignorando las
capas inferiores. 846
Parte I
Sentando las bases
En esta parte:
Bienvenido a Software
Construcción
cc2e.com/0178 Contenido
Temas relacionados
Ya sabes lo que significa "construcción" cuando se usa fuera del desarrollo de software.
“Construcción” es el trabajo que hacen los “trabajadores de la construcción” cuando construyen una
casa, una escuela o un rascacielos. Cuando eras más joven, construías cosas con “papel de
construcción”. En el uso común, "construcción" se refiere al proceso de construcción. El proceso de
construcción puede incluir algunos aspectos de la planificación, el diseño y la verificación de su
trabajo, pero la "construcción" se refiere principalmente a la parte práctica de crear algo.
- Desarrollo de requisitos
- Planificación de la construcción
- Diseño detallado
- Codificación y depuración
- Examen de la unidad
3
4 Capítulo 1: Bienvenido a la construcción de software
- Pruebas de integración
- Integración
- Mantenimiento correctivo
Si ha trabajado en proyectos informales, podría pensar que esta lista representa una gran cantidad
de trámites burocráticos. Si ha trabajado en proyectos que son demasiado formales,saberque esta
lista representa mucha burocracia! Es difícil lograr un equilibrio entre muy poca y demasiada
formalidad, y eso se analiza más adelante en el libro.
Esta noción intuitiva de “construcción” es bastante precisa, pero adolece de falta de perspectiva.
Poner la construcción en su contexto con otras actividades ayuda a mantener el enfoque en las
tareas correctas durante la construcción y enfatiza adecuadamente las actividades importantes que
no son de construcción. La figura 1-1 ilustra el lugar de la construcción en relación con otras
actividades de desarrollo de software.
Problema
Definición
Correctivo
Detallado
Mantenimiento
Requisitos Diseño
Desarrollo
Integración
Codificación y
Construcción
depuración
Planificación
Integración
Pruebas
Unidad
Software Pruebas
Sistema
Arquitectura
Pruebas
Figura 1-1Las actividades de construcción se muestran dentro del círculo gris. La construcción se centra en la
codificación y la depuración, pero también incluye el diseño detallado, las pruebas unitarias, las pruebas de
integración y otras actividades.
1.1 ¿Qué es la construcción de software? 5
Como indica la figura, la construcción consiste principalmente en codificación y depuración, pero también
implica diseño detallado, planificación de la construcción, pruebas unitarias, integración, pruebas de
integración y otras actividades. Si este fuera un libro sobre todos los aspectos del desarrollo de software,
PUNTO CLAVE
presentaría discusiones bien equilibradas de todas las actividades en el proceso de desarrollo. Sin embargo,
debido a que este es un manual de técnicas de construcción, pone un énfasis desequilibrado en la
construcción y solo toca temas relacionados. Si este libro fuera un perro, se dedicaría a la construcción,
movería la cola durante el diseño y las pruebas, y ladraría durante las demás actividades de desarrollo.
En contraste con la vista de tierra plana del desarrollo de software de la figura 1-1, la figura 1-2 muestra la
perspectiva de tierra redonda de este libro.
Problema
Definición
Detallado
Correctivo
Desarrollo
Codificación y Integración
Construcción
Planificación depuración
Integración
Pruebas
Software Unidad
Arquitectura
Pruebas Sistema
Pruebas
Figura 1-2Este libro se centra en la codificación y la depuración, el diseño detallado, la planificación de la construcción, las
pruebas unitarias, la integración, las pruebas de integración y otras actividades en aproximadamente estas proporciones.
La Figura 1-1 y la Figura 1-2 son vistas de alto nivel de las actividades de construcción, pero ¿qué pasa
con los detalles? Estas son algunas de las tareas específicas involucradas en la construcción:
- Verificar que se hayan sentado las bases para que la construcción pueda
continuar con éxito
- Revisar los diseños y el código de bajo nivel de otros miembros del equipo y pedirles que
revisen el suyo
Para obtener una lista aún más completa de las actividades de construcción, consulte los títulos de los capítulos en
la tabla de contenido.
Con tantas actividades en el trabajo de la construcción, podrías decir: "Está bien, Jack, ¿qué
actividades sonnoparte de la construcción? Esa es una pregunta justa. Las actividades importantes
que no son de construcción incluyen la gestión, el desarrollo de requisitos, la arquitectura de
software, el diseño de la interfaz de usuario, las pruebas del sistema y el mantenimiento. Cada una de
estas actividades afecta el éxito final de un proyecto tanto como la construcción, al menos el éxito de
cualquier proyecto que requiera más de una o dos personas y dure más de unas pocas semanas.
Puedes encontrar buenos libros sobre cada actividad; muchos se enumeran en las secciones
"Recursos adicionales" a lo largo del libro y en el Capítulo 35, "Dónde encontrar más información", al
final del libro.
Referencia cruzadaPara obtener La construcción es una gran parte del desarrollo de software.Dependiendo del tamaño del
detalles sobre la relación entre el
proyecto, la construcción generalmente toma del 30 al 80 por ciento del tiempo total invertido en un
tamaño del proyecto y el porcentaje
Referencia cruzadaPara obtener Con un enfoque en la construcción, la productividad del programador individual puede
información sobre variaciones entre
mejorar enormementeUn estudio clásico de Sackman, Erikson y Grant mostró que la
programadores, consulte "Variación
El producto de la construcción, el código fuente, suele ser la única descripción precisa del
software.En muchos proyectos, la única documentación disponible para los programadores es el
propio código. Las especificaciones de requisitos y los documentos de diseño pueden quedar
obsoletos, pero el código fuente siempre está actualizado. En consecuencia, es imperativo que el
código fuente sea de la mejor calidad posible. La aplicación constante de técnicas para mejorar el
código fuente marca la diferencia entre un artilugio de Rube Goldberg y un programa detallado,
correcto y, por lo tanto, informativo. Estas técnicas se aplican con mayor eficacia durante la
construcción.
La construcción es la única actividad que está garantizada para ser hechaEl proyecto de software ideal
pasa por un cuidadoso desarrollo de requisitos y un diseño arquitectónico antes de que comience la
construcción. El proyecto ideal se somete a pruebas de sistema exhaustivas y controladas estadísticamente
PUNTO CLAVE
después de la construcción. Sin embargo, los proyectos imperfectos del mundo real a menudo se saltan los
requisitos y el diseño para saltar a la construcción. Abandonan las pruebas porque tienen demasiados
errores que corregir y se les acabó el tiempo. Pero no importa cuán apresurado o mal planificado sea un
proyecto, no se puede abandonar la construcción; es donde el caucho se encuentra con la carretera. Por lo
tanto, mejorar la construcción es una forma de mejorar cualquier esfuerzo de desarrollo de software, sin
importar cuán abreviado sea.
8 Capítulo 1: Bienvenido a la construcción de software
Puntos clave
- La construcción de software es la actividad central en el desarrollo de software; la
construcción es la única actividad que está garantizada en todos los proyectos.
Tema relacionado
La informática tiene uno de los lenguajes más coloridos de cualquier campo. ¿En qué otro campo puede
entrar en una habitación estéril, cuidadosamente controlada a 68 °F, y encontrar virus, caballos de Troya,
gusanos, insectos, bombas, choques, llamas, cambiadores de sexo retorcidos y errores fatales?
La teoría cinética de los gases se basaba en un modelo de “bola de billar”. Se pensaba que las moléculas de
gas tenían masa y chocaban elásticamente, como lo hacen las bolas de billar, y se desarrollaron muchos
teoremas útiles a partir de este modelo.
La teoría ondulatoria de la luz se desarrolló en gran medida explorando las similitudes entre la
luz y el sonido. La luz y el sonido tienen amplitud (brillo, volumen), frecuencia (color, tono) y
otras propiedades en común. La comparación entre las teorías ondulatorias del sonido y la luz
fue tan productiva que los científicos dedicaron un gran esfuerzo a buscar un medio que
propagara la luz de la misma forma que el aire propaga el sonido. Incluso le dieron un
nombre, "éter", pero nunca encontraron el medio. La analogía que había sido tan fructífera en
algunos aspectos resultó engañosa en este caso.
En general, el poder de los modelos es que son vívidos y pueden entenderse como totalidades
conceptuales. Sugieren propiedades, relaciones y áreas adicionales de investigación. A veces,
un modelo sugiere áreas de investigación que son engañosas, en cuyo caso la metáfora se ha
extendido demasiado. Cuando los científicos buscaron éter, extendieron demasiado su
modelo.
Como era de esperar, algunas metáforas son mejores que otras. Una buena metáfora es
simple, se relaciona bien con otras metáforas relevantes y explica gran parte de la evidencia
experimental y otros fenómenos observados.
Considere el ejemplo de una piedra pesada que se balancea de un lado a otro en una cuerda.
Antes de Galileo, un aristotélico que miraba la piedra oscilante pensaba que un objeto pesado
se movía naturalmente desde una posición más alta a un estado de reposo en una más baja. El
aristotélico pensaría que lo que realmente hacía la piedra era caer con dificultad. Cuando
Galileo vio la piedra oscilante, vio un péndulo. Pensó que lo que en realidad estaba haciendo la
piedra era repetir el mismo movimiento una y otra vez, casi a la perfección.
Los poderes sugestivos de los dos modelos son bastante diferentes. El aristotélico que veía la
piedra oscilante como un objeto que caía observaría el peso de la piedra, la altura a la que se
había elevado y el tiempo que tardaba en detenerse. Para el modelo del péndulo de Galileo,
los factores destacados eran diferentes. Galileo observó el peso de la piedra, el radio de
oscilación del péndulo, el desplazamiento angular y el tiempo por oscilación. Galileo descubrió
leyes que los aristotélicos no pudieron descubrir porque su modelo los llevó a mirar diferentes
fenómenos y hacer diferentes preguntas.
Las metáforas contribuyen a una mayor comprensión de los problemas de desarrollo de software de
la misma manera que contribuyen a una mayor comprensión de las cuestiones científicas. En su
conferencia del Premio Turing de 1973, Charles Bachman describió el cambio de la visión
predominante del universo centrada en la tierra a una visión centrada en el sol. El modelo centrado
en la tierra de Ptolomeo había durado sin ningún desafío serio durante 1400 años. Luego, en 1543,
Copérnico introdujo una teoría heliocéntrica, la idea de que el sol, en lugar de la tierra, era el centro
del universo. Este cambio en los modelos mentales condujo finalmente al descubrimiento de nuevos
planetas, la reclasificación de la luna como satélite en lugar de planeta, y una comprensión diferente
del lugar de la humanidad en el universo.
2.2 Cómo usar metáforas de software 11
El valor de las metáforas no debe Bachman comparó el cambio en astronomía de Ptolemaico a Copernicano con el cambio en la
subestimarse. Las metáforas
programación de computadoras a principios de la década de 1970. Cuando Bachman hizo la comparación
tienen la virtud de un
efecto, las metáforas son una conjunto de datos sobre los que actuaba la computadora (una vista orientada a la base de datos).
forma de interiorizar y abstraer
conceptos, permitiendo que el Hoy es difícil imaginar a alguien pensando que el sol se mueve alrededor de la tierra. De
pensamiento esté en un plano
manera similar, es difícil imaginar a un programador pensando que todos los datos podrían
superior y que se eviten errores de
bajo nivel.
verse como un flujo secuencial de tarjetas. En ambos casos, una vez descartada la vieja teoría,
parece increíble que alguien la haya creído alguna vez. Más fantásticamente, las personas que
—Fernando J. Corbató creían en la vieja teoría pensaron que la nueva teoría era tan ridícula entonces como crees que
la vieja teoría es ahora.
La visión del universo centrada en la Tierra cojeó a los astrónomos que se aferraron a ella después de que se
dispusiera de una teoría mejor. De manera similar, la visión centrada en la computadora del universo informático
cojeó a los informáticos que se aferraron a ella después de que la teoría centrada en la base de datos estuvo
disponible.
Es tentador trivializar el poder de las metáforas. A cada uno de los ejemplos anteriores, la
respuesta natural es decir: “Bueno, por supuesto que la metáfora correcta es más útil. ¡La otra
metáfora estaba equivocada!” Aunque esa es una reacción natural, es simplista. La historia de
la ciencia no es una serie de cambios de la metáfora "incorrecta" a la "correcta". Es una serie de
cambios de metáforas “peores” a “mejores”, de menos inclusivas a más inclusivas, de
sugerentes en un ámbito a sugerentes en otro.
De hecho, muchos modelos que han sido reemplazados por modelos mejores siguen siendo útiles. Los
ingenieros aún resuelven la mayoría de los problemas de ingeniería utilizando la dinámica newtoniana
aunque, en teoría, la dinámica newtoniana ha sido suplantada por la teoría de Einstein.
El desarrollo de software es un campo más joven que la mayoría de las otras ciencias. Todavía no es
lo suficientemente maduro como para tener un conjunto de metáforas estándar. En consecuencia,
tiene una profusión de metáforas complementarias y contradictorias. Algunos son mejores que otros.
Algunos son peores. Lo bien que entienda las metáforas determina lo bien que entienda el desarrollo
de software.
Un algoritmo es un conjunto de instrucciones bien definidas para llevar a cabo una tarea en
particular. Un algoritmo es predecible, determinista y no está sujeto al azar. Un algoritmo dice
12 Capítulo 2: Metáforas para una mejor comprensión del desarrollo de software
cómo ir del punto A al punto B sin desvíos, sin viajes secundarios a los puntos D, E y F, y
sin parar para oler las rosas o tomar una taza de café.
Una heurística es una técnica que te ayuda a buscar una respuesta. Sus resultados están sujetos al
azar porque una heurística le dice solo cómo buscar, no qué encontrar. No te dice cómo llegar
directamente del punto A al punto B; es posible que ni siquiera sepa dónde están el punto A y el
punto B. En efecto, una heurística es un algoritmo con traje de payaso. Es menos predecible, es más
divertido y viene sin una garantía de devolución de dinero de 30 días.
Aquí hay un algoritmo para conducir a la casa de alguien: Tome la autopista 167 sur hasta Puyallup. Tome la
salida de South Hill Mall y conduzca 4.5 millas cuesta arriba. Gire a la derecha en el semáforo junto a la
tienda de comestibles y luego tome la primera a la izquierda. Gire hacia la entrada de la gran casa color
canela a la izquierda, en 714 North Cedar.
Referencia cruzadaPara obtener Aquí hay una heurística para llegar a la casa de alguien: encuentre la última carta que le enviamos.
detalles sobre cómo usar la
Conduzca a la ciudad en la dirección del remitente. Cuando llegues a la ciudad, pregúntale a alguien dónde
heurística en el diseño de software,
La diferencia entre un algoritmo y una heurística es sutil y los dos términos se superponen un
poco. Para los propósitos de este libro, la principal diferencia entre los dos es el nivel de
direccionamiento indirecto de la solución. Un algoritmo te da las instrucciones directamente.
Una heurística le dice cómo descubrir las instrucciones por sí mismo, o al menos dónde
buscarlas.
Tener instrucciones que le digan exactamente cómo resolver sus problemas de programación
ciertamente facilitará la programación y los resultados serán más predecibles. Pero la ciencia de la
programación aún no es tan avanzada y puede que nunca lo sea. La parte más desafiante de la
programación es conceptualizar el problema, y muchos errores en la programación son errores
conceptuales. Debido a que cada programa es conceptualmente único, es difícil o imposible crear un
conjunto general de instrucciones que conduzcan a una solución en cada caso. Por lo tanto, saber
cómo abordar los problemas en general es al menos tan valioso como conocer soluciones específicas
para problemas específicos.
¿Cómo se utilizan las metáforas de software? Úselos para obtener información sobre sus
problemas y procesos de programación. Úselos para ayudarlo a pensar sobre sus actividades
de programación y para ayudarlo a imaginar mejores formas de hacer las cosas. No podrá
mirar una línea de código y decir que viola una de las metáforas descritas en este capítulo. Sin
embargo, con el tiempo, la persona que usa metáforas para iluminar el proceso de desarrollo
de software será percibida como alguien que tiene una mejor comprensión de la programación
y produce un mejor código más rápido que las personas que no las usan.
2.3 Metáforas de software comunes 13
Muchas ideas se derivan de la metáfora de la escritura. Jon Bentley dice que deberías poder sentarte
junto al fuego con una copa de brandy, un buen cigarro y tu perro de caza favorito para disfrutar de
un "programa de alfabetización" como lo harías con una buena novela. Brian Kernighan y PJ Plauger
nombraron su libro de estilo de programaciónLos elementos del estilo de programación (1978)
después del libro de estilo de escrituraLos elementos del estilo(Strunk y White 2000). Los
programadores a menudo hablan de "legibilidad del programa".
3
2
Para el trabajo de un individuo o para proyectos a pequeña escala, la metáfora de escribir cartas funciona
1
adecuadamente, pero para otros propósitos abandona la fiesta antes de tiempo: no describe el desarrollo
DATOS DUROS
de software completa o adecuadamente. La escritura suele ser una actividad de una sola persona, mientras
que un proyecto de software probablemente involucrará a muchas personas con muchas responsabilidades
diferentes. Cuando terminas de escribir una carta, la metes en un sobre y lo envías por correo. Ya no puede
cambiarlo y, para todos los efectos, está completo. El software no es tan difícil de cambiar y casi nunca está
completamente completo. Tanto como el 90 por ciento del esfuerzo de desarrollo en un sistema de software
típico se produce después de su lanzamiento inicial, siendo típicos dos tercios (Pigoski 1997). En la escritura,
se otorga una gran importancia a la originalidad. En la construcción de software, intentar crear un trabajo
verdaderamente original suele ser menos eficaz que centrarse en la reutilización de ideas de diseño, código
y casos de prueba de proyectos anteriores. En resumen, la metáfora de la escritura implica un proceso de
desarrollo de software que es demasiado simple y rígido para ser saludable.
14 Capítulo 2: Metáforas para una mejor comprensión del desarrollo de software
Planee tirar uno; lo harás, de Desafortunadamente, la metáfora de escribir cartas ha sido perpetuada por uno de los libros de
todos modos.
software más populares del planeta, el libro de Fred Brooks.El Hombre-Mes Mítico(Brooks 1995).
—fred brooks
Brooks dice: “Planea tirar uno; lo harás, de todos modos. Esto evoca la imagen de una pila de
Si planea tirar uno, borradores a medio escribir arrojados a una papelera, como se muestra en la Figura 2-1.
tirará dos.
—craig cerouni
Figura 2-1La metáfora de la escritura de cartas sugiere que el proceso de software se basa en costosos
ensayos y errores en lugar de una planificación y un diseño cuidadosos.
Planificar tirar uno a la basura puede ser práctico cuando le escribes a tu tía un cortés "cómo
te va". Pero extender la metáfora de “escribir” software a un plan para desecharlo es un mal
consejo para el desarrollo de software, donde un sistema importante ya cuesta tanto como un
edificio de oficinas de 10 pisos o un transatlántico. Es fácil agarrar el anillo de latón si puede
permitirse el lujo de sentarse en su pony de madera favorito para un número ilimitado de
giros alrededor del carrusel. El truco es conseguirlo la primera vez, o correr varios riesgos
cuando son más baratos. Otras metáforas iluminan mejor las formas de alcanzar tales
objetivos.
En contraste con la metáfora de la escritura rígida, algunos desarrolladores de software dicen que
deberías imaginar la creación de software como algo parecido a plantar semillas y cultivar. Usted
diseña una pieza, codifica una pieza, prueba una pieza y la agrega al sistema poco a poco. Al dar
pequeños pasos, minimiza los problemas en los que puede meterse en cualquier momento.
A veces, una buena técnica se describe con una mala metáfora. En tales casos, trate de
mantener la técnica y proponga una mejor metáfora. En este caso, la técnica incremental
es valiosa, pero la metáfora de la agricultura es terrible.
PUNTO CLAVE
Otras lecturasPara ver una La idea de hacer un poco a la vez puede tener cierta semejanza con la forma en que crecen los
ilustración de una metáfora
cultivos, pero la analogía agrícola es débil y poco informativa, y es fácil de reemplazar con las
agrícola diferente, una que se
aplica al mantenimiento de
mejores metáforas descritas en las siguientes secciones. Es difícil extender la metáfora de la
software, consulte el capítulo agricultura más allá de la simple idea de hacer las cosas poco a poco. Si acepta la metáfora de
"Sobre los orígenes de la intuición
la agricultura, imaginada en la Figura 2-2, es posible que se encuentre hablando de fertilizar el
del diseñador" enRepensar el
rotando en un cultivo de C++ en lugar de cebada, de dejar reposar la tierra durante un año para aumentar el
suministro de nitrógeno en el disco duro.
A veces, la gente habla de hacer crecer el software cuando en realidad se refiere a la acumulación de software. Las
dos metáforas están estrechamente relacionadas, pero la acumulación de software es la imagen más perspicaz.
“Acrecimiento”, en caso de que no tenga un diccionario a mano, significa cualquier crecimiento o aumento de
tamaño por una adición o inclusión externa gradual. La acumulación describe la forma en que una ostra hace una
perla, agregando gradualmente pequeñas cantidades de carbonato de calcio. En geología, “acreción” significa una
adición lenta a la tierra por el depósito de sedimentos transportados por el agua. En términos legales, “acreción”
significa un aumento de tierra a lo largo de las costas de un cuerpo de agua por el depósito de sedimentos
transportados por el agua.
Referencia cruzadaPara obtener detalles Esto no significa que tenga que aprender a crear código a partir de sedimentos transportados por el
sobre cómo aplicar estrategias
agua; significa que debe aprender a agregar a sus sistemas de software una pequeña cantidad a la
incrementales a la integración del sistema,
En el desarrollo incremental, primero crea la versión más simple posible del sistema que se
ejecutará. No tiene que aceptar entradas realistas, no tiene que realizar manipulaciones realistas en
los datos, no tiene que producir resultados realistas, solo tiene que ser un esqueleto lo
suficientemente fuerte como para contener el sistema real a medida que se desarrolla. Podría llamar
a clases ficticias para cada una de las funciones básicas que ha identificado. Este comienzo básico es
como el comienzo de la ostra, una perla con un pequeño grano de arena.
Después de haber formado el esqueleto, poco a poco se acuesta sobre el músculo y la piel.
Cambia cada una de las clases ficticias a clases reales. En lugar de tener su programa
dieciséis Capítulo 2: Metáforas para una mejor comprensión del desarrollo de software
pretende aceptar la entrada, introduce un código que acepta la entrada real. En lugar de hacer que
su programa pretenda producir una salida, introduce un código que produce una salida real. Agrega
un poco de código a la vez hasta que tenga un sistema completamente funcional.
Como metáfora, la fuerza de la metáfora incremental es que no promete demasiado. Es más difícil que la
metáfora de la agricultura extenderse de manera inapropiada. La imagen de una ostra formando una perla
es una buena manera de visualizar el desarrollo incremental o acrecentamiento.
Construir una torre de cuatro pies requiere una mano firme, una superficie nivelada y 10 latas de
cerveza intactas. Construir una torre 100 veces más grande no solo requiere 100 veces más latas de
cerveza. Requiere un tipo diferente de planificación y construcción por completo.
Si está construyendo una estructura simple, por ejemplo, una casa para perros, puede conducir hasta
la tienda de madera y comprar madera y clavos. Al final de la tarde, tendrás una nueva casa para
Fido. Si olvida proporcionar una puerta, como se muestra en la Figura 2-3, o comete algún otro error,
no es un gran problema; puedes arreglarlo o incluso empezar de nuevo desde el principio. Todo lo
que has desperdiciado es parte de una tarde. Este enfoque flexible también es apropiado para
pequeños proyectos de software. Si usa el diseño incorrecto para 1000 líneas de código, puede
refactorizar o comenzar de nuevo por completo sin perder mucho.
2.3 Metáforas de software comunes 17
Figura 2-3La penalización por un error en una estructura simple es solo un poco de tiempo y tal vez
algo de vergüenza.
Si está construyendo una casa, el proceso de construcción es más complicado, al igual que las
consecuencias de un mal diseño. Primero, debe decidir qué tipo de casa desea construir, de manera
análoga en el desarrollo de software a la definición del problema. Luego, usted y un arquitecto deben
presentar un diseño general y aprobarlo. Esto es similar al diseño arquitectónico de software. Dibujas
planos detallados y contratas a un contratista. Esto es similar al diseño de software detallado.
Preparas el sitio de construcción, colocas los cimientos, enmarcas la casa, le pones un revestimiento y
un techo, y la instalas y alambras. Esto es similar a la construcción de software. Cuando la mayor
parte de la casa está terminada, los paisajistas, pintores y decoradores entran para aprovechar al
máximo su propiedad y la casa que ha construido. Esto es similar a la optimización de software.
Durante todo el proceso, varios inspectores vienen a revisar el sitio, los cimientos, el marco, el
cableado y otros elementos inspeccionables. Esto es similar a las revisiones e inspecciones de
software.
Figura 2-4Las estructuras más complicadas requieren una planificación más cuidadosa.
¿Qué otros paralelismos comparten las dos actividades? Al construir una casa, no intentará construir
cosas que pueda comprar ya construidas. Comprará una lavadora y una secadora, un lavaplatos, un
refrigerador y un congelador. A menos que sea un mago mecánico, no considerará construirlos usted
mismo. También comprará gabinetes, mostradores, ventanas, puertas y accesorios de baño
prefabricados. Si está construyendo un sistema de software, hará lo mismo. Hará un uso extensivo de
funciones de lenguaje de alto nivel en lugar de escribir su propio código de nivel de sistema
operativo. También puede usar bibliotecas preconstruidas de clases de contenedores, funciones
científicas, clases de interfaz de usuario y clases de manipulación de bases de datos. Por lo general,
no tiene sentido codificar cosas que puedes comprar listas para usar.
Sin embargo, si está construyendo una casa elegante con muebles de primera clase, es posible que tenga
gabinetes hechos a medida. Es posible que tenga un lavaplatos, un refrigerador y un congelador
incorporados para parecerse al resto de sus gabinetes. Es posible que tenga ventanas hechas a medida en
formas y tamaños inusuales. Esta personalización tiene paralelos en el desarrollo de software. Si está
creando un producto de software de primera clase, puede crear sus propias funciones científicas para
mejorar la velocidad o la precisión. Puede crear sus propias clases de contenedor, clases de interfaz de
usuario y clases de base de datos para darle a su sistema una apariencia y una sensación uniformes y
perfectamente consistentes.
Tanto la construcción de edificios como la construcción de software se benefician de los niveles adecuados
de planificación. Si construye el software en el orden incorrecto, será difícil de codificar, difícil de probar y
difícil de depurar. Puede llevar más tiempo completarlo o el proyecto puede desmoronarse porque el
trabajo de todos es demasiado complejo y, por lo tanto, demasiado confuso cuando se combina todo.
en. Un proyecto bien planificado mejora su capacidad de cambiar de opinión más tarde sobre los
detalles. Cuanta más experiencia tenga con el tipo de software que está creando, más detalles podrá
dar por sentado. Solo quiere asegurarse de planificar lo suficiente para que la falta de planificación
no cree problemas importantes más adelante.
La analogía de la construcción también ayuda a explicar por qué diferentes proyectos de software se
benefician de diferentes enfoques de desarrollo. En la construcción, usaría diferentes niveles de
planificación, diseño y garantía de calidad si está construyendo un almacén o un cobertizo de herramientas
que si está construyendo un centro médico o un reactor nuclear. Usaría enfoques aún diferentes para
construir una escuela, un rascacielos o una casa de tres habitaciones. Del mismo modo, en el software, por
lo general, puede utilizar enfoques de desarrollo ligeros y flexibles, pero a veces necesitará enfoques rígidos
y pesados para lograr objetivos de seguridad y otros objetivos.
Hacer cambios en el software trae otro paralelo con la construcción de edificios. Mover una
pared seis pulgadas cuesta más si la pared soporta carga que si es simplemente una división
entre habitaciones. De manera similar, hacer cambios estructurales en un programa cuesta
más que agregar o eliminar funciones periféricas.
Otras lecturasPara algunos La metáfora de la construcción de edificios podría extenderse en una variedad de otras direcciones,
buenos comentarios sobre
razón por la cual la metáfora es tan poderosa. Muchos términos comunes en el desarrollo de
ampliando la metáfora de la
construcción, consulte "¿Qué
software se derivan de la metáfora de la construcción: arquitectura de software, andamiaje,
sostiene el techo?" (Starr 2003). construcción, clases base y desgarramiento de código. Probablemente escuchará muchos más.
20 Capítulo 2: Metáforas para una mejor comprensión del desarrollo de software
Las personas que son efectivas en el desarrollo de software de alta calidad han pasado años
acumulando docenas de técnicas, trucos y conjuros mágicos. Las técnicas no son reglas; son
herramientas analíticas. Un buen artesano conoce la herramienta adecuada para el trabajo y sabe
PUNTO CLAVE
cómo usarla correctamente. Los programadores también lo hacen. Cuanto más aprenda sobre
programación, más llenará su caja de herramientas mental con herramientas analíticas y el
conocimiento de cuándo usarlas y cómo usarlas correctamente.
Referencia cruzadaPara obtener detalles En software, los consultores a veces le dicen que compre ciertos métodos de desarrollo de
sobre la selección y combinación de
software con exclusión de otros métodos. Eso es desafortunado porque si acepta una sola
métodos en el diseño, consulte la Sección
Combinando Metáforas
Debido a que las metáforas son heurísticas más que algorítmicas, no son mutuamente
excluyentes. Puede utilizar tanto la metáfora de la acumulación como la de la construcción.
Puede usar la escritura si lo desea, y puede combinar la escritura con conducir, cazar hombres
PUNTO CLAVE
lobo o ahogarse en un pozo de alquitrán con dinosaurios. Use cualquier metáfora o
combinación de metáforas que estimule su propio pensamiento o se comunique bien con otros
en su equipo.
El uso de metáforas es un asunto confuso. Debe ampliarlos para beneficiarse de los conocimientos
heurísticos que proporcionan. Pero si los extiende demasiado o en la dirección equivocada, lo engañarán.
Así como puedes hacer un mal uso de cualquier herramienta poderosa, puedes hacer un mal uso de las
metáforas, pero su poder las convierte en una parte valiosa de tu caja de herramientas intelectuales.
Recursos adicionales
cc2e.com/0285 Entre los libros generales sobre metáforas, modelos y paradigmas, el libro clave es de
Thomas Kuhn.
Kuhn, Thomas S.La estructura de las revoluciones científicas, 3d ed. Chicago, IL: The University of
Chicago Press, 1996. El libro de Kuhn sobre cómo surgen, evolucionan y sucumben las teorías
científicas ante otras teorías en un ciclo darwiniano puso patas arriba a la filosofía de la ciencia
cuando se publicó por primera vez en 1962. Es claro y En resumen, y está repleto de interesantes
ejemplos del auge y caída de metáforas, modelos y paradigmas en la ciencia.
Puntos clave
- Las metáforas son heurísticas, no algoritmos. Como tal, tienden a ser un poco descuidados.
- Pensar en las prácticas de desarrollo de software como herramientas en una caja de herramientas
intelectual sugiere además que cada programador tiene muchas herramientas y que ninguna herramienta
es adecuada para cada trabajo. Elegir la herramienta adecuada para cada problema es una clave para ser un
programador eficaz.
cc2e.com/0309 Contenido
- 3.6 Cantidad de tiempo para dedicar a los requisitos previos de Upstream: página 55
Temas relacionados
- Diseño: Capítulo 5
Antes de comenzar la construcción de una casa, un constructor revisa los planos, verifica que se hayan
obtenido todos los permisos y examina los cimientos de la casa. Un constructor se prepara para construir un
rascacielos de una manera, una urbanización de otra manera y una casa para perros de una tercera manera.
No importa cuál sea el proyecto, la preparación se adapta a las necesidades específicas del proyecto y se
realiza concienzudamente antes de que comience la construcción.
Este capítulo describe el trabajo que se debe realizar para preparar la construcción del
software. Al igual que con la construcción de edificios, gran parte del éxito o fracaso del
proyecto ya se ha determinado antes de que comience la construcción. Si los cimientos no se
han colocado bien o la planificación es inadecuada, lo mejor que puede hacer durante la
construcción es minimizar los daños.
El dicho del carpintero, "Mida dos veces, corte una vez" es muy relevante para la parte de
construcción del desarrollo de software, que puede representar hasta el 65 por ciento de los costos
totales del proyecto. Los peores proyectos de software terminan construyendo dos o
23
Traducido del inglés al español - www.onlinedoctranslator.com
24 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
tres veces o más. Hacer dos veces la parte más costosa del proyecto es una idea tan mala en
software como en cualquier otra línea de trabajo.
Aunque este capítulo sienta las bases para la construcción exitosa de software, no trata la
construcción directamente. Si se siente carnívoro o ya está bien versado en el ciclo de vida de la
ingeniería de software, busque la carne de la construcción que comienza en el Capítulo 5,
"Diseño en la construcción". Si no le gusta la idea de los requisitos previos para la construcción,
revise la Sección 3.2, "Determine el tipo de software en el que está trabajando", para ver cómo
se aplican los requisitos previos a su situación y luego eche un vistazo a los datos de la Sección
3.1. , que describe el costo de no cumplir con los requisitos previos.
software Si enfatiza la calidad al final de un proyecto, enfatiza las pruebas del sistema. La prueba es lo que mucha
Calidad." gente piensa cuando piensa en el aseguramiento de la calidad del software. Sin embargo, las pruebas son
solo una parte de una estrategia completa de control de calidad y no es la parte más influyente. Las pruebas
no pueden detectar una falla, como construir el producto incorrecto o construir el producto correcto de
manera incorrecta. Dichos defectos deben resolverse antes que en las pruebas, antes de que comience la
construcción.
PUNTO CLAVE Si enfatiza la calidad al comienzo del proyecto, planea, requiere y diseña un producto de
alta calidad. Si comienza el proceso con diseños para un Pontiac Aztek, puede probarlo
todo lo que quiera y nunca se convertirá en un Rolls-Royce. Puede construir el mejor
Aztek posible, pero si quiere un Rolls-Royce, debe planificar desde el principio para
construir uno. En el desarrollo de software, usted realiza dicha planificación cuando
define el problema, cuando especifica la solución y cuando diseña la solución.
La metodología utilizada Algunas personas han afirmado que las actividades previas, como la arquitectura, el diseño y la planificación de
debe basarse en la elección
proyectos, no son útiles en los proyectos de software modernos. En general, tales afirmaciones no están bien
de lo último y lo mejor, y no
en la ignorancia. Eso respaldadas por investigaciones, pasadas o presentes, o por datos actuales. (Consulte el resto de este capítulo para
también debe combinarse generosamente obtener más detalles). Quienes se oponen a los requisitos previos suelen mostrar ejemplos de requisitos previos que
con lo antiguo y confiable.
se han realizado de manera deficiente y luego señalan que dicho trabajo no es eficaz. Sin embargo, las actividades
—molinos harlan
preliminares se pueden hacer bien y los datos de la industria desde la década de 1970 hasta la actualidad indican
que los proyectos funcionarán mejor si se realizan las actividades de preparación adecuadas antes de que la
La preparación para la construcción no es una ciencia exacta, y el enfoque específico para la reducción de riesgos
debe decidirse proyecto por proyecto. Los detalles pueden variar mucho entre los proyectos. Para obtener más
información sobre esto, consulte la Sección 3.2.
Otras lecturasPara obtener una Una causa común de preparación incompleta es que los desarrolladores que están asignados para
descripción de un programa de
trabajar en las actividades preliminares no tienen la experiencia para llevar a cabo sus tareas. Las
desarrollo profesional que cultiva
estas habilidades, consulte el habilidades necesarias para planificar un proyecto, crear un caso de negocios convincente,
Capítulo 16 deDesarrollo de desarrollar requisitos integrales y precisos y crear arquitecturas de alta calidad están lejos de ser
software profesional
triviales, pero la mayoría de los desarrolladores no han recibido capacitación sobre cómo realizar
(Mc Connell 2004).
estas actividades. Cuando los desarrolladores no saben cómo hacer el trabajo upstream, la
recomendación de "hacer más trabajo upstream" suena como una tontería: si el trabajo no se está
haciendo bien en primer lugar, hacermásde que no será útil! Explicar cómo realizar estas actividades
cc2e.com/0316 está más allá del alcance de este libro, pero las secciones de "Recursos adicionales" al final de este
capítulo brindan numerosas opciones para obtener esa experiencia.
Algunos programadores saben cómo realizar actividades previas, pero no se preparan porque no
pueden resistir la tentación de comenzar a codificar lo antes posible. Si alimentas a tu
26 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
caballo en este abrevadero, tengo dos sugerencias. Sugerencia 1: Lea el argumento en la siguiente sección.
Puede que le diga algunas cosas en las que no ha pensado. Sugerencia 2: Presta atención a los problemas
que experimentas. Solo se necesitan unos pocos programas grandes para aprender que puede evitar
mucho estrés al planificar con anticipación. Deje que su propia experiencia sea su guía.
Una razón final por la que los programadores no se preparan es que los gerentes son notoriamente
antipáticos con los programadores que dedican tiempo a los requisitos previos de construcción.
Personas como Barry Boehm, Grady Booch y Karl Wiegers han estado golpeando los requisitos y los
tambores de diseño durante 25 años, y es de esperar que los gerentes hayan comenzado a
comprender que el desarrollo de software es más que codificar.
Otras lecturasPara muchas Sin embargo, hace unos años, estaba trabajando en un proyecto del Departamento de
variaciones entretenidas sobre
Defensa que se centraba en el desarrollo de requisitos cuando el general del ejército a
este tema, lea el clásico de
Gerald Weinberg,La psicología cargo del proyecto vino de visita. Le dijimos que estábamos desarrollando requisitos y
de la programación que principalmente estábamos hablando con nuestro cliente, capturando requisitos y
informática(Weinberg 1998).
delineando el diseño. Insistió en ver el código de todos modos. Le dijimos que no había
código, pero caminó alrededor de un área de trabajo de 100 personas, decidido a atrapar
a alguien programando. Frustrado por ver a tanta gente fuera de sus escritorios o
trabajando en los requisitos y el diseño, el hombre grande y redondo con la voz alta
finalmente señaló al ingeniero sentado a mi lado y gritó: “¿Qué está haciendo? ¡Debe
estar escribiendo código!” De hecho, el ingeniero estaba trabajando en una utilidad de
formateo de documentos, pero el general quería encontrar el código,
Este fenómeno se conoce como síndrome WISCA o WIMP: ¿Por qué Sam no codifica
nada? o ¿Por qué Mary no está programando?
Si el gerente de su proyecto se hace pasar por un general de brigada y le ordena que comience
a codificar de inmediato, es fácil decir: "¡Sí, señor!" (¿Cuál es el daño? El viejo debe saber de lo
que está hablando). Esta es una mala respuesta, y tiene varias alternativas mejores. Primero,
puede negarse rotundamente a trabajar en un orden ineficaz. Si sus relaciones con su jefe y su
cuenta bancaria son lo suficientemente saludables como para poder hacer esto, buena suerte.
Una segunda alternativa cuestionable es pretender estar codificando cuando no lo estás. Coloque
una lista de programas antiguos en la esquina de su escritorio. Luego siga adelante y desarrolle sus
requisitos y arquitectura, con o sin la aprobación de su jefe. Harás el proyecto más rápido y con
resultados de mayor calidad. Algunas personas encuentran este enfoque éticamente objetable, pero
desde la perspectiva de su jefe, la ignorancia será una bendición.
Tercero, puede educar a su jefe sobre los matices de los proyectos técnicos. Este es un buen enfoque
porque aumenta la cantidad de jefes ilustrados en el mundo. La siguiente subsección presenta una
justificación ampliada para tomarse el tiempo de hacer los requisitos previos antes de la
construcción.
3.1 Importancia de los requisitos previos 27
Finalmente, usted puede encontrar otro trabajo. A pesar de los altibajos económicos, los buenos
programadores siempre escasean (BLS 2002), y la vida es demasiado corta para trabajar en una
tienda de programación sin conocimientos cuando hay muchas alternativas mejores disponibles.
Argumento absolutamente convincente e infalible para cumplir con los requisitos previos
antes de la construcción
Parte de su trabajo como empleado técnico es educar a las personas no técnicas que lo rodean
sobre el proceso de desarrollo. Esta sección te ayudará a lidiar con gerentes y jefes que aún no
han visto la luz. Es un argumento extendido para cumplir con los requisitos y la arquitectura
PUNTO CLAVE
(obtener los aspectos críticos correctamente) antes de comenzar a codificar, probar y depurar.
Aprenda el argumento y luego siéntese con su jefe y tenga una conversación sincera sobre el
proceso de programación.
Apelar a la lógica
Una de las ideas clave en la programación efectiva es que la preparación es importante. Tiene sentido
que antes de comenzar a trabajar en un gran proyecto, debe planificar el proyecto. Los grandes
proyectos requieren más planificación; los proyectos pequeños requieren menos. Desde el punto de
vista de la gestión, la planificación significa determinar la cantidad de tiempo, la cantidad de personas
y la cantidad de computadoras que necesitará el proyecto. Desde un punto de vista técnico, la
planificación significa comprender lo que se quiere construir para no desperdiciar dinero
construyendo algo incorrecto. A veces, los usuarios no están del todo seguros de lo que quieren al
principio, por lo que descubrir lo que realmente quieren puede requerir más esfuerzo del que parece
ideal. Pero eso es más barato que construir algo incorrecto, tirarlo y empezar de nuevo.
Apelación a la analogía
Construir un sistema de software es como cualquier otro proyecto que requiere gente y
dinero. Si está construyendo una casa, hace dibujos arquitectónicos y planos antes de
comenzar a clavar clavos. Tendrás los planos revisados y aprobados antes de verter
concreto. Tener un plan técnico cuenta tanto en el software.
V413HAV
28 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Compare la cadena alimentaria del software con una cadena alimentaria real. En un entorno
ecológicamente sano, las gaviotas comen salmón fresco. Eso es nutritivo para ellos porque el salmón comió
arenque fresco y ellos, a su vez, comieron insectos de agua dulce. El resultado es una cadena alimentaria
saludable. En programación, si tiene alimentos saludables en cada etapa de la cadena alimenticia, el
resultado es un código saludable escrito por programadores felices.
En un ambiente contaminado, los insectos acuáticos han estado nadando en los desechos nucleares,
los arenques están contaminados con PCB y los salmones que comen los arenques nadaron a través
de los derrames de petróleo. Desafortunadamente, las gaviotas se encuentran al final de la cadena
alimenticia, por lo que no comen solo el aceite del salmón malo. También comen los PCB y los
desechos nucleares del arenque y las chinches de agua. En la programación, si sus requisitos están
contaminados, contaminan la arquitectura y la arquitectura, a su vez, contamina la construcción. Esto
conduce a programadores gruñones y desnutridos y software radiactivo y contaminado que está
plagado de defectos.
Si está planeando un proyecto altamente iterativo, deberá identificar los requisitos críticos y los
elementos arquitectónicos que se aplican a cada pieza que está construyendo antes de comenzar la
construcción. Un constructor que está construyendo una urbanización no necesita conocer cada
detalle de cada casa en la urbanización antes de comenzar la construcción de la primera casa. Pero el
constructor inspeccionará el sitio, trazará un mapa de las líneas eléctricas y de alcantarillado, y así
sucesivamente. Si el constructor no se prepara bien, la construcción puede retrasarse cuando sea
necesario excavar una línea de alcantarillado debajo de una casa que ya se ha construido.
Los estudios de los últimos 25 años han demostrado de manera concluyente que vale la pena hacer las
cosas bien la primera vez. Los cambios innecesarios son caros.
3.1 Importancia de los requisitos previos 29
En general, el principio es encontrar un error lo más cercano posible al momento en que se introdujo.
Cuanto más tiempo permanezca el defecto en la cadena alimenticia del software, más daño causará
más adelante en la cadena. Dado que los requisitos se realizan primero, los defectos de los requisitos
tienen el potencial de permanecer en el sistema por más tiempo y ser más costosos. Los defectos
insertados en el software aguas arriba también tienden a tener efectos más amplios que los
insertados aguas abajo. Eso también encarece los primeros defectos.
3
2
La tabla 3-1 muestra el costo relativo de corregir los defectos dependiendo de cuándo se
1
presentan y cuándo se encuentran.
DATOS DUROS
Tabla 3-1 Costo promedio de reparar defectos en función de cuándo se presentan y detectan
Tiempo detectado
Tiempo introducido Requisitos Arquitectura Construcción Prueba del sistema Posteriores a la liberación
Fuente: Adaptado de “Inspecciones de diseño y código para reducir errores en el desarrollo de programas” (Fagan 1976),Eliminación de
defectos de software (Dunn 1984), “Mejora de procesos de software en Hughes Aircraft” (Humphrey, Snyder y Willis 1991), “Cálculo del
retorno de la inversión a partir de una gestión de requisitos más eficaz” (Leffingwell 1997), “Despliegue generalizado de Hughes Aircraft de
un software de mejora continua Process” (Willis et al. 1998), “An Economic Release Decision Model: Insights into Software Project
Management” (Grady 1999), “Lo que hemos aprendido sobre la lucha contra los defectos” (Shull et al. 2002), yEquilibrar la agilidad y la
disciplina: una guía para perplejos(Boehm y Turner 2004).
Los datos de la tabla 3-1 muestran que, por ejemplo, un defecto de arquitectura que cuesta
$1000 reparar cuando se crea la arquitectura puede costar $15 000 durante la prueba del
sistema. La figura 3-1 ilustra el mismo fenómeno.
30 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Fase en la que se
Costo
introduce un defecto
Requisitos
Arquitectura
Construcción
Figura 3-1El costo de reparar un defecto aumenta dramáticamente a medida que aumenta el tiempo desde que se
presenta hasta que se detecta. Esto sigue siendo cierto ya sea que el proyecto sea altamente secuencial (haciendo el
100 por ciento de los requisitos y el diseño por adelantado) o altamente iterativo (haciendo el 5 por ciento de los
requisitos y el diseño por adelantado).
3
2
El proyecto promedio todavía ejerce la mayor parte de su esfuerzo de corrección de defectos en el lado
1
derecho de la Figura 3-1, lo que significa que la depuración y el retrabajo asociado toman alrededor del 50
DATOS DUROS
por ciento del tiempo empleado en un ciclo típico de desarrollo de software (Mills 1983; Boehm 1987a;
Cooper y Mullen 1993; Fishman 1996; Haley 1996; Wheeler, Brykczynski y Meeson 1996; Jones 1998; Shull et
al. 2002; Wiegers 2002). Docenas de empresas han descubierto que el simple hecho de centrarse en corregir
los defectos más temprano que tarde en un proyecto puede reducir los costos y los cronogramas de
desarrollo por dos o más factores (McConnell 2004). Este es un incentivo saludable para encontrar y
solucionar sus problemas lo antes posible.
Cuando crea que su jefe entiende la importancia de trabajar en los requisitos previos antes de pasar
a la construcción, pruebe la siguiente prueba para estar seguro.
- Será mejor que comencemos a codificar de inmediato porque vamos a tener que
depurar mucho.
- No hemos planeado mucho tiempo para las pruebas porque no vamos a encontrar muchos
defectos.
3.2 Determine el tipo de software en el que está trabajando 31
- Hemos investigado tanto los requisitos y el diseño que no puedo pensar en ningún problema importante
Si aún no está convencido de que los requisitos previos se aplican a su proyecto, la siguiente sección
lo ayudará a decidir.
Tabla 3-2 Buenas prácticas típicas para tres tipos comunes de proyectos de software
tipo de software
Misión crítica Incrustado
Sistemas de Negocios Sistemas Sistemas críticos para la vida
Sistema de nómina
Ciclo vital Desarrollo ágil entrega por etapas entrega por etapas
modelos (Programa Extremo-
Evolutivo Desarrollo en espiral
ming, Scrum, tiempo-
entrega Entrega evolutiva
desarrollo de cajas,
y así) Desarrollo en espiral
Evolutivo
creación de prototipos
32 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Tabla 3-2 Buenas prácticas típicas para tres tipos comunes de proyectos de software
tipo de software
Misión crítica Incrustado
Sistemas de Negocios Sistemas Sistemas críticos para la vida
control planificación
cambio formal
control Cambio riguroso
control
Requisitos requisito informal- Requisito semiformal Requisitos formales
Especificación de elementos Especificación de elementos especificación
En proyectos reales, encontrará infinitas variaciones sobre los tres temas presentados en esta tabla; sin
embargo, las generalidades de la tabla son esclarecedoras. Los proyectos de sistemas de negocios tienden a
beneficiarse de enfoques altamente iterativos, en los que la planificación, los requisitos,
3.2 Determine el tipo de software en el que está trabajando 33
y la arquitectura se intercalan con la construcción, las pruebas de sistemas y las actividades de control de
calidad. Los sistemas críticos para la vida tienden a requerir enfoques más secuenciales. La estabilidad de
los requisitos es parte de lo que se necesita para garantizar niveles ultra altos de confiabilidad.
Algunos escritores han afirmado que los proyectos que utilizan técnicas iterativas no
necesitan centrarse mucho en los requisitos previos, pero ese punto de vista está mal
informado. Los enfoques iterativos tienden a reducir el impacto del trabajo preliminar
inadecuado, pero no lo eliminan. Considere los ejemplos que se muestran en la Tabla 3-3
de proyectos que no se enfocan en los requisitos previos. Un proyecto se lleva a cabo
secuencialmente y se basa únicamente en pruebas para descubrir defectos; el otro se
lleva a cabo iterativamente y descubre defectos a medida que avanza. El primer enfoque
retrasa la mayor parte del trabajo de corrección de defectos hasta el final del proyecto, lo
que aumenta los costos, como se indica en la Tabla 3-1. El enfoque iterativo absorbe el
retrabajo poco a poco a lo largo del proyecto, lo que reduce el costo total. Los datos de
esta tabla y la siguiente son solo para fines ilustrativos,
El proyecto iterativo que abrevia o elimina requisitos previos diferirá en dos formas de un
proyecto secuencial que hace lo mismo. Primero, los costos promedio de corrección de
defectos serán más bajos porque los defectos tenderán a detectarse más cerca del momento
en que se insertaron en el software. Sin embargo, los defectos aún se detectarán tarde en cada
iteración, y corregirlos requerirá que se rediseñen, recodifiquen y vuelvan a probar partes del
software, lo que hace que el costo de la corrección de defectos sea más alto de lo necesario.
34 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
En segundo lugar, con los enfoques iterativos, los costos se absorberán poco a poco a lo
largo del proyecto, en lugar de agruparse al final. Cuando todo se asiente, el costo total
será similar pero no parecerá tan alto porque el precio se habrá pagado en pequeñas
cuotas a lo largo del proyecto, en lugar de pagarse todo de una vez al final.
Como ilustra la Tabla 3-4, centrarse en los requisitos previos puede reducir los costos
independientemente de si utiliza un enfoque iterativo o secuencial. Los enfoques iterativos suelen
ser una mejor opción por muchas razones, pero un enfoque iterativo que ignora los requisitos
previos puede terminar costando mucho más que un proyecto secuencial que presta mucha
atención a los requisitos previos.
Como sugiere la Tabla 3-4, la mayoría de los proyectos no son completamente secuenciales ni
completamente iterativos. No es práctico especificar el 100 por ciento de los requisitos o el diseño por
adelantado, pero la mayoría de los proyectos encuentran valor en la identificación temprana de al menos
PUNTO CLAVE
los requisitos más críticos y los elementos arquitectónicos.
Referencia cruzadaPara obtener detalles Una regla general común es planear especificar alrededor del 80 por ciento de los requisitos por
sobre cómo adaptar su enfoque de
adelantado, asignar tiempo para que se especifiquen requisitos adicionales más adelante y luego
desarrollo para programas de diferentes
Requisitos
Arquitectura
Diseño detallado
Construcción
Tiempo
Figura 3-2Las actividades se superpondrán hasta cierto punto en la mayoría de los proyectos, incluso en aquellos que son
muy secuenciales.
Requisitos
Arquitectura
Diseño detallado
Diseño detallado
Construcción
Tiempo
Figura 3-3En otros proyectos, las actividades se superpondrán durante la duración del proyecto. Una
clave para una construcción exitosa es comprender el grado en que se han completado los requisitos
previos y ajustar su enfoque en consecuencia.
- Es probable que el costo de cambiar los requisitos, el diseño y el código en sentido descendente sea
alto.
- Los requisitos no se entienden bien o espera que sean inestables por otras
razones.
- El diseño es complejo, desafiante o ambos.
- Es probable que el costo de cambiar los requisitos, el diseño y el código en sentido descendente sea bajo.
Siendo el software lo que es, los enfoques iterativos son mucho más útiles que los enfoques
secuenciales. Puede adaptar los requisitos previos a su proyecto específico haciéndolos más o menos
formales y más o menos completos, como mejor le parezca. Para una discusión detallada de los
diferentes enfoques de proyectos grandes y pequeños (también conocidos como los diferentes
enfoques de proyectos formales e informales), consulte el Capítulo 27.
El impacto neto en los requisitos previos de construcción es que primero debe determinar qué requisitos
previos de construcción se adaptan bien a su proyecto. Algunos proyectos dedican muy poco tiempo a los
requisitos previos, lo que expone a la construcción a una tasa innecesariamente alta de cambios
desestabilizadores e impide que el proyecto avance de manera constante. Algunos proyectos hacen
demasiado por adelantado; se adhieren obstinadamente a los requisitos y planes que han sido invalidados
por los descubrimientos aguas abajo, y que también pueden impedir el progreso durante la construcción.
Ahora que ha estudiado la Tabla 3-2 y ha determinado qué requisitos previos son apropiados para su
proyecto, el resto de este capítulo describe cómo determinar si cada requisito previo de construcción
específico ha sido "prerrequisito" o "previamente destruido".
Una definición de problema define cuál es el problema sin ninguna referencia a las posibles
soluciones. Es una declaración simple, tal vez una o dos páginas, y debería sonar como un problema.
La afirmación “No podemos cumplir con los pedidos del Gigatron” suena como un problema y es una
buena definición de problema. La declaración "Necesitamos optimizar nuestro sistema de entrada de
datos automatizado para mantenernos al día con los pedidos del Gigatron" es una mala definición del
problema. No suena como un problema; suena como una solución.
Como se muestra en la Figura 3-4, la definición del problema viene antes del trabajo de requisitos
detallados, que es una investigación más profunda del problema.
Futuro
Mejoras
Construcción
Arquitectura
Requisitos
Figura 3-4La definición del problema sienta las bases para el resto del proceso de
programación.
La definición del problema debe estar en el idioma del usuario y el problema debe describirse desde
el punto de vista del usuario. Por lo general, no debe establecerse en términos informáticos técnicos.
La mejor solución podría no ser un programa de computadora. Suponga que necesita un informe que
muestre su ganancia anual. Ya tiene informes computarizados que muestran las ganancias
trimestrales. Si está encerrado en la mentalidad de programador, razonará que agregar un informe
anual a un sistema que ya genera informes trimestrales debería ser fácil. Luego, le pagará a un
programador para que escriba y depure un programa que consume mucho tiempo y que calcula las
ganancias anuales. Si no está encerrado en la mentalidad de programador, le pagará a su secretaria
para que cree las cifras anuales tomándose un minuto para sumar las cifras trimestrales en una
calculadora de bolsillo.
La excepción a esta regla se aplica cuando el problema está en la computadora: los tiempos de compilación son
demasiado lentos o las herramientas de programación tienen errores. Entonces es apropiado plantear el problema
Como sugiere la figura 3-5, sin una buena definición del problema, es posible que se esfuerce por resolver
el problema equivocado.
38 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
La penalización por no definir el problema es que puede perder mucho tiempo resolviendo el
problema equivocado. Esta es una penalización doble porque tampoco resuelves el problema
correcto.
PUNTO CLAVE
Los requisitos explícitos también ayudan a evitar discusiones. Usted decide el alcance del
sistema antes de comenzar a programar. Si tiene un desacuerdo con otro programador
sobre lo que se supone que debe hacer el programa, puede resolverlo consultando los
requisitos escritos.
Prestar atención a los requisitos ayuda a minimizar los cambios en un sistema después de que
comienza el desarrollo. Si encuentra un error de codificación durante la codificación, cambia algunas
líneas de código y continúa el trabajo. Si encuentra un error de requisitos durante la codificación,
PUNTO CLAVE
debe modificar el diseño para cumplir con el requisito modificado. Es posible que deba desechar
parte del diseño anterior y, debido a que tiene que acomodar el código que ya está escrito, el nuevo
diseño llevará más tiempo del que hubiera tomado en primer lugar. También hay que descartar
3.4 Requisitos Prerrequisito 39
el código y los casos de prueba afectados por el cambio de requisitos y escribir nuevos códigos y casos de prueba.
Incluso el código que de otro modo no se ve afectado debe volver a probarse para que pueda estar seguro de que
los cambios en otras áreas no han introducido ningún error nuevo.
3
2
Como se informó en la Tabla 3-1, los datos de numerosas organizaciones indican que, en proyectos
1
grandes, un error en los requisitos detectado durante la etapa de arquitectura suele ser 3 veces más
DATOS DUROS
costoso de corregir que si se detectara durante la etapa de requisitos. Si se detecta durante la
codificación, es de 5 a 10 veces más costoso; durante la prueba del sistema, 10 veces; y posterior al
lanzamiento, entre 10 y 100 veces más costoso que si se detectara durante el desarrollo de los
requisitos. En proyectos más pequeños con costos administrativos más bajos, el multiplicador
posterior al lanzamiento está más cerca de 5–10 que de 100 (Boehm y Turner 2004). En cualquier
caso, no es dinero lo que le gustaría que le quitaran de su salario.
Especificar adecuadamente los requisitos es clave para el éxito del proyecto, quizás incluso
más importante que las técnicas de construcción efectivas. (Consulte la figura 3-6.) Se han
escrito muchos buenos libros sobre cómo especificar bien los requisitos. En consecuencia, las
próximas secciones no le dicen cómo hacer un buen trabajo al especificar los requisitos, le
dicen cómo determinar si los requisitos se han hecho bien y cómo aprovechar al máximo los
requisitos que tiene.
Figura 3-6Sin buenos requisitos, puede tener el problema general correcto pero no dar en el blanco
en aspectos específicos del problema.
—Anónimo pruebas de una manera ordenada, predecible y tranquila. ¡Este es el paraíso del
software! Tiene gastos predecibles y nunca tiene que preocuparse por una función que
cueste 100 veces más implementarla porque su usuario no pensó en ella hasta que
terminó de depurar.
40 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Está bien esperar que una vez que su cliente haya aceptado un documento de requisitos, no se
necesitarán cambios. Sin embargo, en un proyecto típico, el cliente no puede describir de
manera confiable lo que se necesita antes de escribir el código. El problema no es que los
clientes sean una forma de vida inferior. Así como cuanto más trabajas con el proyecto, mejor
lo entiendes, cuanto más trabajan con él, mejor lo entienden. El proceso de desarrollo ayuda a
los clientes a comprender mejor sus propias necesidades, y esta es una fuente importante de
cambios en los requisitos (Curtis, Krasner e Iscoe 1988; Jones 1998; Wiegers 2003). Un plan
para seguir estrictamente los requisitos es en realidad un plan para no responder a su cliente.
3
2
¿Cuánto cambio es típico? Los estudios en IBM y otras compañías han encontrado que el
1
proyecto promedio experimenta alrededor de un 25 por ciento de cambio en los requisitos
DATOS DUROS
durante el desarrollo (Boehm 1981, Jones 1994, Jones 2000), lo que representa del 70 al 85 por
ciento del retrabajo en un proyecto típico (Leffingwell 1997 , Wiegers 2003).
Tal vez pienses que el Pontiac Aztek fue el mejor automóvil que se haya fabricado, que pertenezcas a
la Sociedad de la Tierra Plana y hagas una peregrinación al lugar de aterrizaje extraterrestre en
Roswell, Nuevo México, cada cuatro años. Si lo hace, siga adelante y crea que los requisitos no
cambiarán en sus proyectos. Si, por el contrario, ha dejado de creer en Santa Claus y el hada de los
dientes, o al menos ha dejado de admitirlo, puede tomar varias medidas para minimizar el impacto
de los cambios en los requisitos.
PUNTO CLAVE Use la lista de verificación de requisitos al final de la sección para evaluar la calidad de sus
requisitosSi sus requisitos no son lo suficientemente buenos, detenga el trabajo, haga una copia de
seguridad y corrija antes de continuar. Claro, parece que te estás atrasando si dejas de codificar en
esta etapa. Pero si conduce de Chicago a Los Ángeles, ¿es una pérdida de tiempo detenerse y mirar
un mapa de carreteras cuando ve señales de Nueva York? No. Si no se dirige en la dirección correcta,
deténgase y verifique su curso.
Asegúrese de que todos conozcan el costo de los cambios en los requisitosLos clientes se
emocionan cuando piensan en una nueva característica. En su entusiasmo, su sangre se diluye y
corre hacia su bulbo raquídeo y se marean, olvidando todas las reuniones que tuvo para discutir los
requisitos, la ceremonia de firma y el documento de requisitos completo. La forma más fácil de
manejar a esas personas intoxicadas con características es decir: "Caramba, eso
3.4 Requisitos Prerrequisito 41
Suena como una gran idea. Dado que no está en el documento de requisitos, elaboraré un
cronograma revisado y una estimación de costos para que pueda decidir si desea hacerlo ahora o
más tarde”. Las palabras "horario" y "costo" son más aleccionadoras que el café y una ducha fría, y
muchos "imprescindibles" se convertirán rápidamente en "buenos para tener".
Si su organización no es sensible a la importancia de hacer los requisitos primero, señale que los cambios en
el momento de los requisitos son mucho más económicos que los cambios posteriores. Use el "Argumento
absolutamente convincente e infalible para cumplir con los requisitos previos antes de la construcción" de
este capítulo.
Referencia cruzadaPara obtener detalles Establezca un procedimiento de control de cambiosSi el entusiasmo de su cliente persiste,
sobre el manejo de cambios en el diseño y
considere establecer una junta de control de cambios formal para revisar dichos cambios
el código, consulte la Sección 28.2,
“Configuración
propuestos. Está bien que los clientes cambien de opinión y se den cuenta de que necesitan más
Administración." capacidades. El problema es que sugieren cambios con tanta frecuencia que no puedes seguirles el
ritmo. Tener un procedimiento incorporado para controlar los cambios hace felices a todos. Está
contento porque sabe que tendrá que trabajar con cambios solo en momentos específicos. Sus
clientes están contentos porque saben que tiene un plan para manejar su información.
Referencia cruzadaPara obtener Utilice enfoques de desarrollo que se adapten a los cambios.Algunos enfoques de desarrollo
detalles sobre los enfoques de
maximizan su capacidad para responder a los requisitos cambiantes. Un enfoque de creación de
desarrollo iterativo, consulte "Iterar"
Otras lecturasPara obtener Volcar el proyectoSi los requisitos son especialmente malos o volátiles y ninguna
detalles sobre los enfoques de
de las sugerencias anteriores funciona, cancele el proyecto. Incluso si realmente no
desarrollo que admiten requisitos
flexibles, consulteDesarrollo
puede cancelar el proyecto, piense en cómo sería cancelarlo. Piense en cuánto peor
rápido(McConnell 1996). tendría que ponerse antes de cancelarlo. Si hay un caso en el que lo desecharías, al
menos pregúntate cuánta diferencia hay entre tu caso y ese caso.
Referencia cruzadaPara obtener Esté atento al caso de negocios para el proyectoMuchos problemas de requisitos
detalles sobre las diferencias entre
desaparecen ante sus ojos cuando vuelve a referirse a la razón comercial para realizar el
proyectos formales e informales (a
- ¿Se especifican todos los formatos de salida para páginas web, informes, etc.?
- ¿Están especificadas todas las interfaces de comunicación externas, incluidos los protocolos de
- ¿Se especifican los datos utilizados en cada tarea y los datos resultantes de cada tarea?
Requisitos Calidad
- ¿Los requisitos están escritos en el idioma del usuario? ¿Los usuarios lo creen
así?
- ¿Cada elemento es relevante para el problema y su solución? ¿Se puede rastrear cada
elemento hasta su origen en el entorno del problema?
a través de 9.
Clements et al. 2003). La arquitectura también se conoce como "arquitectura del sistema", "diseño de
alto nivel" y "diseño de alto nivel". Por lo general, la arquitectura se describe en un solo documento
denominado "especificación de arquitectura" o "diseño de nivel superior". Algunas personas hacen
una distinción entre arquitectura y alto nivel.
44 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
diseño: la arquitectura se refiere a las restricciones de diseño que se aplican en todo el sistema, mientras que el
diseño de alto nivel se refiere a las restricciones de diseño que se aplican en el nivel de subsistema o clase múltiple,
pero no necesariamente en todo el sistema.
Debido a que este libro trata sobre la construcción, esta sección no le dice cómo desarrollar una
arquitectura de software; se centra en cómo determinar la calidad de una arquitectura existente. Sin
embargo, debido a que la arquitectura está un paso más cerca de la construcción que de los
requisitos, la discusión de la arquitectura es más detallada que la discusión de los requisitos.
¿Por qué tener la arquitectura como requisito previo? Porque la calidad de la arquitectura determina
la integridad conceptual del sistema. Eso a su vez determina la calidad final del sistema. Una
arquitectura bien pensada proporciona la estructura necesaria para mantener la integridad
PUNTO CLAVE
conceptual de un sistema desde los niveles superiores hasta los inferiores. Proporciona orientación a
los programadores, a un nivel de detalle apropiado para las habilidades de los programadores y para
el trabajo en cuestión. Divide el trabajo para que varios desarrolladores o varios equipos de
desarrollo puedan trabajar de forma independiente.
La buena arquitectura facilita la construcción. La mala arquitectura hace que la construcción sea
casi imposible. La figura 3-7 ilustra otro problema con una mala arquitectura.
Figura 3-7Sin una buena arquitectura de software, es posible que tenga el problema correcto pero la
solución incorrecta. Puede ser imposible tener una construcción exitosa.
3
2
Los cambios arquitectónicos son costosos de realizar durante la construcción o más adelante. El tiempo
1
necesario para corregir un error en una arquitectura de software es del mismo orden que el necesario para
DATOS DUROS
corregir un error de requisitos, es decir, más que el necesario para corregir un error de codificación (Basili y
Perricone 1984, Willis 1998). Los cambios de arquitectura son como cambios de requisitos en el sentido de
que los cambios aparentemente pequeños pueden tener un gran alcance. Ya sea que los cambios en la
arquitectura surjan de la necesidad de corregir errores o de realizar mejoras, cuanto antes pueda identificar
los cambios, mejor.
3.5 Prerrequisito de arquitectura 45
Si no puedes explicarle algo a Una arquitectura de sistema primero necesita una descripción general que describa el sistema en
un niño de seis años,
términos generales. Sin una visión general de este tipo, tendrá dificultades para construir una
realmente no lo entiendes tú
mismo. imagen coherente a partir de mil detalles o incluso una docena de clases individuales. Si el sistema
—Albert Einstein fuera un pequeño rompecabezas de 12 piezas, tu hijo de un año podría resolverlo entre cucharadas
de espárragos colados. Un rompecabezas de 12 subsistemas es más difícil de armar, y si no puede
armarlo, no entenderá cómo una clase que está desarrollando contribuye al sistema.
Referencia cruzadaPara obtener detalles La arquitectura debe definir los principales bloques de construcción de un programa. Dependiendo
sobre los bloques de construcción de
del tamaño del programa, cada bloque de construcción puede ser una sola clase o puede ser un
diferentes tamaños en el diseño, consulte
Referencia cruzadaMinimizar lo que cada Debe estar bien definido de qué es responsable cada bloque de construcción. Un bloque de
bloque de construcción sabe sobre otros
construcción debe tener un área de responsabilidad y debe saber lo menos posible sobre las áreas
bloques de construcción es una parte
Las reglas de comunicación para cada bloque de construcción deben estar bien definidas. La arquitectura
debe describir qué otros bloques de construcción puede usar directamente el bloque de construcción,
cuáles puede usar indirectamente y cuáles no debe usar en absoluto.
Clases principales
Referencia cruzadaPara obtener detalles La arquitectura debe especificar las clases principales que se utilizarán. Debe identificar las
sobre el diseño de clases, consulte el
responsabilidades de cada clase principal y cómo la clase interactuará con otras clases. Debe incluir
Capítulo 6, “Clases trabajadoras”.
descripciones de las jerarquías de clases, de las transiciones de estado y de la persistencia de los
objetos. Si el sistema es lo suficientemente grande, debe describir cómo se organizan las clases en
subsistemas.
La arquitectura debe describir otros diseños de clase que se consideraron y dar razones
para preferir la organización que se eligió. La arquitectura no necesita especificar cada
clase en el sistema. Apunte a la regla 80/20: especifique el 20 por ciento de las clases que
constituyen el 80 por ciento del comportamiento del sistema (Jacobsen, Booch y
Rumbaugh 1999; Kruchten 2000).
Diseño de datos
Referencia cruzadaPara obtener detalles La arquitectura debe describir los principales archivos y diseños de tablas que se utilizarán.
sobre cómo trabajar con variables,
Debe describir las alternativas que se consideraron y justificar las elecciones que se hicieron. Si
consulte los Capítulos 10 a 13.
la aplicación mantiene una lista de ID de clientes y los arquitectos han optado por representar
la lista de ID mediante una lista de acceso secuencial, el documento debe explicar por qué una
lista de acceso secuencial es mejor que una lista de acceso aleatorio, pila o hash. mesa.
Durante la construcción, dicha información le da una idea de las mentes de los arquitectos.
Durante el mantenimiento, la misma información es una ayuda invaluable. Sin él, estás viendo
una película extranjera sin subtítulos.
Normalmente, solo un subsistema o clase debe acceder directamente a los datos, excepto a través
de clases o rutinas de acceso que permiten el acceso a los datos de manera controlada y abstracta.
Esto se explica con más detalle en "Ocultar secretos (Ocultar información)" en la Sección 5.3.
más de 30 segundos desfasados. En ese caso, se debe describir el impacto que tiene la regla
en el enfoque de la arquitectura para mantener la información del cliente actualizada y
sincronizada.
La arquitectura debe modularizarse para que se pueda sustituir por una nueva interfaz de usuario sin
afectar las reglas comerciales y las partes de salida del programa. Por ejemplo, la arquitectura
debería hacer que sea bastante fácil cortar un grupo de clases de interfaz interactiva y conectar un
grupo de clases de línea de comandos. Esta capacidad suele ser útil, especialmente porque las
interfaces de línea de comandos son convenientes para las pruebas de software a nivel de unidad o
subsistema.
cc2e.com/0393 El diseño de interfaces de usuario merece su propia discusión, pero está fuera del
alcance de este libro.
Administracion de recursos
La arquitectura debe describir un plan para administrar recursos escasos, como conexiones de bases
de datos, subprocesos y identificadores. La gestión de la memoria es otra área importante que debe
tratar la arquitectura en áreas de aplicaciones con limitaciones de memoria, como el desarrollo de
controladores y los sistemas integrados. La arquitectura debe estimar los recursos utilizados para
casos nominales y extremos. En un caso simple, las estimaciones deberían mostrar que los recursos
necesarios están dentro de las capacidades del entorno de implementación previsto. En un caso más
complejo, es posible que se requiera que la aplicación administre de manera más activa sus propios
recursos. Si es así, el administrador de recursos debe diseñarse tan cuidadosamente como cualquier
otra parte del sistema.
cc2e.com/0330 Seguridad
Otras lecturasPara una excelente La arquitectura debe describir el enfoque de la seguridad a nivel de diseño y de código. Si no se ha creado
discusión sobre la seguridad del
previamente un modelo de amenazas, debe construirse en el momento de la arquitectura. Las pautas de
software, consulteEscribir código
Actuación
Otras lecturasPara obtener Si el rendimiento es una preocupación, los objetivos de rendimiento deben especificarse en los requisitos. Los
información adicional sobre el diseño
objetivos de rendimiento pueden incluir el uso de recursos, en cuyo caso los objetivos también deben especificar
de sistemas para el rendimiento,
Sistemas de software(1990). La arquitectura debe proporcionar estimaciones y explicar por qué los arquitectos creen que los
objetivos son alcanzables. Si ciertas áreas corren el riesgo de no cumplir sus objetivos, la arquitectura
debería decirlo. Si ciertas áreas requieren el uso de algoritmos o tipos de datos específicos para
cumplir con sus objetivos de rendimiento, la arquitectura debería decirlo. La arquitectura también
puede incluir presupuestos de espacio y tiempo para cada clase u objeto.
Escalabilidad
La escalabilidad es la capacidad de un sistema para crecer para satisfacer las demandas futuras. La
arquitectura debe describir cómo abordará el sistema el crecimiento de la cantidad de usuarios, la cantidad
de servidores, la cantidad de nodos de red, la cantidad de registros de la base de datos, el tamaño de los
registros de la base de datos, el volumen de transacciones, etc. Si no se espera que el sistema crezca y la
escalabilidad no es un problema, la arquitectura debe hacer explícita esa suposición.
interoperabilidad
Internacionalización/Localización
De entrada y salida
Procesamiento de errores
3
2
El procesamiento de errores se está convirtiendo en uno de los problemas más espinosos de la
1
informática moderna, y no puede permitirse el lujo de tratarlo al azar. Algunas personas han
DATOS DUROS
estimado que hasta el 90 por ciento del código de un programa se escribe para casos excepcionales
de procesamiento de errores o mantenimiento, lo que implica que solo el 10 por ciento se escribe
para casos nominales (Shaw en Bentley 1982). Con tanto código dedicado a manejar errores, se debe
explicar en detalle en la arquitectura una estrategia para manejarlos consistentemente.
El manejo de errores a menudo se trata como un problema de nivel de convención de codificación, si es que
se trata. Pero debido a que tiene implicaciones en todo el sistema, se trata mejor a nivel arquitectónico. Aquí
hay algunas preguntas a considerar:
- ¿La detección de errores es activa o pasiva? El sistema puede anticipar errores de forma activa, por ejemplo,
comprobando la validez de la entrada del usuario, o puede responder pasivamente a ellos solo cuando no
puede evitarlos, por ejemplo, cuando una combinación de entradas del usuario produce un desbordamiento
numérico. Puede despejar el camino o limpiar el desorden. Nuevamente, en cualquier caso, la elección tiene
- ¿Cómo propaga los errores el programa? Una vez que detecta un error, puede descartar
inmediatamente los datos que causaron el error, puede tratar el error como un error y entrar en un
estado de procesamiento de errores, o puede esperar hasta que se complete todo el procesamiento
y notificar al usuario que se detectaron errores. (en algún lugar).
Referencia cruzadaUn método - Dentro del programa, ¿a qué nivel se manejan los errores? Puede manejarlos en el
consistente para manejar parámetros
punto de detección, pasarlos a una clase de manejo de errores o pasarlos a la cadena de
incorrectos es otro aspecto de la
estrategia de procesamiento de
llamadas.
errores que debe abordarse
- ¿Cuál es el nivel de responsabilidad de cada clase para validar sus datos de entrada? ¿Cada
arquitectónicamente. Para ver
ejemplos, consulte el Capítulo 8, clase es responsable de validar sus propios datos o hay un grupo de clases responsables de
“Programación defensiva”. validar los datos del sistema? ¿Pueden las clases de cualquier nivel asumir que los datos que
reciben están limpios?
Tolerancia a fallos
Otras lecturasPara una buena La arquitectura también debe indicar el tipo de tolerancia a fallas que se espera. La tolerancia a fallas
introducción a la tolerancia a
es una colección de técnicas que aumentan la confiabilidad de un sistema al detectar errores,
errores, consulte la edición de
buena introducción, los artículos Por ejemplo, un sistema podría hacer que el cálculo de la raíz cuadrada de un número sea
citan muchos libros y artículos tolerante a fallas de varias maneras:
clave sobre el tema.
- El sistema puede hacer una copia de seguridad e intentarlo de nuevo cuando detecta una falla. Si la
primera respuesta es incorrecta, retrocedería hasta un punto en el que sabía que todo estaba bien y
continuaría desde allí.
- El sistema puede tener un código auxiliar para usar si detecta una falla en el código
principal. En el ejemplo, si la primera respuesta parece ser incorrecta, el sistema
cambia a una rutina de raíz cuadrada alternativa y la usa en su lugar.
- El sistema podría usar un algoritmo de votación. Puede tener tres clases de raíz
cuadrada y cada una usa un método diferente. Cada clase calcula la raíz cuadrada y
luego el sistema compara los resultados. Según el tipo de tolerancia a errores
integrado en el sistema, utiliza la media, la mediana o la moda de los tres
resultados.
- El sistema podría reemplazar el valor erróneo con un valor falso que sabe que tiene
un efecto benigno en el resto del sistema.
Otros enfoques de tolerancia a fallas incluyen hacer que el sistema cambie a un estado de
operación parcial oa un estado de funcionalidad degradada cuando detecta un error. Puede
apagarse o reiniciarse automáticamente. Estos ejemplos son necesariamente simplistas. La
tolerancia a fallas es un tema fascinante y complejo; desafortunadamente, está fuera del
alcance de este libro.
3.5 Prerrequisito de arquitectura 51
Factibilidad Arquitectónica
Los diseñadores pueden tener inquietudes sobre la capacidad de un sistema para cumplir con sus
objetivos de rendimiento, trabajar dentro de las limitaciones de recursos o contar con el soporte
adecuado de los entornos de implementación. La arquitectura debe demostrar que el sistema es
técnicamente factible. Si la inviabilidad en cualquier área pudiera hacer que el proyecto no funcione,
la arquitectura debe indicar cómo se han investigado esos problemas, a través de prototipos de
prueba de concepto, investigación u otros medios. Estos riesgos deben resolverse antes de que
comience la construcción a gran escala.
sobreingeniería
La robustez es la capacidad de un sistema para continuar funcionando después de detectar un error.
A menudo, una arquitectura especifica un sistema más robusto que el especificado por los requisitos.
Una razón es que un sistema compuesto por muchas partes que son mínimamente robustas puede
ser menos robusto de lo que se requiere en general. En software, la cadena no es tan fuerte como su
eslabón más débil; es tan débil como todos los eslabones débiles multiplicados juntos. La
arquitectura debe indicar claramente si los programadores deben errar por el lado de la ingeniería
excesiva o por el lado de hacer lo más simple que funcione.
Referencia cruzadaPara obtener una lista La solución más radical para construir software es no construirlo en absoluto, sino comprarlo o descargar software
de los tipos de bibliotecas y componentes
de código abierto de forma gratuita. Puede comprar controles de interfaz gráfica de usuario, administradores de
de software disponibles comercialmente,
de texto: la lista es casi interminable. Una de las mayores ventajas de la programación en entornos GUI modernos es
diálogo, controladores de teclado y mouse, código que funciona automáticamente con cualquier impresora o
monitor, etc.
Si la arquitectura no utiliza componentes listos para usar, debe explicar las formas en que
espera que los componentes personalizados superen las bibliotecas y los componentes listos
para usar.
52 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Decisiones de reutilización
Si el plan requiere el uso de software preexistente, casos de prueba, formatos de datos u otros
materiales, la arquitectura debe explicar cómo se hará que el software reutilizado se ajuste a
los otros objetivos arquitectónicos, si se hará para cumplir.
Cambio de estrategia
Referencia cruzadaPara obtener detalles Dado que la creación de un producto de software es un proceso de aprendizaje tanto para los
sobre el manejo de cambios de manera
programadores como para los usuarios, es probable que el producto cambie a lo largo de su desarrollo. Los
sistemática, consulte la Sección 28.2,
“Gestión de la configuración”.
cambios surgen de tipos de datos y formatos de archivo volátiles, cambios en la funcionalidad, nuevas
funciones, etc. Los cambios pueden ser nuevas capacidades que probablemente resulten de mejoras
planificadas, o pueden ser capacidades que no llegaron a la primera versión del sistema. En consecuencia,
uno de los principales desafíos que enfrenta un arquitecto de software es hacer que la arquitectura sea lo
suficientemente flexible para adaptarse a los posibles cambios.
Los errores de diseño a menudo son La arquitectura debe describir claramente una estrategia para manejar los cambios. La arquitectura
sutiles y ocurren por evolución, y las
debe mostrar que se han considerado posibles mejoras y que las mejoras más probables son
suposiciones iniciales se olvidan a
Referencia cruzadaPara obtener una La arquitectura debe indicar las estrategias que se utilizan para retrasar el compromiso. Por ejemplo,
explicación completa de la demora en el
la arquitectura puede especificar que se utilice una técnica basada en tablas en lugar de una técnica
compromiso, consulte "Elija el tiempo
Referencia cruzadaPara obtener más Una buena especificación de arquitectura se caracteriza por discusiones de las clases en
información sobre cómo interactúan
el sistema, de la información que está oculta en cada clase y de las razones para incluir y
los atributos de calidad, consulte la
Es posible que conozca formas en que se cambió la arquitectura durante su desarrollo. Cada
cambio debe encajar perfectamente con el concepto general. La arquitectura no debería parecerse
a un proyecto de ley de asignaciones del Congreso de los EE. UU. completo con cláusulas de
despilfarro para el distrito de origen de cada representante.
Los objetivos de la arquitectura deben estar claramente establecidos. Un diseño para un sistema con
un objetivo principal de modificabilidad será diferente de uno con un objetivo de desempeño sin
compromisos, incluso si ambos sistemas tienen la misma función.
La arquitectura debe describir las motivaciones de todas las decisiones importantes. Tenga
cuidado con las justificaciones de “siempre lo hemos hecho así”. Una historia cuenta que Beth
quería cocinar un estofado de acuerdo con una receta galardonada de estofado heredada en la
familia de su esposo. Su esposo, Abdul, dijo que su madre le había enseñado a espolvorearlo
con sal y pimienta, cortarle los dos extremos, ponerlo en la sartén, taparlo y cocinarlo. Beth
preguntó: "¿Por qué cortas ambos extremos?" Abdul dijo: “No lo sé. Siempre lo he hecho así.
Déjame preguntarle a mi madre. Él la llamó y ella dijo: “No lo sé. Siempre lo he hecho así.
Déjame preguntarle a tu abuela. Llamó a su abuela, quien le dijo: “No sé por qué lo haces así.
Lo hice de esa manera porque era demasiado grande para caber en mi sartén”.
La arquitectura debe identificar explícitamente las áreas de riesgo. Debe explicar por qué son
riesgosos y qué pasos se han tomado para minimizar el riesgo.
La arquitectura debe contener múltiples vistas. Los planos de una casa incluirán elevaciones, planos
de planta, planos de estructura, diagramas eléctricos y otras vistas de la casa. Las descripciones de la
arquitectura del software también se benefician al proporcionar diferentes vistas del sistema que
eliminan errores e inconsistencias y ayudan a los programadores a comprender completamente el
diseño del sistema (Kruchten 1995).
54 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Aquí hay una lista de problemas que una buena arquitectura debe abordar. La lista no pretende ser
una guía completa de la arquitectura, sino una forma pragmática de evaluar el contenido nutricional
de lo que obtiene al final de la cadena alimenticia del software del programador. Utilice esta lista de
verificación como punto de partida para su propia lista de verificación. Al igual que con la lista de
verificación de requisitos, si está trabajando en un proyecto informal, encontrará algunos elementos
en los que ni siquiera necesita pensar. Si está trabajando en un proyecto más grande, la mayoría de
los elementos serán útiles.
- ¿Están bien definidos los componentes básicos principales, incluidas sus áreas de
responsabilidad y sus interfaces con otros componentes básicos?
- ¿Todas las funciones enumeradas en los requisitos están cubiertas con sensatez, ni con
demasiados ni con pocos componentes básicos?
- ¿La interfaz de usuario está modularizada para que los cambios no afecten al resto del
programa?
- ¿Se describen y justifican las estimaciones de uso de recursos y una estrategia para la gestión
de recursos para recursos escasos como subprocesos, conexiones de base de datos,
identificadores, ancho de banda de red, etc.?
- ¿Alguna parte tiene una arquitectura excesiva o insuficiente? ¿Se establecen explícitamente las
expectativas en esta área?
en este capítulo.
tendrá que trabajar con un analista de requisitos para resolver los problemas de requisitos que
se identifican al principio de la construcción. Permita tiempo para consultar con el analista de
requisitos y para que el analista de requisitos revise los requisitos antes de tener una versión
viable de los requisitos.
Si los requisitos son inestables y está trabajando en un proyecto pequeño e informal, probablemente deba
resolver los problemas de requisitos usted mismo. Deje tiempo para definir los requisitos lo suficientemente
bien como para que su volatilidad tenga un impacto mínimo en la construcción.
56 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
Referencia cruzadaPara Si los requisitos son inestables en cualquier proyecto, formal o informal, trate el trabajo de
enfoques para el manejo
requisitos como su propio proyecto. Calcule el tiempo para el resto del proyecto una vez que
cambiar los requisitos, consulte
"Requisitos de manejo
haya terminado con los requisitos. Este es un enfoque sensato ya que nadie puede esperar
Cambios durante la construcción” en razonablemente que calcule su cronograma antes de saber lo que está construyendo. Es como
la Sección 3.4, anteriormente en este
si fueras un contratista llamado a trabajar en una casa. Su cliente dice: "¿Cuánto costará hacer
capítulo.
el trabajo?" Preguntas razonablemente: "¿Qué quieres que haga?" Su cliente dice: "No puedo
decirle, pero ¿cuánto costará?" Agradece razonablemente al cliente por hacerle perder el
tiempo y se va a casa.
Con un edificio, está claro que no es razonable que los clientes pidan una oferta antes de
decirte lo que vas a construir. Tus clientes no querrían que aparecieras con madera, martillo y
clavos y comenzaras a gastar su dinero antes de que el arquitecto haya terminado los planos.
Sin embargo, la gente tiende a entender menos el desarrollo de software de lo que entienden
de dos por cuatro y de yeso, por lo que es posible que los clientes con los que trabaja no
entiendan de inmediato por qué desea planificar el desarrollo de requisitos como un proyecto
separado. Es posible que deba explicarles su razonamiento.
Al asignar tiempo para la arquitectura de software, utilice un enfoque similar al utilizado para el
desarrollo de requisitos. Si el software es de un tipo con el que no ha trabajado antes, permita más
tiempo para la incertidumbre de diseñar en una nueva área. Asegúrese de que el tiempo que
necesita para crear una buena arquitectura no le quite el tiempo que necesita para un buen trabajo
en otras áreas. Si es necesario, planifique el trabajo de arquitectura también como un proyecto
separado.
Recursos adicionales
cc2e.com/0344 Los siguientes son más recursos sobre los requisitos:
cc2e.com/0351 Requisitos
Aquí hay algunos libros que brindan muchos más detalles sobre el desarrollo de requisitos:
Wiegers, Karl.Requisitos de Software, 2ª ed. Redmond, WA: Microsoft Press, 2003. Este es un
libro práctico centrado en profesionales que describe los elementos básicos de las actividades
de requisitos, incluida la obtención de requisitos, el análisis de requisitos, la especificación de
requisitos, la validación de requisitos y la gestión de requisitos.
Gil, Tom.Ingeniería Competitiva. Reading, MA: Addison-Wesley, 2004. Este libro describe el
lenguaje de requisitos de Gilb, conocido como “lenguaje P”. El libro cubre el enfoque
específico de Gilb para la ingeniería de requisitos, el diseño y la evaluación del diseño, y la
gestión evolutiva de proyectos. Este libro se puede descargar del sitio web de Gilb en
cc2e.com/0358 www.gilb.com.
Recursos adicionales 57
Abran, Alain, et al. Swebok: Guía para el cuerpo de conocimientos de ingeniería de software. Los
Alamitos, CA: IEEE Computer Society Press, 2001. Contiene una descripción detallada del conjunto de
conocimientos sobre requisitos de software. También se puede descargar desde www.swebok.org.
cc2e.com/0365
En los últimos años se han publicado numerosos libros sobre arquitectura de software. Aquí están
algunos de los mejores:
Clemente, Paul, ed.Documentación de arquitecturas de software: vistas y más allá. Boston, MA:
Addison-Wesley, 2003.
Hay muchos libros disponibles que trazan diferentes enfoques para llevar a cabo un proyecto de
software. Algunos son más secuenciales y otros son más iterativos.
Kruchten, Philippe.El proceso unificado racional: una introducción, 2ª ed. Reading, MA: Addison-
Wesley, 2000. Este libro presenta un enfoque de proyecto que está “centrado en la arquitectura y
dirigido por casos de uso”. Me gustaGuía de supervivencia de proyectos de software, se centra en el
trabajo inicial que proporciona una buena previsibilidad a largo plazo de los costos y los
cronogramas, alta calidad y flexibilidad moderada. El enfoque de este libro requiere un uso algo más
sofisticado que los enfoques descritos enGuía de supervivencia de proyectos de softwareyExplicación
de la programación extrema: aceptar el cambio.
Jacobson, Ivar, Grady Booch y James Rumbaugh.El proceso de desarrollo de software unificado
. Reading, MA: Addison-Wesley, 1999. Este libro es un tratamiento más profundo de los temas
tratados enEl proceso unificado racional: una introducción, 2ª ed.
McConnell, Steve.Desarrollo rápido. Redmond, WA: Microsoft Press, 1996. Este libro presenta un enfoque de caja de
herramientas para la planificación de proyectos. Un planificador de proyectos experimentado puede utilizar las
herramientas presentadas en este libro para crear un plan de proyecto que se adapte en gran medida a las
necesidades únicas de un proyecto.
Boehm, Barry y Richard Turner.Equilibrar la agilidad y la disciplina: una guía para perplejos.
Boston, MA: Addison-Wesley, 2003. Este libro explora el contraste entre el desarrollo ágil y los
estilos de desarrollo basados en planes. El capítulo 3 tiene cuatro especialmente
Puntos clave 59
secciones reveladoras: "Un día típico usando PSP/TSP", "Un día típico usando programación extrema", "Un
día de crisis usando PSP/TSP" y "Un día de crisis usando programación extrema". El Capítulo 5 trata sobre el
uso del riesgo para equilibrar la agilidad, lo que proporciona una guía incisiva para seleccionar entre
métodos ágiles y basados en planes. El capítulo 6, “Conclusiones”, también está bien equilibrado y brinda
una gran perspectiva. El Apéndice E es una mina de oro de datos empíricos sobre prácticas ágiles.
Larman, Craig.Desarrollo ágil e iterativo: una guía para gerentes. Boston, MA: Addison Wesley, 2004.
Esta es una introducción bien investigada a los estilos de desarrollo flexibles y evolutivos. Ofrece una
descripción general de Scrum, la programación extrema, el proceso unificado y Evo.
- ¿Están los requisitos lo suficientemente bien definidos y estables para comenzar la construcción?
- ¿La arquitectura está lo suficientemente bien definida para comenzar la construcción? (Consulte la lista de verificación
- ¿Se han abordado otros riesgos exclusivos de su proyecto en particular, de modo que
la construcción no esté expuesta a más riesgos de los necesarios?
Puntos clave
- El objetivo general de la preparación para la construcción es la reducción de riesgos. Asegúrese de que
- Si desea desarrollar software de alta calidad, la atención a la calidad debe ser parte del
proceso de desarrollo de software desde el principio hasta el final. La atención a la
calidad al principio tiene una mayor influencia en la calidad del producto que la atención
al final.
- Parte del trabajo de un programador es educar a los jefes y compañeros de trabajo sobre el
proceso de desarrollo de software, incluida la importancia de una preparación adecuada antes de
que comience la programación.
- El tipo de proyecto en el que está trabajando afecta significativamente los requisitos previos de
construcción: muchos proyectos deben ser altamente iterativos y algunos deben ser más
secuenciales.
- Si no se ha especificado una buena definición del problema, es posible que esté resolviendo el
problema incorrecto durante la construcción.
60 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba
- Si no se ha realizado un buen diseño arquitectónico, es posible que esté resolviendo el problema correcto de
manera incorrecta durante la construcción. El costo de los cambios arquitectónicos aumenta a medida que se
escribe más código para la arquitectura incorrecta, así que asegúrese de que la arquitectura también sea la
correcta.
Temas relacionados
Una vez que esté seguro de que se han establecido las bases adecuadas para la construcción, la preparación
se dirige hacia decisiones más específicas de construcción. El Capítulo 3, "Mida dos veces, corte una vez:
requisitos previos de la parte superior", analizó el software equivalente a los planos y los permisos de
construcción. Es posible que no haya tenido mucho control sobre esos preparativos, por lo que el enfoque
de ese capítulo fue evaluar con qué tiene que trabajar cuando comience la construcción. Este capítulo se
centra en los preparativos de los que los programadores individuales y los líderes técnicos son responsables,
directa o indirectamente. Analiza el equivalente de software de cómo seleccionar herramientas específicas
para su cinturón de herramientas y cómo cargar su camión antes de dirigirse al lugar de trabajo.
Si cree que ya ha leído lo suficiente sobre los preparativos para la construcción, puede pasar
directamente al Capítulo 5, "Diseño en la construcción".
61
62 Capítulo 4: Decisiones clave de construcción
de Europa Occidental podría realizar la operación de división para los números más
grandes. Este hecho le habría parecido una pura imposibilidad... Nuestro moderno
poder de cómputo fácil con fracciones decimales es el resultado casi milagroso del
descubrimiento gradual de una notación perfecta.
Los estudios han demostrado que la elección del lenguaje de programación afecta la productividad y la calidad del
Los programadores son más productivos usando un lenguaje familiar que uno desconocido. Los
datos del modelo de estimación Cocomo II muestran que los programadores que trabajan en un
lenguaje que han utilizado durante tres años o más son un 30 por ciento más productivos que los
programadores con experiencia equivalente que son nuevos en un lenguaje (Boehm et al. 2000). Un
estudio anterior de IBM encontró que los programadores que tenían una amplia experiencia con un
lenguaje de programación eran tres veces más productivos que aquellos con una experiencia mínima
(Walston y Felix 1977). (Cocomo II es más cuidadoso en aislar los efectos de factores individuales, lo
que explica los diferentes resultados de los dos estudios).
3
2
Los programadores que trabajan con lenguajes de alto nivel logran una mejor productividad y
1
calidad que aquellos que trabajan con lenguajes de bajo nivel. Se ha atribuido a lenguajes como C++,
DATOS DUROS
Java, Smalltalk y Visual Basic la mejora de la productividad, la confiabilidad, la simplicidad y la
comprensibilidad en factores de 5 a 15 sobre lenguajes de bajo nivel como ensamblador y C (Brooks
1987, Jones 1998, Boehm 2000 ). Ahorra tiempo cuando no necesita tener una ceremonia de
premiación cada vez que una declaración C hace lo que se supone que debe hacer. Además, los
lenguajes de nivel superior son más expresivos que los de nivel inferior. Cada línea de código dice
más. La tabla 4-1 muestra las proporciones típicas de declaraciones fuente en varios lenguajes de
alto nivel con respecto al código equivalente en C. Una proporción más alta significa que cada línea
de código en el lenguaje enumerado logra más que cada línea de código en C.
Algunos lenguajes son mejores para expresar conceptos de programación que otros. Puede
establecer un paralelismo entre lenguajes naturales como el inglés y lenguajes de
programación como Java y C++. En el caso de las lenguas naturales, los lingüistas Sapir y Whorf
plantean la hipótesis de una relación entre el poder expresivo de una lengua y la capacidad de
pensar determinados pensamientos. La hipótesis de Sapir-Whorf dice que tu capacidad para
pensar un pensamiento depende de conocer palabras capaces de expresar el pensamiento. Si
no conoce las palabras, no puede expresar el pensamiento y es posible que ni siquiera pueda
formularlo (Whorf 1956).
Los programadores pueden verse influenciados de manera similar por sus lenguajes. Las palabras
disponibles en un lenguaje de programación para expresar sus pensamientos de programación ciertamente
determinan cómo expresa sus pensamientos e incluso podrían determinar qué pensamientos puede
expresar.
Descripciones de lenguaje
Las historias de desarrollo de algunos lenguajes son interesantes, al igual que sus capacidades
generales. Aquí hay descripciones de los idiomas más comunes en uso hoy en día.
ada
Ada es un lenguaje de programación de alto nivel y propósito general basado en Pascal. Fue
desarrollado bajo los auspicios del Departamento de Defensa y es especialmente adecuado
para sistemas integrados y en tiempo real. Ada enfatiza la abstracción de datos y la ocultación
de información y lo obliga a diferenciar entre las partes públicas y privadas de cada clase y
paquete. “Ada” fue elegido como el nombre del lenguaje en honor a Ada Lovelace, una
matemática que se considera la primera programadora del mundo. Hoy en día, Ada se utiliza
principalmente en sistemas militares, espaciales y de aviónica.
lenguaje ensamblador
El lenguaje ensamblador, o "ensamblador", es un tipo de lenguaje de bajo nivel en el que cada declaración
corresponde a una sola instrucción de máquina. Debido a que las declaraciones usan instrucciones de
máquina específicas, un lenguaje ensamblador es específico para un procesador en particular, por ejemplo,
CPU específicas de Intel o Motorola. Ensamblador es considerado como el lenguaje de segunda generación.
La mayoría de los programadores lo evitan a menos que estén superando los límites de la velocidad de
ejecución o el tamaño del código.
64 Capítulo 4: Decisiones clave de construcción
C
C es un lenguaje de propósito general de nivel medio que se asoció originalmente con el sistema operativo
UNIX. C tiene algunas características de lenguaje de alto nivel, como datos estructurados, flujo de control
estructurado, independencia de la máquina y un amplio conjunto de operadores. También se le ha llamado
un "lenguaje ensamblador portátil" porque hace un uso extensivo de punteros y direcciones, tiene algunas
construcciones de bajo nivel, como la manipulación de bits, y tiene tipos débiles.
C fue desarrollado en la década de 1970 en Bell Labs. Originalmente fue diseñado y utilizado
en el DEC PDP-11, cuyo sistema operativo, compilador C y programas de aplicación UNIX
fueron escritos en C. En 1988, se emitió un estándar ANSI para codificar C, que fue revisado en
1999. C fue el estándar de facto para la programación de microcomputadoras y estaciones de
trabajo en las décadas de 1980 y 1990.
C++
C++, un lenguaje orientado a objetos basado en C, fue desarrollado en Bell Laboratories en la
década de 1980. Además de ser compatible con C, C++ proporciona clases, polimorfismo,
manejo de excepciones, plantillas y proporciona una verificación de tipos más robusta que C.
También proporciona una biblioteca estándar amplia y potente.
C#
C# es un entorno de programación y un lenguaje orientado a objetos de propósito general
desarrollado por Microsoft con una sintaxis similar a C, C++ y Java, y proporciona amplias
herramientas que ayudan al desarrollo en las plataformas de Microsoft.
Cobol
Cobol es un lenguaje de programación similar al inglés que se desarrolló originalmente en
1959-1961 para que lo usara el Departamento de Defensa. Cobol se usa principalmente para
aplicaciones comerciales y sigue siendo uno de los lenguajes más utilizados en la actualidad, solo
superado por Visual Basic en popularidad (Feiman y Driver 2002). Cobol se ha actualizado a lo largo
de los años para incluir funciones matemáticas y capacidades orientadas a objetos. El acrónimo
“Cobol” significa lenguaje común orientado a los negocios.
Fortran
Fortran fue el primer lenguaje informático de alto nivel, que introdujo las ideas de variables y bucles
de alto nivel. “Fortran” significa TRADUCCIÓN DE FÓRMULAS. Fortran se desarrolló originalmente en
la década de 1950 y ha visto varias revisiones importantes, incluido Fortran 77 en 1977, que agregó
instrucciones if-then-else estructuradas en bloques y manipulaciones de cadenas de caracteres.
Fortran 90 agregó tipos de datos definidos por el usuario, punteros, clases y un amplio conjunto de
operaciones en matrices. Fortran se utiliza principalmente en aplicaciones científicas y de ingeniería.
4.1 Elección del lenguaje de programación sesenta y cinco
Java
Java es un lenguaje orientado a objetos con una sintaxis similar a C y C++ que fue desarrollado por
Sun Microsystems, Inc. Java fue diseñado para ejecutarse en cualquier plataforma al convertir el
código fuente de Java en código de bytes, que luego se ejecuta en cada plataforma dentro de un
entorno. conocida como máquina virtual. Java tiene un uso generalizado para programar aplicaciones
web.
JavaScript
Perl
Perl es un lenguaje de manejo de cadenas que se basa en C y varias utilidades de UNIX. Perl se utiliza a
menudo para tareas de administración del sistema, como la creación de scripts de compilación, así como
para la generación y el procesamiento de informes. También se utiliza para crear aplicaciones web como
Slashdot. El acrónimo “Perl” significa Lenguaje práctico de extracción e informes.
PHP
PHP es un lenguaje de secuencias de comandos de código abierto con una sintaxis simple similar a Perl,
Bourne Shell, JavaScript y C. PHP se ejecuta en todos los principales sistemas operativos para ejecutar
funciones interactivas en el servidor. Se puede incrustar en páginas web para acceder y presentar
información de la base de datos. El acrónimo "PHP" originalmente significaba Página de inicio personal,
pero ahora significa PHP: Procesador de hipertexto.
Pitón
Python es un lenguaje interpretado, interactivo y orientado a objetos que se ejecuta en numerosos
entornos. Se usa más comúnmente para escribir scripts y pequeñas aplicaciones web y también
contiene algo de soporte para crear programas más grandes.
sql
SQL es el lenguaje estándar de facto para consultar, actualizar y administrar bases de datos
relacionales. "SQL" significa lenguaje de consulta estructurado. A diferencia de otros lenguajes
enumerados en esta sección, SQL es un "lenguaje declarativo", lo que significa que no define una
secuencia de operaciones, sino el resultado de algunas operaciones.
básico visual
La versión original de Basic era un lenguaje de alto nivel desarrollado en Dartmouth College en
la década de 1960. El acrónimo BASIC significa Beginner's All-purpose Symbolic
66 Capítulo 4: Decisiones clave de construcción
Código de Instrucción. Visual Basic es una versión de programación visual de alto nivel, orientada a
objetos, de Basic desarrollada por Microsoft que fue diseñada originalmente para crear aplicaciones
de Microsoft Windows. Desde entonces, se ha ampliado para admitir la personalización de
aplicaciones de escritorio como Microsoft Office, la creación de programas web y otras aplicaciones.
Los expertos informan que a principios de la década de 2000, más desarrolladores profesionales
trabajaban en Visual Basic que en cualquier otro lenguaje (Feiman y Driver 2002).
11.3 a 11.5.
la arquitectura que la guía y consistente internamente. Ese es el punto de las pautas de
construcción para nombres de variables, nombres de clases, nombres de rutinas,
convenciones de formato y convenciones de comentarios.
¿Qué pasaría si tuviera un gran diseño para una pintura, pero una parte fuera clásica, una
impresionista y una cubista? No tendría integridad conceptual sin importar qué tan de cerca
siguieras su gran diseño. Se vería como un collage. Un programa también necesita integridad
de bajo nivel.
Antes de que comience la construcción, explique las convenciones de programación que usará. Los
detalles de la convención de codificación tienen tal nivel de precisión que es casi imposible
adaptarlos al software una vez escrito. Los detalles de tales convenciones se proporcionan a lo largo
PUNTO CLAVE
del libro.
esta nueva tecnología estará en ascenso, y la programación web tal como la conozco hoy
(2004) estará a punto de desaparecer. Estos ciclos de tecnología, u ondas, implican diferentes
prácticas de programación dependiendo de dónde te encuentres en la onda.
En entornos tecnológicos maduros, el final de la ola, como la programación web a mediados de la década de
2000, nos beneficiamos de una rica infraestructura de desarrollo de software. Los entornos de Latewave
brindan numerosas opciones de lenguajes de programación, verificación integral de errores para el código
escrito en esos lenguajes, potentes herramientas de depuración y optimización automática y confiable del
rendimiento. Los compiladores están casi libres de errores. Las herramientas están bien documentadas en la
literatura de proveedores, en libros y artículos de terceros y en extensos recursos web. Las herramientas
están integradas, por lo que puede crear UI, bases de datos, informes y lógica comercial desde un solo
entorno. Si tiene problemas, puede encontrar fácilmente las peculiaridades de las herramientas descritas en
las Preguntas frecuentes. Muchos consultores y clases de capacitación también están disponibles.
En entornos de ola temprana (programación web a mediados de la década de 1990, por ejemplo), la
situación es la opuesta. Hay pocas opciones de lenguajes de programación disponibles, y esos lenguajes
tienden a tener errores y estar mal documentados. Los programadores dedican una gran cantidad de
tiempo simplemente a tratar de descubrir cómo funciona el lenguaje en lugar de escribir código nuevo. Los
programadores también dedican incontables horas a solucionar errores en los productos de lenguaje, el
sistema operativo subyacente y otras herramientas. Las herramientas de programación en entornos de onda
temprana tienden a ser primitivas. Es posible que los depuradores no existan en absoluto, y los
optimizadores del compilador siguen siendo solo un destello en el ojo de algunos programadores. Los
proveedores revisan su versión del compilador con frecuencia y parece que cada nueva versión rompe
partes significativas de su código. Las herramientas no están integradas, por lo que tiende a trabajar con
diferentes herramientas para la interfaz de usuario, la base de datos, los informes, y lógica empresarial. Las
herramientas tienden a no ser muy compatibles, y puede gastar una cantidad significativa de esfuerzo solo
para mantener la funcionalidad existente trabajando contra la avalancha de versiones de bibliotecas y
compiladores. Si tiene problemas, existe literatura de referencia en la Web de alguna forma, pero no
siempre es confiable y, si la literatura disponible sirve de guía, cada vez que encuentre un problema,
parecerá que es el primero en solucionarlo. hazlo
Estos comentarios pueden parecer una recomendación para evitar la programación de onda
temprana, pero esa no es su intención. Algunas de las aplicaciones más innovadoras surgen de los
primeros programas, como Turbo Pascal, Lotus 123, Microsoft Word y el navegador Mosaic. El punto
es que la forma en que pase sus días de programación dependerá de dónde se encuentre en la ola
tecnológica. Si se encuentra en la última parte de la ola, puede planear pasar la mayor parte del día
constantemente escribiendo nuevas funciones. Si está en la primera parte de la ola, puede suponer
que dedicará una parte considerable de su tiempo a tratar de descubrir las características no
documentadas de su lenguaje de programación, depurando errores que resultan ser defectos en el
código de la biblioteca, revisando código para que funcione con una nueva versión de la biblioteca de
algún proveedor, y así sucesivamente.
Cuando se encuentre trabajando en un entorno primitivo, tenga en cuenta que las prácticas de
programación descritas en este libro pueden ayudarlo aún más que en entornos maduros.
68 Capítulo 4: Decisiones clave de construcción
entornos. Como señaló David Gries, sus herramientas de programación no tienen que determinar
cómo piensa acerca de la programación (1981). Gries hace una distinción entre la programaciónenun
lenguaje vs programacióndentroun idioma. Los programadores que programan "en" un lenguaje
limitan sus pensamientos a construcciones que el lenguaje soporta directamente. Si las
herramientas del lenguaje son primitivas, los pensamientos del programador también lo serán.
Los programadores que programan "en" un lenguaje primero deciden qué pensamientos quieren
expresar y luego determinan cómo expresar esos pensamientos usando las herramientas provistas
por su lenguaje específico.
En consecuencia, adopté una convención de diseño que permitía que el archivo .frm (el archivo de
formulario) solo recuperara datos de la base de datos y los almacenara nuevamente en la base de
datos. No estaba permitido comunicar esos datos directamente a otras partes del programa. Cada
formulario admitía una rutina IsFormCompleted(), que la rutina de llamada utilizaba para determinar
si el formulario que se había activado había guardado sus datos. IsFormCompleted() era la única
rutina pública que los formularios podían tener. Los formularios tampoco podían contener ninguna
lógica comercial. El resto del código tenía que estar contenido en un archivo .bas asociado, incluidas
las comprobaciones de validez de las entradas en el formulario.
Visual Basic no fomentaba este tipo de enfoque. Animó a los programadores a poner
tanto código en el archivo .frm como fuera posible, y no facilitó que el archivo .frm
volviera a llamar a un archivo .bas asociado.
Esta convención era bastante simple, pero a medida que profundizaba en mi proyecto, descubrí que
me ayudaba a evitar numerosos casos en los que habría estado escribiendo código complicado sin la
convención. Habría estado cargando formularios pero manteniéndolos ocultos para poder llamar a
las rutinas de verificación de validez de datos dentro de ellos, o habría estado copiando el código de
los formularios en otras ubicaciones y luego manteniendo el código paralelo en varios lugares. La
convención IsFormCompleted() también simplificó las cosas. Debido a que todos los formularios
funcionaban exactamente de la misma manera, nunca tuve que cuestionar la semántica de
IsFormCompleted(); significaba lo mismo cada vez que se usaba.
4.4 Selección de las principales prácticas de construcción 69
Visual Basic no admitía esta convención directamente, pero mi uso de una convención de
programación simple: programacióndentroel idioma: compensó la falta de estructura del
idioma en ese momento y ayudó a mantener el proyecto intelectualmente manejable.
La siguiente lista de verificación resume las prácticas específicas que debe decidir
conscientemente incluir o excluir durante la construcción. Los detalles de estas prácticas se
encuentran a lo largo del libro.
Codificación
Trabajo en equipo
“Herramientas de programación”.
- ¿Ha seleccionado una herramienta de control de revisiones?
- ¿Ha seleccionado un idioma y una versión de idioma o una versión del compilador?
Puntos clave
- Cada lenguaje de programación tiene fortalezas y debilidades. Sea consciente de las
fortalezas y debilidades específicas del lenguaje que está usando.
- Existen más prácticas de construcción de las que puede usar en un solo proyecto. Elija
conscientemente las prácticas que mejor se adapten a su proyecto.
- Pregúntese si las prácticas de programación que está utilizando son una respuesta al
lenguaje de programación que está utilizando o controlado por él. Recuerda programar
dentroel lenguaje, en lugar de la programacióneneso.
En esta parte:
Diseño en Construcción
cc2e.com/0578 Contenido
Temas relacionados
- Refactorización: Capítulo 24
Algunas personas podrían argumentar que el diseño no es realmente una actividad de construcción, pero en
proyectos pequeños, muchas actividades se consideran construcción, a menudo incluido el diseño. En algunos
proyectos más grandes, una arquitectura formal podría abordar solo los problemas a nivel del sistema y podría
dejarse intencionalmente mucho trabajo de diseño para la construcción. En otros proyectos grandes, el diseño
puede tener la intención de ser lo suficientemente detallado para que la codificación sea bastante mecánica, pero el
diseño rara vez es tan completo: el programador generalmente diseña parte del programa, oficialmente o de otra
manera.
Referencia cruzadaPara obtener detalles En proyectos pequeños e informales, se hace mucho diseño mientras el programador se sienta frente al
sobre los diferentes niveles de formalidad
teclado. El "diseño" podría ser simplemente escribir una interfaz de clase en pseudocódigo antes de escribir
requeridos en proyectos grandes y
El diseño es un tema enorme, por lo que en este capítulo solo se consideran algunos aspectos. Una gran
parte del buen diseño de clases o rutinas está determinada por la arquitectura del sistema, así que
73
74 Capítulo 5: Diseño en la Construcción
Asegúrese de que se haya satisfecho el requisito previo de arquitectura discutido en la Sección 3.5.
Se realiza aún más trabajo de diseño a nivel de clases y rutinas individuales, que se describen en el
Capítulo 6, “Clases de trabajo”, y el Capítulo 7, “Rutinas de alta calidad”.
Si ya está familiarizado con los temas de diseño de software, es posible que desee ver los aspectos más
destacados en las secciones sobre desafíos de diseño en la Sección 5.1 y heurísticas clave en la Sección 5.3.
El diseño también está marcado por numerosos desafíos, que se describen en esta sección.
—David Parnas y Este es un buen ejemplo de un problema perverso porque, hasta que el puente colapsó, sus
Paul Clements
ingenieros no sabían que la aerodinámica debía ser considerada hasta tal punto. Solo
construyendo el puente (resolviendo el problema) pudieron aprender acerca de la
consideración adicional en el problema que les permitió construir otro puente que aún está en
pie.
Tribuna de noticias matutinas
Figura 5-1El puente de Tacoma Narrows: un ejemplo de un problema perverso.
Una de las principales diferencias entre los programas que desarrolla en la escuela y los que desarrolla como
profesional es que los problemas de diseño que resuelven los programas escolares rara vez, si es que alguna
vez, son perversos. Las asignaciones de programación en la escuela están diseñadas para moverte en línea
recta de principio a fin. Probablemente querrás pintar con alquitrán y plumas a un profesor que te dio una
tarea de programación, luego cambió la tarea tan pronto como terminaste el diseño y luego la cambiaste
nuevamente justo cuando estabas a punto de entregar el programa completo. Pero ese mismo proceso es
una realidad cotidiana en la programación profesional.
El diseño de software terminado debe verse bien organizado y limpio, pero el proceso utilizado
para desarrollar el diseño no es tan ordenado como el resultado final.
Otras lecturasPara una El diseño es descuidado porque das muchos pasos en falso y te metes en muchos callejones sin
exploración más completa de este
salida: cometes muchos errores. De hecho, cometer errores es el objetivo del diseño: es más
punto de vista, consulte "Un
Referencia cruzadaPara obtener una El diseño también es descuidado porque es difícil saber cuándo su diseño es "suficientemente
mejor respuesta a esta pregunta, consulte
bueno". ¿Cuánto detalle es suficiente? ¿Cuánto diseño se debe hacer con una notación de
"¿Cuánto cuesta el diseño?"
En un mundo ideal, cada sistema podría ejecutarse instantáneamente, consumir cero espacio de
almacenamiento, usar ancho de banda de red cero, nunca contener errores y construirlo sin costo alguno.
En el mundo real, una parte clave del trabajo del diseñador es sopesar las características de diseño que
compiten y lograr un equilibrio entre esas características. Si una tasa de respuesta rápida es más importante
que minimizar el tiempo de desarrollo, un diseñador elegirá un diseño. Si minimizar el tiempo de desarrollo
es más importante, un buen diseñador creará un diseño diferente.
El diseño no es determinista
Si envía a tres personas a diseñar el mismo programa, pueden regresar fácilmente con tres diseños
muy diferentes, cada uno de los cuales podría ser perfectamente aceptable. Puede haber más de una
forma de despellejar a un gato, pero por lo general hay docenas de formas de diseñar un programa
de computadora.
Debido a que el diseño no es determinista, las técnicas de diseño tienden a ser heurísticas, "reglas
generales" o "cosas para probar que a veces funcionan", en lugar de procesos repetibles que garantizan que
producirán resultados predecibles. El diseño implica ensayo y error. Una herramienta o técnica de diseño
PUNTO CLAVE
que funcionó bien en un trabajo o en un aspecto de un trabajo podría no funcionar tan bien en el próximo
proyecto. Ninguna herramienta es adecuada para todo.
El diseño es emergente
cc2e.com/0539 Una forma ordenada de resumir estos atributos del diseño es decir que el diseño es “emergente”.
Los diseños no surgen completamente formados directamente del cerebro de alguien. Evolucionan y
mejoran a través de revisiones de diseño, discusiones informales, experiencia en escribir el código
en sí y experiencia en la revisión del código.
5.2 Conceptos clave de diseño 77
Otras lecturasEl software no es Prácticamente todos los sistemas experimentan algún grado de cambios de diseño durante su
el único tipo de estructura que
desarrollo inicial, y luego, por lo general, cambian en mayor medida a medida que se extienden a
cambia con el tiempo. Las
estructuras físicas también
versiones posteriores. El grado en que el cambio es beneficioso o aceptable depende de la naturaleza
evolucionan—verCómo edificios del software que se está construyendo.
Aprender(Marca 1995).
Brooks argumenta que el desarrollo de software se dificulta debido a dos clases diferentes de
problemas: elbásicoy elaccidental. Al referirse a estos dos términos, Brooks se basa en una
tradición filosófica que se remonta a Aristóteles. En filosofía, las propiedades esenciales son las
propiedades que debe tener una cosa para ser esa cosa. Un automóvil debe tener un motor,
ruedas y puertas para ser un automóvil. Si no tiene ninguna de esas propiedades esenciales,
no es realmente un automóvil.
Las propiedades accidentales son las propiedades que casualmente tiene una cosa, propiedades que
realmente no se relacionan con si la cosa es lo que es. Un automóvil podría tener un V8, un 4 cilindros
turboalimentado o algún otro tipo de motor y ser un automóvil independientemente de ese detalle. Un
coche puede tener dos puertas o cuatro; podría tener ruedas delgadas o ruedas magnéticas. Todos esos
detalles son propiedades accidentales. También podría pensar en las propiedades accidentales como
incidental,discrecional,opcional, ycasualidad.
Referencia cruzadaLas dificultades Brooks observa que las principales dificultades accidentales del software se abordaron hace mucho tiempo.
accidentales son más prominentes en
Por ejemplo, las dificultades accidentales relacionadas con la sintaxis torpe del lenguaje se eliminaron en
el desarrollo de onda temprana que
La raíz de todas estas dificultades esenciales es la complejidad, tanto accidental como esencial.
Hay dos formas de construir Cuando las encuestas de proyectos de software informan las causas del fracaso del proyecto, rara
un diseño de software: una
vez identifican las razones técnicas como las causas principales del fracaso del proyecto. Los
forma es hacerlo tan simple
que hayaobviamente proyectos fallan con mayor frecuencia debido a requisitos deficientes, mala planificación o mala
carencias, y la otra es gestión. Pero cuando los proyectos fracasan por motivos principalmente técnicos, el motivo suele
hacerlo tan complicado que
ser una complejidad descontrolada. Se permite que el software se vuelva tan complejo que nadie
no hayaobviodeficiencias
sabe realmente lo que hace. Cuando un proyecto llega al punto en el que nadie comprende
—COCHE Hoare completamente el impacto que los cambios de código en un área tendrán en otras áreas, el
progreso se detiene.
Un síntoma de que se ha atascado en Dijkstra señaló que el cráneo de nadie es realmente lo suficientemente grande como para contener un
una sobrecarga de complejidad es
programa informático moderno (Dijkstra 1972), lo que significa que nosotros, como desarrolladores de
cuando se encuentra aplicando
cuyo automóvil se descompone, mantenga en el aire a la vez, es más probable que deje caer una de las bolas, lo que provocará un error de
por lo que pone agua en la diseño o codificación.
batería y vacía los ceniceros.
Mantener rutinas cortas ayuda a reducir su carga de trabajo mental. Escribir programas en términos
del dominio del problema, en lugar de en términos de detalles de implementación de bajo nivel, y
trabajar al más alto nivel de abstracción reduce la carga en su cerebro.
La conclusión es que los programadores que compensan las limitaciones humanas inherentes
escriben un código que es más fácil de entender para ellos y para los demás y que tiene menos
errores.
- Minimice la cantidad de complejidad esencial con la que el cerebro de cualquier persona tiene que lidiar
en un momento dado.
PUNTO CLAVE
- Evite que la complejidad accidental prolifere innecesariamente.
Una vez que comprende que todos los demás objetivos técnicos en el software son secundarios a la
gestión de la complejidad, muchas consideraciones de diseño se vuelven sencillas.
80 Capítulo 5: Diseño en la Construcción
20.1, “Características de la calidad del suelen ser difíciles de entender. En su lugar, haga diseños "simples" y "fáciles de entender". Si su
software”. diseño no le permite ignorar con seguridad la mayoría de las otras partes del programa cuando está
inmerso en una parte específica, el diseño no está haciendo su trabajo.
Bajo acoplamientoEl acoplamiento flexible significa diseñar de modo que se mantengan al mínimo las
conexiones entre las diferentes partes de un programa. Utilice los principios de buenas abstracciones en
interfaces de clase, encapsulación y ocultación de información para diseñar clases con la menor cantidad de
interconexiones posible. La conectividad mínima minimiza el trabajo durante la integración, las pruebas y el
mantenimiento.
ReutilizaciónLa reutilización significa diseñar el sistema para que pueda reutilizar partes de él
en otros sistemas.
Alta fan-inFan-in alto se refiere a tener una gran cantidad de clases que usan una clase
determinada. Un fan-in alto implica que un sistema ha sido diseñado para hacer un buen uso de las
clases de utilidad en los niveles más bajos del sistema.
5.2 Conceptos clave de diseño 81
Fan-out de bajo a medioFan-out de bajo a medio significa que una clase determinada utiliza un
número bajo a medio de otras clases. Una gran distribución (más de siete) indica que una clase
utiliza una gran cantidad de otras clases y, por lo tanto, puede ser demasiado compleja. Los
investigadores han encontrado que el principio de abanico bajo es beneficioso ya sea que esté
considerando la cantidad de rutinas llamadas desde dentro de una rutina o la cantidad de clases
utilizadas dentro de una clase (Card y Glass 1990; Basili, Briand y Melo 1996) .
FlacuraDelgadez significa diseñar el sistema de modo que no tenga partes adicionales (Wirth 1995,
McConnell 1997). Voltaire dijo que un libro no está terminado cuando no se le puede agregar nada
más, sino cuando no se le puede quitar nada más. En el software, esto es especialmente cierto
porque se debe desarrollar, revisar, probar y considerar código adicional cuando se modifica el otro
código. Las versiones futuras del software deben seguir siendo compatibles con versiones anteriores
del código adicional. La pregunta fatal es "Es fácil, entonces, ¿qué dañaremos al ponerlo?"
Referencia cruzadaPara obtener más Por ejemplo, si está escribiendo un sistema moderno que tiene que usar una gran cantidad de código
información sobre cómo trabajar con sistemas
antiguo y mal diseñado, escriba una capa del nuevo sistema que sea responsable de interactuar con
antiguos, consulte la Sección 24.5, “Estrategias
de refactorización”.
el código antiguo. Diseñe la capa para que oculte la mala calidad del código anterior, presentando un
conjunto coherente de servicios a las capas más nuevas. Luego, haga que el resto del sistema use
esas clases en lugar del código anterior. Los efectos beneficiosos del diseño estratificado en tal caso
son (1) compartimenta el desorden del código incorrecto y (2) si alguna vez se le permite desechar el
código anterior o refactorizarlo, no necesitará modificar ningún código nuevo. código excepto la capa
de interfaz.
Referencia cruzadaUn tipo de Técnicas estándarCuanto más se base un sistema en piezas exóticas, más intimidante será para
estandarización especialmente
alguien que intente entenderlo por primera vez. Trate de darle a todo el sistema una sensación de
valioso es el uso de patrones
de diseño, que se analizan en familiaridad mediante el uso de enfoques comunes y estandarizados.
"Buscar patrones de diseño
comunes" en la Sección 5.3.
82 Capítulo 5: Diseño en la Construcción
Niveles de Diseño
El diseño es necesario en varios niveles diferentes de detalle en un sistema de software. Algunas técnicas de diseño
se aplican en todos los niveles y otras se aplican solo en uno o dos. La Figura 5-2 ilustra los niveles.
1Sistema de software
2 División en subsistemas/paquetes
En otras palabras, y este El primer nivel es todo el sistema. Algunos programadores pasan directamente del nivel del
es el sólido principio
sistema al diseño de clases, pero generalmente es beneficioso pensar en combinaciones de
sobre el que se basa todo
el éxito de la Corporación clases de nivel superior, como subsistemas o paquetes.
en toda la Galaxia, su
los defectos de diseño fundamentales Nivel 2: División en Subsistemas o Paquetes
están completamente ocultos por sus
defectos de diseño superficiales. El producto principal del diseño en este nivel es la identificación de todos los subsistemas principales. Los
—douglas adams subsistemas pueden ser grandes: base de datos, interfaz de usuario, reglas comerciales, intérprete de comandos,
5.2 Conceptos clave de diseño 83
motor de informes, etc. La principal actividad de diseño en este nivel es decidir cómo dividir el
programa en subsistemas principales y definir cómo se le permite a cada subsistema usar otros
subsistemas. La división a este nivel suele ser necesaria en cualquier proyecto que lleve más de unas
pocas semanas. Dentro de cada subsistema, se pueden usar diferentes métodos de diseño, eligiendo
el enfoque que mejor se adapte a cada parte del sistema. En la Figura 5-2, el diseño en este nivel está
marcado con un2.
De particular importancia en este nivel son las reglas sobre cómo se pueden comunicar los diversos
subsistemas. Si todos los subsistemas pueden comunicarse con todos los demás subsistemas, pierde
el beneficio de separarlos en absoluto. Haga que cada subsistema tenga sentido restringiendo las
comunicaciones.
Suponga, por ejemplo, que define un sistema con seis subsistemas, como se muestra en
la Figura 5-3. Cuando no haya reglas, entrará en juego la segunda ley de la
termodinámica y aumentará la entropía del sistema. Una forma en que aumenta la
entropía es que, sin restricciones en las comunicaciones entre los subsistemas, la
comunicación ocurrirá sin restricciones, como en la figura 5-4.
Solicitud
Almacenamiento de datos
Clases de nivel
Solicitud
Almacenamiento de datos
Clases de nivel
Figura 5-4Un ejemplo de lo que sucede sin restricciones en las comunicaciones entre
subsistemas.
Traducido del inglés al español - www.onlinedoctranslator.com
Como puede ver, cada subsistema termina comunicándose directamente con todos los demás
subsistemas, lo que plantea algunas preguntas importantes:
- ¿Qué sucede cuando intenta utilizar las reglas de negocio en otro sistema?
- ¿Qué sucede cuando desea colocar una nueva interfaz de usuario en el sistema, tal vez una interfaz de
- ¿Qué sucede cuando desea colocar el almacenamiento de datos en una máquina remota?
Puede pensar en las líneas entre los subsistemas como mangueras con agua corriendo a
través de ellas. Si desea alcanzar y sacar un subsistema, ese subsistema tendrá algunas
mangueras conectadas. Cuantas más mangueras tenga que desconectar y volver a conectar,
más se mojará. Desea diseñar su sistema de modo que si extrae un subsistema para usarlo en
otro lugar, no tendrá muchas mangueras para volver a conectar y esas mangueras se volverán
a conectar fácilmente.
Con previsión, todos estos problemas se pueden abordar con poco trabajo adicional. Permita la
comunicación entre subsistemas solo en base a la "necesidad de saber", y es mejor que sea una
buenorazón. En caso de duda, es más fácil restringir la comunicación temprano y relajarla más tarde
que relajarla temprano y luego tratar de reforzarla después de haber codificado varios cientos de
llamadas entre subsistemas. La figura 5-5 muestra cómo unas pocas pautas de comunicación
podrían cambiar el sistema representado en la figura 5-4.
Solicitud
Almacenamiento de datos
Clases de nivel
Figura 5-5Con algunas reglas de comunicación, puede simplificar significativamente las interacciones de los
subsistemas.
Para que las conexiones sean fáciles de comprender y mantener, pérese del lado de las
relaciones simples entre subsistemas. La relación más simple es tener rutinas de llamada de un
subsistema en otro. Una relación más complicada es hacer que un subsistema contenga clases
de otro. La relación más complicada es hacer que las clases de un subsistema hereden de las
clases de otro.
5.2 Conceptos clave de diseño 85
Una buena regla general es que un diagrama a nivel de sistema como el de la figura 5-5 debe ser un
gráfico acíclico. En otras palabras, un programa no debe contener relaciones circulares en las que la
clase A use la clase B, la clase B use la clase C y la clase C use la clase A.
Referencia cruzadaPara obtener más Reglas del negocioLas reglas comerciales son las leyes, reglamentos, políticas y procedimientos que usted
información sobre cómo simplificar la
codifica en un sistema informático. Si está escribiendo un sistema de nómina, puede codificar las reglas del
lógica de negocios expresándola en tablas,
Interfaz de usuarioCree un subsistema para aislar los componentes de la interfaz de usuario para que la
interfaz de usuario pueda evolucionar sin dañar el resto del programa. En la mayoría de los casos, un
subsistema de interfaz de usuario utiliza varios subsistemas o clases subordinados para la interfaz GUI, la
interfaz de línea de comandos, las operaciones de menú, la gestión de ventanas, el sistema de ayuda, etc.
Acceso a la base de datosPuede ocultar los detalles de implementación del acceso a una base de datos para
que la mayor parte del programa no tenga que preocuparse por los detalles complicados de manipular
estructuras de bajo nivel y pueda manejar los datos en términos de cómo se usan en el nivel de problema
empresarial. . Los subsistemas que ocultan los detalles de implementación proporcionan un valioso nivel de
abstracción que reduce la complejidad de un programa. Centralizan las operaciones de la base de datos en
un solo lugar y reducen la posibilidad de errores al trabajar con los datos. Facilitan el cambio de la estructura
de diseño de la base de datos sin cambiar la mayor parte del programa.
dependencias del sistemaEmpaquete las dependencias del sistema operativo en un subsistema por
la misma razón que empaqueta las dependencias de hardware. Si está desarrollando un programa
para Microsoft Windows, por ejemplo, ¿por qué limitarse al entorno de Windows? Aísle las llamadas
de Windows en un subsistema de interfaz de Windows. Si más tarde desea mover su programa a Mac
OS o Linux, todo lo que tendrá que cambiar es el subsistema de interfaz. Un subsistema de interfaz
puede ser demasiado extenso para que lo implemente por su cuenta, pero tales subsistemas están
fácilmente disponibles en cualquiera de varias bibliotecas de códigos comerciales.
86 Capítulo 5: Diseño en la Construcción
Los detalles de las formas en que cada clase interactúa con el resto del sistema también se especifican a
medida que se especifican las clases. En particular, se define la interfaz de la clase. En general, la principal
actividad de diseño en este nivel es asegurarse de que todos los subsistemas se hayan descompuesto a un
nivel de detalle lo suficientemente fino como para que pueda implementar sus partes como clases
individuales.
Referencia cruzadaPara obtener detalles La división de los subsistemas en clases suele ser necesaria en cualquier proyecto que lleve
sobre las características de las clases de
más de unos pocos días. Si el proyecto es grande, la división es claramente distinta de la
alta calidad, consulte el Capítulo 6, “Clases
trabajadoras”.
partición del programa del Nivel 2. Si el proyecto es muy pequeño, puede pasar directamente
de la vista de todo el sistema del Nivel 1 a la vista de clases del Nivel 3.
Clases frente a objetosUn concepto clave en el diseño orientado a objetos es la diferenciación entre
objetos y clases. Un objeto es cualquier entidad específica que existe en su programa en tiempo de
ejecución. Una clase es lo estático que ves en la lista de programas. Un objeto es algo dinámico con
valores y atributos específicos que ves cuando ejecutas el programa. Por ejemplo, podría declarar
una clasePersonaque tenía atributos de nombre, edad, género, etc. En tiempo de ejecución tendrías
los objetosnancy,madeja, diana,tony, y así sucesivamente, es decir, instancias específicas de la clase.
Si está familiarizado con los términos de la base de datos, es lo mismo que la distinción entre
"esquema" e "instancia". Podría pensar en la clase como el cortador de galletas y el objeto como la
galleta. Este libro usa los términos de manera informal y generalmente se refiere a clases y objetos
de manera más o menos intercambiable.
El acto de definir completamente las rutinas de la clase a menudo da como resultado una mejor
comprensión de la interfaz de la clase y eso provoca los cambios correspondientes en la interfaz, es decir,
los cambios en el Nivel 3.
Este nivel de descomposición y diseño a menudo se deja en manos del programador individual, y es
necesario en cualquier proyecto que lleve más de unas pocas horas. No es necesario hacerlo
formalmente, pero al menos debe hacerse mentalmente.
5.3 Elementos básicos del diseño: heurística 87
Referencia cruzadaPara obtener El diseño a nivel de rutina consiste en diseñar la funcionalidad detallada de las rutinas
detalles sobre la creación de rutinas
individuales. El diseño de la rutina interna generalmente se deja al programador individual que
de alta calidad, consulte el Capítulo 7,
No preguntes primero qué hace el El primer y más popular enfoque para identificar alternativas de diseño es el enfoque
sistema; ¡pregunte PARA QUÉ lo hace!
orientado a objetos "según el libro", que se enfoca en identificar objetos sintéticos y del
—Bertrand Meyer
mundo real.
Referencia cruzadaPara obtener - Identificar los objetos y sus atributos (métodos y datos).
más detalles sobre el diseño
mediante clases, consulte el - Determine qué se puede hacer con cada objeto.
Capítulo 6, “Clases de trabajo”.
- Determine qué puede hacer cada objeto con otros objetos.
- Determine las partes de cada objeto que serán visibles para otros objetos:
qué partes serán públicas y cuáles privadas.
Identificar los objetos y sus atributos.Los programas de computadora generalmente se basan en entidades del
mundo real. Por ejemplo, podría basar un sistema de facturación de tiempo en empleados, clientes, tarjetas de
tiempo y facturas del mundo real. La Figura 5-6 muestra una vista orientada a objetos de dicho sistema de
facturación.
Cliente
Empleado
nombre
ObtenerHorasPorMes() IngresarPago()
... ...
1 facturaciónEmpleado 1 1clienteafacturar
clienteafacturar
facturas
* * *
Tarjeta de tiempo Factura
fecha * 0..1
FacturaParaCliente()
código de proyecto
registros de facturación
...
...
Figura 5-6Este sistema de facturación se compone de cuatro objetos principales. Los objetos se han
simplificado para este ejemplo.
Identificar los atributos de los objetos no es más complicado que identificar los objetos mismos. Cada objeto
tiene características que son relevantes para el programa de computadora. Por ejemplo, en el sistema de
facturación de horas, un objeto de empleado tiene un nombre, un cargo y una tasa de facturación. Un
objeto de cliente tiene un nombre, una dirección de facturación y un saldo de cuenta. Un objeto de factura
tiene un importe de facturación, un nombre de cliente, una fecha de facturación, etc.
Los objetos en un sistema de interfaz gráfica de usuario incluirían ventanas, cuadros de diálogo, botones,
fuentes y herramientas de dibujo. Un examen más detallado del dominio del problema podría producir
mejores opciones para los objetos de software que una asignación uno a uno a los objetos del mundo real,
pero los objetos del mundo real son un buen lugar para comenzar.
Determinar qué se puede hacer con cada objeto.Se puede realizar una variedad de operaciones en cada objeto.
En el sistema de facturación que se muestra en la Figura 5-6, un objeto de empleado podría tener un cambio en el
cargo o la tasa de facturación, un objeto de cliente podría tener un cambio en su nombre o dirección de facturación,
y así sucesivamente.
Determine qué puede hacer cada objeto con otros objetosEste paso es justo lo que parece.
Las dos cosas genéricas que los objetos pueden hacer entre sí son la contención y la herencia.
¿Qué objetos puedencontener¿Qué otros objetos? ¿Qué objetos puedenheredar
5.3 Elementos básicos del diseño: heurística 89
de¿Qué otros objetos? En la Figura 5-6, un objeto de tarjeta de tiempo puede contener un objeto de
empleado y un objeto de cliente, y una factura puede contener una o más tarjetas de tiempo. Además, una
factura puede indicar que se ha facturado a un cliente, y un cliente puede introducir pagos contra una
factura. Un sistema más complicado incluiría interacciones adicionales.
Referencia cruzadaPara obtener detalles Determine las partes de cada objeto que serán visibles para otros objetosUna de las decisiones de
sobre las clases y la ocultación de
diseño clave es identificar las partes de un objeto que deben hacerse públicas y aquellas que deben
información, consulte "Ocultar secretos
Definir las interfaces de cada objetoDefina las interfaces formales, sintácticas y de nivel de lenguaje
de programación para cada objeto. Los datos y métodos que el objeto expone a todos los demás
objetos se denominan "interfaz pública" del objeto. Las partes del objeto que expone a los objetos
derivados a través de la herencia se denominan "interfaz protegida" del objeto. Piense en ambos
tipos de interfaces.
Cuando termine de seguir los pasos para lograr una organización del sistema orientada a objetos de
nivel superior, iterará de dos maneras. Repetirá en la organización del sistema de nivel superior para
obtener una mejor organización de las clases. También iterará en cada una de las clases que ha
definido, llevando el diseño de cada clase a un nivel más detallado.
Las clases base son abstracciones que le permiten concentrarse en los atributos comunes de un
conjunto de clases derivadas e ignorar los detalles de las clases específicas mientras trabaja en la
clase base. Una buena interfaz de clase es una abstracción que le permite concentrarse en la interfaz
sin tener que preocuparse por el funcionamiento interno de la clase. La interfaz para una rutina bien
diseñada brinda el mismo beneficio a un nivel de detalle más bajo, y la interfaz para un paquete o
subsistema bien diseñado brinda ese beneficio a un nivel de detalle más alto.
Figura 5-7La abstracción le permite tener una visión más simple de un concepto complejo.
Referencia cruzadaPara obtener más Los desarrolladores de software a veces construyen sistemas a nivel de fibra de madera, molécula de barniz
detalles sobre la abstracción en el
y molécula de acero. Esto hace que los sistemas sean demasiado complejos e intelectualmente difíciles de
diseño de clases, consulte "Buena
Los buenos programadores crean abstracciones en el nivel de la interfaz de rutina, el nivel de la interfaz de
clase y el nivel de la interfaz del paquete, en otras palabras, el nivel del picaporte, el nivel de la puerta y el
nivel de la casa, y eso respalda una programación más rápida y segura.
Como sugiere la Figura 5-8, la encapsulación ayuda a administrar la complejidad al prohibirle mirar la
complejidad. La sección titulada "Buena encapsulación" en la Sección 6.2 proporciona más
antecedentes sobre la encapsulación en su aplicación al diseño de clases.
5.3 Diseño
Figura 5-8La encapsulación dice que, no solo se le permite tener una visión más simple de un concepto
complejo, sino que tambiénnopermitió mirar cualquiera de los detalles del complejo concepto. Lo que ves
es lo que obtienes, ¡es todo lo que obtienes!
La definición de similitudes y diferencias entre dichos objetos se denomina "herencia" porque los
empleados específicos de medio tiempo y de tiempo completo heredan características del tipo de
empleado general.
La herencia simplifica la programación porque escribe una rutina general para manejar cualquier cosa que
dependa de las propiedades generales de una puerta y luego escribe rutinas específicas para manejar
operaciones específicas en tipos específicos de puertas. Algunas operaciones, como
92 Capítulo 5: Diseño en la Construcción
Abierto()oCerca(), podría aplicarse independientemente de si la puerta es una puerta sólida, una puerta
interior, una puerta exterior, una puerta mosquitera, una puerta francesa o una puerta corrediza de vidrio.
La capacidad de un lenguaje para soportar operaciones comoAbierto()oCerca()sin saber hasta el momento
de la ejecución con qué tipo de puerta se está tratando se llama "polimorfismo". Los lenguajes orientados a
objetos como C++, Java y versiones posteriores de Microsoft Visual Basic admiten la herencia y el
polimorfismo.
La herencia es una de las herramientas más poderosas de la programación orientada a objetos. Puede proporcionar
grandes beneficios cuando se usa bien, y puede causar un gran daño cuando se usa ingenuamente. Para obtener
La ocultación de información llamó la atención del público por primera vez en un artículo publicado por
David Parnas en 1972 llamado "Sobre los criterios que se utilizarán en la descomposición de sistemas en
módulos". La ocultación de información se caracteriza por la idea de "secretos", decisiones de diseño e
implementación que un desarrollador de software oculta en un lugar del resto de un programa.
En la edición 20 Aniversario deEl mes del hombre mítico, Fred Brooks concluyó que su crítica a
la ocultación de información era una de las pocas formas en que la primera edición de su libro
estaba equivocada. “Parnas tenía razón y yo estaba equivocado acerca de la ocultación de
información”, proclamó (Brooks 1995). Barry Boehm informó que la ocultación de información
era una técnica poderosa para eliminar la repetición del trabajo y señaló que era
particularmente eficaz en entornos incrementales y de gran cambio (Boehm 1987).
En la ocultación de información, cada clase (o paquete o rutina) se caracteriza por las decisiones de
diseño o construcción que oculta a todas las demás clases. El secreto puede ser un área que
probablemente cambie, el formato de un archivo, la forma en que se implementa un tipo de datos o
un área que debe aislarse del resto del programa para que los errores en esa área causen el menor
daño. como sea posible. El trabajo de la clase es mantener oculta esta información y proteger su
propio derecho a la privacidad. Cambios menores en un sistema.
5.3 Elementos básicos del diseño: heurística 93
pueden afectar varias rutinas dentro de una clase, pero no deberían extenderse más allá de la interfaz
de la clase.
Esfuércese por interfaces de clase Una tarea clave en el diseño de una clase es decidir qué características deben conocerse fuera de la
que sean completas y mínimas.
clase y cuáles deben permanecer en secreto. Una clase puede usar 25 rutinas y exponer solo 5 de
—scott meyers ellas, usando las otras 20 internamente. Una clase puede usar varios tipos de datos y no exponer
información sobre ellos. Este aspecto del diseño de la clase también se conoce como "visibilidad", ya
que tiene que ver con qué características de la clase son "visibles" o "expuestas" fuera de la clase.
La interfaz de una clase debe revelar lo menos posible sobre su funcionamiento interno. Como se
muestra en la figura 5-9, una clase se parece mucho a un iceberg: siete octavos están bajo el agua y
solo se puede ver el octavo que está sobre la superficie.
Figura 5-9Una buena interfaz de clase es como la punta de un iceberg, dejando la mayor parte de la clase sin
exponer.
Diseñar la interfaz de clase es un proceso iterativo como cualquier otro aspecto del diseño. Si no
obtiene la interfaz correcta la primera vez, intente unas cuantas veces más hasta que se estabilice. Si
no se estabiliza, debe intentar un enfoque diferente.
Suponga que tiene un programa en el que se supone que cada objeto tiene una ID única
almacenada en una variable miembro llamadaidentificación. Un enfoque de diseño sería usar
números enteros para los ID y almacenar el ID más alto asignado hasta ahora en una variable global
llamada g_maxId. A medida que se asigna cada nuevo objeto, tal vez en el constructor de cada
objeto, simplemente podría usar elid = ++g_maxIddeclaración, que garantizaría una identificación
única, y agregaría el mínimo absoluto de código en cada lugar donde se crea un objeto. ¿Qué podría
salir mal con eso?
94 Capítulo 5: Diseño en la Construcción
Muchas cosas podrían salir mal. ¿Qué sucede si desea reservar rangos de identificaciones para propósitos
especiales? ¿Qué sucede si desea utilizar identificaciones no secuenciales para mejorar la seguridad? ¿Qué
sucede si desea poder reutilizar las identificaciones de los objetos que han sido destruidos? ¿Qué sucede si
desea agregar una afirmación que se activa cuando asigna más ID que el número máximo que ha
anticipado? Si asignó identificaciones mediante la difusiónid = ++g_maxIddeclaraciones a lo largo de su
programa, tendría que cambiar el código asociado con cada una de esas declaraciones. Y, si su programa
tiene subprocesos múltiples, este enfoque no será seguro para subprocesos.
La forma en que se crean los nuevos ID es una decisión de diseño que debe ocultar. Si usas la frase++
g_maxIda lo largo de su programa, expone la forma en que se crea una nueva ID, que es
simplemente incrementandog_maxId. Si en cambio pones elid = NuevoId() declaración a lo largo de
su programa, oculta la información sobre cómo se crean las nuevas identificaciones. Dentro de
NuevoId()rutina, es posible que aún tenga solo una línea de código,devolver ( ++g_maxId )o su
equivalente, pero si luego decide reservar ciertos rangos de identificaciones para fines especiales o
para reutilizar identificaciones antiguas, puede realizar esos cambios dentro del NuevoId()rutina en
sí, sin tocar docenas o cientos deid = NuevoId()declaraciones. No importa cuán complicadas sean las
revisiones internasNuevoId()podrían convertirse, no afectarían a ninguna otra parte del programa.
Ahora suponga que descubre que necesita cambiar el tipo de ID de un número entero a una
cadena. Si ha extendido declaraciones de variables comoidentificación internaa lo largo de su
programa, su uso delNuevoId()la rutina no ayudará. Todavía tendrá que pasar por su
programa y hacer docenas o cientos de cambios.
Un secreto adicional para ocultar es el tipo de identificación. Al exponer el hecho de que los ID son números
enteros, alienta a los programadores a realizar operaciones con números enteros como>,<,=en ellos. En C+
+, podría usar un simpledefinición de tipopara declarar que sus identificaciones son deTipo de identificación:
un tipo definido por el usuario que se resuelve enEn t—en lugar de declararlos directamente como del tipo
En t. Alternativamente, en C++ y otros lenguajes podrías crear un simpleTipo de identificaciónclase. Una vez
más, ocultar una decisión de diseño marca una gran diferencia en la cantidad de código afectado por un
cambio.
La ocultación de información es útil en todos los niveles de diseño, desde el uso de constantes con nombre en
lugar de literales, hasta la creación de tipos de datos, el diseño de clases, el diseño de rutinas y el diseño de
subsistemas.
PUNTO CLAVE
- Ocultar la complejidad para que su cerebro no tenga que lidiar con ella a menos que esté
específicamente preocupado por ella
- Ocultar las fuentes de cambio para que cuando ocurra el cambio, los efectos se localicen
5.3 Elementos básicos del diseño: heurística 95
Las fuentes de complejidad incluyen tipos de datos complicados, estructuras de archivos, pruebas
booleanas, algoritmos involucrados, etc. Una lista completa de fuentes de cambio se describe más adelante
en este capítulo.
Otras lecturasPartes de esta En algunos casos, ocultar información es realmente imposible, pero la mayoría de las barreras para
sección están adaptadas de
ocultar información son bloqueos mentales construidos por el uso habitual de otras técnicas.
“Designing Software for Ease
of Extension and
Distribución excesiva de información.Una barrera común para ocultar información es
Contraction” (Parnas 1979).
una distribución excesiva de información en todo el sistema. Es posible que haya
codificado el literal100a lo largo de un sistema. Usando100como literal descentraliza las
referencias a él. Es mejor ocultar la información en un solo lugar, en un constante
MAX_EMPLEADOStal vez, cuyo valor se cambia en un solo lugar.
Referencia cruzadaPara obtener más Otro ejemplo más sería un elemento de datos globales, tal vez una matriz de datos de empleados con
información sobre el acceso a datos
un máximo de 1000 elementos a los que se accede a través de un programa. Si el programa usa los
globales a través de interfaces de clase,
Dependencias circularesUna barrera más sutil para ocultar información son las dependencias
circulares, como cuando una rutina en claseAllama a una rutina en claseB, y una rutina en claseB
llama a una rutina en claseA.
Evite tales bucles de dependencia. Hacen que sea difícil probar un sistema porque no se puede probar
ninguna clase.Ao claseBhasta que al menos una parte de la otra esté lista.
Datos de clase confundidos con datos globalesSi es un programador concienzudo, una de las barreras
para ocultar información de manera efectiva podría ser pensar en los datos de clase como datos globales y
evitarlos porque quiere evitar los problemas asociados con los datos globales. Si bien el camino hacia el
infierno de la programación está pavimentado con variables globales, los datos de clase presentan
muchos menos riesgos.
Los datos globales generalmente están sujetos a dos problemas: las rutinas operan en datos
globales sin saber que otras rutinas están operando en ellos, y las rutinas saben que otras rutinas
están operando en los datos globales pero no saben exactamente lo que están haciendo para eso.
Los datos de clase no están sujetos a ninguno de estos problemas. El acceso directo a los datos está
restringido a unas pocas rutinas organizadas en una sola clase. Las rutinas son conscientes de que
otras rutinas operan en los datos y saben exactamente qué otras rutinas son.
96 Capítulo 5: Diseño en la Construcción
Por supuesto, toda esta discusión asume que su sistema hace uso de clases pequeñas bien
diseñadas. Si su programa está diseñado para usar clases enormes que contienen docenas de
rutinas cada una, la distinción entre datos de clase y datos globales comenzará a desdibujarse y los
datos de clase estarán sujetos a muchos de los mismos problemas que los datos globales.
Referencia cruzadaLas Penalizaciones de rendimiento percibidasUna barrera final para ocultar información puede ser un
optimizaciones de rendimiento a nivel
intento de evitar penalizaciones de rendimiento tanto a nivel de arquitectura como de codificación.
de código se analizan en el Capítulo
La preocupación más común está en el nivel de codificación. La preocupación es que el acceso a elementos
de datos incurre indirectamente en penalizaciones de rendimiento en tiempo de ejecución para niveles
adicionales de instanciaciones de objetos, llamadas de rutina, etc. Esta preocupación es prematura. Hasta
que pueda medir el rendimiento del sistema e identificar los cuellos de botella, la mejor manera de
prepararse para el trabajo de rendimiento a nivel de código es crear un diseño altamente modular. Cuando
detecte puntos calientes más tarde, puede optimizar clases y rutinas individuales sin afectar el resto del
sistema.
3
2 La ocultación de información es una de las pocas técnicas teóricas que indiscutiblemente ha demostrado su
1
valor en la práctica, lo cual ha sido cierto durante mucho tiempo (Boehm 1987a). Hace años se descubrió que
DATOS DUROS los programas grandes que ocultan información son más fáciles de modificar, por un factor de 4, que los
programas que no lo hacen (Korson y Vaishnavi 1986). Además, la ocultación de información es parte de la
base tanto del diseño estructurado como del diseño orientado a objetos.
La ocultación de información tiene un poder heurístico único, una habilidad única para inspirar soluciones
de diseño efectivas. El diseño tradicional orientado a objetos brinda el poder heurístico de modelar el
mundo en objetos, pero el pensamiento de objetos no lo ayudaría a evitar declarar la ID como unEn ten
lugar de unTipo de identificación. El diseñador orientado a objetos preguntaría: "¿Debería tratarse una ID
como un objeto?" Según los estándares de codificación del proyecto, una respuesta "Sí" puede significar que
el programador tiene que escribir un constructor, un destructor, un operador de copia y un operador de
asignación; comentarlo todo; y colóquelo bajo el control de configuración. La mayoría de los programadores
decidirían: “No, no vale la pena crear una clase completa solo para una ID. solo usaréEn ts."
Tenga en cuenta lo que acaba de suceder. Ni siquiera se consideró una alternativa de diseño útil, la de
simplemente ocultar el tipo de datos de la identificación. Si, en cambio, el diseñador hubiera preguntado:
"¿Qué pasa con la identificación debería ocultarse?" bien podría haber decidido ocultar su tipo detrás de una
declaración de tipo simple que sustituyeTipo de identificaciónporEn t. La diferencia entre el diseño
orientado a objetos y la ocultación de información en este ejemplo es más sutil que un choque de reglas y
regulaciones explícitas. El diseño orientado a objetos aprobaría esta decisión de diseño tanto como lo haría
la ocultación de información. Más bien, la diferencia es una de las heurísticas:
5.3 Elementos básicos del diseño: heurística 97
La ocultación de información también puede ser útil para diseñar la interfaz pública de una clase. La brecha
entre la teoría y la práctica en el diseño de clases es amplia, y entre muchos diseñadores de clases la
decisión sobre qué poner en la interfaz pública de una clase equivale a decidir qué interfaz sería la más
conveniente para usar, lo que generalmente resulta en exponer la mayor parte de la clase como sea posible.
Por lo que he visto, algunos programadores prefieren exponer todos los datos privados de una clase que
escribir 10 líneas adicionales de código para mantener intactos los secretos de la clase.
Preguntar "¿Qué necesita ocultar esta clase?" va al corazón de la cuestión del diseño de la interfaz. Si
puede poner una función o datos en la interfaz pública de la clase sin comprometer sus secretos,
hágalo. De lo contrario, no lo hagas.
Preguntar qué debe ocultarse respalda las buenas decisiones de diseño en todos los niveles.
Promueve el uso de constantes con nombre en lugar de literales en el nivel de construcción. Ayuda a
crear buenos nombres de rutinas y parámetros dentro de las clases. Guía las decisiones sobre las
descomposiciones e interconexiones de clases y subsistemas a nivel del sistema.
PUNTO CLAVE
Otras lecturaslos Un estudio de grandes diseñadores encontró que un atributo que tenían en común era su
El enfoque descrito en esta
capacidad para anticipar el cambio (Glass 1995). Adaptarse a los cambios es uno de los
sección está adaptado de
“Diseño de software para la aspectos más desafiantes del buen diseño de programas. El objetivo es aislar áreas inestables
facilidad de extensión y para que el efecto de un cambio se limite a una rutina, clase o paquete. Estos son los pasos que
contracción” (Parnas 1979).
debe seguir para prepararse para tales perturbaciones.
1. Identifique los elementos que parece probable que cambien.Si los requisitos se han hecho
bien, incluyen una lista de cambios potenciales y la probabilidad de cada cambio.
En tal caso, es fácil identificar los posibles cambios. Si los requisitos no cubren los
cambios potenciales, vea la discusión que sigue de las áreas que probablemente
cambien en cualquier proyecto.
3. Aísle los elementos que parezcan propensos a cambiar.Diseñe las interfaces entre clases para
que sean insensibles a los posibles cambios. Diseñe las interfaces para que los cambios se
limiten al interior de la clase y el exterior no se vea afectado. Cualquier otra clase que utilice la
clase modificada no debe saber que se ha producido el cambio.
La interfaz de la clase debe proteger sus secretos.
98 Capítulo 5: Diseño en la Construcción
Referencia cruzadaUna de las técnicas Reglas del negocioLas reglas comerciales tienden a ser la fuente de frecuentes cambios de
más poderosas para anticipar el cambio
software. El Congreso cambia la estructura tributaria, un sindicato renegocia su contrato o una
es utilizar métodos basados en tablas.
Entrada y salidaEn un nivel de diseño ligeramente más alto que las interfaces de hardware sin procesar, la
entrada/salida es un área volátil. Si su aplicación crea sus propios archivos de datos, el formato de archivo
probablemente cambiará a medida que su aplicación se vuelva más sofisticada. Los formatos de entrada y
salida a nivel de usuario también cambiarán: la posición de los campos en la página, la cantidad de campos
en cada página, la secuencia de campos, etc. En general, es una buena idea examinar todas las interfaces
externas en busca de posibles cambios.
Áreas difíciles de diseño y construcción.Es una buena idea ocultar las áreas difíciles de
diseño y construcción porque es posible que no se hayan hecho bien y es posible que deba
volver a hacerlas. Compartimentarlos y minimizar el impacto que su mal diseño o construcción
pueda tener en el resto del sistema.
Variables de estadoLas variables de estado indican el estado de un programa y tienden a cambiarse con
más frecuencia que la mayoría de los demás datos. En un escenario típico, podría definir originalmente una
variable de estado de error como una variable booleana y luego decidir que
5.3 Elementos básicos del diseño: heurística 99
se implementaría mejor como un tipo enumerado con los valoresTipo de error_Ninguno, Tipo
de error_Advertencia, yErrorType_Fatal.
- No utilice una variable booleana como variable de estado. Utilice un tipo enumerado en su
lugar. Es común agregar un nuevo estado a una variable de estado, y agregar un nuevo tipo
a un tipo enumerado requiere una mera recompilación en lugar de una revisión importante
de cada línea de código que verifica la variable.
Otras lecturasEsta Una buena técnica para identificar áreas que puedan cambiar es primero identificar el subconjunto
discusión se basa en el
mínimo del programa que podría ser de utilidad para el usuario. El subconjunto constituye el núcleo
enfoque descrito en “Sobre
el diseño y desarrollo de del sistema y es poco probable que cambie. A continuación, defina incrementos mínimos para el
familias de sistema. Pueden ser tan pequeños que parecen triviales. Al considerar los cambios funcionales,
programas” (Parnas 1976).
asegúrese de considerar también los cambios cualitativos: hacer que el programa sea seguro para
subprocesos, hacerlo localizable, etc. Estas áreas de mejora potencial constituyen cambios
potenciales al sistema; diseñe estas áreas utilizando los principios de ocultación de información. Al
identificar primero el núcleo, puede ver qué componentes son realmente complementos y luego
extrapolar y ocultar las mejoras desde allí.
100 Capítulo 5: Diseño en la Construcción
El acoplamiento describe qué tan estrechamente se relaciona una clase o rutina con otras clases o
rutinas. El objetivo es crear clases y rutinas con relaciones pequeñas, directas, visibles y flexibles con
otras clases y rutinas, lo que se conoce como "acoplamiento flexible". El concepto de acoplamiento se
aplica igualmente a clases y rutinas, por lo que durante el resto de esta discusión usaré la palabra
"módulo" para referirme tanto a clases como a rutinas.
Un buen acoplamiento entre módulos es lo suficientemente suelto como para que un módulo pueda ser
utilizado fácilmente por otros módulos. Los vagones de ferrocarril en miniatura se acoplan mediante
ganchos opuestos que se enganchan cuando se juntan. Conectar dos autos es fácil: simplemente empuja los
autos juntos. Imagínese cuánto más difícil sería si tuviera que atornillar las cosas, o conectar un conjunto de
cables, o si pudiera conectar solo ciertos tipos de automóviles a otros tipos de automóviles. El acoplamiento
de maquetas de vagones de ferrocarril funciona porque es lo más sencillo posible. En el software, haga que
las conexiones entre los módulos sean lo más simples posible.
Intenta crear módulos que dependan poco de otros módulos. Hágalos desapegados, como lo son los
socios comerciales, en lugar de apegados, como lo son los gemelos siameses. Una rutina como
pecado() está débilmente acoplado porque todo lo que necesita saber se le pasa con un valor que
representa un ángulo en grados. Una rutina comoInitVars( var 1, var2, var3, ..., varN )está acoplado
más estrechamente porque, con todas las variables que debe pasar, el módulo que llama
prácticamente sabe lo que sucede dentroInitVars(). Dos clases que dependen del uso de los mismos
datos globales por parte de la otra están aun más estrechamente acopladas.
Criterios de acoplamiento
Aquí hay varios criterios para usar en la evaluación del acoplamiento entre módulos:
TamañoEl tamaño se refiere al número de conexiones entre módulos. Con acoplamiento, pequeño
es hermoso porque es menos trabajo conectar otros módulos a un módulo que tiene una interfaz
más pequeña. Una rutina que toma un parámetro está menos acoplada a los módulos que lo llaman
que una rutina que toma seis parámetros. Una clase con cuatro métodos públicos bien definidos
está menos acoplada a los módulos que la usan que una clase que expone 37 métodos públicos.
FlexibilidadLa flexibilidad se refiere a la facilidad con la que puede cambiar las conexiones entre los
módulos. Idealmente, desea algo más parecido al conector USB de su computadora que un cable
pelado y una pistola de soldar. La flexibilidad es en parte un producto del otro
5.3 Elementos básicos del diseño: heurística 101
características de acoplamiento, pero también es un poco diferente. Suponga que tiene una rutina
que busca la cantidad de vacaciones que recibe un empleado cada año, dada una fecha de
contratación y una clasificación de trabajo. Nombra la rutinaLookupVacationBenefit(). Supongamos
que en otro módulo tienes unempleadoobjeto que contiene la fecha de contratación y la clasificación
del puesto, entre otras cosas, y ese módulo pasa el objeto aLookupVacationBenefit().
Desde el punto de vista de los otros criterios, los dos módulos parecerían débilmente
acoplados. losempleadola conexión entre los dos módulos es visible y solo hay una conexión.
Ahora suponga que necesita usar elLookupVacationBenefit()módulo de un tercer módulo que
no tiene unempleadoobjeto pero que sí tiene una fecha de contratación y una clasificación
laboral. De repenteLookupVacationBenefit()parece menos amigable, poco dispuesto a
asociarse con el nuevo módulo.
El final feliz de la historia es que un módulo hostil puede hacer amigos si está dispuesto a ser
flexible; en este caso, al cambiar para tomar específicamente la fecha de contratación y la
clasificación del trabajo en lugar deempleado.
En resumen, cuanto más fácilmente otros módulos pueden llamar a un módulo, más
débilmente acoplado está, y eso es bueno porque es más flexible y fácil de mantener. Al crear
una estructura de sistema, divida el programa a lo largo de las líneas de interconexión
mínima. Si un programa fuera un trozo de madera, intentaría dividirlo con el grano.
Tipos de acoplamiento
Acoplamiento de objeto simpleUn módulo es un objeto simple acoplado a un objeto si instancia ese
objeto. Este tipo de acoplamiento está bien.
- Módulo 1pasa una bandera de control aMódulo2eso diceMódulo2qué hacer. Este enfoque
requiereMódulo 1hacer suposiciones sobre el funcionamiento interno de Módulo2, es decir,
quéMódulo2va a hacer con la bandera de control. SiMódulo2 define un tipo de datos
específico para el indicador de control (tipo u objeto enumerado), este uso probablemente sea
correcto.
- Módulo2usa datos globales después de que los datos globales hayan sido modificados por
Módulo 1. Este enfoque requiereMódulo2asumir queMódulo 1ha modificado los datos en las
formasMódulo2necesita ser modificado, y esoMódulo 1ha sido llamado en el momento
adecuado.
- Módulo 1La interfaz de indica que suMódulo1.Inicializar()La rutina debe llamarse antes
de suMódulo1.Rutina()se llama.Módulo2saber esoMódulo1.Rutina() llamadas
Módulo1.Inicializar()de todos modos, así que solo instanciaMódulo 1y llamadas
Módulo1.Rutina()sin llamarMódulo1.Inicializar()primero.
El acoplamiento semántico es peligroso porque cambiar el código en el módulo utilizado puede romper el
código en el módulo en uso de formas que el compilador no puede detectar en absoluto. Cuando un código
como este se rompe, se rompe de formas sutiles que parecen no estar relacionadas con el cambio realizado
en el módulo usado, lo que convierte la depuración en una tarea de Sisyphean.
El punto del acoplamiento flexible es que un módulo efectivo proporciona un nivel adicional de
abstracción: una vez que lo escribe, puede darlo por sentado. Reduce la complejidad general del
programa y le permite concentrarse en una cosa a la vez. Si usar un módulo requiere que se
concentre en más de una cosa a la vez (conocimiento de su funcionamiento interno, modificación de
datos globales, funcionalidad incierta), se pierde el poder de abstracción y se reduce o elimina la
capacidad del módulo para ayudar a administrar la complejidad.
Las clases y las rutinas son ante todo herramientas intelectuales para reducir la complejidad. Si
no están simplificando su trabajo, no están haciendo su trabajo.
PUNTO CLAVE
5.3 Elementos básicos del diseño: heurística 103
Los patrones brindan varios beneficios que el diseño totalmente personalizado no ofrece:
Los patrones reducen la complejidad al proporcionar abstracciones listas para usarSi dice: "Este
código usa un método de fábrica para crear instancias de clases derivadas", otros programadores en
su proyecto entenderán que su código involucra un conjunto bastante rico de interrelaciones y
protocolos de programación, todos los cuales se invocan cuando se refiere a la patrón de diseño de
Factory Method.
Factory Method es un patrón que le permite crear instancias de cualquier clase derivada de una clase base
específica sin necesidad de realizar un seguimiento de las clases derivadas individuales en cualquier lugar
que no sea Factory Method. Para una buena discusión del patrón del método de fábrica, consulte
"Reemplazar constructor con el método de fábrica" enrefactorización(Fowler 1999).
No tiene que deletrear cada línea de código para que otros programadores entiendan el enfoque
de diseño que se encuentra en su código.
Los patrones reducen los errores al institucionalizar los detalles de las soluciones comunesLos
problemas de diseño de software contienen matices que emergen completamente solo después de
que el problema se haya resuelto una o dos veces (o tres veces, o cuatro veces, o...). Debido a que los
patrones representan formas estandarizadas de resolver problemas comunes, encarnan la sabiduría
acumulada durante años de intentar resolver esos problemas y también encarnan las correcciones a
los falsos intentos que la gente ha hecho para resolver esos problemas.
Por lo tanto, usar un patrón de diseño es conceptualmente similar a usar código de biblioteca en lugar de escribir
uno propio. Claro, todos han escrito un Quicksort personalizado varias veces, pero ¿cuáles son las probabilidades de
que su versión personalizada sea completamente correcta en el primer intento? Del mismo modo, numerosos
problemas de diseño son lo suficientemente similares a problemas anteriores, por lo que es mejor utilizar una
Los patrones proporcionan valor heurístico al sugerir alternativas de diseñoUn diseñador que esté
familiarizado con los patrones comunes puede revisar fácilmente una lista de patrones y preguntar "¿Cuál
de estos patrones se ajusta a mi problema de diseño?" Recorrer un conjunto de alternativas familiares es
infinitamente más fácil que crear una solución de diseño personalizado de la nada. Y el código que surge de
un patrón familiar también será más fácil de entender para los lectores del código de lo que sería un código
completamente personalizado.
104 Capítulo 5: Diseño en la Construcción
Si aún no está familiarizado con los patrones de diseño, la Tabla 5-1 resume algunos de los
patrones más comunes para estimular su interés.
Patrón Descripción
Fábrica abstracta Admite la creación de conjuntos de objetos relacionados especificando el tipo de
conjunto pero no los tipos de cada objeto específico.
iterador Un objeto de servidor que proporciona acceso a cada elemento de un conjunto de forma
secuencial.
Observador Mantiene varios objetos sincronizados entre sí al hacer que un objeto sea
responsable de notificar al conjunto de objetos relacionados sobre los
cambios en cualquier miembro del conjunto.
único Proporciona acceso global a una clase que tiene una y solo una instancia.
Estrategia Define un conjunto de algoritmos o comportamientos que son
dinámicamente intercambiables entre sí.
Si no ha visto patrones de diseño antes, su reacción a las descripciones en la Tabla 5-1 podría ser
"Claro, ya conozco la mayoría de estas ideas". Esa reacción es una gran parte de por qué los patrones
de diseño son valiosos. Los patrones son familiares para la mayoría de los programadores
experimentados, y asignarles nombres reconocibles respalda una comunicación eficiente y efectiva
sobre ellos.
5.3 Elementos básicos del diseño: heurística 105
Una trampa potencial con los patrones es el código de ajuste forzado para usar un patrón. En algunos casos,
cambiar ligeramente el código para que se ajuste a un patrón bien reconocido mejorará la comprensión del
código. Pero si el código tiene que cambiarse demasiado, forzarlo a que parezca un patrón estándar a veces
puede aumentar la complejidad.
Otra trampa potencial con los patrones es la característica-itis: usar un patrón por el deseo de
probar un patrón en lugar de porque el patrón es una solución de diseño adecuada.
En general, los patrones de diseño son una herramienta poderosa para administrar la complejidad. Puede leer
descripciones más detalladas en cualquiera de los buenos libros que se enumeran al final de este capítulo.
Otras heurísticas
Las secciones anteriores describen las principales heurísticas de diseño de software. A continuación se presentan
algunas otras heurísticas que pueden no ser útiles con tanta frecuencia, pero que vale la pena mencionar.
La cohesión surgió del diseño estructurado y generalmente se analiza en el mismo contexto que el
acoplamiento. La cohesión se refiere a qué tan cerca todas las rutinas en una clase o todo el código en una
rutina apoyan un propósito central: qué tan enfocada está la clase. Las clases que contienen funcionalidad
fuertemente relacionada se describen como que tienen una fuerte cohesión, y el objetivo heurístico es hacer
que la cohesión sea lo más fuerte posible. La cohesión es una herramienta útil para administrar la
complejidad porque cuanto más el código de una clase respalda un propósito central, más fácilmente su
cerebro puede recordar todo lo que hace el código.
Pensar en la cohesión a nivel de rutina ha sido una heurística útil durante décadas y sigue
siendo útil hoy. A nivel de clase, la heurística de la cohesión ha sido subsumida en gran medida
por la heurística más amplia de abstracciones bien definidas, que se discutió anteriormente en
este capítulo y en el Capítulo 6. Las abstracciones también son útiles a nivel de rutina, pero en
un nivel más uniforme. base con cohesión a ese nivel de detalle.
Crear jerarquías
Una jerarquía es una estructura de información escalonada en la que la representación más general o
abstracta de los conceptos está contenida en la parte superior de la jerarquía, con representaciones cada vez
más detalladas y especializadas en los niveles inferiores de la jerarquía. En el software, las jerarquías se
encuentran en las jerarquías de clases y, como se ilustra en el nivel 4 de la figura 5-2, también en las
jerarquías de llamada de rutinas.
Las jerarquías han sido una herramienta importante para administrar conjuntos complejos de información
durante al menos 2000 años. Aristóteles utilizó una jerarquía para organizar el reino animal. Los humanos
frecuentemente usan esquemas para organizar información compleja (como este libro). Los investigadores
han descubierto que las personas generalmente encuentran que las jerarquías son una forma natural de
organizar información compleja. Cuando dibujan un objeto complejo como una casa, lo dibujan
jerárquicamente. Primero dibujan el contorno de la casa, luego las ventanas
106 Capítulo 5: Diseño en la Construcción
y puertas, y luego más detalles. No dibujan la casa ladrillo por ladrillo, teja por
teja o clavo por clavo (Simon 1996).
Las jerarquías son una herramienta útil para lograr el imperativo técnico principal del
software porque le permiten concentrarse solo en el nivel de detalle que le preocupa
actualmente. Los detalles no desaparecen por completo; simplemente se llevan a otro
nivel para que pueda pensar en ellos cuando quiera en lugar de pensar en todos los
detalles todo el tiempo.
Referencia cruzadaPara obtener más A un nivel más detallado, pensar en la interfaz de cada clase como un contrato con el resto del
información sobre los contratos, consulte
programa puede generar buenos conocimientos. Por lo general, el contrato es algo así como "Si
"Usar aserciones para documentar y
Los contratos son útiles para gestionar la complejidad porque, al menos en teoría, el objeto puede ignorar con
seguridad cualquier comportamiento no contractual. En la práctica, este problema es mucho más difícil.
Asignar responsabilidades
Otra heurística es pensar en cómo se deben asignar las responsabilidades a los objetos.
Preguntar de qué debe ser responsable cada objeto es similar a preguntar qué información
debe ocultar, pero creo que puede producir respuestas más amplias, lo que le da un valor
único a la heurística.
Un proceso de pensamiento que puede generar ideas de diseño interesantes es preguntarse cómo se
verá el sistema si lo diseña para facilitar las pruebas. ¿Necesita separar la interfaz de usuario del resto
del código para poder ejercitarla de forma independiente? ¿Necesita organizar cada subsistema para
que minimice las dependencias de otros subsistemas? Diseñar para la prueba tiende a resultar en
interfaces de clase más formalizadas, lo que generalmente es beneficioso.
Evite el fracaso
Las fallas de seguridad de alto perfil de varios sistemas bien conocidos en los últimos años hacen que sea difícil no
estar de acuerdo en que deberíamos encontrar formas de aplicar los conocimientos de fallas de diseño de
Petroski al software.
Referencia cruzadaPara obtener más información El tiempo de vinculación se refiere al tiempo que un valor específico está vinculado a una variable. El código
sobre el tiempo de vinculación, consulte la Sección 10.6,
que se enlaza temprano tiende a ser más simple, pero también tiende a ser menos flexible. A veces, puede
“Tiempo de vinculación”.
obtener una buena perspectiva del diseño al hacer preguntas como estas: ¿Qué pasa si vinculo estos valores
antes? ¿Qué pasa si vinculo estos valores más tarde? ¿Qué pasa si inicialicé esta tabla aquí mismo en el
código? ¿Qué pasa si leo el valor de esta variable del usuario en tiempo de ejecución?
PJ Plauger dice que su principal preocupación es "El principio de un lugar correcto: debe haber un
lugar correcto para buscar cualquier pieza de código no trivial y un lugar correcto para realizar un
cambio de mantenimiento probable" (Plauger 1993). El control se puede centralizar en clases,
rutinas, macros de preprocesador,#incluirarchivos: incluso una constante con nombre es un ejemplo
de un punto central de control.
En caso de duda, utilice la fuerza Una poderosa herramienta heurística es la fuerza bruta. No lo subestimes. Una solución de fuerza
bruta.
bruta que funciona es mejor que una solución elegante que no funciona. Puede llevar mucho tiempo
—Mayordomo Lampson
lograr que una solución elegante funcione. Al describir la historia de los algoritmos de búsqueda, por
ejemplo, Donald Knuth señaló que, aunque la primera descripción de un algoritmo de búsqueda
binaria se publicó en 1946, se necesitaron otros 16 años para que alguien publicara un algoritmo que
buscara correctamente listas de todos los tamaños ( Knuth 1998). Una búsqueda binaria es más
elegante, pero una búsqueda secuencial de fuerza bruta suele ser suficiente.
dibujar un diagrama
Los diagramas son otra poderosa herramienta heurística. Una imagen vale más que 1000
palabras, algo así. En realidad, desea omitir la mayoría de las 1000 palabras porque un punto
de usar una imagen es que una imagen puede representar el problema en un nivel más alto
de abstracción. A veces desea tratar el problema en detalle, pero otras veces desea poder
trabajar con más generalidad.
El objetivo de la modularidad es hacer que cada rutina o clase sea como una "caja
negra": sabes lo que entra y lo que sale, pero no sabes lo que sucede dentro. A
108 Capítulo 5: Diseño en la Construcción
black box tiene una interfaz tan simple y una funcionalidad tan bien definida que para cualquier
entrada específica puede predecir con precisión la salida correspondiente.
- Crear jerarquías
- Asignar responsabilidades
- Evite el fracaso
- dibujar un diagrama
Los enfoques de diseño en software pueden aprender de los enfoques de diseño en otros campos. Uno de
los libros originales sobre heurística en la resolución de problemas fue el de G. Polya.Cómo resolverlo(1957).
El enfoque generalizado de resolución de problemas de Polya se centra en la resolución de problemas en
matemáticas. La figura 5-10 es un resumen de su enfoque, adaptado de un resumen similar en su libro
(énfasis suyo).
¿Has visto el problema antes? ¿O ha visto el mismo problema en una forma ligeramente
diferente?¿Conoces algún problema relacionado?¿Conoces algún teorema que pueda ser
útil?
¡Mira lo desconocido!Y trata de pensar en un problema familiar que tenga la misma o
similar incógnita.Aquí hay un problema relacionado con el tuyo y resuelto antes. ¿Puedes
usarlo? ¿Puedes usar su resultado? ¿Puedes usar su método? ¿Debe introducir algún
elemento auxiliar para hacer posible su uso?
¿Puedes replantear el problema? ¿Puedes replantearlo todavía de manera diferente? Vuelve a las
definiciones.
Si no puede resolver el problema propuesto, intente resolver primero algún problema
relacionado. ¿Te imaginas un problema relacionado más accesible? ¿Un problema más general?
¿Un problema más especial? ¿Un problema análogo? ¿Puedes resolver una parte del problema?
Mantenga solo una parte de la condición, deje caer la otra parte; ¿Hasta qué punto se determina
entonces la incógnita, cómo puede variar? ¿Puedes derivar algo útil de los datos? ¿Puedes pensar
en otros datos apropiados para determinar la incógnita? ¿Puedes cambiar la incógnita o los datos,
o ambos si es necesario, para que la nueva incógnita y los nuevos datos estén más cerca uno del
otro?
¿Usaste todos los datos? ¿Usaste toda la condición? ¿Ha tenido en cuenta todas
las nociones esenciales involucradas en el problema?
Figura 5-10G. Polya desarrolló un enfoque para la resolución de problemas en matemáticas que también es
útil para resolver problemas en el diseño de software (Polya 1957).
110 Capítulo 5: Diseño en la Construcción
Una de las pautas más efectivas es no quedarse atascado en un solo enfoque. Si diagramar el
diseño en UML no funciona, escríbalo en inglés. Escriba un breve programa de prueba. Pruebe
un enfoque completamente diferente. Piense en una solución de fuerza bruta. Sigue
delineando y dibujando con tu lápiz, y tu cerebro te seguirá. Si todo lo demás falla, aléjese del
problema. Sal a caminar literalmente, o piensa en otra cosa antes de volver al problema. Si ha
dado lo mejor de sí y no está llegando a ninguna parte, dejarlo de lado por un tiempo a
menudo produce resultados más rápidamente que la persistencia.
No es necesario que resuelva todo el problema de diseño de una sola vez. Si se atasca,
recuerde que se debe decidir un punto, pero reconozca que aún no tiene suficiente
información para resolver ese problema específico. ¿Por qué luchar por el último 20 por ciento
del diseño cuando encajará fácilmente la próxima vez? ¿Por qué tomar malas decisiones
basadas en una experiencia limitada con el diseño cuando puede tomar buenas decisiones
basadas en más experiencia posterior? Algunas personas se sienten incómodas si no llegan al
cierre después de un ciclo de diseño, pero después de haber creado algunos diseños sin
resolver problemas prematuramente, parecerá natural dejar los problemas sin resolver hasta
que tenga más información (Zahniser 1992, Beck 2000) .
Iterar
Es posible que haya tenido una experiencia en la que aprendió tanto al escribir un programa
que desearía poder escribirlo de nuevo, armado con los conocimientos que obtuvo al escribirlo
la primera vez. El mismo fenómeno se aplica al diseño, pero los ciclos de diseño son más cortos
y los efectos posteriores son mayores, por lo que puede darse el lujo de dar vueltas en el ciclo
del diseño varias veces.
El diseño es un proceso iterativo. Por lo general, no vas del punto A solo al punto B; vas
del punto A al punto B y de vuelta al punto A.
PUNTO CLAVE A medida que recorre los diseños candidatos y prueba diferentes enfoques, observará vistas de
alto y bajo nivel. El panorama general que obtiene al trabajar con problemas de alto nivel lo
ayudará a poner en perspectiva los detalles de bajo nivel. Los detalles que obtenga al trabajar
con problemas de bajo nivel proporcionarán una base sólida para las decisiones de alto nivel. El
tira y afloja entre el nivel superior y el nivel inferior
5.4 Prácticas de diseño 111
consideraciones es una dinámica saludable; crea una estructura estresada que es más estable que una
construida completamente de arriba hacia abajo o de abajo hacia arriba.
Muchos programadores, muchas personas, para el caso, tienen problemas para oscilar
entre consideraciones de alto y bajo nivel. Cambiar de una vista de un sistema a otra es
mentalmente extenuante, pero es esencial para crear diseños efectivos. Para ejercicios
entretenidos para mejorar su flexibilidad mental, leaSuperación conceptual(Adams 2001),
descrito en la sección “Recursos adicionales” al final del capítulo.
Referencia cruzadaLa refactorización es Cuando se te ocurra un primer intento de diseño que parezca lo suficientemente bueno, ¡no te
una forma segura de probar diferentes
detengas! El segundo intento casi siempre es mejor que el primero, y aprende cosas en cada
alternativas en el código. Para obtener más
Divide y conquistaras
Como señaló Edsger Dijkstra, el cráneo de nadie es lo suficientemente grande como para contener todos los
detalles de un programa complejo, y eso se aplica igualmente al diseño. Divida el programa en diferentes
áreas de interés y luego aborde cada una de esas áreas individualmente. Si te encuentras con un callejón sin
salida en una de las áreas, ¡itera!
"De arriba hacia abajo" y "de abajo hacia arriba" pueden sonar anticuados, pero brindan información
valiosa sobre la creación de diseños orientados a objetos. El diseño de arriba hacia abajo comienza
en un alto nivel de abstracción. Defina clases base u otros elementos de diseño no específicos. A
medida que desarrolla el diseño, aumenta el nivel de detalle, identificando clases derivadas, clases
colaboradoras y otros elementos de diseño detallados.
El diseño de abajo hacia arriba comienza con detalles y trabaja hacia generalidades. Por lo
general, comienza identificando objetos concretos y luego generaliza agregaciones de objetos y
clases base a partir de esos detalles.
Algunas personas argumentan con vehemencia que lo mejor es comenzar con generalidades y trabajar hacia los
detalles, y algunos argumentan que realmente no se pueden identificar los principios generales de diseño hasta que
se hayan resuelto los detalles significativos. Aquí están los argumentos de ambos lados.
112 Capítulo 5: Diseño en la Construcción
El principio rector detrás del enfoque de arriba hacia abajo es la idea de que el cerebro
humano puede concentrarse solo en una cierta cantidad de detalles a la vez. Si comienza con
clases generales y las descompone en clases más especializadas paso a paso, su cerebro no se
ve obligado a lidiar con demasiados detalles a la vez.
A veces, el enfoque de arriba hacia abajo es tan abstracto que es difícil comenzar. Si necesita trabajar
con algo más tangible, pruebe el enfoque de diseño de abajo hacia arriba. Pregúntese: "¿Qué sé que
debe hacer este sistema?" Sin duda, usted puede responder a esa pregunta. Puede identificar
algunas responsabilidades de bajo nivel que puede asignar a clases concretas. Por ejemplo, es
posible que sepa que un sistema necesita formatear un informe en particular, calcular datos para ese
informe, centrar sus encabezados, mostrar el informe en la pantalla, imprimir el informe en una
impresora, etc. Después de identificar varias responsabilidades de bajo nivel, por lo general
comenzará a sentirse lo suficientemente cómodo como para volver a buscar en la parte superior.
En algunos otros casos, los principales atributos del problema de diseño se dictan desde abajo.
Es posible que deba interactuar con dispositivos de hardware cuyos requisitos de interfaz
dicten grandes partes de su diseño.
Aquí hay algunas cosas que debe tener en cuenta al hacer una composición de abajo hacia arriba:
- Continúe con el siguiente nivel hacia arriba, o vuelva a la parte superior e intente bajar de nuevo.
5.4 Prácticas de diseño 113
La diferencia clave entre las estrategias de arriba hacia abajo y de abajo hacia arriba es que una
es una estrategia de descomposición y la otra es una estrategia de composición. Uno parte del
problema general y lo descompone en partes manejables; el otro comienza con piezas
manejables y construye una solución general. Ambos enfoques tienen fortalezas y debilidades
que querrá considerar cuando los aplique a sus problemas de diseño.
La fuerza del diseño de arriba hacia abajo es que es fácil. Las personas son buenas para dividir algo
grande en componentes más pequeños, y los programadores son especialmente buenos en eso.
Otro punto fuerte del diseño de arriba hacia abajo es que puede diferir los detalles de construcción. Dado que los
sistemas a menudo se ven perturbados por cambios en los detalles de construcción (por ejemplo, cambios en la
estructura de un archivo o en el formato de un informe), es útil saber desde el principio que esos detalles deben
ocultarse en las clases en la parte inferior de la jerarquía.
Una de las fortalezas del enfoque de abajo hacia arriba es que generalmente da como resultado una
identificación temprana de la funcionalidad de utilidad necesaria, lo que da como resultado un diseño
compacto y bien factorizado. Si ya se han construido sistemas similares, el enfoque de abajo hacia arriba le
permite comenzar el diseño del nuevo sistema mirando partes del sistema anterior y preguntando "¿Qué
puedo reutilizar?"
Una debilidad del enfoque de composición de abajo hacia arriba es que es difícil de usar exclusivamente. La
mayoría de las personas son mejores tomando un concepto grande y dividiéndolo en conceptos más
pequeños que tomando conceptos pequeños y convirtiéndolos en uno grande. Es como el viejo problema
de armarlo usted mismo: pensé que había terminado, entonces, ¿por qué la caja todavía tiene partes?
Afortunadamente, no tiene que utilizar exclusivamente el enfoque de composición de abajo hacia arriba.
Otra debilidad de la estrategia de diseño de abajo hacia arriba es que a veces descubres que no puedes
construir un programa a partir de las piezas con las que comenzaste. No puedes construir un avión con
ladrillos, y es posible que tengas que trabajar en la parte superior antes de saber qué tipo de piezas
necesitas en la parte inferior.
Para resumir, de arriba hacia abajo tiende a comenzar de manera simple, pero a veces la complejidad de
bajo nivel vuelve a la parte superior, y esas ondas pueden hacer que las cosas sean más complejas de lo que
realmente deberían ser. De abajo hacia arriba tiende a comenzar complejo, pero identificar esa complejidad
desde el principio conduce a un mejor diseño de las clases de nivel superior, ¡si la complejidad no torpedea
todo el sistema primero!
En el análisis final, el diseño de arriba hacia abajo y de abajo hacia arriba no son estrategias competitivas,
son mutuamente beneficiosas. El diseño es un proceso heurístico, lo que significa que no se garantiza que
ninguna solución funcione siempre. El diseño contiene elementos de prueba y error. Pruebe una variedad
de enfoques hasta que encuentre uno que funcione bien.
Traducido del inglés al español - www.onlinedoctranslator.com
Prototipos Experimentales
cc2e.com/0599 A veces, no puede saber realmente si un diseño funcionará hasta que comprenda mejor algunos
detalles de implementación. Es posible que no sepa si una organización de base de datos en
particular funcionará hasta que sepa si cumplirá con sus objetivos de rendimiento. Es posible que no
sepa si un diseño de subsistema en particular funcionará hasta que seleccione las bibliotecas GUI
específicas con las que trabajará. Estos son ejemplos de la "maldad" esencial del diseño de software:
no puede definir completamente el problema de diseño hasta que lo haya resuelto al menos
parcialmente.
Una técnica general para abordar estas preguntas a bajo costo es la creación de prototipos experimentales.
La palabra “creación de prototipos” significa muchas cosas diferentes para diferentes personas (McConnell
1996). En este contexto, la creación de prototipos significa escribir la cantidad mínima absoluta de código
desechable que se necesita para responder a una pregunta de diseño específica.
La creación de prototipos funciona mal cuando los desarrolladores no son disciplinados a la hora de escribir
elmínimo absolutode código necesario para responder una pregunta. Supongamos que la pregunta de
diseño es: "¿Puede el marco de la base de datos que hemos seleccionado admitir el volumen de
transacciones que necesitamos?" No necesita escribir ningún código de producción para responder esa
pregunta. Ni siquiera necesita saber los detalles de la base de datos. Solo necesita saber lo suficiente para
aproximar el espacio del problema: número de tablas, número de entradas en las tablas, etc. Luego puede
escribir un código de creación de prototipos muy simple que use tablas con nombres comoTabla 1,Tabla 2, y
columna1, ycolumna2, rellene las tablas con datos no deseados y realice sus pruebas de rendimiento.
Un último riesgo de creación de prototipos surge cuando los desarrolladores no tratan el código
comotirar a la basura código. Descubrí que no es posible que las personas escriban la cantidad
mínima absoluta de código para responder una pregunta si creen que el código eventualmente
terminará en el sistema de producción. Terminan implementando el sistema en lugar de crear
prototipos. Al adoptar la actitud de que una vez que se responda la pregunta, el código se desechará,
puede minimizar este riesgo. Una forma de evitar este problema es crear prototipos en una
tecnología diferente a la del código de producción. Puede crear un prototipo de un diseño de Java en
Python o simular una interfaz de usuario en Microsoft PowerPoint. Si crea prototipos usando la
tecnología de producción, un estándar práctico que puede ayudar es requerir que los nombres de
clases o paquetes para el código del prototipo tengan el prefijo prototipo. Eso al menos hace que un
programador se lo piense dos veces antes de tratar de extender el código prototipo (Stephens 2003).
5.4 Prácticas de diseño 115
Utilizado con disciplina, la creación de prototipos es la herramienta de trabajo que tiene un diseñador para combatir
la maldad del diseño. Usado sin disciplina, la creación de prototipos agrega algo de maldad propia.
Diseño colaborativo
Referencia cruzadaPara obtener más En diseño, dos cabezas a menudo son mejores que una, ya sea que esas dos cabezas estén organizadas
detalles sobre el desarrollo
formal o informalmente. La colaboración puede tomar cualquiera de varias formas:
colaborativo, consulte el Capítulo 21,
“Construcción colaborativa”.
- Te acercas informalmente al escritorio de un compañero de trabajo y pides intercambiar algunas
ideas.
- Usted y su compañero de trabajo se sientan juntos en una sala de conferencias y dibujan alternativas
de diseño en una pizarra.
- Usted y su compañero de trabajo se sientan juntos frente al teclado y hacen un diseño detallado en
el lenguaje de programación que está usando, es decir, pueden usar la programación en pareja,
descrita en el Capítulo 21, “Construcción colaborativa”.
- Usted programa una reunión para analizar sus ideas de diseño con uno o más compañeros de
trabajo.
- Programa una inspección formal con toda la estructura descrita en el Capítulo 21.
- No trabajas con nadie que pueda revisar tu trabajo, por lo que haces un trabajo
inicial, lo guardas en un cajón y vuelves a él una semana después. Habrás olvidado
lo suficiente como para poder darte una crítica bastante buena.
apresuramos en el proceso de Una pregunta relacionada es qué tan formal hacer el diseño. ¿Necesita diagramas de diseño
diseño.
formales y pulidos, o serían suficientes instantáneas digitales de algunos dibujos en una
—glenford-myers
pizarra?
116 Capítulo 5: Diseño en la Construcción
Decidir cuánto diseño hacer antes de comenzar la codificación a gran escala y cuánta
formalidad usar para documentar ese diseño no es una ciencia exacta. Se debe considerar la
experiencia del equipo, la vida útil esperada del sistema, el nivel deseado de confiabilidad y el
tamaño del proyecto y del equipo. La Tabla 5-2 resume cómo cada uno de estos factores
influye en el enfoque de diseño.
Dos o más de estos factores pueden entrar en juego en cualquier proyecto específico y, en algunos
casos, los factores pueden proporcionar consejos contradictorios. Por ejemplo, es posible que tenga
un equipo con mucha experiencia trabajando en software crítico para la seguridad. En ese caso,
probablemente querrá errar por el nivel más alto de detalle y formalidad del diseño. En tales casos,
deberá sopesar la importancia de cada factor y emitir un juicio sobre lo que más importa.
Si el nivel de diseño se deja a cada individuo, entonces, cuando el diseño descienda al nivel de
una tarea que ha realizado antes o a una simple modificación o extensión de dicha tarea,
probablemente esté listo para dejar de diseñar y empezar a codificar.
5.4 Prácticas de diseño 117
Si no puedo decidir qué tan profundamente investigar un diseño antes de comenzar a codificar, tiendo a
errar por el lado de entrar en más detalles. Los mayores errores de diseño surgen de casos en los que
pensé que fui lo suficientemente lejos, pero luego resultó que no fui lo suficientemente lejos como para
darme cuenta de que había desafíos de diseño adicionales. En otras palabras, los mayores problemas de
diseño tienden a surgir no de áreas que sabía que eran difíciles y para las que creé malos diseños, sino de
áreas que pensé que eran fáciles y para las que no creé ningún diseño. Raramente encuentro proyectos que
sufren por haber hecho demasiado trabajo de diseño.
Nunca he conocido a un ser Por otro lado, de vez en cuando he visto proyectos que están sufriendo de demasiado diseño.
humano que quisiera leer
documentación. La Ley de Gresham establece que “la actividad programada tiende a expulsar
17.000 páginas de
documentación, y si las la actividad no programada” (Simon 1965). Una carrera prematura para pulir la descripción de
hubiera, lo mataría para un diseño es un buen ejemplo de esa ley. Preferiría que el 80 por ciento del esfuerzo de diseño
sacarlo del acervo genético.
se dedique a la creación y exploración de numerosas alternativas de diseño y el 20 por ciento a
—José Costello
la creación de documentación menos pulida que el 20 por ciento a la creación de alternativas
de diseño mediocres y el 80 por ciento a la documentación pulida de diseños que no son Muy
bueno.
La mala noticia es que, en nuestra Insertar documentación de diseño en el propio código.Documente las decisiones de diseño clave en los
opinión, nunca encontraremos la
comentarios del código, normalmente en el archivo o en el encabezado de la clase. Cuando combina este
piedra filosofal. Nunca encontraremos
Escribir resúmenes por correo electrónicoDespués de una discusión de diseño, adopte la práctica de designar a
alguien para que escriba un resumen de la discusión, especialmente lo que se decidió, y envíelo al equipo del
proyecto. Archive una copia del correo electrónico en la carpeta de correo electrónico pública del proyecto.
118 Capítulo 5: Diseño en la Construcción
Usa una cámara digitalUna barrera común para documentar diseños es el tedio de crear
dibujos de diseño en algunas herramientas de dibujo populares. Pero las opciones de
documentación no se limitan a las dos opciones de "capturar el diseño en una notación formal
bien formateada" frente a "ninguna documentación de diseño".
Tomar fotografías de dibujos de pizarra con una cámara digital y luego incrustar esas imágenes en
documentos tradicionales puede ser una forma sencilla de obtener el 80 por ciento del beneficio de guardar
los dibujos de diseño haciendo aproximadamente el 1 por ciento del trabajo requerido si usa una
herramienta de dibujo. .
Guardar rotafolios de diseñoNo hay ninguna ley que diga que la documentación de su diseño deba caber
en papel tamaño carta estándar. Si realiza sus dibujos de diseño en papel de rotafolio grande, simplemente
puede archivar los rotafolios en un lugar conveniente o, mejor aún, publicarlos en las paredes alrededor del
área del proyecto para que las personas puedan consultarlos fácilmente y actualizarlos cuando lo necesiten.
necesario.
cc2e.com/0513 Usar tarjetas CRC (Clase, Responsabilidad, Colaborador)Otra alternativa de baja tecnología
para documentar diseños es usar fichas. En cada tarjeta, los diseñadores escriben un nombre
de clase, responsabilidades de la clase y colaboradores (otras clases que cooperan con la
clase). Luego, un grupo de diseño trabaja con las tarjetas hasta que están satisfechos de haber
creado un buen diseño. En ese momento, simplemente puede guardar las tarjetas para
referencia futura. Las fichas son baratas, no intimidantes y portátiles, y fomentan la
interacción grupal (Beck 1991).
Cree diagramas UML en los niveles apropiados de detalleUna técnica popular para diagramar
diseños se llama Lenguaje de modelado unificado (UML), que está definido por el Grupo de gestión
de objetos (Fowler 2004). La figura 5-6, anteriormente en este capítulo, era un ejemplo de un
diagrama de clases UML. UML proporciona un rico conjunto de representaciones formalizadas para
entidades y relaciones de diseño. Puede usar versiones informales de UML para explorar y discutir
enfoques de diseño. Comience con bocetos mínimos y agregue detalles solo después de haberse
concentrado en una solución de diseño final. Debido a que UML está estandarizado, admite el
entendimiento común en la comunicación de ideas de diseño y puede acelerar el proceso de
considerar alternativas de diseño cuando se trabaja en grupo.
Estas técnicas pueden funcionar en varias combinaciones, así que siéntase libre de mezclar y combinar estos
enfoques proyecto por proyecto o incluso dentro de diferentes áreas de un solo proyecto.
Las personas que predican el diseño de Mientras escribo esta edición a mediados de la década de 2000, algunos swamis de software están discutiendo a
software como una actividad disciplinada
favor de no hacer ningún diseño en absoluto. “Big Design Up Front esBDUF," ellos dicen. “BDUF es malo. ¡Es mejor
gastan una cantidad considerable
suficientemente estructurados u En diez años, el péndulo ha pasado de “diseñar todo” a “diseñar nada”. Pero la alternativa a
orientados a objetos para alcanzar el BDUF no es ningún diseño por adelantado, es un diseño pequeño por adelantado (LDUF) o
nirvana en esta vida. Todos llevamos
diseño suficiente por adelantado:ENUF.
una especie de pecado original por
que la mayoría de nosotros somos la perfección. Pero aunque no se puede saber con certeza la cantidad correcta de diseño, se
mejores diseñadores de lo que jamás garantiza que dos cantidades de diseño serán incorrectas cada vez: diseñar hasta el último detalle y
reconocerán los puristas.
no diseñar nada en absoluto. ¡Las dos posiciones defendidas por los extremistas en ambos extremos
—PJ Plauger de la escala resultan ser las únicas dos posiciones que siempre están equivocadas!
Como dice PJ Plauger, “Cuanto más dogmático sea sobre la aplicación de un método de diseño,
menos problemas de la vida real resolverá” (Plauger 1993). Trate el diseño como un proceso
perverso, descuidado y heurístico. No te conformes con el primer diseño que se te ocurra. Colaborar.
Esfuércese por la simplicidad. Crea prototipos cuando lo necesites. Iterar, iterar e iterar de nuevo.
Estarás feliz con tus diseños.
Recursos adicionales
cc2e.com/0520 El diseño de software es un campo rico con abundantes recursos. El desafío es
identificar qué recursos serán más útiles. Aquí hay algunas sugerencias.
Riel, Arturo J.Heurística de diseño orientado a objetos. Reading, MA: Addison-Wesley, 1996.
Este libro es fácil de leer y se enfoca en el diseño a nivel de clase.
Plauger, PJProgramación a propósito: ensayos sobre diseño de software. Englewood Cliffs, NJ: PTR
Prentice Hall, 1993. Obtuve tantos consejos sobre un buen diseño de software leyendo este libro
como de cualquier otro libro que haya leído. Plauger está bien versado en una amplia variedad de
enfoques de diseño, es pragmático y es un gran escritor.
120 Capítulo 5: Diseño en la Construcción
RaymondEric S.El arte de la programación UNIX. Boston, MA: Addison-Wesley, 2004. Esta es una mirada bien
investigada al diseño de software a través de lentes de colores UNIX. La Sección 1.6 es una explicación
especialmente concisa de 12 páginas de 17 principios clave de diseño de UNIX.
Parnas, David L. y Paul C. Clements. "Un proceso de diseño racional: cómo y por qué
falsificarlo".Transacciones IEEE sobre ingeniería de softwareSE-12, núm. 2 (febrero de 1986):
251–57. Este artículo clásico describe la brecha entre cómo se diseñan realmente los
programas y cómo a veces desearías que se diseñaran. El punto principal es que nadie pasa
realmente por un proceso de diseño racional y ordenado, pero apuntar a él hace que al final se
obtengan mejores diseños.
No tengo conocimiento de ningún tratamiento integral de la ocultación de información. La mayoría de los libros de texto de
ingeniería de software lo tratan brevemente, frecuentemente en el contexto de las técnicas orientadas a objetos. Los tres
artículos de Parnas que se enumeran a continuación son las presentaciones fundamentales de la idea y probablemente sigan
Parnas, David L. "Sobre los criterios que se utilizarán para descomponer sistemas en
módulos". Comunicaciones de la ACM5, núm. 12 (diciembre de 1972): 1053-58.
Parnas, David L., Paul C. Clements y DM Weiss. "La estructura modular de los sistemas
complejos".Transacciones IEEE sobre ingeniería de softwareSE-11, núm. 3 (marzo de 1985):
259-66.
Patrones de diseño
Gama, Erich, et al.Patrones de diseño. Reading, MA: Addison-Wesley, 1995. Este libro de
"Gang of Four" es el libro seminal sobre patrones de diseño.
Diseño en General
Adams, James L.Blockbusting conceptual: una guía para mejores ideas, 4ª ed. Cambridge, MA:
Perseus Publishing, 2001. Aunque no trata específicamente sobre el diseño de software, este libro se
escribió para enseñar diseño a los estudiantes de ingeniería de Stanford. Incluso si nunca diseñas
nada, el libro es una discusión fascinante sobre los procesos de pensamiento creativo. Incluye
muchos ejercicios sobre los tipos de pensamiento necesarios para un diseño efectivo. También
contiene una bibliografía bien anotada sobre diseño y pensamiento creativo. Si te gusta resolver
problemas, este libro te gustará.
Polia, G.Cómo resolverlo: un nuevo aspecto del método matemático, 2ª ed. Princeton, NJ: Princeton
University Press, 1957. Esta discusión sobre heurística y resolución de problemas se centra en las
matemáticas pero es aplicable al desarrollo de software. El libro de Polya fue el primero escrito sobre
el uso de la heurística en la resolución de problemas matemáticos. Hace una clara distinción entre las
heurísticas desordenadas que se usan para descubrir soluciones y las técnicas más ordenadas que se
usan para presentarlas una vez que se han descubierto. No es fácil de leer, pero si está interesado en
la heurística, eventualmente lo leerá, lo quiera o no. El libro de Polya deja en claro que la resolución
de problemas no es una actividad determinista y que adherirse a una sola metodología es como
caminar con los pies encadenados. En un momento, Microsoft entregó este libro a todos sus nuevos
programadores.
Simón, Herbert.Las Ciencias de lo Artificial, 3d ed. Cambridge, MA: MIT Press, 1996. Este libro
fascinante establece una distinción entre las ciencias que se ocupan del mundo natural
(biología, geología, etc.) y las ciencias que se ocupan del mundo artificial creado por los
humanos (negocios, arquitectura e informática). Ciencias). Luego se discuten las características
de las ciencias de lo artificial, enfatizando la ciencia del diseño. Tiene un tono académico y vale
la pena leerlo para cualquiera que esté interesado en una carrera en desarrollo de software o
cualquier otro campo "artificial".
vidrio, roberto l.Creatividad de software. Englewood Cliffs, NJ: Prentice Hall PTR, 1995. ¿El desarrollo
de software está más controlado por la teoría o por la práctica? ¿Es principalmente creativo o es
principalmente determinista? ¿Qué cualidades intelectuales necesita un desarrollador de software?
Este libro contiene una discusión interesante sobre la naturaleza del desarrollo de software con un
énfasis especial en el diseño.
Estándares
IEEE Std 1016-1998, práctica recomendada para descripciones de diseño de software. Este
documento contiene el estándar IEEE-ANSI para descripciones de diseño de software. Describe lo
que debe incluirse en un documento de diseño de software.
Prácticas de diseño
- ¿Ha iterado, seleccionando el mejor de varios intentos en lugar del primer
intento?
- ¿Ha capturado su trabajo de diseño utilizando una técnica adecuada como Wiki,
correo electrónico, rotafolios, fotografía digital, UML, tarjetas CRC o comentarios
en el propio código?
Objetivos de diseño
- ¿Las clases están diseñadas para una interacción mínima entre sí?
Puntos clave 123
- ¿Están las clases y los subsistemas diseñados para que pueda usarlos en otros
sistemas?
Puntos clave
- El imperativo técnico principal del software esgestionar la complejidad. Esto se ve muy favorecido
por un enfoque de diseño en la simplicidad.
- El buen diseño es iterativo; Cuantas más posibilidades de diseño pruebe, mejor será
su diseño final.
- Mucha información útil e interesante sobre diseño está disponible fuera de este libro.
Las perspectivas presentadas aquí son solo la punta del iceberg.
Capítulo 6
clases trabajadoras
cc2e.com/0665 Contenido
Temas relacionados
- Refactorización: Capítulo 24
Una clase es una colección de datos y rutinas que comparten una responsabilidad cohesiva y bien definida.
Una clase también puede ser una colección de rutinas que proporciona un conjunto cohesivo de servicios
incluso si no hay datos comunes involucrados. Una clave para ser un programador efectivo es maximizar la
PUNTO CLAVE
parte de un programa que puede ignorar de manera segura mientras trabaja en cualquier sección de
código. Las clases son la herramienta principal para lograr ese objetivo.
Este capítulo contiene una síntesis de consejos para crear clases de alta calidad. Si todavía se está
calentando con los conceptos orientados a objetos, este capítulo puede ser demasiado avanzado.
Asegúrese de haber leído el Capítulo 5, “Diseño en la construcción”. Luego comience con la Sección
6.1, “Fundamentos de clases: Tipos de datos abstractos (ADT)”, y continúe con las secciones
restantes. Si ya está familiarizado con los conceptos básicos de las clases, puede hojear la Sección 6.1
y luego sumergirse en la discusión de las interfaces de clase en la Sección 6.2. La sección "Recursos
adicionales" al final de este capítulo contiene sugerencias para lecturas introductorias, lecturas
avanzadas y recursos específicos del lenguaje de programación.
125
126 Capítulo 6: Clases obreras
Referencia cruzadaPensar Comprender los ADT es esencial para comprender la programación orientada a objetos. Sin
primero en los ADT y luego en las
comprender los ADT, los programadores crean clases que son "clases" solo de nombre; en realidad,
clases es un ejemplo de
programación.dentroun lenguaje
son poco más que estuches de transporte convenientes para colecciones de datos y rutinas
vs programación en uno. Consulte vagamente relacionadas. Con una comprensión de los ADT, los programadores pueden crear clases
la Sección 4.3, “Su ubicación en la
que son más fáciles de implementar inicialmente y más fáciles de modificar con el tiempo.
ola tecnológica” y la Sección 34.4,
“Programe en su idioma, no en él”.
Tradicionalmente, los libros de programación se vuelven matemáticos cuando llegan al tema de los tipos de
datos abstractos. Tienden a hacer afirmaciones como "Uno puede pensar en un tipo de datos abstractos
como un modelo matemático con una colección de operaciones definidas en él". Dichos libros hacen que
parezca que nunca usaría un tipo de datos abstracto, excepto como ayuda para dormir.
Tales explicaciones secas de tipos de datos abstractos pierden completamente el punto. Los tipos de datos
abstractos son emocionantes porque puede usarlos para manipular entidades del mundo real en lugar de
entidades de implementación de bajo nivel. En lugar de insertar un nodo en una lista vinculada, puede
agregar una celda a una hoja de cálculo, un nuevo tipo de ventana a una lista de tipos de ventanas u otro
automóvil de pasajeros a una simulación de tren. ¡Aproveche el poder de poder trabajar en el dominio del
problema en lugar de en el dominio de implementación de bajo nivel!
Suponga que está escribiendo un programa para controlar la salida de texto a la pantalla utilizando una
variedad de tipos de letra, tamaños de puntos y atributos de fuente (como negrita y cursiva). Parte del
programa manipula las fuentes del texto. Si usa un ADT, tendrá un grupo de rutinas de fuentes incluidas
con los datos (los nombres de los tipos de letra, los tamaños de puntos y los atributos de las fuentes) con los
que operan. La colección de rutinas de fuentes y datos es un ADT.
Si no está utilizando ADT, adoptará un enfoque ad hoc para manipular las fuentes. Por ejemplo, si
necesita cambiar a un tamaño de fuente de 12 puntos, que resulta ser de 16 píxeles de alto, tendrá
un código como este:
fuente actual.tamaño = 16
6.1 Fundamentos de clase: tipos de datos abstractos (ADT) 127
Si ha creado una colección de rutinas de biblioteca, el código podría ser un poco más
legible:
currentFont.size = PointsToPixels( 12 )
currentFont.sizeInPixels = PointsToPixels( 12 )
Si necesita configurar una fuente en negrita, es posible que tenga un código como este que usa una fuente lógicaoy
una constante hexadecimal0x02:
Si tiene suerte, tendrá algo más limpio que eso, pero lo mejor que obtendrá con un
enfoque ad hoc es algo como esto:
fuenteActual.bold = Verdadero
Al igual que con el tamaño de fuente, la limitación es que se requiere el código del cliente para controlar los
Si programa de esta manera, es probable que tenga líneas similares en muchos lugares de su
programa.
V413HAV
128 Capítulo 6: Clases obreras
Puedes hacer que la interfaz sea más informativa.Código comofuente actual.tamaño = 16es
ambiguo porquedieciséispodría ser un tamaño en píxeles o puntos. El contexto no te dice cuál
es cuál. La recopilación de todas las operaciones similares en un ADT le permite definir toda la
interfaz en términos de puntos o píxeles, o para diferenciar claramente entre los dos, lo que
ayuda a evitar confundirlos.
DATOS DUROS
rutinas a lo largo de líneas funcionales y otro que estaba dividido en ocho rutinas de tipo de datos
abstractos ( 1981). Los estudiantes que usaron el programa de tipo de datos abstracto obtuvieron un
30 por ciento más que los estudiantes que usaron la versión funcional.
No tiene que pasar datos por todo su programaEn los ejemplos recién presentados, usted tiene
que cambiarfuente actualdirectamente o pasarlo a cada rutina que trabaje con fuentes. Si.usa un tipo
de datos abstracto, no tiene que pasarfuente actualen todo el programa y tampoco tienes que
convertirlo en datos globales. El ADT tiene una estructura que contienefuente actualdatos de . Solo
las rutinas que forman parte del ADT acceden directamente a los datos. Las rutinas que no forman
parte del ADT no tienen que preocuparse por los datos.
Puede trabajar con entidades del mundo real en lugar de con estructuras de implementación
de bajo nivelPuede definir operaciones relacionadas con fuentes para que la mayor parte del
programa opere únicamente en términos de fuentes en lugar de accesos a matrices, definiciones de
estructuras yVerdaderoyFalso.
6.1 Fundamentos de clase: tipos de datos abstractos (ADT) 129
En este caso, para definir un tipo de datos abstracto, definiría algunas rutinas para controlar las
fuentes, tal vez así:
tamañoEnPuntos )
currentFont.SetSizeInPoints( currentFont.SetSizeInPixels( )
tamaño en píxeles currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName
)
El código dentro de estas rutinas probablemente sería corto; probablemente sería similar al código
que vio anteriormente en el enfoque ad hoc del problema de fuentes. La diferencia es que ha aislado
las operaciones de fuentes en un conjunto de rutinas. Eso proporciona un mejor nivel de abstracción
PUNTO CLAVE
para que el resto de su programa trabaje con fuentes, y le brinda una capa de protección contra
cambios en las operaciones de fuentes.
sistemarefrigeración.GetTemperature()
Velocidad )
sistemarefrigeración.SetCirculationRate(sistemarefrigeración.OpenValve(
número de válvula)
sistemaderefrigeración.CloseValve( numero de valvula )
El entorno específico determinaría el código escrito para implementar cada una de estas
operaciones. El resto del programa podría ocuparse del sistema de refrigeración a través de estas
funciones y no tendría que preocuparse por los detalles internos de las implementaciones de la
estructura de datos, las limitaciones de la estructura de datos, los cambios, etc.
Aquí hay más ejemplos de tipos de datos abstractos y posibles operaciones en ellos:
Lista Pila
Inicializar lista Luz Inicializar pila
Insertar elemento en la lista Encender Empuje el elemento a la pila
Mostrar pantalla de ayuda Eliminar elemento del menú Establecer ubicación de archivo actual
Yon puede derivar varias pautas de un estudio de estos ejemplos; esas pautas se
describen en las siguientes subsecciones:
Cree o use tipos de datos típicos de bajo nivel como ADT, no como tipos de datos de bajo nivelLa
mayoría de las discusiones sobre ADT se centran en representar tipos de datos típicos de bajo nivel como
ADT. Como puede ver en los ejemplos, puede representar una pila, una lista y una cola, así como
prácticamente cualquier otro tipo de datos típico, como un ADT.
La pregunta que debe hacerse es: "¿Qué representa esta pila, lista o cola?" Si una pila representa un
conjunto de empleados, trate el ADT como empleados en lugar de como una pila. Si una lista representa un
conjunto de registros de facturación, trátela como registros de facturación en lugar de como una lista. Si
una cola representa celdas en una hoja de cálculo, trátela como una colección de celdas en lugar de un
elemento genérico en una cola. Disfrute del mayor nivel de abstracción posible.
Tratar objetos comunes como archivos como ADTLa mayoría de los lenguajes incluyen algunos tipos de
datos abstractos con los que probablemente esté familiarizado pero que no considere ADT. Las operaciones
con archivos son un buen ejemplo. Mientras escribe en el disco, el sistema operativo le ahorra la molestia de
colocar el cabezal de lectura/escritura en una dirección física específica, asignar un nuevo sector de disco
cuando agota uno anterior e interpretar códigos de error crípticos. El sistema operativo proporciona un
primer nivel de abstracción y los ADT para ese nivel. Los lenguajes de alto nivel proporcionan un segundo
nivel de abstracción y ADT para ese nivel superior. Un lenguaje de alto nivel lo protege de los detalles
complicados de generar llamadas al sistema operativo y manipular los búferes de datos. Le permite tratar
una porción de espacio en disco como un "archivo".
Puede superponer ADT de manera similar. Si desea utilizar un ADT en un nivel que ofrece
operaciones de nivel de estructura de datos (como empujar y abrir una pila), está bien. Puede
crear otro nivel encima de ese que funcione al nivel del problema del mundo real.
6.1 Fundamentos de clase: tipos de datos abstractos (ADT) 131
Trate incluso artículos simples como ADTNo es necesario tener un tipo de datos formidable
para justificar el uso de un tipo de datos abstracto. Uno de los ADT en la lista de ejemplo es
una luz que solo admite dos operaciones: encenderla y apagarla. Puede pensar que sería un
desperdicio aislar las operaciones simples de "encendido" y "apagado" en sus propias rutinas,
pero incluso las operaciones simples pueden beneficiarse del uso de ADT. Poner la luz y sus
operaciones en un ADT hace que el código sea más autodocumentado y más fácil de cambiar,
limita las posibles consecuencias de los cambios alEnciende la luz()yTurnLight-Off ()rutinas y
reduce el número de elementos de datos que tiene que pasar.
Consulte un ADT independientemente del medio en el que esté almacenadoSuponga que tiene una
tabla de tasas de seguros que es tan grande que siempre se almacena en el disco. Es posible que tenga la
tentación de referirse a ella como una "tasaexpediente” y crear rutinas de acceso comoRateFile.Read(). Sin
embargo, cuando se refiere a él como un archivo, está exponiendo más información sobre los datos de la
que necesita. Si alguna vez cambia el programa para que la tabla esté en la memoria en lugar de en el disco,
el código que se refiere a ella como un archivo será incorrecto, engañoso y confuso. Intente que los
nombres de las clases y las rutinas de acceso sean independientes de cómo se almacenan los datos y, en su
lugar, consulte el tipo de datos abstractos, como la tabla de tasas de seguros. Eso le daría a su clase y
acceso a nombres de rutina comorateTable.Read()o simplementetasas.Leer().
currentFont.SetSize( sizeInPoints )
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )
132 Capítulo 6: Clases obreras
SetCurrentFontSize( tamañoEnPuntos )
SetCurrentFontBoldOn()
SetCurrentFontBoldOff()
SetCurrentFontItalicOn()
SetCurrentFontItalicOff()
SetCurrentFontTypeFace( faceName )
Si desea trabajar con más de una fuente a la vez, deberá agregar servicios para crear y
eliminar instancias de fuentes, tal vez estos:
CrearFuente( ID de fuente )
EliminarFuente( ID de fuente )
EstablecerFuenteActual( ID de fuente )
- Opción 1: identifique explícitamente las instancias cada vez que use los servicios de ADT. En este
caso, no tienes la noción de una "fuente actual". PasasID de fuentea cada rutina que manipula
fuentes. losFuenteLas funciones realizan un seguimiento de los datos subyacentes, y el código del
cliente debe realizar un seguimiento solo de losID de fuente. Esto requiere agregarID de fuente
como un parámetro para cada rutina de fuente.
- Opción 2: Proporcionar de forma explícita los datos utilizados por los servicios de ADT. En este
enfoque, usted declara los datos que usa ADT dentro de cada rutina que usa un servicio ADT.
En otras palabras, creas unFuentetipo de datos que pasa a cada una de las rutinas de servicio
de ADT. Debe diseñar las rutinas de servicio de ADT para que utilicen el Fuentedatos que se les
pasan cada vez que se les llama. El código del cliente no necesita una identificación de fuente
si usa este enfoque porque realiza un seguimiento de los datos de la fuente en sí. (Aunque los
datos están disponibles directamente desde elFuentetipo de datos, debe acceder a ellos solo
con las rutinas de servicio de ADT. A esto se le llama mantener la estructura “cerrada”).
La ventaja de este enfoque es que las rutinas de servicio de ADT no tienen que buscar
información de fuente en función de una ID de fuente. La desventaja es que expone los datos
de fuentes al resto del programa, lo que aumenta la probabilidad de que el código del cliente
haga uso de los detalles de implementación de ADT que deberían haber permanecido ocultos
dentro de ADT.
- Opción 3: Usa instancias implícitas (con mucho cuidado). Diseñe un nuevo servicio para llamar para
hacer que una instancia de fuente específica sea la actual, algo así comoEstablecer fuente actual (id
de fuente). Establecer la fuente actual hace que todos los demás servicios usen la fuente actual
cuando se les llama. Si utiliza este enfoque, no necesitaID de fuentecomo parámetro a los otros
servicios. Para aplicaciones simples, esto puede agilizar el uso de
6.2 Interfaces de buena clase 133
Múltiples instancias. Para aplicaciones complejas, esta dependencia del estado en todo el
sistema significa que debe realizar un seguimiento de la instancia de fuente actual en todo el
código que utiliza elFuentefunciones La complejidad tiende a proliferar y, para aplicaciones de
cualquier tamaño, existen mejores alternativas.
Dentro del tipo de datos abstracto, tendrá una gran cantidad de opciones para manejar múltiples
instancias, pero afuera, esto resume las opciones si está trabajando en un lenguaje no orientado a
objetos.
ADT y Clases
Los tipos de datos abstractos forman la base del concepto de clases. En los lenguajes que admiten
clases, puede implementar cada tipo de datos abstractos como su propia clase. Las clases
generalmente involucran los conceptos adicionales de herencia y polimorfismo. Una forma de
pensar en una clase es como un tipo de datos abstracto más herencia y polimorfismo.
buena abstracción
Como se describe en "Formar abstracciones consistentes" en la Sección 5.3, la abstracción es la
capacidad de ver una operación compleja en una forma simplificada. Una interfaz de clase
proporciona una abstracción de la implementación que se oculta detrás de la interfaz. La interfaz de
la clase debe ofrecer un grupo de rutinas que claramente pertenecen juntas.
Es posible que tenga una clase que implemente un empleado. Contendría datos que describen
el nombre, la dirección, el número de teléfono, etc. del empleado. Ofrecería servicios para
inicializar y utilizar un empleado. Así es como podría verse.
Ejemplo en C++ de una interfaz de clase que presenta una buena abstracción
Referencia cruzadaLos ejemplos de clase Empleado {
código en este libro están formateados público:
usando una codificación // constructores y destructores públicos Employee();
convención que enfatiza la
similitud de estilos en varios Empleado(
idiomas. Para obtener detalles Nombre completo nombre,
en la Sección 11.4. );
~Empleado virtual();
134 Capítulo 6: Clases obreras
// público rutinas
Nombre completo ObtenerNombre() constante;
...
privado:
...
};
Internamente, esta clase puede tener rutinas y datos adicionales para admitir estos servicios,
pero los usuarios de la clase no necesitan saber nada sobre ellos. La abstracción de la interfaz
de clase es excelente porque cada rutina en la interfaz está trabajando hacia un final
consistente.
Una clase que presente una pobre abstracción sería una que contuviera una colección de
funciones misceláneas. Aquí hay un ejemplo:
Ejemplo en C++ de una interfaz de clase que presenta una abstracción deficiente
programa de clase {
público:
CODIFICACIÓN ...
HORROR
// público rutinas
void InitializeCommandStack(); void
PushCommand (Comando comando); Dominio
PopCommand();
vacío Pila de Comandos de Apagado();
vacío Inicializar formato de informe ();
void FormatReport( Informe informe ); void
PrintReport( Informe informe ); vacío
InicializarDatosGlobales();
vacío ApagarDatosGlobales();
...
privado:
...
};
Suponga que una clase contiene rutinas para trabajar con una pila de comandos, formatear
informes, imprimir informes e inicializar datos globales. Es difícil ver alguna conexión entre la pila de
comandos y las rutinas de informes o los datos globales. La interfaz de clase no presenta una
abstracción consistente, por lo que la clase tiene poca cohesión. Las rutinas deben reorganizarse en
clases más enfocadas, cada una de las cuales proporciona una mejor abstracción en su interfaz.
Si estas rutinas fueran parte de unProgramaclase, podrían revisarse para presentar una
abstracción consistente, así:
6.2 Interfaces de buena clase 135
Ejemplo en C++ de una interfaz de clase que presenta una mejor abstracción
programa de clase {
público:
...
// público rutinas
vacío InitializeUserInterface();
vacío ApagarInterfazUsuario();
vacío InitializeReports();
vacío Informes de cierre ();
...
privado:
...
};
La limpieza de esta interfaz supone que algunas de las rutinas originales se movieron a otras
clases más apropiadas y algunas se convirtieron en rutinas privadas utilizadas porInicializar
interfaz de usuario ()y las demás rutinas.
Aquí hay un ejemplo de una clase que presenta una interfaz que es inconsistente porque su
nivel de abstracción no es uniforme:
CODIFICACIÓN ...
HORROR
// rutinas públicas
La abstracción de estas rutinas void AddEmployee (empleado empleado); void
está en el nivel de “empleado”. RemoveEmployee (empleado empleado);
Empleado SiguienteElementoEnLista();
Esta clase presenta dos ADT: unEmpleadoy unListContainer. Este tipo de abstracción mixta
suele surgir cuando un programador usa una clase de contenedor u otras clases de biblioteca
para la implementación y no oculta el hecho de que se usa una clase de biblioteca. Pregúntese
si el hecho de que se use una clase contenedora debería ser parte de la abstracción. Por lo
general, ese es un detalle de implementación que debe ocultarse del resto del programa, así:
Los programadores podrían argumentar que heredar deListContaineres conveniente porque admite
polimorfismo, lo que permite una búsqueda externa o una función de clasificación que toma un
Contenedor de listaobjeto. Ese argumento no pasa la prueba principal de la herencia, que es: "¿Se usa
la herencia solo para las relaciones "es un"?" heredar deListContainersignificaría queCenso de
empleados“es un"ListContainer, lo que obviamente no es cierto. Si la abstracción de laCenso de
empleadosobjeto es que se puede buscar u ordenar, que debe incorporarse como una parte explícita
y consistente de la interfaz de clase.
Si piensa en las rutinas públicas de la clase como una esclusa de aire que evita que el agua entre en un
submarino, las rutinas públicas inconsistentes son paneles con fugas en la clase. Es posible que los paneles
con fugas no dejen entrar el agua tan rápido como una esclusa de aire abierta, pero si les das suficiente
tiempo, igual hundirán el bote. En la práctica, esto es lo que sucede cuando mezclas niveles de abstracción.
A medida que se modifica el programa, los niveles mixtos de abstracción hacen que el programa sea cada
vez más difícil de entender y se degrada gradualmente hasta que se vuelve imposible de mantener.
El control de hoja de cálculo era mucho más complicado que el control de cuadrícula, proporcionando
alrededor de 150 rutinas a las 15 del control de cuadrícula. Dado que nuestro objetivo era usar un control de
cuadrícula, no un control de hoja de cálculo, asignamos a un programador para que escribiera una clase
contenedora para ocultar el hecho. que estábamos usando un control de hoja de cálculo como control de
cuadrícula. El programador se quejó bastante acerca de los gastos generales y la burocracia innecesarios, se
fue y regresó un par de días después con una clase contenedora que expuso fielmente las 150 rutinas del
control de la hoja de cálculo.
Esto no era lo que se necesitaba. Queríamos una interfaz de control de cuadrícula que
encapsulara el hecho de que, detrás de escena, estábamos usando un control de hoja de
cálculo mucho más complicado. El programador debería haber expuesto solo las 15 rutinas de
control de cuadrícula más una decimosexta rutina que admitía el coloreado de celdas. Al
exponer las 150 rutinas, el programador creó la posibilidad de que, si alguna vez quisiéramos
cambiar la implementación subyacente, podríamos dar soporte a 150 rutinas públicas. El
programador no logró la encapsulación que buscábamos, además de crear mucho más trabajo
del necesario para sí mismo.
Según las circunstancias específicas, la abstracción adecuada puede ser un control de hoja de
cálculo o un control de cuadrícula. Cuando tenga que elegir entre dos abstracciones similares,
asegúrese de elegir la correcta.
Proporcionar servicios en parejas con sus opuestos.La mayoría de las operaciones tienen
operaciones correspondientes, iguales y opuestas. Si tiene una operación que enciende una luz,
probablemente necesitará una para apagarla. Si tiene una operación para agregar un elemento a una
lista, probablemente necesitará una para eliminar un elemento de la lista. Si tiene una operación para
activar un elemento de menú, probablemente necesitará una para desactivar un elemento. Cuando
diseñe una clase, verifique cada rutina pública para determinar si necesita su complemento. No cree
un opuesto gratuitamente, pero verifique si lo necesita.
Mover información no relacionada a otra claseEn algunos casos, encontrará que la mitad de las
rutinas de una clase funcionan con la mitad de los datos de la clase y la mitad de las rutinas
funcionan con la otra mitad de los datos. En tal caso, realmente tiene dos clases disfrazadas como
una sola. ¡Rómpelos!
Haga que las interfaces sean programáticas en lugar de semánticas cuando sea posibleCada
interfaz consta de una parte programática y una parte semántica. La parte programática consta de
los tipos de datos y otros atributos de la interfaz que el compilador puede aplicar. La parte semántica
de la interfaz consiste en las suposiciones sobre cómo se usará la interfaz, que el compilador no
puede aplicar. La interfaz semántica incluye consideraciones como “RutinaAhay que llamar antes
Rutina B" o "RutinaAse estrellará simiembro de datos1no se inicializa antes de pasar aRutinaA.” La
interfaz semántica debe documentarse en los comentarios, pero trate de mantener las interfaces
mínimamente dependientes de la documentación. Cualquier aspecto de una interfaz que el
compilador no pueda aplicar es un aspecto que probablemente se utilice de forma indebida. Busque
formas de convertir elementos de la interfaz semántica en elementos de la interfaz programática
utilizandoafirmau otras técnicas.
138 Capítulo 6: Clases obreras
Referencia cruzadaPara obtener más Cuidado con la erosión de la abstracción de la interfaz bajo modificaciónA medida que se
sugerencias sobre cómo preservar la
modifica y amplía una clase, a menudo descubre que se necesita funcionalidad adicional, que no
calidad del código a medida que se
Ejemplo en C++ de una interfaz de clase que se está erosionando con el mantenimiento
empleado de clase {
público:
CODIFICACIÓN ...
HORROR
// público rutinas
Nombre completo ObtenerNombre() const;
Dirección ObtenerDirección() constante;
...
bool IsJobClassificationValid( JobClassification jobClass ); bool
IsZipCodeValid( Dirección dirección );
bool IsPhoneNumberValid( PhoneNumber phoneNumber );
Lo que comenzó como una abstracción limpia en una muestra de código anterior se ha convertido en
una mezcolanza de funciones que solo están vagamente relacionadas. No existe una conexión lógica
entre los empleados y las rutinas que verifican códigos postales, números de teléfono o
clasificaciones de trabajo. Las rutinas que exponen los detalles de la consulta SQL están en un nivel
de abstracción mucho más bajo que elEmpleadoclase, y rompen elEmpleadoabstracción.
Descubrí que centrarse en la abstracción presentada por la interfaz de clase tiende a proporcionar
más información sobre el diseño de la clase que centrarse en la cohesión de la clase. Si ve que una
clase tiene una cohesión débil y no está seguro de cómo corregirla, pregúntese si la clase presenta
una abstracción consistente en su lugar.
6.2 Interfaces de buena clase 139
Buena encapsulación
Referencia cruzadaPara obtener más Como se discutió en la Sección 5.3, la encapsulación es un concepto más fuerte que la abstracción. La
información sobre la encapsulación, consulte
abstracción ayuda a administrar la complejidad al proporcionar modelos que le permiten ignorar los
"Encapsular detalles de
El factor más importante Minimizar la accesibilidad de clases y miembrosMinimizar la accesibilidad es una de varias
que distingue a un módulo
reglas que están diseñadas para fomentar la encapsulación. Si se pregunta si una rutina
bien diseñado de uno mal
diseñado es el grado en que específica debe ser pública, privada o protegida, una escuela de pensamiento es que debe
el módulo oculta sus datos favorecer el nivel más estricto de privacidad que sea viable (Meyers 1998, Bloch 2001). Creo que
internos y otros detalles de
es una buena pauta, pero creo que la pauta más importante es: "¿Qué preserva mejor la
implementación de otros
módulos.
integridad de la abstracción de la interfaz?" Si exponer la rutina es consistente con la
—Josué Bloch abstracción, probablemente esté bien exponerla. Si no está seguro, ocultar más es
generalmente mejor que ocultar menos.
No exponga los datos de los miembros en públicoExponer los datos de los miembros es una
violación de la encapsulación y limita su control sobre la abstracción. Como señala Arthur Riel, un
Punto clase que expone
flotar X;
flotar y;
flotar z;
está violando la encapsulación porque el código del cliente es libre de jugar conPuntodatos de
yPuntoni siquiera sabrá necesariamente cuándo se han cambiado sus valores (Riel 1996). Sin
embargo, unPuntoclase que expone
flotar ObtenerX();
flotar ObtenerY();
flotar ObtenerZ();
C++, sin embargo, la estructura del lenguaje requiere que los programadores divulguen los detalles
de implementación en la interfaz de clase. Aquí hay un ejemplo:
);
...
Nombre completo ObtenerNombre() constante;
IncluidoprivadoLas declaraciones en el archivo de encabezado de clase pueden parecer una pequeña transgresión,
pero alienta a otros programadores a examinar los detalles de implementación. En este caso, el código de cliente
está destinado a utilizar elDirecciónescriba para las direcciones, pero el archivo de encabezado expone el detalle de
implementación que las direcciones se almacenan como Instrumentos de cuerda.
Scott Meyers describe una forma común de abordar este problema en el artículo 34 deC++ efectivo,
2ª ed. (Meyers 1998). Separa la interfaz de la clase de la implementación de la clase. Dentro de la
declaración de clase, incluya un puntero a la implementación de la clase, pero no incluya ningún otro
detalle de implementación.
Si ya ha escrito mucho código que no usa este enfoque para su proyecto, puede decidir que no
vale la pena el esfuerzo de convertir una montaña de código existente para usar este enfoque.
pero cuando tuleercódigo que expone sus detalles de implementación, puede resistir la
tentación de peinar a través de laprivadosección de la interfaz de clase en busca de pistas de
implementación.
Evita las clases de amigosEn algunas circunstancias, como el patrón de estado, las clases de amigos
se pueden usar de una manera disciplinada que contribuye a manejar la complejidad (Gamma et al.
1995). Pero, en general, las clases amigas violan la encapsulación. Expanden la cantidad de código en
el que tiene que pensar en un momento dado, lo que aumenta la complejidad.
No coloque una rutina en la interfaz pública solo porque solo usa rutinas públicas
El hecho de que una rutina use solo rutinas públicas no es una consideración
importante. En su lugar, pregunte si exponer la rutina sería consistente con la
abstracción presentada por la interfaz.
Preferir la conveniencia del tiempo de lectura a la conveniencia del tiempo de escrituraEl código se lee
muchas más veces de las que se escribe, incluso durante el desarrollo inicial. Favorecer una técnica que
acelera la conveniencia del tiempo de escritura a expensas de la conveniencia del tiempo de lectura es una
economía falsa. Esto es especialmente aplicable a la creación de interfaces de clase. Incluso si una rutina no
se ajusta del todo a la abstracción de la interfaz, a veces es tentador agregar una rutina a una interfaz que
sería conveniente para el cliente particular de una clase en la que está trabajando en ese momento. Pero
agregar esa rutina es el primer paso hacia una pendiente resbaladiza, y es mejor no dar ni el primer paso.
No es abstracto si tiene que mirar Tenga mucho, mucho cuidado con las violaciones semánticas de la encapsulaciónEn un momento
la implementación subyacente
pensé que cuando aprendiera a evitar errores de sintaxis estaría libre en casa. Pronto descubrí que
para comprender lo que está
sucediendo.
aprender a evitar errores de sintaxis simplemente me había comprado un boleto para un teatro
—PJ Plauger completamente nuevo de errores de codificación, la mayoría de los cuales eran más difíciles de diagnosticar
y corregir que los errores de sintaxis.
la encapsulación semántica es otra cuestión completamente diferente. Estos son algunos ejemplos de las formas en que un
El problema con cada uno de estos ejemplos es que hacen que el código del cliente no
dependa de la interfaz pública de la clase, sino de su implementación privada. Cada vez que se
encuentra mirando la implementación de una clase para descubrir cómo usar la clase, no está
PUNTO CLAVE
programando la interfaz; estas programandomedianteLa interfaza la implementación. Si está
programando a través de la interfaz, la encapsulación se rompe, y una vez que la
encapsulación comienza a fallar, la abstracción no se queda atrás.
Tenga cuidado con el acoplamiento que está demasiado apretado“Acoplamiento” se refiere a qué tan
estrecha es la conexión entre dos clases. En general, cuanto más floja sea la conexión, mejor. Varias pautas
generales fluyen de este concepto:
- hacer datosprivadomás bien queprotegidoen una clase base para hacer que las clases derivadas estén
- Evite exponer los datos de los miembros en la interfaz pública de una clase.
Implementar “tiene un” a través de la contenciónUna forma de pensar en la contención es como una relación de
“tiene un”. Por ejemplo, un empleado "tiene un" nombre, "tiene un" número de teléfono, "tiene una" identificación
fiscal, etc. Por lo general, puede lograr esto haciendo que el nombre, el número de teléfono y los datos del miembro
Implementar “tiene un” a través de la herencia privada como último recursoEn algunos casos, es
posible que descubras que no puedes lograr la contención haciendo que un objeto sea miembro de
otro. En ese caso, algunos expertos sugieren heredar en privado del objeto contenido (Meyers 1998,
Sutter 2000). La razón principal por la que haría eso es configurar la clase contenedora para acceder a
funciones de miembros protegidos o datos de miembros protegidos de la clase que está contenida.
En la práctica, este enfoque crea una relación demasiado cómoda con la clase antecesora y viola la
encapsulación. Tiende a señalar errores de diseño que deberían resolverse de alguna otra forma que
no sea mediante herencia privada.
Sea crítico con las clases que contienen más de siete miembros de datosSe ha encontrado
que el número “7±2” es un número de elementos discretos que una persona puede recordar
mientras realiza otras tareas (Miller 1956). Si una clase contiene más de siete datos
Traducido del inglés al español - www.onlinedoctranslator.com
miembros, considere si la clase debe descomponerse en múltiples clases más pequeñas (Riel 1996).
Es posible que se equivoque más hacia el extremo superior de 7±2 si los miembros de datos son
tipos de datos primitivos como números enteros y cadenas, más hacia el extremo inferior de 7±2 si
los miembros de datos son objetos complejos.
- Para cada rutina miembro, ¿la rutina será visible para las clases derivadas? ¿Tendrá una
implementación por defecto? ¿Será reemplazable la implementación predeterminada?
- Para cada miembro de datos (incluidas variables, constantes con nombre, enumeraciones,
etc.), ¿será visible el miembro de datos para las clases derivadas?
La regla más importante en Implementar "es un" a través de la herencia públicaCuando un programador decide crear
la programación orientada a
una nueva clase heredando de una clase existente, ese programador está diciendo que la
objetos con C++ es esta: la
herencia pública significa nueva clase "es una" versión más especializada de la clase anterior. La clase base establece
"es un". Memoriza esta expectativas sobre cómo operará la clase derivada e impone restricciones sobre cómo puede
regla.
operar la clase derivada (Meyers 1998).
—scott meyers
Si la clase derivada no se va a adherircompletamenteal mismo contrato de interfaz definido por
la clase base, la herencia no es la técnica de implementación correcta. Considere la posibilidad
de contener o realizar un cambio más arriba en la jerarquía de herencia.
En otras palabras, todas las rutinas definidas en la clase base deberían significar lo mismo
cuando se usan en cada una de las clases derivadas.
Si tiene una clase base deCuentay clases derivadas deCuenta corriente,Cuenta de ahorros, y
Cuenta de Préstamo Automático, un programador debería poder invocar cualquiera de las
rutinas derivadas deCuentaen cualquiera deCuentasubtipos de sin importar qué subtipo es un
objeto de cuenta específico.
Si un programa ha sido escrito para que el principio de sustitución de Liskov sea verdadero, la
herencia es una herramienta poderosa para reducir la complejidad porque un programador puede
enfocarse en los atributos genéricos de un objeto sin preocuparse por los detalles. Si un
programador debe pensar constantemente en las diferencias semánticas en las implementaciones de
subclases, entonces la herencia aumenta la complejidad en lugar de reducirla. Supongamos que un
programador tiene que pensar esto: "Si llamo alTasa de interés()rutina enCuenta corrienteoCuenta de
ahorros, me devuelve los intereses que paga el banco, pero si llamoTasa de interés()en Cuenta de
Préstamo AutomáticoTengo que cambiar el letrero porque devuelve el interés que el consumidor
paga al banco”. Según LSP,Cuenta de Préstamo Automáticono debe heredar delCuentaclase base en
este ejemplo porque la semántica de laTasa de interés()rutina no son lo mismo que la semántica de la
clase baseTasa de interés()rutina.
Asegúrate de heredar solo lo que quieras heredarUna clase derivada puede heredar interfaces de
rutinas miembro, implementaciones o ambas. La tabla 6-1 muestra las variaciones de cómo se
pueden implementar y anular las rutinas.
Como sugiere la tabla, las rutinas heredadas vienen en tres sabores básicos:
Cuando elige implementar una nueva clase a través de la herencia, piense en el tipo de
herencia que desea para cada rutina miembro. Tenga cuidado con heredar la implementación
solo porque hereda una interfaz, y tenga cuidado con heredar una interfaz solo porque desea
heredar una implementación. Si desea utilizar la implementación de una clase pero no su
interfaz, utilice la contención en lugar de la herencia.
Sospeche de las clases de las que solo hay una instancia.Una sola instancia podría indicar
que el diseño confunde objetos con clases. Considere si podría simplemente crear un objeto
en lugar de una nueva clase. ¿Se puede representar la variación de la clase derivada en los
datos en lugar de como una clase distinta? El patrón Singleton es una notable excepción a esta
directriz.
Sospeche de las clases base de las que solo hay una clase derivadaCuando veo una clase base
que tiene solo una clase derivada, sospecho que algún programador ha estado "diseñando por
adelantado", tratando de anticipar necesidades futuras, generalmente sin comprender
completamente cuáles son esas necesidades futuras. La mejor manera de prepararse para el trabajo
futuro es no diseñar capas adicionales de clases base que "podrían ser necesarias algún día"; es
hacer que el trabajo actual sea lo más claro, directo y simple posible. Eso significa no crear más
estructura de herencia de la que sea absolutamente necesaria.
Sospeche de las clases que anulan una rutina y no hacen nada dentro de la rutina
derivadaEsto suele indicar un error en el diseño de la clase base. Por ejemplo, suponga que
tiene una claseGatoy una rutinaRascar()y suponga que finalmente descubre que a algunos
gatos les han quitado las garras y no pueden rascarse. Es posible que tenga la tentación de
crear una clase derivada deGatonombradaGato sin rasguñosy anular elRascar()rutina de no
hacer nada. Este enfoque presenta varios problemas:
- Con el tiempo, este enfoque da lugar a un código cuyo mantenimiento es confuso porque las
interfaces y el comportamiento de las clases antepasadas implican poco o nada sobre el
comportamiento de sus descendientes.
El lugar para solucionar este problema no está en la clase base, sino en el originalGatoclase. Crear un
Garrasclase y contener eso dentro de lagatosclase. El problema de raíz era la suposición de que todos
los gatos arañan, así que solucione el problema en el origen, en lugar de simplemente vendarlo en el
destino.
Evite los árboles de herencia profundosLa programación orientada a objetos proporciona una
gran cantidad de técnicas para gestionar la complejidad. Pero toda herramienta poderosa tiene sus
peligros, y algunas técnicas orientadas a objetos tienden a aumentar la complejidad en lugar de
reducirla.
En su excelente libroHeurística de diseño orientado a objetos(1996), Arthur Riel sugiere limitar las
jerarquías de herencia a un máximo de seis niveles. Riel basa su recomendación en el "número
mágico 7±2", pero creo que eso es muy optimista. En mi experiencia, la mayoría de las personas
tienen problemas para hacer malabarismos con más de dos o tres niveles de herencia en sus
cerebros a la vez. El "número mágico 7±2" probablemente se aplica mejor como un límite a lanúmero
total de subclasesde una clase base en lugar del número de niveles en un árbol de herencia.
Se ha encontrado que los árboles de herencia profundos están significativamente asociados con
mayores tasas de fallas (Basili, Briand y Melo 1996). Cualquiera que haya intentado depurar una
jerarquía de herencia compleja sabe por qué. Los árboles de herencia profundos aumentan la
complejidad, que es exactamente lo contrario de lo que se debe lograr con la herencia. Tenga en
cuenta la misión técnica principal. Asegúrese de utilizar la herencia para evitar la duplicación de
código y paraminimizar la complejidad.
Por otro lado, a vecescasoLas declaraciones se utilizan para separar tipos de objetos o comportamientos
verdaderamente diferentes. Aquí hay un ejemplo de uncasodeclaración que es apropiada en un programa
orientado a objetos:
Ejemplo de C++ de una declaración de caso que probablemente no debería ser reemplazada
por polimorfismo
cambiar ( ui.Command() ) {
caso Command_OpenFile:
Abrir documento();
descanso;
caso Command_Print:
Impresión();
descanso;
caso Command_Save:
Ahorrar();
descanso;
caso Comando_Salir:
Cerrar();
descanso;
...
}
En este caso, sería posible crear una clase base con clases derivadas y un polimórfico
HacerComando()rutina para cada comando (como en el patrón Comando). Pero en un
caso simple como este, el significado deHacerComando()estaría tan diluido que no
tendría sentido, y elcasodeclaración es la solución más comprensible.
Haga que todos los datos sean privados, no protegidosComo dice Joshua Bloch, “La herencia
rompe la encapsulación” (2001). Cuando hereda de un objeto, obtiene acceso privilegiado a las
rutinas y datos protegidos de ese objeto. Si la clase derivada realmente necesita acceso a los
atributos de la clase base, proporcione funciones de acceso protegidas en su lugar.
Herencia múltiple
El único hecho indiscutible La herencia es una herramienta poderosa. Es como usar una motosierra para cortar un árbol en lugar de
sobre la herencia múltiple en
una sierra manual de corte transversal. Puede ser increíblemente útil cuando se usa con cuidado, pero es
C++ es que abre una caja de
Pandora de complejidades que peligroso en manos de alguien que no toma las precauciones adecuadas.
simplemente no existen bajo la
herencia única.
—scott meyers
6.3 Problemas de diseño e implementación 149
Si la herencia es una motosierra, la herencia múltiple es una motosierra de la década de 1950 sin protector
de hoja, sin apagado automático y con un motor complicado. Hay momentos en que una herramienta de
este tipo es valiosa; Sin embargo, es mejor que dejes la herramienta en el garaje donde no pueda causar
ningún daño.
Aunque algunos expertos recomiendan un uso amplio de la herencia múltiple (Meyer 1997), según
mi experiencia, la herencia múltiple es útil principalmente para definir "mixins", clases simples que se
usan para agregar un conjunto de propiedades a un objeto. Los mixins se denominan mixins porque
permiten que las propiedades se "mezclen" con las clases derivadas. Los mixins pueden ser clases
comoVisualizable,persistente,Serializable, oOrdenable. Los mixins son casi siempre abstractos y no
están destinados a ser instanciados independientemente de otros objetos.
Los mixins requieren el uso de herencia múltiple, pero no están sujetos al clásico
problema de herencia de diamantes asociado con la herencia múltiple, siempre que todos
los mixins sean realmente independientes entre sí. También hacen que el diseño sea más
comprensible al "fragmentar" los atributos. Un programador tendrá más facilidad para
entender que un objeto usa los mixinsVisualizableyPersistenteque entender que un
objeto usa las 11 rutinas más específicas que de otro modo serían necesarias para
implementar esas dos propiedades.
Java y Visual Basic reconocen el valor de los mixins al permitir la herencia múltiple de
interfaces, pero solo la herencia de una sola clase. C++ admite la herencia múltiple tanto de la
interfaz como de la implementación. Los programadores deben usar herencia múltiple solo
después de considerar cuidadosamente las alternativas y sopesar el impacto en la
complejidad y comprensibilidad del sistema.
Referencia cruzadaPara obtener más - Si varias clases comparten datos comunes pero no comportamiento, cree un objeto común
información sobre la complejidad,
que esas clases puedan contener.
consulte “El imperativo técnico principal
Minimizar el número de rutinas diferentes llamadas por una claseUn estudio encontró que la
cantidad de fallas en una clase se correlacionó estadísticamente con la cantidad total de rutinas que
se llamaron desde dentro de una clase (Basili, Briand y Melo 1996). El mismo estudio encontró que
cuantas más clases usaba una clase, mayor tendía a ser su tasa de fallas. Estos conceptos a veces se
denominan "abanico".
Otras lecturasBueno Minimice las llamadas rutinarias indirectas a otras clasesLas conexiones directas son bastante
relatos de la Ley de Deméter
peligrosas. Conexiones indirectas, comocuenta.ContactPerson().DaytimeContact-
se pueden encontrar en
Programador pragmático
Info().PhoneNumber()- tienden a ser aún más peligrosos. Los investigadores han formulado una
(Hunt y Thomas 2000), regla llamada "Ley de Deméter" (Lieberherr y Holland 1989), que esencialmente establece que el
Aplicación de UML y patrones
Objeto A puede llamar a cualquiera de sus propias rutinas. Si el Objeto A instancia un Objeto B,
(Larman 2001), yFundamentos
del Diseño Orientado a
puede llamar a cualquiera de las rutinas del Objeto B. Pero debe evitar llamar a rutinas en objetos
Objetos en UML(Page-Jones provistos por el Objeto B. En elcuentaejemplo anterior, eso significacuenta.PersonaContacto()esta
2000). bien perocuenta.ContactPerson().DaytimeContactInfo()no es.
Esta es una explicación simplificada. Consulte los recursos adicionales al final de este capítulo para obtener
más detalles.
En general, minimice la medida en que una clase colabora con otras clases.Trate de
minimizar todo lo siguiente:
Constructores
Las siguientes son algunas pautas que se aplican específicamente a los constructores. Las pautas para los
constructores son bastante similares en todos los idiomas (C ++, Java y Visual Basic, de todos modos). Los
destructores varían más, por lo que debe consultar los materiales enumerados en la sección "Recursos
adicionales" de este capítulo para obtener información sobre los destructores.
Inicialice todos los datos de miembros en todos los constructores, si es posibleInicializar todos los miembros
de datos en todos los constructores es una práctica de programación defensiva económica.
Otras lecturasEl código para Hacer cumplir la propiedad singleton mediante el uso de un constructor privadoSi desea definir
hacer esto en C++ sería similar.
una clase que permita instanciar solo un objeto, puede aplicar esto ocultando todos los
Para más detalles, consulteC++
más efectivo, Artículo 26 (Meyers
constructores de la clase y luego proporcionando unGetInstance estático()rutina para acceder a la
1998). instancia única de la clase. He aquí un ejemplo de cómo funcionaría:
// rutinas públicas
Aquí está la rutina pública público estático MaxId GetInstance () {
que proporciona acceso a la volver m_instancia;
instancia única. }
...
// miembros privados
Aquí está el caso único. privado estático final MaxId m_instance = new MaxId(); . . .
Preferir copias profundas a copias superficiales hasta que se demuestre lo contrarioUna de las
decisiones más importantes que tomará acerca de los objetos complejos es implementar copias profundas o
superficiales del objeto. Una copia en profundidad de un objeto es una copia de los miembros de los datos
del objeto; una copia superficial generalmente solo apunta o se refiere a una sola copia de referencia,
aunque los significados específicos de "profundo" y "superficial" varían.
La motivación para crear copias superficiales suele ser mejorar el rendimiento. Aunque crear varias copias
de objetos grandes puede resultar estéticamente ofensivo, rara vez provoca un impacto medible en el
rendimiento. Una pequeña cantidad de objetos puede causar problemas de rendimiento, pero los
programadores son notoriamente malos para adivinar qué código realmente causa problemas. (Para
obtener más información, consulte el Capítulo 25, “Estrategias de ajuste de código”).
152 Capítulo 6: Clases obreras
Debido a que es una mala compensación agregar complejidad para ganancias de rendimiento dudosas, un buen enfoque
para las copias profundas frente a las superficiales es preferir las copias profundas hasta que se demuestre lo contrario.
Las copias profundas son más sencillas de codificar y mantener que las copias superficiales. Además del
código que contendría cualquier tipo de objeto, las copias superficiales agregan código para contar
referencias, garantizar copias seguras de objetos, comparaciones seguras, eliminaciones seguras, etc. Este
código puede ser propenso a errores y debe evitarlo a menos que haya una razón convincente para crearlo.
Si encuentra que necesita utilizar un enfoque de copia superficial, Scott MeyersC++ más
efectivo, Item 29 (1996) contiene una excelente discusión de los problemas en C++. Martín
Fowlerrefactorización(1999) describe los pasos específicos necesarios para convertir de copias
superficiales a copias profundas y de copias profundas a copias superficiales. (Fowler los llama
objetos de referencia y objetos de valor).
Referencia cruzadaPara obtener más Modelar objetos del mundo realModelar objetos del mundo real puede no ser la única razón para
información sobre la identificación de objetos
crear una clase, ¡pero sigue siendo una buena razón! Cree una clase para cada tipo de objeto del
del mundo real, consulte "Buscar objetos del
Modelar objetos abstractosOtra buena razón para crear una clase es modelar unobjeto abstracto—
un objeto que no es un objeto concreto del mundo real pero que proporciona una abstracción de
otros objetos concretos. Un buen ejemplo es el clásico.Formaobjeto.Circuloy Cuadradoexiste
realmente, peroFormaes una abstracción de otras formas específicas.
En los proyectos de programación, las abstracciones no están listas comoFormaEs decir, tenemos
que trabajar más duro para llegar a abstracciones limpias. El proceso de extraer conceptos
abstractos de entidades del mundo real no es determinista, y diferentes diseñadores abstraerán
diferentes generalidades. Si no supiéramos acerca de formas geométricas como círculos, cuadrados
y triángulos, por ejemplo, podríamos encontrar formas más inusuales como la forma de calabaza, la
forma de colinabo y la forma de Pontiac Aztek. Crear objetos abstractos apropiados es uno de los
mayores desafíos en el diseño orientado a objetos.
Reducir la complejidadLa razón más importante para crear una clase es reducir la
complejidad de un programa. Cree una clase para ocultar información para que no tenga que
pensar en ella. Claro, tendrás que pensarlo cuando escribas la clase. Pero después de
PUNTO CLAVE
escribirlo, debería poder olvidar los detalles y usar la clase sin ningún conocimiento de su
funcionamiento interno. Otras razones para crear clases: minimizar el tamaño del código,
6.4 Razones para crear una clase 153
mejorar la capacidad de mantenimiento y mejorar la corrección también son buenas razones, pero
sin el poder de abstracción de las clases, los programas complejos serían imposibles de manejar
intelectualmente.
Ocultar detalles de implementaciónEl deseo de ocultar los detalles de implementación es una excelente
razón para crear una clase, ya sea que los detalles sean tan complicados como el acceso a una base de datos
enrevesada o tan mundanos como si un miembro de datos específico se almacena como un número o una
cadena.
Limitar los efectos de los cambiosAísle las áreas que probablemente cambien para que los efectos de los
cambios se limiten al alcance de una sola clase o de unas pocas clases. Diseñe de modo que las áreas que
tienen más probabilidades de cambiar sean las más fáciles de cambiar. Las áreas que probablemente
cambien incluyen dependencias de hardware, entrada/salida, tipos de datos complejos y reglas comerciales.
La subsección titulada “Ocultar secretos (ocultar información)” en la Sección 5.3 describió varias fuentes
comunes de cambio.
Referencia cruzadaPara una Ocultar datos globalesSi necesita usar datos globales, puede ocultar sus detalles de implementación
discusión de los problemas asociados
detrás de una interfaz de clase. Trabajar con datos globales a través de rutinas de acceso proporciona
con el uso de datos globales,
Optimizar el paso de parámetrosSi está pasando un parámetro entre varias rutinas, eso podría
indicar la necesidad de factorizar esas rutinas en una clase que comparte el parámetro como datos
de objeto. Simplificar el paso de parámetros no es un objetivo en sí mismo, pero pasar muchos datos
sugiere que una organización de clase diferente podría funcionar mejor.
Referencia cruzadaPara obtener detalles Hacer puntos centrales de controlEs una buena idea controlar cada tarea en un solo lugar. El
sobre la ocultación de información,
control asume muchas formas. El conocimiento del número de entradas en una tabla es una forma.
consulte "Ocultar secretos (Ocultar
La idea del control centralizado es similar a la ocultación de información, pero tiene un poder heurístico
único que hace que valga la pena agregarlo a su caja de herramientas de programación.
Facilitar el código reutilizableEl código colocado en clases bien factorizadas se puede reutilizar en
otros programas más fácilmente que el mismo código incrustado en una clase más grande. Incluso si
una sección de código se llama desde un solo lugar en el programa y es comprensible como parte de
una clase más grande, tiene sentido colocarla en su propia clase si esa pieza de código se puede usar
en otro programa.
3 El Laboratorio de Ingeniería de Software de la NASA estudió diez proyectos que perseguían la reutilización
2
1
de forma agresiva (McGarry, Waligora y McDermott 1989). Tanto en el enfoque orientado a objetos como en
DATOS DUROS
el orientado funcionalmente, los proyectos iniciales no pudieron tomar gran parte de su código de
proyectos anteriores porque los proyectos anteriores no habían establecido una base de código suficiente.
Posteriormente, los proyectos que utilizaron diseño funcional pudieron tomar alrededor del 35 por ciento
de su código de proyectos anteriores. Los proyectos que utilizaron un enfoque orientado a objetos pudieron
tomar más del 70 por ciento de su código de proyectos anteriores. Si puede evitar escribir el 70 por ciento
de su código planificando con anticipación, ¡hágalo!
Referencia cruzadaPara obtener más En particular, el núcleo del enfoque de la NASA para crear clases reutilizables no implica "diseñar
información sobre la implementación de
para reutilizar". La NASA identifica candidatos de reutilización al final de sus proyectos. Luego
la cantidad mínima de funcionalidad
Plan para una familia de programasSi espera que se modifique un programa, es una
buena idea aislar las partes que espera cambiar colocándolas en sus propias clases.
Luego puede modificar las clases sin afectar el resto del programa, o puede colocar
clases completamente nuevas en su lugar. Pensar no sólo en cómo se verá un programa,
sino en cómo se verá toda la familia de programas es una heurística poderosa para
anticipar categorías enteras de cambios (Parnas 1976).
Hace varios años dirigí un equipo que escribió una serie de programas utilizados por nuestros clientes para
vender seguros. Tuvimos que adaptar cada programa a las tarifas de seguro del cliente específico, el
formato de informe de cotización, etc. Pero muchas partes de los programas eran similares: las clases que
ingresaban información sobre clientes potenciales, que almacenaban información en una base de datos de
clientes, que buscaban tarifas, que calculaban tarifas totales para un grupo, etc. El equipo factorizó el
programa para que cada parte que variaba de un cliente a otro estuviera en su propia clase. La
programación inicial podría haber tomado tres meses más o menos, pero cuando obtuvimos un nuevo
cliente, simplemente escribimos un puñado de nuevas clases para el nuevo cliente y las colocamos en el
resto del código. Unos días de trabajo y, ¡voilá!, ¡software personalizado!
6.4 Razones para crear una clase 155
Operaciones relacionadas con paquetesEn los casos en los que no pueda ocultar información, compartir
datos o planificar la flexibilidad, aún puede empaquetar conjuntos de operaciones en grupos sensibles,
como funciones trigonométricas, funciones estadísticas, rutinas de manipulación de cadenas, rutinas de
manipulación de bits, rutinas gráficas , y así. Las clases son un medio de combinar operaciones relacionadas.
También puede usar paquetes, espacios de nombres o archivos de encabezado, según el idioma en el que
esté trabajando.
Clases a evitar
Si bien las clases en general son buenas, puedes encontrarte con algunas trampas. Aquí hay algunas
clases para evitar.
Evite crear clases de diosEvite crear clases omniscientes que sean omniscientes y
todopoderosas. Si una clase pasa su tiempo recuperando datos de otras clases usando
Obtener()yEstablecer()rutinas (es decir, profundizar en su negocio y decirles qué hacer),
pregúntese si esa funcionalidad podría organizarse mejor en esas otras clases en lugar
de en la clase dios (Riel 1996).
Referencia cruzadaEste tipo de clase se Eliminar clases irrelevantesSi una clase consta solo de datos pero no de comportamiento,
suele denominar estructura. Para obtener
pregúntese si realmente es una clase y considere degradarla para que los datos de sus miembros se
más información sobre las estructuras,
Evite las clases con nombres de verbosUna clase que solo tiene comportamiento pero no datos generalmente no
es realmente una clase. Considere convertir una clase comoInicialización de la base de datos ()oGenerador de
cadenas ()en una rutina en alguna otra clase.
- Reducir la complejidad
- aislar la complejidad
Estas son algunas de las áreas relacionadas con la clase que varían significativamente según el
idioma:
Las discusiones detalladas de estos temas están más allá del alcance de este libro, pero la sección
"Recursos adicionales" apunta a buenos recursos específicos del idioma.
"Niveles de diseño" en
de software ha avanzado en gran parte al aumentar la granularidad de las agregaciones con las que
Sección 5.2. tenemos que trabajar. La primera agregación que tuvimos fue la declaración, que
6.6 Más allá de las clases: paquetes 157
en ese momento parecía un gran paso adelante de las instrucciones de la máquina. Luego vinieron las
subrutinas y más tarde las clases.
- Convenciones de nomenclatura que diferencian qué clases son públicas y cuáles son
para uso privado del paquete
- Reglas que definen qué paquetes pueden usar qué otros paquetes,
incluido si el uso puede ser herencia, contención o ambos.
Estas soluciones alternativas son buenos ejemplos de la distinción entre programaciónenun lenguaje
vs programacióndentroun idioma. Para obtener más información sobre esta distinción, consulte la
Sección 34.4, “Programe en su idioma, no en él”.
proceso de programación de
Abstracción
pseudocódigo" en el Capítulo 9, página - ¿La clase tiene un propósito central?
233.
- ¿Los servicios de la clase son lo suficientemente completos como para que otras clases no tengan que
Encapsulación
- ¿La clase minimiza la accesibilidad a sus miembros?
- ¿La clase oculta sus detalles de implementación de otras clases tanto como lo
permite el lenguaje de programación?
- ¿Evita la clase hacer suposiciones sobre sus usuarios, incluidas sus clases
derivadas?
Herencia
- ¿La herencia se usa solo para modelar relaciones "es un", es decir, las clases
derivadas se adhieren al principio de sustitución de Liskov?
- ¿Todos los miembros de datos de la clase base son privados en lugar de protegidos?
- ¿La clase minimiza las llamadas de rutina directas e indirectas a otras clases?
- ¿La clase está diseñada para usarse como copias profundas en lugar de copias superficiales a
menos que haya una razón mesurada para crear copias superficiales?
- ¿Ha investigado los problemas específicos del lenguaje para las clases en su lenguaje
de programación específico?
Recursos adicionales 159
Recursos adicionales
Clases en General
cc2e.com/0679 MEYER, Bertrand.Construcción de software orientada a objetos, 2ª ed. New York, NY: Prentice Hall
PTR, 1997. Este libro contiene una discusión detallada de los tipos de datos abstractos y explica cómo
forman la base para las clases. Los capítulos 14 a 16 analizan la herencia en profundidad. Meyer
proporciona un argumento a favor de la herencia múltiple en el Capítulo 15.
Riel, Arturo J.Heurística de diseño orientado a objetos. Reading, MA: Addison-Wesley, 1996. Este libro
contiene numerosas sugerencias para mejorar el diseño del programa, principalmente a nivel de
clase. Evité el libro durante varios años porque parecía demasiado grande. ¡Hablemos de personas
en invernaderos! Sin embargo, el cuerpo del libro tiene solo unas 200 páginas. La escritura de Riel es
accesible y agradable. El contenido es enfocado y práctico.
C++
cc2e.com/0686 Meyers, Scott.C++ eficaz: 50 formas específicas de mejorar sus programas y diseños, 2ª
ed. Reading, MA: Addison-Wesley, 1998.
Meyers, Scott, 1996,C++ más eficaz: 35 nuevas formas de mejorar sus programas y diseños.
Reading, MA: Addison-Wesley, 1996. Ambos libros de Meyers son referencias canónicas para
los programadores de C++. Los libros son entretenidos y ayudan a inculcar la apreciación de un
abogado de idiomas por los matices de C++.
Java
cc2e.com/0693 Bloch, Joshua.Guía efectiva del lenguaje de programación Java. Boston, MA: Addison-Wesley, 2001. El
libro de Bloch brinda muchos buenos consejos específicos de Java, además de presentar buenas
prácticas orientadas a objetos más generales.
básico visual
cc2e.com/0600 Los siguientes libros son buenas referencias sobre clases en Visual Basic:
Foxall, James.Estándares prácticos para Microsoft Visual Basic .NET. Redmond, WA:
Microsoft Press, 2003.
Puntos clave
- Las interfaces de clase deben proporcionar una abstracción consistente. Muchos problemas surgen
de la violación de este único principio.
- Una interfaz de clase debe ocultar algo: una interfaz de sistema, una decisión de diseño o un
detalle de implementación.
- La contención suele ser preferible a la herencia a menos que esté modelando una
relación "es un".
- La herencia es una herramienta útil, pero agrega complejidad, lo que va en contra del
imperativo técnico primario del software de administrar la complejidad.
- Las clases son su principal herramienta para gestionar la complejidad. Preste a su diseño
tanta atención como sea necesario para lograr ese objetivo.
Capítulo 7
Temas relacionados
El Capítulo 6 describió los detalles de la creación de clases. Este capítulo se enfoca en las rutinas, en
las características que marcan la diferencia entre una buena rutina y una mala. Si prefiere leer acerca
de los problemas que afectan el diseño de las rutinas antes de profundizar en los detalles esenciales,
asegúrese de leer primero el Capítulo 5, "Diseño en construcción", y vuelva a este capítulo más
adelante. Algunos atributos importantes de las rutinas de alta calidad también se analizan en el
Capítulo 8, “Programación defensiva”. Si está más interesado en leer acerca de los pasos para crear
rutinas y clases, el Capítulo 9, "El proceso de programación de pseudocódigo", podría ser un mejor
lugar para comenzar.
Antes de saltar a los detalles de las rutinas de alta calidad, será útil precisar dos términos
básicos. ¿Qué es una “rutina”? Una rutina es un método o procedimiento individual invocable
para un solo propósito. Los ejemplos incluyen una función en C++, un método en Java, una
función o subprocedimiento en Microsoft Visual Basic. Para algunos usos, las macros en C y C+
+ también pueden considerarse como rutinas. Puede aplicar muchas de las técnicas para crear
una rutina de alta calidad a estas variantes.
que es unalta calidad¿rutina? Esa es una pregunta más difícil. Quizás la respuesta más fácil es
mostrar lo que no es una rutina de alta calidad. Aquí hay un ejemplo de una rutina de baja calidad:
161
162 Capítulo 7: Rutinas de alta calidad
{
ent yo;
para ( i = 0; i < 100; i++ ) {
inputRec.revenue[i] = 0;
inputRec.expense[i] = corpExpense[ crntQtr ][ i ]; }
ActualizarCorpDatabase(empRec);
estimRevenue = ytdRevenue * 4.0 / (doble) crntQtr; color nuevo
= color anterior;
estado = ÉXITO;
if ( tipo de gasto == 1 ) {
para ( i = 0; i < 12; i++ )
beneficio[i] = ingresos[i] - gastos.tipo1[i];
}
más si ( tipo de gasto == 2 ) {
beneficio[i] = ingresos[i] - gastos.tipo2[i]; }
¿Qué tiene de malo esta rutina? Aquí hay una pista: debería poder encontrar al menos 10
problemas diferentes con él. Una vez que haya creado su propia lista, mire la siguiente
lista:
- La rutina no tiene un solo propósito. Inicializa algunas variables, escribe en una base de datos,
hace algunos cálculos, ninguno de los cuales parece estar relacionado entre sí de ninguna
manera. Una rutina debe tener un único propósito claramente definido.
- La rutina utiliza varios números mágicos:100,4.0, 12,2, y3. Los números mágicos se
analizan en la Sección 12.1, “Números en general”.
- Los parámetros de la rutina están mal ordenados y no están documentados. (El orden de los
parámetros se analiza en este capítulo. La documentación se analiza en el Capítulo 32).
cc2e.com/0799 Aparte de la computadora en sí, la rutina es el mayor invento de la informática. La rutina hace
que los programas sean más fáciles de leer y comprender que cualquier otra característica de
Referencia cruzadaLa clase
sobre cómo usar las clases de La rutina es también la mejor técnica jamás inventada para ahorrar espacio y mejorar el
manera efectiva, consulte el
rendimiento. Imagine cuánto más grande sería su código si tuviera que repetir el código para
Capítulo 6, "Clases de trabajo".
cada llamada a una rutina en lugar de bifurcarse a la rutina. Imagínese lo difícil que sería
realizar mejoras de rendimiento en el mismo código utilizado en una docena de lugares en
lugar de hacerlo todo en una sola rutina. La rutina hace posible la programación moderna.
“Está bien”, dices, “ya sé que las rutinas son geniales y programo con ellas todo el tiempo. Esta
discusión parece una especie de remedio, entonces, ¿qué quieres que haga al respecto?
Quiero que comprenda que existen muchas razones válidas para crear una rutina y que hay formas
correctas y formas incorrectas de hacerlo. Como estudiante de pregrado en ciencias de la
computación, pensé que la razón principal para crear una rutina era evitar el código duplicado. El
libro de texto introductorio que usé decía que las rutinas eran buenas porque evitar la duplicación
hacía que un programa fuera más fácil de desarrollar, depurar, documentar y mantener. Período.
Aparte de los detalles sintácticos sobre cómo usar parámetros y variables locales, ese era el alcance
de la cobertura del libro de texto. No fue una buena ni completa explicación de la teoría y práctica de
las rutinas. Las siguientes secciones contienen una explicación mucho mejor.
164 Capítulo 7: Rutinas de alta calidad
Reducir la complejidadLa razón más importante para crear una rutina es reducir la
complejidad de un programa. Cree una rutina para ocultar información para que no tenga que
pensar en ella. Claro, tendrás que pensarlo cuando escribas la rutina. Pero después de
PUNTO CLAVE
escribirlo, debería poder olvidar los detalles y usar la rutina sin ningún conocimiento de su
funcionamiento interno. Otras razones para crear rutinas (minimizar el tamaño del código,
mejorar la capacidad de mantenimiento y mejorar la corrección) también son buenas razones,
pero sin el poder de abstracción de las rutinas, los programas complejos serían imposibles de
manejar intelectualmente.
Una indicación de que una rutina debe separarse de otra rutina es el anidamiento profundo de
un bucle interno o un condicional. Reduzca la complejidad de la rutina contenedora extrayendo
la parte anidada y colocándola en su propia rutina.
Introducir una abstracción intermedia comprensiblePoner una sección de código en una rutina
bien nombrada es una de las mejores formas de documentar su propósito. En lugar de leer una serie
de declaraciones como
más
nombre de la hoja = ""
terminara si
La nueva rutina es tan corta que casi todo lo que necesita para la documentación es un buen
nombre. El nombre introduce un mayor nivel de abstracción que las ocho líneas de código
originales, lo que hace que el código sea más legible y fácil de entender, y reduce la
complejidad dentro de la rutina que originalmente contenía el código.
Evite el código duplicadoSin duda, la razón más popular para crear una rutina es evitar el
código duplicado. De hecho, la creación de código similar en dos rutinas implica un error en la
descomposición. Extraiga el código duplicado de ambas rutinas, coloque una versión genérica
del código común en una clase base y luego mueva las dos rutinas especializadas a subclases.
Alternativamente, puede migrar el código común a su propia rutina y luego dejar que ambos
llamen a la parte que se colocó en la nueva rutina. Con el código en un solo lugar, ahorra el
espacio que habría utilizado el código duplicado. Las modificaciones serán más fáciles porque
necesitará modificar el código en una sola ubicación. los
7.1 Razones válidas para crear una rutina 165
el código será más confiable porque tendrá que verificar solo un lugar para asegurarse de que el
código sea correcto. Las modificaciones serán más confiables porque evitará realizar modificaciones
sucesivas y ligeramente diferentes bajo la suposición errónea de que ha realizado modificaciones
idénticas.
Subclases de soporteNecesita menos código nuevo para anular una rutina corta y bien factorizada
que una rutina larga y mal factorizada. También reducirá la posibilidad de error en las
implementaciones de subclases si mantiene simples las rutinas reemplazables.
Ocultar secuenciasEs una buena idea ocultar el orden en que se procesan los eventos. Por
ejemplo, si el programa normalmente obtiene datos del usuario y luego obtiene datos
auxiliares de un archivo, ni la rutina que obtiene los datos del usuario ni la rutina que obtiene
los datos del archivo deben depender de que la otra rutina se realice primero. Se puede
encontrar otro ejemplo de una secuencia cuando tiene dos líneas de código que leen la parte
superior de una pila y disminuyen unapilararribavariable. Coloque esas dos líneas de código en
unPopStack()rutina para ocultar la suposición sobre el orden en que se deben realizar las dos
operaciones. Ocultar esa suposición será mejor que convertirla en código de un extremo al
otro del sistema.
Darle a la prueba una función propia enfatiza su importancia. Fomenta un esfuerzo adicional para que los
detalles de la prueba sean legibles dentro de su función. El resultado es que tanto el flujo principal del
código como la prueba en sí se vuelven más claros. La simplificación de una prueba booleana es un ejemplo
de reducción de la complejidad, que se discutió anteriormente.
Referencia cruzadaPara obtener detalles ¿Para asegurarse de que todas las rutinas sean pequeñas?No. Con tantas buenas razones para poner
sobre la ocultación de información,
código en una rutina, esta es innecesaria. De hecho, algunos trabajos se realizan mejor en una sola rutina
consulte "Ocultar secretos (Ocultar
Uno de los bloqueos mentales más fuertes para crear rutinas efectivas es la renuencia a crear una
rutina simple para un propósito simple. Construir una rutina completa que contenga dos o tres líneas
de código puede parecer una exageración, pero la experiencia demuestra lo útil que puede ser una
PUNTO CLAVE
buena rutina pequeña.
Las pequeñas rutinas ofrecen varias ventajas. Una es que mejoran la legibilidad. Una vez tuve la
siguiente línea de código en aproximadamente una docena de lugares en un programa:
Esta no es la línea de código más complicada que jamás haya leído. La mayoría de la gente
eventualmente se daría cuenta de que convierte una medida en unidades de dispositivo a una
medida en puntos. Verían que cada una de las doce líneas hacía lo mismo. Sin embargo, podría
haber sido más claro, así que creé una rutina bien nombrada para hacer la conversión en un solo
lugar:
Este ejemplo sugiere otra razón para poner operaciones pequeñas en funciones: las operaciones
pequeñas tienden a convertirse en operaciones más grandes. No lo sabía cuando escribí la rutina,
pero bajo ciertas condiciones y cuando ciertos dispositivos estaban activos,Dispositivo-
UnidadesPerlnch()devuelto0. Eso significaba que tenía que dar cuenta de la división por cero, lo que
tomó tres líneas más de código:
7.1 Razones válidas para crear una rutina 167
Si esa línea de código original todavía hubiera estado en una docena de lugares, la prueba se habría
repetido una docena de veces, para un total de 36 líneas de código nuevas. Una simple rutina redujo las 36
líneas nuevas a 3.
- Reducir la complejidad
- Subclases de soporte
- Ocultar secuencias
- Mejora la portabilidad
- Mejorar el rendimiento
Además, muchas de las razones para crear una clase también son buenas razones para crear una
rutina:
- aislar la complejidad
Referencia cruzadaPara una Para las rutinas, la cohesión se refiere a qué tan estrechamente están relacionadas las operaciones
discusión sobre la cohesión en
en una rutina. Algunos programadores prefieren el término "fortaleza": ¿qué tan fuertemente
general, consulte “Apuntar a una
3
2
La recompensa es una mayor confiabilidad. Un estudio de 450 rutinas encontró que el 50 por ciento
1
de las rutinas altamente cohesivas estaban libres de fallas, mientras que solo el 18 por ciento de las
DATOS DUROS
rutinas con baja cohesión estaban libres de fallas (Card, Church y Agresti 1986). Otro estudio de 450
rutinas diferentes (que es solo una coincidencia inusual) encontró que las rutinas con las relaciones
de acoplamiento a cohesión más altas tenían 7 veces más errores que aquellas con las relaciones de
acoplamiento a cohesión más bajas y eran 20 veces más costosas arreglar (Selby y Basili 1991).
Cohesión funcionales el mejor y más fuerte tipo de cohesión, que ocurre cuando una rutina
realiza una y sólo una operación. Los ejemplos de rutinas altamente cohesivas incluyen
pecado(),ObtenerNombreDeCliente(),BorrarArchivo(),Calcular pago de préstamo (), y
EdadDesde-Fecha de nacimiento(). Por supuesto, esta evaluación de su cohesión asume que
las rutinas hacen lo que sus nombres dicen que hacen; si hacen algo más, son menos
cohesivas y mal nombradas.
- Cohesión secuencialexiste cuando una rutina contiene operaciones que deben realizarse
en un orden específico, que comparten datos paso a paso y que no constituyen una
función completa cuando se realizan juntas.
Un ejemplo de cohesión secuencial es una rutina que, dada una fecha de nacimiento,
calcula la edad y el tiempo hasta la jubilación de un empleado. Si la rutina calcula la
edad y luego usa ese resultado para calcular el tiempo de jubilación del empleado, tiene
cohesión secuencial. Si la rutina calcula la edad y luego calcula el tiempo hasta la
jubilación en un cómputo completamente separado que usa los mismos datos de fecha
de nacimiento, solo tiene cohesión comunicacional.
7.2 Diseño a nivel de rutina 169
¿Cómo haría que la rutina fuera funcionalmente cohesiva? Crearía rutinas separadas para calcular la
edad de un empleado dada una fecha de nacimiento y calcular el tiempo hasta la jubilación dada una
fecha de nacimiento. La rutina del tiempo hasta la jubilación podría llamarse rutina de la edad.
Ambos tendrían cohesión funcional. Otras rutinas podrían llamar a cualquiera de las rutinas oa
ambas rutinas.
- Cohesión comunicacionalocurre cuando las operaciones en una rutina hacen uso de los
mismos datos y no están relacionadas de ninguna otra manera. Si una rutina imprime un
informe de resumen y luego reinicializa los datos de resumen que se le pasan, la rutina tiene
cohesión comunicacional: las dos operaciones están relacionadas solo por el hecho de que
usan los mismos datos.
Para dar a esta rutina una mejor cohesión, los datos de resumen deben reinicializarse
cerca de donde se crearon, que no deberían estar en la rutina de impresión de informes.
Divida las operaciones en rutinas individuales. El primero imprime el informe. El segundo
reinicializa los datos, cerca del código que crea o modifica los datos. Llame a ambas
rutinas desde la rutina de nivel superior que originalmente llamó la rutina
comunicacionalmente cohesiva.
Para evitar este problema, piense en las rutinas temporales como organizadoras de otros eventos.
losPuesta en marcha()La rutina, por ejemplo, podría leer un archivo de configuración, inicializar un
archivo borrador, configurar un administrador de memoria y mostrar una pantalla inicial. Para que
sea más efectivo, haga que la rutina cohesiva temporalmente llame a otras rutinas para realizar
actividades específicas en lugar de realizar las operaciones directamente. De esa manera, quedará
claro que el objetivo de la rutina es orquestar actividades en lugar de hacerlas directamente.
Los demás tipos de cohesión son generalmente inaceptables. Dan como resultado un código
mal organizado, difícil de depurar y difícil de modificar. Si una rutina tiene mala cohesión, es
mejor esforzarse en reescribirla para tener una mejor cohesión que invertir en un diagnóstico
preciso del problema. Sin embargo, saber qué evitar puede ser útil, así que aquí están los tipos
de cohesión inaceptables:
170 Capítulo 7: Rutinas de alta calidad
Para lograr una mejor cohesión, ponga las operaciones separadas en sus propias rutinas.
Asegúrese de que la rutina de llamada tenga un solo trabajo completo:ObtenerEmpleado()
más bien queGetFirstPartOfEmployeeData(). Probablemente necesitará modificar las rutinas
que obtienen el resto de los datos también. Es común modificar dos o más rutinas originales
antes de lograr la cohesión funcional en alguna de ellas.
Referencia cruzadaAunque la Sin embargo, por lo general está bien crear una rutina lógicamente cohesiva si su código
rutina podría tener una mejor
consta únicamente de una serie desiocasosentencias y llamadas a otras rutinas. En tal
cohesión, un problema de diseño
consulte "Reemplazar
entornos interactivos como Apple Macintosh, Microsoft Windows y otros entornos de
condicionales con polimorfismo GUI.
(especialmentecaso
de variables".
Describe todo lo que hace la rutina.En el nombre de la rutina, describa todos los resultados y
efectos secundarios. Si una rutina calcula los totales del informe y abre un archivo de salida,Compute-
ReportTotals()no es un nombre adecuado para la rutina.ComputeReportTotalsAndOpen-OutputFile()
es un nombre adecuado pero es demasiado largo y tonto. Si tiene rutinas con efectos secundarios,
tendrá muchos nombres largos y tontos. La cura no es usar nombres de rutinas menos descriptivos;
la cura es programar para que hagas que las cosas sucedan directamente en lugar de tener efectos
secundarios.
Evite los verbos sin sentido, vagos o insípidos.Algunos verbos son elásticos, estirados para
cubrir casi cualquier significado. nombres de rutina comoManejaCálculo(),RealizarServicios(),
Usuario de salida(),Entrada de proceso (), yTratoConSalida()no te diga lo que hacen las rutinas.
A lo sumo, estos nombres le indican que las rutinas tienen algo que ver con cálculos, servicios,
usuarios, entrada y salida. La excepción sería cuando el verbo “manejar” se usara en el sentido
técnico específico de manejar un evento.
A veces, el único problema con una rutina es que su nombre es insípido; la rutina en sí
podría estar bien diseñada. SiManejarSalida()se reemplaza conFormato y salida de
impresión (), tienes una idea bastante buena de lo que hace la rutina.
PUNTO CLAVE
En otros casos, el verbo es vago porque las operaciones realizadas por la rutina son vagas. La
rutina adolece de una debilidad de propósito, y el nombre débil es un síntoma. Si ese es el
caso, la mejor solución es reestructurar la rutina y las rutinas relacionadas para que todas
tengan propósitos más sólidos y nombres más sólidos que las describan con precisión.
No diferencie los nombres de las rutinas únicamente por el númeroUn desarrollador escribió
todo su código en una gran función. Luego tomó cada 15 líneas y creó funciones llamadas Parte 1,
Parte 2, y así. Después de eso, creó una función de alto nivel que llamó a cada parte. Este método de
CODIFICACIÓN
HORROR
crear y nombrar rutinas es especialmente atroz (y raro, espero). Pero los programadores a veces
usan números para diferenciar rutinas con nombres comoUsuario de salida,SalidaUsuario1, y
SalidaUsuario2. Los números al final de estos nombres no proporcionan ninguna indicación de las
diferentes abstracciones que representan las rutinas y, por lo tanto, las rutinas están mal
nombradas.
Hacer nombres de rutinas tan largos como sea necesarioLa investigación muestra que la longitud
promedio óptima para un nombre de variable es de 9 a 15 caracteres. Las rutinas tienden a ser más com-
172 Capítulo 7: Rutinas de alta calidad
plicados que las variables, y los buenos nombres para ellos tienden a ser más largos. Por otro lado, los
nombres de las rutinas a menudo se adjuntan a los nombres de los objetos, lo que esencialmente
proporciona parte del nombre de forma gratuita. En general, el énfasis al crear un nombre de rutina debe
ser hacer que el nombre sea lo más claro posible, lo que significa que debe hacer que su nombre sea tan
largo o corto como sea necesario para que sea comprensible.
Referencia cruzadaPara conocer Para nombrar una función, use una descripción del valor de retornoUna función devuelve un valor, y la
la distinción entre procedimientos
función debe tener el nombre del valor que devuelve. Por ejemplo,porque(), IDcliente.Siguiente(),
y funciones, consulte la Sección
En los lenguajes orientados a objetos, no necesita incluir el nombre del objeto en el nombre del
procedimiento porque el objeto mismo está incluido en la llamada. Invocas rutinas con sentencias
comodocumento.Imprimir(),orderInfo.Comprobar(), yingresos mensuales.Calc(). nombres como
documento.ImprimirDocumento()son redundantes y pueden volverse imprecisos cuando se
transfieren a clases derivadas. SiControlares una clase derivada deDocumento,comprobar.Imprimir()
parece claramente estar imprimiendo un cheque, mientras quecomprobar.ImprimirDocumento()
parece que podría estar imprimiendo un registro de chequera o un estado de cuenta mensual, pero
no parece que esté imprimiendo un cheque.
Referencia cruzadaPara obtener una lista Usa los opuestos con precisiónEl uso de convenciones de nomenclatura para los opuestos ayuda a
similar de opuestos en nombres de
la coherencia, lo que ayuda a la legibilidad. pares opuestos comoprimero últimose entienden
variables, consulte “Opuestos comunes en
nombres de variables”.
comúnmente. pares opuestos comoArchivoAbrir()y_cerrar()no son simétricas y son confusas. Aquí
Nombres” en la Sección 11.1. hay algunos opuestos comunes:
El código de uno de mis proyectos asignó a cada objeto un identificador único. Olvidamos establecer
una convención para nombrar las rutinas que devolverían el identificador del objeto, por lo que
teníamos nombres de rutina como estos:
7.4 ¿Cuánto puede durar una rutina? 173
empleado.id.Get()
dependiente.GetId()
supervisor()
Identificación del candidato()
La mejor longitud máxima teórica a menudo se describe como una pantalla o una o dos páginas de
listado de programas, aproximadamente de 50 a 150 líneas. Con este espíritu, IBM alguna vez limitó
las rutinas a 50 líneas y TRW las limitó a dos páginas (McCabe 1976). Los programas modernos
tienden a tener volúmenes de rutinas extremadamente cortas mezcladas con algunas rutinas más
largas. Sin embargo, las rutinas largas están lejos de extinguirse. Poco antes de terminar este libro,
visité dos sitios de clientes en un mes. Los programadores de un sitio estaban lidiando con una
rutina que tenía unas 4.000 líneas de código, y los programadores del otro sitio estaban tratando de
dominar una rutina que tenía más de 12.000 líneas.
A lo largo de los años se ha acumulado una montaña de investigaciones sobre la duración de las rutinas,
algunas de las cuales son aplicables a los programas modernos y otras no:
3
2
- Un estudio de Basili y Perricone encontró que el tamaño de la rutina estaba inversamente
1
correlacionado con los errores: a medida que aumentaba el tamaño de las rutinas (hasta 200 líneas
DATOS DUROS
de código), el número de errores por línea de código disminuía (Basili y Perricone 1984).
- Otro estudio encontró que el tamaño de la rutina no se correlacionó con los errores, aunque
la complejidad estructural y la cantidad de datos se correlacionaron con los errores (Shen et
al. 1985).
Traducido del inglés al español - www.onlinedoctranslator.com
- Un estudio de 1986 encontró que las rutinas pequeñas (32 líneas de código o menos) no
estaban correlacionadas con un menor costo o tasa de fallas (Card, Church y Agresti 1986;
Card y Glass 1990). La evidencia sugirió que las rutinas más grandes (65 líneas de código o
más) eran más baratas de desarrollar por línea de código.
- Un estudio empírico de 450 rutinas encontró que las rutinas pequeñas (aquellas con menos de 143
declaraciones fuente, incluidos los comentarios) tenían un 23 por ciento más de errores por línea de
código que las rutinas más grandes, pero eran 2,4 veces menos costosas de corregir que las rutinas
más grandes (Selby y Basili 1991). ).
- Otro estudio encontró que el código necesitaba ser cambiado menos cuando las rutinas tenían un
promedio de 100 a 150 líneas de código (Lind y Vairavan 1989).
- Un estudio de IBM encontró que las rutinas más propensas a errores eran aquellas que tenían
más de 500 líneas de código. Más allá de las 500 líneas, la tasa de error tendía a ser
proporcional al tamaño de la rutina (Jones 1986a).
¿Dónde deja todo esto la cuestión de la longitud de las rutinas en los programas orientados a
objetos? Un gran porcentaje de las rutinas en los programas orientados a objetos serán rutinas de
acceso, que serán muy cortas. De vez en cuando, un algoritmo complejo conducirá a una rutina más
larga y, en esas circunstancias, se debe permitir que la rutina crezca orgánicamente hasta 100 o 200
líneas. (Una línea es una línea de código fuente sin comentarios ni en blanco.) Décadas de evidencia
dicen que las rutinas de tal longitud no son más propensas a errores que las rutinas más cortas. Deje
que cuestiones como la cohesión de la rutina, la profundidad del anidamiento, la cantidad de
variables, la cantidad de puntos de decisión, la cantidad de comentarios necesarios para explicar la
rutina y otras consideraciones relacionadas con la complejidad dicten la duración de la rutina en
lugar de imponer una restricción de longitud per se. .
Dicho esto, si desea escribir rutinas de más de 200 líneas, tenga cuidado. Ninguno de los estudios
que informaron reducción de costos, reducción de tasas de error, o ambos con rutinas más grandes
distinguieron entre tamaños superiores a 200 líneas, y es probable que se encuentre con un límite
superior de comprensibilidad al pasar 200 líneas de código.
DATOS DUROS
errores eran errores de interfaz interna, errores en la comunicación entre rutinas. Aquí hay algunas
pautas para minimizar los problemas de la interfaz:
Referencia cruzadaPara obtener detalles Poner los parámetros en orden de entrada-modificación-salidaEn lugar de ordenar los parámetros al
sobre la rutina de documentación
azar o alfabéticamente, enumere los parámetros que son solo de entrada primero, de entrada y salida en
parámetros, consulte “Rutinas de
procedimiento ChangeSentenceCase(
casoDeseado: en StringCase; oración:
dentro fuera Oración
);
...
procedimiento ImprimirNúmeroPágina(
número de página: en entero;
estado: fuera Tipo de estado
);
Esta convención de ordenamiento entra en conflicto con la convención de la biblioteca C de colocar primero el
parámetro modificado. La convención de entrada-modificación-salida tiene más sentido para mí, pero si usted
ordena los parámetros de alguna manera de manera consistente, seguirá haciendo un servicio a los lectores de su
código.
vacío InvertirMatriz(
EN Matriz matriz original,
AFUERA Matriz * matriz de resultados
);
...
vacío ChangeSentenceCase(
EN StringCase caso deseado,
ENTRADA SALIDA Oración *sentenceToEdit
);
...
vacío ImprimirNúmeroPágina(
EN número de página int,
AFUERA Tipo de estado y estado
);
En este caso, elENyAFUERALas macro-palabras clave se utilizan con fines de documentación. Para
hacer que el valor de un parámetro pueda ser cambiado por la rutina llamada, el parámetro aún
debe pasarse como un puntero o como un parámetro de referencia.
176 Capítulo 7: Rutinas de alta calidad
Antes de adoptar esta técnica, asegúrese de considerar un par de inconvenientes importantes. Definiendo el
tuyoENyAFUERALas palabras clave amplían el lenguaje C++ de una manera que no será familiar para la
mayoría de las personas que lean su código. Si amplía el lenguaje de esta manera, asegúrese de hacerlo de
manera consistente, preferiblemente en todo el proyecto. Una segunda limitación es que elENyAFUERAel
compilador no podrá hacer cumplir las palabras clave, lo que significa que podría etiquetar un parámetro
comoENy luego modificarlo dentro de la rutina de todos modos. Eso podría hacer que un lector de su código
asuma que el código es correcto cuando no lo es. Usando C++constanteLa palabra clave normalmente será
el medio preferible para identificar parámetros de solo entrada.
Si varias rutinas usan parámetros similares, coloque los parámetros similares en un orden
consistenteEl orden de los parámetros de rutina puede ser mnemotécnico y el orden incoherente
puede hacer que los parámetros sean difíciles de recordar. Por ejemplo, en C, lafprintf()La rutina es la
misma que laimprimirf()excepto que agrega un archivo como primer argumento. Una rutina similar,
fputs(), es lo mismo quepone()excepto que agrega un archivo como último argumento. Esta es una
diferencia sin sentido y agravante que hace que los parámetros de estas rutinas sean más difíciles de
recordar de lo necesario.
Por otro lado, la rutinastrncpy()en C toma los argumentos cadena de destino, cadena de
origen y número máximo de bytes, en ese orden, y la rutinamemcpy()toma los mismos
argumentos en el mismo orden. La similitud entre las dos rutinas ayuda a recordar los
parámetros en cualquiera de las rutinas.
3
2
Usa todos los parámetrosSi pasa un parámetro a una rutina, utilícelo. Si no lo está utilizando,
1
elimine el parámetro de la interfaz de rutina. Los parámetros no utilizados se correlacionan con
DATOS DUROS
una mayor tasa de error. En un estudio, el 46 por ciento de las rutinas sin variables sin usar no
tenían errores, y solo entre el 17 y el 29 por ciento de las rutinas con más de una variable sin
referencia no tenían errores (Card, Church y Agresti 1986).
Esta regla para eliminar parámetros no utilizados tiene una excepción. Si está compilando parte de su
programa de forma condicional, puede compilar partes de una rutina que use un determinado
parámetro. Esté nervioso por esta práctica, pero si está convencido de que funciona, también está
bien. En general, si tiene una buena razón para no usar un parámetro, continúe y déjelo en su lugar.
Si no tiene una buena razón, haga el esfuerzo de limpiar el código.
Poner las variables de estado o error al finalPor convención, las variables de estado y las variables que
indican que se ha producido un error van en último lugar en la lista de parámetros. Son secundarios al
objetivo principal de la rutina y son parámetros de solo salida, por lo que es una convención sensata.
No use parámetros de rutina como variables de trabajoEs peligroso usar los parámetros pasados
a una rutina como variables de trabajo. Utilice variables locales en su lugar. Por ejemplo, en el
siguiente fragmento de Java, la variablevalor de entradase utiliza incorrectamente para almacenar
resultados intermedios de un cálculo:
7.5 Cómo utilizar los parámetros de rutina 177
entradaya no contiene el }
valor que se ingresó.
En este fragmento de código,valor de entradaes engañoso porque para cuando la ejecución llega a la última
línea,valor de entradaya no contiene el valor de entrada; contiene un valor calculado basado en parte en el
valor de entrada y, por lo tanto, tiene un nombre incorrecto. Si luego necesita modificar la rutina para usar
el valor de entrada original en algún otro lugar, probablemente usarávalor de entrada y suponga que
contiene el valor de entrada original cuando en realidad no lo contiene.
Un mejor enfoque es evitar los problemas actuales y futuros mediante el uso explícito de
variables de trabajo. El siguiente fragmento de código demuestra la técnica:
Asignar el valor de entrada a una variable de trabajo enfatiza de dónde proviene el valor.
Elimina la posibilidad de que una variable de la lista de parámetros sea modificada
accidentalmente. En C++, el compilador puede hacer cumplir esta práctica usando la palabra
claveconstante. Si designa un parámetro comoconstante, no puede modificar su valor dentro
de una rutina.
178 Capítulo 7: Rutinas de alta calidad
Referencia cruzadaPara obtener Documente las suposiciones de la interfaz sobre los parámetrosSi asume que los datos que
detalles sobre las suposiciones de la
se pasan a su rutina tienen ciertas características, documente las suposiciones a medida que
interfaz, consulte la introducción al
Código."
comentar sus suposiciones, use aserciones para ponerlas en código.
- Significados de los códigos de estado y valores de error si no se usan los tipos enumerados
3
2
Limite el número de parámetros de una rutina a unos sieteSiete es un número mágico para la
1
comprensión de las personas. La investigación psicológica ha encontrado que las personas
DATOS DUROS
generalmente no pueden realizar un seguimiento de más de siete fragmentos de información a la
vez (Miller 1956). Este descubrimiento se ha aplicado a una enorme cantidad de disciplinas, y parece
seguro conjeturar que la mayoría de las personas no pueden realizar un seguimiento de más de
siete parámetros de rutina a la vez.
En la práctica, cuánto puede limitar la cantidad de parámetros depende de cómo su lenguaje maneje
los tipos de datos complejos. Si programa en un lenguaje moderno que admita datos estructurados,
puede pasar un tipo de datos compuesto que contenga 13 campos y considerarlo como un
"fragmento" mental de datos. Si programa en un lenguaje más primitivo, es posible que deba pasar
los 13 campos individualmente.
Referencia cruzadaPara obtener Si se encuentra pasando constantemente más de unos pocos argumentos, el acoplamiento entre sus
detalles sobre cómo pensar en las
rutinas es demasiado estrecho. Diseñe la rutina o grupo de rutinas para reducir el acoplamiento. Si
interfaces, consulte "Buena
Pasar las variables u objetos que la rutina necesita para mantener su abstracción de interfaz
Hay dos escuelas de pensamiento en competencia sobre cómo pasar miembros de un objeto a una
rutina. Suponga que tiene un objeto que expone datos a través de 10 rutinas de acceso y la rutina
llamada necesita tres de esos elementos de datos para hacer su trabajo.
Los defensores de la primera escuela de pensamiento argumentan que solo se deben aprobar
los tres elementos específicos que necesita la rutina. Argumentan que hacer esto mantendrá
las conexiones entre las rutinas al mínimo; reducir el acoplamiento; y hacerlos más fáciles de
entender, reutilizar, etc. Dicen que pasar todo el objeto a una rutina viola el principio de
encapsulación al exponer potencialmente las 10 rutinas de acceso a la rutina que se llama.
Los defensores de la segunda escuela argumentan que se debe aprobar todo el objeto.
Argumentan que la interfaz puede permanecer más estable si la rutina llamada tiene la
flexibilidad de usar miembros adicionales del objeto sin cambiar la interfaz de la rutina.
Argumentan que pasar tres elementos específicos viola la encapsulación al exponer qué
elementos de datos específicos está usando la rutina.
Creo que ambas reglas son simplistas y pasan por alto la consideración más importante:
¿Qué abstracción presenta la interfaz de la rutina?Si la abstracción es que la rutina
espera que tenga tres elementos de datos específicos, y es solo una coincidencia que
esos tres elementos los proporcione el mismo objeto, entonces debe pasar los tres
elementos de datos específicos individualmente. Sin embargo, si la abstracción es que
siempre tendrá ese objeto en particular a mano y la rutina hará una u otra cosa con ese
objeto, entonces realmente rompe la abstracción cuando expone los tres elementos de
datos específicos.
Si está pasando todo el objeto y se encuentra creando el objeto, llenándolo con los tres
elementos que necesita la rutina llamada y luego sacando esos elementos del objeto después
de que se llama la rutina, eso es una indicación de que debe estar pasando los tres elementos
específicos en lugar de todo el objeto. (En general, el código que "se configura" para una
llamada a una rutina o "se quita" después de una llamada a una rutina es una indicación de
que la rutina no está bien diseñada).
Usar parámetros con nombreEn algunos idiomas, puede asociar explícitamente parámetros
formales con parámetros reales. Esto hace que el uso de parámetros sea más autodocumentado y
ayuda a evitar errores de parámetros no coincidentes. Aquí hay un ejemplo en Visual Basic:
Esta técnica es especialmente útil cuando tiene listas más largas que el promedio de argumentos con tipos
idénticos, lo que aumenta las posibilidades de que pueda insertar un parámetro que no coincida sin que el
compilador lo detecte. La asociación explícita de parámetros puede ser una exageración en muchos
entornos, pero en entornos críticos para la seguridad u otros entornos de alta confiabilidad, la garantía
adicional de que los parámetros coinciden de la manera esperada puede valer la pena.
Asegúrese de que los parámetros reales coincidan con los parámetros formalesLos parámetros formales,
también conocidos como “parámetros ficticios”, son las variables declaradas en una definición de rutina. Los
parámetros reales son las variables, constantes o expresiones utilizadas en las llamadas de rutina reales.
Un error común es colocar el tipo incorrecto de variable en una llamada de rutina; por ejemplo, usar
un número entero cuando se necesita un punto flotante. (Este es un problema solo en lenguajes de
tipo débil como C cuando no está usando advertencias completas del compilador. Los lenguajes de
tipo fuerte como C ++ y Java no tienen este problema). Cuando los argumentos se ingresan solo, esto
rara vez es un problema; por lo general, el compilador convierte el tipo real en el tipo formal antes de
pasarlo a la rutina. Si es un problema, normalmente su compilador le da una advertencia. Pero en
algunos casos, particularmente cuando el argumento se usa tanto para la entrada como para la
salida, puede verse afectado al pasar el tipo de argumento incorrecto.
Desarrolle el hábito de verificar los tipos de argumentos en las listas de parámetros y prestar atención a las
advertencias del compilador sobre los tipos de parámetros que no coinciden.
7.6 Consideraciones especiales en el uso de funciones 181
Una práctica de programación común es tener una función que opere como un procedimiento y
devuelva un valor de estado. Lógicamente, funciona como un procedimiento, pero como devuelve un
valor, es oficialmente una función. Por ejemplo, puede tener una rutina llamadaFormato de salida ()
usado con unreporteobjeto en sentencias como esta:
La alternativa es crear un procedimiento que tenga una variable de estado como parámetro explícito,
que promueva código como este fragmento:
Prefiero el segundo estilo de codificación, no porque sea muy estricto con la diferencia entre
funciones y procedimientos, sino porque hace una clara separación entre la llamada de rutina y
la prueba del valor de estado. Combinar la llamada y la prueba en una sola línea de código
aumenta la densidad de la instrucción y, en consecuencia, su complejidad. El siguiente uso de
una función también está bien:
En resumen, use una función si el propósito principal de la rutina es devolver el valor indicado por
el nombre de la función. De lo contrario, utilice un procedimiento.
PUNTO CLAVE
El uso de una función crea el riesgo de que la función devuelva un valor de retorno incorrecto.
Esto suele suceder cuando la función tiene varias rutas posibles y una de las rutas no establece
un valor de retorno. Para reducir este riesgo, haga lo siguiente:
Compruebe todas las rutas de retorno posiblesAl crear una función, ejecute mentalmente cada ruta para
asegurarse de que la función devuelva un valor en todas las circunstancias posibles. Es una buena práctica
inicializar el valor de retorno al comienzo de la función a un valor predeterminado; esto proporciona una red
de seguridad en caso de que no se establezca el valor de retorno correcto.
No devuelva referencias o punteros a datos localesTan pronto como finalice la rutina y los datos locales
queden fuera del alcance, la referencia o el puntero a los datos locales no serán válidos. Si un objeto necesita
devolver información sobre sus datos internos, debe guardar la información como datos de miembro de
clase. A continuación, debe proporcionar funciones de acceso que devuelvan los valores de los elementos de
datos de miembros en lugar de referencias o punteros a datos locales.
sus propias herramientas de Expresiones de macro completamente entre paréntesisDebido a que las macros y sus argumentos
programación”. se expanden en el código, tenga cuidado de que se expandan de la forma que desee. Un problema
común radica en crear una macro como esta:
Esto está cerca, pero todavía no hay cigarro. Si utilizaCubo()en una expresión que tiene
operadores con mayor precedencia que la multiplicación, el(a)*(a)*(a)será desgarrado. Para
evitar eso, encierre toda la expresión entre paréntesis:
Rodee las macros de varias instrucciones con llavesUna macro puede tener varias declaraciones,
lo cual es un problema si la trata como si fuera una sola declaración. Aquí hay un ejemplo de una
macro que se dirige hacia problemas:
for ( número de entradas = 0; número de entradas < número de entradas; número de entradas++ )
LookupEntry( cuenta de entradas, índice de tabla [ cuenta de entradas ] );
Esta macro tiene problemas porque no funciona como lo haría una función normal. Como se
muestra, la única parte de la macro que se ejecuta en elporloop es la primera línea de la
macro:
La práctica de usar macros como sustitutos de las llamadas a funciones generalmente se considera
arriesgada y difícil de entender (mala práctica de programación), así que use esta técnica solo si sus
circunstancias específicas lo requieren.
Nombre macros que se expanden a código como rutinas para que puedan ser reemplazadas por
rutinas si es necesarioLa convención en C++ para nombrar macros es usar todas las letras mayúsculas. Sin
embargo, si la macro se puede reemplazar por una rutina, asígnele un nombre utilizando la convención de
nomenclatura para rutinas. De esa manera, puede reemplazar macros con rutinas y viceversa sin cambiar
nada más que la rutina involucrada.
184 Capítulo 7: Rutinas de alta calidad
Como señala Bjarne Stroustrup, diseñador de C++, “Casi todas las macros muestran una falla en el
lenguaje de programación, en el programa o en el programador... Cuando usa macros, debe esperar
un servicio inferior de herramientas como depuradores, herramientas de referencia cruzada y
PUNTO CLAVE
perfiladores” (Stroustrup 1997). Las macros son útiles para admitir la compilación condicional
(consulte la Sección 8.6, “Ayudas para la depuración”), pero los programadores cuidadosos
generalmente usan una macro como alternativa a una rutina solo como último recurso.
Rutinas en línea
C++ admite unaen líneapalabra clave. Una rutina en línea permite al programador tratar el código como una rutina
en el momento de la escritura del código, pero el compilador generalmente convertirá cada instancia de la rutina en
código en línea en el momento de la compilación. La teoría es queen líneapuede ayudar a producir un código
altamente eficiente que evita la sobrecarga de llamadas de rutina.
Use rutinas en línea con moderaciónLas rutinas en línea violan la encapsulación porque C ++ requiere que
el programador coloque el código para la implementación de la rutina en línea en el archivo de encabezado,
lo que lo expone a todos los programadores que usan el archivo de encabezado.
Las rutinas en línea requieren que se genere el código completo de una rutina cada vez que se invoca la rutina, lo
que para una rutina en línea de cualquier tamaño aumentará el tamaño del código. Eso puede crear sus propios
problemas.
7.7 Rutinas macro y rutinas en línea 185
El resultado final de la inserción por razones de rendimiento es el mismo que el resultado final
de cualquier otra técnica de codificación motivada por el rendimiento: perfilar el código y
medir la mejora. Si la ganancia de rendimiento anticipada no justifica la molestia de perfilar el
código para verificar la mejora, tampoco justifica la erosión en la calidad del código.
de los pasos utilizados para crear una - ¿Todas las partes de la rutina que se beneficiarían de ser puestas en sus
rutina, consulte la lista de verificación "El
propias rutinas han sido puestas en sus propias rutinas?
proceso de programación de
pseudocódigo" en el Capítulo 9, página - ¿El nombre de la rutina es un nombre fuerte y claro de verbo más objeto para un
215.
procedimiento o una descripción del valor devuelto para una función?
- ¿La lista de parámetros de la rutina, tomada como un todo, presenta una abstracción de
interfaz consistente?
Puntos clave
- La razón más importante para crear una rutina es mejorar la manejabilidad intelectual
de un programa, y puede crear una rutina por muchas otras buenas razones. Ahorrar
espacio es una razón menor; mejor legibilidad, confiabilidad y modificabilidad son
mejores razones.
- Las funciones deben usarse solo cuando el propósito principal de la función es devolver
el valor específico descrito por el nombre de la función.
- Los programadores cuidadosos usan las macrorutinas con cuidado y solo como último recurso.
Capítulo 8
Programación defensiva
cc2e.com/0861 Contenido
- 8.5 Bloquee su programa para contener el daño causado por los errores: página 203
Temas relacionados
- Diseño para el cambio: "Identificar áreas con probabilidad de cambio" en la Sección 5.3
- Depuración: Capítulo 23
Este capítulo describe cómo protegerse del frío y cruel mundo de los datos no válidos, los
eventos que “nunca” pueden ocurrir y los errores de otros programadores. Si es un
programador experimentado, puede saltarse la siguiente sección sobre el manejo de datos de
entrada y comenzar con la Sección 8.2, que revisa el uso de aserciones.
187
188 Capítulo 8: Programación defensiva
Para el software de producción, la entrada y salida de basura no es suficiente. Un buen programa nunca saca
basura, independientemente de lo que reciba. Un buen programa usa "basura entra, nada sale", "basura
entra, mensaje de error sale" o "no se permite la entrada de basura". Según los estándares actuales, "entra
PUNTO CLAVE
basura, sale basura" es la marca de un programa descuidado y no seguro.
Verifique los valores de todos los datos de fuentes externasAl obtener datos de un archivo, un usuario,
la red o alguna otra interfaz externa, asegúrese de que los datos estén dentro del rango permitido.
Asegúrese de que los valores numéricos estén dentro de las tolerancias y que las cadenas sean lo
suficientemente cortas para manejarlas. Si se pretende que una cadena represente un rango restringido de
valores (como una identificación de transacción financiera o algo similar), asegúrese de que la cadena sea
válida para el propósito previsto; en caso contrario rechazarlo. Si está trabajando en una aplicación segura,
desconfíe especialmente de los datos que puedan atacar su sistema: intentos de desbordamiento de búfer,
comandos SQL inyectados, código HTML o XML inyectado, desbordamiento de enteros, datos pasados a
llamadas del sistema, etc.
Verifique los valores de todos los parámetros de entrada de rutinaVerificar los valores de los
parámetros de entrada de la rutina es esencialmente lo mismo que verificar los datos que provienen
de una fuente externa, excepto que los datos provienen de otra rutina en lugar de una interfaz
externa. La discusión en la Sección 8.5, “Proteja su programa para contener el daño causado por los
errores”, proporciona una forma práctica de determinar qué rutinas necesitan verificar sus entradas.
Decidir cómo manejar entradas incorrectasUna vez que ha detectado un parámetro no válido,
¿qué hace con él? Dependiendo de la situación, puede elegir cualquiera de una docena de enfoques
diferentes, que se describen en detalle en la Sección 8.3, "Técnicas de manejo de errores", más
adelante en este capítulo.
Como sugiere la Figura 8-1, protegerse de problemas aparentemente pequeños puede marcar
una diferencia mayor de lo que piensa. El resto de este capítulo describe opciones específicas
para verificar datos de fuentes externas, verificar parámetros de entrada y manejar entradas
incorrectas.
Mike Siegel/The Seattle Times
Figura 8-1Parte del puente flotante de la Interestatal 90 en Seattle se hundió durante una tormenta porque
los tanques de flotación quedaron descubiertos, se llenaron de agua y el puente se volvió demasiado
pesado para flotar. Durante la construcción, protegerse contra las cosas pequeñas es más importante de lo
que piensa.
8.2 Afirmaciones
Una aserción es un código que se usa durante el desarrollo, generalmente una rutina o una macro,
que permite que un programa se verifique a sí mismo mientras se ejecuta. Cuando una afirmación es
verdadera, eso significa que todo está funcionando como se esperaba. Cuando es falso, significa que
ha detectado un error inesperado en el código. Por ejemplo, si el sistema asume que un archivo de
información de clientes nunca tendrá más de 50.000 registros, el programa podría contener una
afirmación de que el número de registros es menor o igual a 50.000. Siempre que el número de
registros sea inferior o igual a 50.000, la afirmación permanecerá en silencio. Sin embargo, si
encuentra más de 50.000 registros, "afirmará" en voz alta que hay un error en el programa.
Las aserciones son especialmente útiles en programas grandes y complicados y en programas de alta
confiabilidad. Permiten a los programadores eliminar más rápidamente las suposiciones de interfaz que no
coinciden, los errores que aparecen cuando se modifica el código, etc.
PUNTO CLAVE
Una aserción generalmente toma dos argumentos: una expresión booleana que describe la
suposición que se supone que es cierta y un mensaje para mostrar si no lo es. Así es como se vería
una aserción de Java si la variabledenominadorse esperaba que fuera distinto de cero:
190 Capítulo 8: Programación defensiva
- Que un archivo o flujo esté abierto (o cerrado) cuando una rutina comience a ejecutarse (o
cuando termine de ejecutarse)
- Que un archivo o flujo está al principio (o al final) cuando una rutina comienza a ejecutarse (o
cuando termina de ejecutarse)
- Que un archivo o flujo está abierto para solo lectura, solo escritura o lectura y escritura
- Que el valor de una variable de solo entrada no sea cambiado por una rutina
- Que una matriz u otro contenedor pasado a una rutina puede contener al menosX número
de elementos de datos
- Que un contenedor esté vacío (o lleno) cuando una rutina comienza a ejecutarse (o cuando
finaliza)
- Que los resultados de una rutina complicada y altamente optimizada coincidan con los resultados de
una rutina más lenta pero claramente escrita
Por supuesto, estos son solo los conceptos básicos, y sus propias rutinas contendrán muchas más
suposiciones específicas que puede documentar usando aserciones.
Normalmente, no desea que los usuarios vean mensajes de aserción en el código de producción; Las
aserciones se utilizan principalmente durante el desarrollo y el mantenimiento. Las aserciones normalmente
se compilan en el código en el momento del desarrollo y se compilan fuera del código para la producción.
Durante el desarrollo, las afirmaciones eliminan suposiciones contradictorias, condiciones inesperadas,
valores incorrectos que se pasan a las rutinas, etc. Durante la producción, se pueden compilar fuera del
código para que las aserciones no degraden el rendimiento del sistema.
8.2 Afirmaciones 191
Use el código de manejo de errores para las condiciones que espera que ocurran; usar aserciones para
condiciones que deberíannuncaocurrirLas afirmaciones verifican las condiciones que deberían nunca
ocurrir. El código de manejo de errores verifica circunstancias fuera de lo nominal que pueden no ocurrir
muy a menudo, pero que el programador que escribió el código ha anticipado y que deben ser manejadas
por el código de producción. El manejo de errores generalmente verifica si hay datos de entrada
incorrectos; las aserciones comprueban si hay errores en el código.
Si se usa código de manejo de errores para abordar una condición anómala, el manejo de errores
permitirá que el programa responda al error correctamente. Si se activa una aserción por una
condición anómala, la acción correctiva no es simplemente manejar un error correctamente; la
acción correctiva es cambiar el código fuente del programa, volver a compilar y lanzar una nueva
versión del software.
Una buena manera de pensar en las afirmaciones es como documentación ejecutable: no puede confiar en ellas
para hacer que el código funcione, pero pueden documentar las suposiciones de forma más activa que los
Referencia cruzadaPodría ver Ejemplo de Visual Basic de un Uso Peligroso de una Aserción
esto como uno de los muchos Debug.Assert( PerformAction() ) ' No se pudo realizar la acción
problemas asociados con poner
Para obtener más ejemplos, El problema con este código es que, si no compilas las aserciones, no compilas el código que
consulte "Uso de solo una
realiza la acción. Coloque sentencias ejecutables en sus propias líneas, asigne los resultados a
instrucción por línea" en
Sección 31.5.
las variables de estado y pruebe las variables de estado en su lugar. He aquí un ejemplo de un
uso seguro de una aserción:
Otras lecturasPara obtener más Use aserciones para documentar y verificar condiciones previas y posterioresLas
información sobre las condiciones
condiciones previas y posteriores son parte de un enfoque para el diseño y desarrollo de
previas y posteriores, consulte
condiciones previasson las propiedades que el código de cliente de una rutina o clase promete que serán
verdaderas antes de llamar a la rutina o instanciar el objeto. Las condiciones previas son las obligaciones del
código de cliente con el código al que llama.
poscondicionesson las propiedades que la rutina o clase promete que serán verdaderas cuando
termine de ejecutarse. Las poscondiciones son las obligaciones de la rutina o clase con el código que
la usa.
Las aserciones son una herramienta útil para documentar condiciones previas y posteriores. Los
comentarios se pueden usar para documentar condiciones previas y posteriores, pero, a diferencia de los
comentarios, las afirmaciones pueden verificar dinámicamente si las condiciones previas y posteriores son
verdaderas.
En el siguiente ejemplo, las aserciones se utilizan para documentar las condiciones previas
y posteriores delVelocidadrutina.
Ejemplo de Visual Basic del uso de aserciones para documentar condiciones previas y
posteriores
Función Privada Velocidad ( _
ByVal latitud como único, _ ByVal
longitud como único, _ ByVal elevación
como único _ ) como único
...
' Postcondiciones
Debug.Assert (0 <= returnVelocity y returnVelocity <= 600)
Referencia cruzadaPara obtener más Para un código muy robusto, afirme y luego maneje el error de todos modosPara cualquier condición
información sobre robustez, consulte
de error dada, una rutina generalmente usará una aserción o un código de manejo de errores, pero no
"Robustez frente a corrección" en la
Pero los programas y proyectos del mundo real tienden a ser demasiado complicados para confiar
únicamente en afirmaciones. En un sistema grande y de larga duración, diferentes diseñadores
pueden diseñar diferentes piezas durante un período de 5 a 10 años o más. Los diseñadores estarán
separados en el tiempo, a través de numerosas versiones. Sus diseños se centrarán en diferentes
tecnologías en diferentes puntos del desarrollo del sistema. Los diseñadores estarán separados
geográficamente, especialmente si partes del sistema se adquieren de fuentes externas. Los
programadores habrán trabajado con diferentes estándares de codificación en diferentes puntos de
la vida útil del sistema. En un gran equipo de desarrollo, algunos programadores inevitablemente
serán más concienzudos que otros y algunas partes del código se revisarán más rigurosamente que
otras partes del código. Algunos programadores probarán la unidad de su código más a fondo que
otros. Con equipos de prueba trabajando en diferentes regiones geográficas y sujetos a presiones
comerciales que dan como resultado una cobertura de prueba que varía con cada versión, tampoco
puede contar con pruebas de regresión integrales a nivel del sistema.
En tales circunstancias, tanto las aserciones como el código de manejo de errores pueden usarse
para abordar el mismo error. En el código fuente de Microsoft Word, por ejemplo, se afirman las
condiciones que siempre deberían ser verdaderas, pero dichos errores también son manejados por
el código de manejo de errores en caso de que la afirmación falle. Para aplicaciones
extremadamente grandes, complejas y duraderas como Word, las aserciones son valiosas porque
ayudan a eliminar tantos errores de tiempo de desarrollo como sea posible. Pero la aplicación es tan
compleja (millones de líneas de código) y ha pasado por tantas generaciones de modificaciones que
no es realista suponer que todos los errores concebibles serán detectados y corregidos antes de que
se envíe el software, por lo que los errores deben manejarse. en la versión de producción del sistema
también.
194 Capítulo 8: Programación defensiva
Ejemplo de Visual Basic del uso de aserciones para documentar condiciones previas y
posteriores
Función Privada Velocidad ( _
ByRef latitud como único, _ ByRef
longitud como único, _ ByRef elevación
como único _ ) como único
' Desinfectar datos de entrada. Los valores deben estar dentro de los rangos afirmados
anteriormente, ' pero si un valor no está dentro de su rango válido, se cambiará al ' valor legal más
cercano
Aquí está el código que maneja datos de entrada Si ( latitud < -90 ) Entonces
incorrectos en tiempo de ejecución. latitud = -90
ElseIf ( latitud > 90 ) Entonces
latitud = 90
Terminara si
Devolver un valor neutroA veces, la mejor respuesta a los datos erróneos es continuar operando y
simplemente devolver un valor que se sabe que es inofensivo. Un cálculo numérico puede devolver 0.
Una operación de cadena puede devolver una cadena vacía o una operación de puntero puede
devolver un puntero vacío. Una rutina de dibujo que obtiene un valor de entrada incorrecto para el
color en un videojuego podría usar el color de fondo o de primer plano predeterminado. Sin
embargo, una rutina de dibujo que muestre datos de rayos X para pacientes con cáncer no querría
mostrar un "valor neutral". En ese caso, sería mejor cerrar el programa que mostrar datos incorrectos
del paciente.
8.3 Técnicas de manejo de errores 195
Devuelve la misma respuesta que la vez anteriorSi el software de lectura del termómetro no
obtiene una lectura una vez, simplemente podría devolver el mismo valor que la última vez.
Dependiendo de la aplicación, es poco probable que las temperaturas cambien mucho en 1/100 de
segundo. En un videojuego, si detecta una solicitud para pintar parte de la pantalla con un color no
válido, simplemente puede devolver el mismo color utilizado anteriormente. Pero si está autorizando
transacciones en un cajero automático, probablemente no querrá usar la "misma respuesta que la
última vez", ¡esa sería el número de cuenta bancaria del usuario anterior!
Sustituir el valor legal más cercanoEn algunos casos, puede optar por devolver el valor legal más
cercano, como en elVelocidadejemplo anterior. Este suele ser un enfoque razonable cuando se toman
lecturas de un instrumento calibrado. El termómetro puede estar calibrado entre 0 y 100 grados
centígrados, por ejemplo. Si detecta una lectura inferior a 0, puede sustituirla por 0, que es el valor
legal más cercano. Si detecta un valor superior a 100, puede sustituirlo por 100. Para una operación
de cadena, si se informa que la longitud de una cadena es inferior a 0, puede sustituirlo por 0. Mi
automóvil utiliza este enfoque para el manejo de errores cada vez que retrocedo. Dado que mi
velocímetro no muestra velocidades negativas, cuando retrocedo, simplemente muestra una
velocidad de 0, el valor legal más cercano.
Devolver un código de errorPuede decidir que solo ciertas partes de un sistema manejarán
los errores. Otras partes no manejarán los errores localmente; simplemente informarán que se
ha detectado un error y confiarán en que alguna otra rutina superior en la jerarquía de
llamadas manejará el error. El mecanismo específico para notificar al resto del sistema que se
ha producido un error podría ser cualquiera de los siguientes:
En este caso, el mecanismo específico de informe de errores es menos importante que la decisión sobre qué
partes del sistema manejarán los errores directamente y cuáles simplemente informarán que han ocurrido.
Si la seguridad es un problema, asegúrese de que las rutinas de llamadas siempre verifiquen los códigos de
retorno.
196 Capítulo 8: Programación defensiva
Este enfoque tiene una importante implicación de seguridad. Si su código ha encontrado una
saturación de búfer, es posible que un atacante haya comprometido la dirección de la rutina u objeto
del controlador. Por lo tanto, una vez que se ha producido una saturación del búfer mientras se
ejecuta una aplicación, ya no es seguro utilizar este enfoque.
Maneje el error de la manera que mejor funcione localmenteAlgunos diseños exigen el manejo
de todos los errores localmente: la decisión de qué método específico de manejo de errores usar se
deja en manos del programador que diseña e implementa la parte del sistema que encuentra el
error.
Este enfoque brinda a los desarrolladores individuales una gran flexibilidad, pero crea un riesgo
significativo de que el rendimiento general del sistema no satisfaga sus requisitos de corrección o
solidez (más sobre esto en un momento). Dependiendo de cómo los desarrolladores terminen
manejando errores específicos, este enfoque también tiene el potencial de difundir el código de la
interfaz de usuario en todo el sistema, lo que expone al programa a todos los problemas asociados
con la visualización de mensajes de error.
CerrarAlgunos sistemas se apagan cada vez que detectan un error. Este enfoque es útil en
aplicaciones críticas para la seguridad. Por ejemplo, si el software que controla el equipo de radiación
para el tratamiento de pacientes con cáncer recibe datos de entrada incorrectos para la dosis de
radiación, ¿cuál es su mejor respuesta de manejo de errores? ¿Debería usar el mismo valor que la
última vez? ¿Debe utilizar el valor legal más cercano? ¿Debería usar un valor neutral? En este caso,
apagar es la mejor opción. Preferiríamos reiniciar la máquina que correr el riesgo de administrar la
dosis incorrecta.
Se puede utilizar un enfoque similar para mejorar la seguridad de Microsoft Windows. De forma
predeterminada, Windows continúa funcionando incluso cuando su registro de seguridad está lleno. Pero
puede configurar Windows para que detenga el servidor si el registro de seguridad se llena, lo que puede
ser apropiado en un entorno crítico para la seguridad.
8.3 Técnicas de manejo de errores 197
Robustez vs Corrección
Como nos muestran los ejemplos de videojuegos y rayos X, el estilo de procesamiento de errores
más apropiado depende del tipo de software en el que se produce el error. Estos ejemplos también
ilustran que el procesamiento de errores generalmente favorece una mayor corrección o una mayor
solidez. Los desarrolladores tienden a usar estos términos de manera informal, pero, estrictamente
hablando, estos términos se encuentran en extremos opuestos de la escala entre sí.Exactitud
significa nunca devolver un resultado inexacto; no devolver ningún resultado es mejor que devolver
un resultado inexacto.Robustezsignifica siempre tratar de hacer algo que permita que el software
siga funcionando, incluso si eso conduce a resultados que a veces son inexactos.
Las aplicaciones críticas para la seguridad tienden a preferir la corrección a la robustez. Es mejor no
devolver ningún resultado que devolver un resultado incorrecto. La máquina de radiación es un buen
ejemplo de este principio.
Con tantas opciones, debe tener cuidado de manejar parámetros inválidos de manera consistente en
todo el programa. La forma en que se manejan los errores afecta la capacidad del software para
cumplir con los requisitos relacionados con la corrección, la solidez y otros atributos no funcionales.
PUNTO CLAVE
Decidir sobre un enfoque general para los malos parámetros es una decisión arquitectónica o de
diseño de alto nivel y debe abordarse en uno de esos niveles.
Una vez que decida el enfoque, asegúrese de seguirlo de manera consistente. Si decide que el código
de alto nivel maneje los errores y el código de bajo nivel simplemente informe los errores,
¡asegúrese de que el código de alto nivel realmente maneje los errores! Algunos lenguajes le dan la
opción de ignorar el hecho de que una función devuelve un código de error; en C++, no es necesario
que haga nada con el valor de retorno de una función, ¡pero no ignore la información de error!
Pruebe el valor de retorno de la función. Si no espera que la función produzca un error, verifíquela
de todos modos. El objetivo de la programación defensiva es protegerse contra errores que no
espera.
Esta directriz es válida tanto para las funciones del sistema como para sus propias funciones. A menos que
haya establecido una pauta arquitectónica de no verificar las llamadas del sistema en busca de errores,
verifique los códigos de error después de cada llamada. Si detecta un error, incluya el número de error y la
descripción del error.
198 Capítulo 8: Programación defensiva
8.4 Excepciones
Las excepciones son un medio específico por el cual el código puede transmitir errores o
eventos excepcionales al código que lo llamó. Si el código en una rutina encuentra una
condición inesperada que no sabe cómo manejar, lanza una excepción, esencialmente
levantando las manos y gritando: "No sé qué hacer al respecto, espero que alguien más lo
sepa". cómo manejarlo!” El código que no tiene sentido del contexto de un error puede
devolver el control a otras partes del sistema que podrían tener una mejor capacidad para
interpretar el error y hacer algo útil al respecto.
Las excepciones también se pueden usar para corregir la lógica enredada dentro de un solo tramo de
código, como "Reescribir conintentar-finalmente” ejemplo en la Sección 17.3. La estructura básica de una
excepción es que una rutina utilizalanzarpara lanzar un objeto de excepción. El código en alguna otra rutina
en la jerarquía de llamadascapturala excepción dentro de untrata de atraparlobloquear.
Los lenguajes populares varían en la forma en que implementan las excepciones. La Tabla 8-1 resume las
principales diferencias en tres de ellos:
Excepción
Atributo C++ Java básico visual
Trata de atraparloapoyo sí sí sí
Try-catch-finalmente no sí sí
apoyo
Que puede ser Excepciónobjeto o Excepciónobjeto o Excepciónobjeto o
arrojado objeto derivado de objeto derivado de objeto derivado de
Excepciónclase; puntero de Excepciónclase Excepciónclase
objeto; referencia al objeto
encia; tipo de datos
como cadena o int
excepción"
Excepciones lanzadas No Sí No
debe ser definido
en la interfaz de clase
Excepciones capturadas No Sí No
debe ser definido
en la interfaz de clase
8.4 Excepciones 199
Los programas que usan Las excepciones tienen un atributo en común con la herencia: usadas juiciosamente, pueden reducir
excepciones como parte de su
la complejidad. Usados imprudentemente, pueden hacer que el código sea casi imposible de seguir.
procesamiento normal sufren
todos los problemas de legibilidad
Esta sección contiene sugerencias para aprovechar los beneficios de las excepciones y evitar las
y mantenibilidad del código dificultades que a menudo se asocian con ellas.
spaghetti clásico.
—Andy Hunt y Dave Use excepciones para notificar a otras partes del programa sobre errores que no deben
Thomas
ignorarseEl beneficio primordial de las excepciones es su capacidad para señalar condiciones de
error de tal manera que no se pueden ignorar (Meyers 1996). Otros enfoques para el manejo de
errores crean la posibilidad de que una condición de error pueda propagarse a través de un código
base sin ser detectado. Las excepciones eliminan esa posibilidad.
Lanzar una excepción solo para condiciones que son realmente excepcionalesLas excepciones deben
reservarse para condiciones que son verdaderamente excepcionales; en otras palabras, para condiciones
que no pueden ser abordadas por otras prácticas de codificación. Las excepciones se usan en circunstancias
similares a las afirmaciones: para eventos que no solo son infrecuentes, sino también para eventos que
deberíannuncaocurrir.
Las excepciones representan una compensación entre una forma poderosa de manejar condiciones
inesperadas por un lado y una mayor complejidad por el otro. Las excepciones debilitan la
encapsulación al requerir que el código que llama a una rutina sepa qué excepciones pueden
generarse dentro del código que se llama. Eso aumenta la complejidad del código, lo que va en
contra de lo que el Capítulo 5, "Diseño en construcción", se refiere como el imperativo técnico
principal del software: administrar la complejidad.
No use una excepción para pasar la pelotaSi una condición de error se puede manejar localmente,
hágalo localmente. No lance una excepción no detectada en una sección de código si puede manejar
el error localmente.
Evite lanzar excepciones en constructores y destructores a menos que los atrape en el mismo
lugarLas reglas sobre cómo se procesan las excepciones se vuelven muy complicadas muy
rápidamente cuando se lanzan excepciones en constructores y destructores. En C++, por ejemplo, no
se llama a los destructores a menos que un objeto esté completamente construido, lo que significa
que si el código dentro de un constructor genera una excepción, no se llamará al destructor, lo que
genera una posible fuga de recursos (Meyers 1996, Stroustrup 1997 ). Se aplican reglas igualmente
complicadas a las excepciones dentro de los destructores.
Los abogados del lenguaje podrían decir que recordar reglas como estas es "trivial", pero los
programadores que son simples mortales tendrán problemas para recordarlas. Es una mejor práctica
de programación simplemente evitar la complejidad adicional que crea dicho código al no escribir ese
tipo de código en primer lugar.
Referencia cruzadaPara obtener más Lanzar excepciones en el nivel correcto de abstracciónUna rutina debe presentar una abstracción
información sobre cómo mantener
consistente en su interfaz, al igual que una clase. Las excepciones lanzadas son parte de la interfaz de
abstracciones de interfaz coherentes,
Cuando elige pasar una excepción a la persona que llama, asegúrese de que el nivel de abstracción de la
excepción sea consistente con la abstracción de la interfaz de rutina. He aquí un ejemplo de lo que no se
debe hacer:
Mal ejemplo de Java de una clase que arroja una excepción en un nivel de abstracción
inconsistente
clase Empleado {
CODIFICACIÓN
HORROR ...
Aquí está la declaración de la TaxId público GetTaxId() lanza EOFException {
excepción que está en un nivel ...
inconsistente de abstracción. }
...
}
En cambio, elObtenerIdTaxId()el código debe devolver una excepción que sea coherente con la
interfaz de clase de la que forma parte, como esta:
Buen ejemplo de Java de una clase que lanza una excepción a un nivel de
abstracción constante
clase Empleado {
...
Aquí está la declaración de la public TaxId GetTaxId() lanza EmployeeDataNotAvailable {
excepción que contribuye a ...
un nivel consistente de }
abstracción. ...
}
error de índice, asegúrese de que el mensaje de excepción incluya los límites superior e inferior de la
matriz y el valor del índice ilegal.
Tal enfoque dice que el código dentro delprobarbloque es incorrecto porque genera una
excepción sin motivo, o el código dentro delcapturablock es incorrecto porque no maneja una
excepción válida. Determine cuál es la causa raíz del problema y luego solucione el problema.
probarbloque o elcapturabloquear.
Ocasionalmente, puede encontrar circunstancias excepcionales en las que una excepción en un nivel
inferior realmente no representa una excepción en el nivel de abstracción de la rutina de llamada. Si ese es
el caso, al menos documente por qué un vacíocapturabloque es apropiado. Podría “documentar” ese caso
con comentarios o registrando un mensaje en un archivo, de la siguiente manera:
Conozca las excepciones que arroja el código de su bibliotecaSi está trabajando en un lenguaje que no
requiere una rutina o clase para definir las excepciones que lanza, asegúrese de saber qué excepciones
lanza cualquier código de biblioteca que use. Si no detecta una excepción generada por el código de la
biblioteca, su programa colapsará tan rápido como si no detectara una excepción que usted mismo generó.
Si el código de la biblioteca no documenta las excepciones que genera, cree un código de creación de
prototipos para ejercitar las bibliotecas y eliminar las excepciones.
Aquí hay un ejemplo de un controlador de excepciones simple que simplemente imprime un mensaje de
diagnóstico:
mensaje = "Excepción: " & thisException.Message & "." & ControlChars.CrLf & _
"Clase: " & className & ControlChars.CrLf & _
"Rutina:" & thisException.TargetSite.Name & ControlChars.CrLf caption = "Excepción"
Finalizar sub
...
Atrapar objeto de excepción como excepción
ReportException(CLASS_NAME,ExceptionObject) Finalizar
intento
El código en esta versión deReportException()es simple. En una aplicación real, puede hacer que el código
sea tan simple o tan elaborado como sea necesario para satisfacer sus necesidades de manejo de
excepciones.
- Considere crear su propia clase de excepción específica del proyecto, que puede servir como
clase base para todas las excepciones lanzadas en su proyecto. Esto admite la centralización y
estandarización de registros, informes de errores, etc.
- Definir las circunstancias específicas bajo las cuales se permite el uso del códigoatraparsintaxis
para realizar el procesamiento de errores localmente.
- Defina las circunstancias específicas en las que se permite que el código genere una
excepción que no se manejará localmente.
Referencia cruzadaPara Considere alternativas a las excepcionesVarios lenguajes de programación han admitido
numerosos enfoques alternativos
excepciones durante 5 a 10 años o más, pero ha surgido poca sabiduría convencional sobre cómo
de manejo de errores, consulte
Sección 8.3, "Técnicas de manejo de
usarlos de manera segura.
errores", anteriormente en este
capítulo. Algunos programadores usan excepciones para manejar errores simplemente porque su lenguaje
proporciona ese mecanismo particular de manejo de errores. Siempre debe considerar el conjunto
completo de alternativas de manejo de errores: manejar el error localmente, propagar el error
usando un código de error, registrar información de depuración en un archivo, apagar el sistema o
usar algún otro enfoque. Manejar errores con excepciones solo porque su lenguaje proporciona
manejo de excepciones es un ejemplo clásico de programaciónenun lenguaje en lugar de
programacióndentroun idioma. (Para obtener detalles sobre esa distinción, consulte la Sección 4.3,
"Su ubicación en la ola tecnológica" y la Sección 34.4, "Programe en su idioma, no en él".
Finalmente, considere si su programa realmente necesita manejar excepciones, punto. Como señala
Bjarne Stroustrup, a veces la mejor respuesta a un error grave en tiempo de ejecución es liberar
todos los recursos adquiridos y abortar. Deje que el usuario vuelva a ejecutar el programa con la
entrada adecuada (Stroustrup 1997).
Una forma de hacer barricadas con fines de programación defensiva es designar ciertas interfaces
como límites a las áreas "seguras". Compruebe los datos que cruzan los límites de una caja fuerte
Traducido del inglés al español - www.onlinedoctranslator.com
área de validez, y responda con sensatez si los datos no son válidos. La figura 8-2 ilustra este
concepto.
Interno Interno
Dominio Validación Clase 3 Clase 4
Interfaz de línea Clase 1
Interno Interno
Clase 5 Clase 6
Tiempo real Validación
Fuente de datos Clase 2
Interno Interno
Clase 7 Clase 8
Externo Validación
archivos Clasenorte Interno Interno
Clase 9 Clase 10
Los datos aquí son Estas clases son responsables Estas clases pueden
se supone que esta sucio para limpiar los datos. Ellos asumir que los datos están limpios
Figura 8-2Definir algunas partes del software que funcionan con datos sucios y otras que
funcionan con datos limpios puede ser una forma efectiva de liberar a la mayoría del código de la
responsabilidad de verificar datos incorrectos.
Este mismo enfoque se puede utilizar a nivel de clase. Los métodos públicos de la clase asumen que los
datos no son seguros y son responsables de verificar los datos y desinfectarlos. Una vez que los datos han
sido aceptados por los métodos públicos de la clase, los métodos privados de la clase pueden asumir que
los datos están seguros.
Otra forma de pensar en este enfoque es como una técnica de quirófano. Los datos se esterilizan
antes de que se les permita ingresar al quirófano. Se supone que todo lo que está en el quirófano es
seguro. La decisión de diseño clave es decidir qué poner en la sala de operaciones, qué mantener
fuera y dónde colocar las puertas: qué rutinas se consideran dentro de la zona de seguridad, cuáles
están fuera y cuáles desinfectan los datos. La forma más fácil de hacer esto suele ser desinfectando
los datos externos a medida que llegan, pero los datos a menudo necesitan ser desinfectados en
más de un nivel, por lo que a veces se requieren múltiples niveles de esterilización.
Otro aspecto clave de la programación defensiva es el uso de ayudas de depuración, que pueden ser un
poderoso aliado para detectar errores rápidamente.
Un programa en el que trabajé hizo un uso extensivo de una lista enlazada cuádruple. El código de la lista
enlazada era propenso a errores y la lista enlazada tendía a corromperse. Agregué una opción de menú para
verificar la integridad de la lista vinculada.
En el modo de depuración, Microsoft Word contiene código en el bucle inactivo que comprueba la
integridad delDocumentoobjeto cada pocos segundos. Esto ayuda a detectar la corrupción de datos
rápidamente y facilita el diagnóstico de errores.
PUNTO CLAVE
206 Capítulo 8: Programación defensiva
Cuanto antes introduzca las ayudas de depuración, más le ayudarán. Por lo general, no hará el
esfuerzo de escribir una ayuda de depuración hasta después de haber sido atacado por un problema
varias veces. Sin embargo, si escribe la ayuda después de la primera vez, o si usa una de un proyecto
anterior, le ayudará a lo largo del proyecto.
usarcasoDeclaraciones" en la Sección
David LeBlanc se refieren a este enfoque como “programación ofensiva” (Howard y
15.2. LeBlanc 2003).
Supongamos que tienes uncasodeclaración de que espera manejar sólo cinco tipos de eventos.
Durante el desarrollo, se debe usar el caso predeterminado para generar una advertencia que diga
"¡Oye! ¡Aquí hay otro caso! ¡Arreglar el programa!” Sin embargo, durante la producción, el caso
predeterminado debería hacer algo más elegante, como escribir un mensaje en un archivo de
registro de errores.
Un programa muerto Aquí hay algunas formas en que puede programar ofensivamente:
normalmente hace mucho menos
daño que uno lisiado. - Cerciorarseafirmars cancelar el programa. No permita que los programadores se acostumbren a
—andy caza y
pulsar la tecla Intro para evitar un problema conocido. Haga que el problema sea lo
dave thomass
suficientemente doloroso como para que se solucione.
- Llene completamente cualquier memoria asignada para que pueda detectar errores de asignación de
memoria.
- Rellene por completo los archivos o flujos asignados para eliminar cualquier error de formato de archivo.
- Configure el programa para que se envíe por correo electrónico los archivos de registro de errores
para que pueda ver los tipos de errores que se producen en el software publicado, si es adecuado
para el tipo de software que está desarrollando.
A veces la mejor defensa es un buen ataque. Fracasa fuerte durante el desarrollo para que puedas
fallar menos durante la producción.
Si está escribiendo código para su propio uso, podría estar bien dejar todo el código de depuración en el
programa. Si está escribiendo código para uso comercial, la penalización de rendimiento en tamaño y
velocidad puede ser prohibitiva. Plan para evitar mezclar el código de depuración dentro y fuera de un
programa. Aquí hay varias maneras de hacerlo:
8.6 Ayudas para la depuración 207
Referencia cruzadaPara obtener Use herramientas de control de versiones y herramientas de compilación como ant y makeLas herramientas de control
detalles sobre el control de
de versiones pueden crear diferentes versiones de un programa a partir de los mismos archivos fuente. En el modo de
versiones, consulte la Sección 28.2,
"Gestión de la configuración".
desarrollo, puede configurar la herramienta de compilación para incluir todo el código de depuración. En el modo de
producción, puede configurarlo para excluir cualquier código de depuración que no desee en la versión comercial.
Ejemplo de C++ del uso del preprocesador directamente para controlar el código de depuración
# terminara si
Este tema tiene varias variaciones. Más que simplemente definirDEPURAR, puede asignarle un valor
y luego probar el valor en lugar de probar si está definido. De esa manera, puede diferenciar entre
diferentes niveles de código de depuración. Es posible que tenga algún código de depuración que
desee en su programa todo el tiempo, por lo que lo rodea con una declaración como#si DEPURAR > 0
. Otro código de depuración puede ser solo para fines específicos, por lo que puede rodearlo con una
declaración como#si DEPURAR == POINTER_ERROR. En otros lugares, es posible que desee
establecer niveles de depuración, por lo que podría tener declaraciones como#si DEPURAR > NIVEL_A
.
Si no te gusta tener#si está definido ()s distribuidos a lo largo de su código, puede escribir una
macro de preprocesador para realizar la misma tarea. Aquí hay un ejemplo:
Ejemplo de C++ del uso de una macro de preprocesador para controlar el código de depuración
# definir DEPURAR
# si está definido (DEBUG)
# define DebugCode (fragmento_de_código) {fragmento_de_código}
# más
# define DebugCode (fragmento_de_código)
# terminara si
...
CódigoDepuración(
);
...
208 Capítulo 8: Programación defensiva
Al igual que en el primer ejemplo de uso del preprocesador, esta técnica se puede modificar de
varias maneras que la hacen más sofisticada que incluir completamente todo el código de
depuración o excluirlo completamente.
Referencia cruzadaPara obtener Escriba su propio preprocesadorSi un idioma no incluye un preprocesador, es bastante fácil
más información sobre los
escribir uno para incluir y excluir el código de depuración. Establezca una convención para
preprocesadores y para conocer las
Referencia cruzadaPara obtener detalles Usar stubs de depuraciónEn muchos casos, puede llamar a una rutina para realizar comprobaciones
sobre los stubs, consulte "Construcción de
de depuración. Durante el desarrollo, la rutina puede realizar varias operaciones antes de que el
andamios para probar rutinas
Puede comenzar con una rutina diseñada para verificar los punteros que se le pasan:
Cuando el código esté listo para la producción, es posible que no desee todos los gastos generales
asociados con esta verificación de puntero. Puede intercambiar la rutina anterior e intercambiar esta
rutina:
Esta no es una revisión exhaustiva de todas las formas en que puede planear eliminar las ayudas de
depuración, pero debería ser suficiente para darle una idea de algunas cosas que funcionarán en su
entorno.
Dejar en el código que comprueba errores importantesDecida qué áreas del programa pueden darse el
lujo de tener errores no detectados y qué áreas no. Por ejemplo, si estuviera escribiendo un programa de
hoja de cálculo, podría darse el lujo de tener errores no detectados en el área de actualización de pantalla
del programa porque la penalización principal por un error es solo una pantalla desordenada. No podía
permitirse el lujo de tener errores no detectados en el motor de cálculo porque tales errores podrían dar
lugar a resultados sutilmente incorrectos en la hoja de cálculo de alguien. La mayoría de los usuarios
prefieren sufrir una pantalla desordenada que cálculos de impuestos incorrectos y una auditoría del IRS.
Quitar el código que busca errores trivialesSi un error tiene consecuencias verdaderamente triviales,
elimine el código que lo verifica. En el ejemplo anterior, podría eliminar el código que verifica la
actualización de la pantalla de la hoja de cálculo. “Eliminar” no significa eliminar físicamente el código.
Significa usar control de versiones, interruptores de precompilador o alguna otra técnica para compilar el
programa sin ese código en particular. Si el espacio no es un problema, puede dejar el código de verificación
de errores pero hacer que registre los mensajes en un archivo de registro de errores discretamente.
Eliminar el código que provoca bloqueos gravesComo mencioné, durante el desarrollo, cuando su
programa detecta un error, desea que el error sea lo más evidente posible para que pueda
solucionarlo. A menudo, la mejor manera de lograr ese objetivo es hacer que el programa imprima
un mensaje de depuración y se bloquee cuando detecte un error. Esto es útil incluso para errores
menores.
210 Capítulo 8: Programación defensiva
Durante la producción, sus usuarios necesitan la oportunidad de guardar su trabajo antes de que el
programa se bloquee y probablemente estén dispuestos a tolerar algunas anomalías a cambio de
mantener el programa funcionando el tiempo suficiente para que puedan hacerlo. Los usuarios no
aprecian nada que resulte en la pérdida de su trabajo, independientemente de cuánto ayude a
depurar y, en última instancia, mejore la calidad del programa. Si su programa contiene código de
depuración que podría provocar una pérdida de datos, sáquelo de la versión de producción.
Dejar código que ayude a que el programa se cuelgue correctamenteSi su programa contiene código de
depuración que detecta errores potencialmente fatales, deje el código que permite que el programa se
bloquee correctamente. En el Mars Pathfinder, por ejemplo, los ingenieros dejaron parte del código de
depuración por diseño. Se produjo un error después de que el Pathfinder aterrizara. Mediante el uso de las
ayudas de depuración que se habían dejado, los ingenieros del JPL pudieron diagnosticar el problema y
cargar el código revisado en el Pathfinder, y el Pathfinder completó su misión a la perfección (marzo de
1999).
Errores de registro para su personal de soporte técnicoConsidere dejar las ayudas de depuración
en el código de producción, pero cambie su comportamiento para que sea apropiado para la versión
de producción. Si ha cargado su código con afirmaciones que detienen el programa durante el
desarrollo, podría considerar cambiar la rutina de afirmación para registrar mensajes en un archivo
durante la producción en lugar de eliminarlos por completo.
Asegúrese de que los mensajes de error que deje sean amigablesSi deja mensajes de error internos en el
programa, verifique que estén en un lenguaje amigable para el usuario. En uno de mis primeros programas,
recibí una llamada de un usuario que informó que había recibido un mensaje que decía "¡Tienes una mala
asignación de punteros, Aliento de perro!" Afortunadamente para mí, ella tenía sentido del humor. Un
enfoque común y efectivo es notificar al usuario sobre un "error interno" y enumerar una dirección de correo
electrónico o un número de teléfono que el usuario puede usar para informarlo.
—Mark Twain pesado y lento. Lo que es peor, el código adicional necesario para la programación defensiva agrega
complejidad al software. El código instalado para la programación defensiva no es inmune a los
defectos, y es tan probable que encuentre un defecto en el código de programación defensiva como
en cualquier otro código, más probable si escribe el código casualmente. Piense en dónde necesita
estar a la defensiva y establezca sus prioridades de programación defensiva en consecuencia.
8.8 Estar a la defensiva sobre la programación defensiva 211
- ¿Se han utilizado afirmaciones solo para documentar condiciones que nunca deberían
ocurrir?
- ¿Se han creado barricadas para contener el efecto dañino de los errores y reducir
la cantidad de código que debe preocuparse por el procesamiento de errores?
- ¿Se han instalado las ayudas de depuración de tal manera que puedan
activarse o desactivarse sin mucho alboroto?
- ¿Ha utilizado técnicas de programación ofensiva para que los errores sean difíciles de
pasar por alto durante el desarrollo?
Excepciones
- ¿Ha definido su proyecto un enfoque estandarizado para el manejo de excepciones?
- ¿Están todas las excepciones en los niveles apropiados de abstracción para las rutinas
que las lanzan?
Temas de seguridad
- ¿El código que verifica si hay datos de entrada incorrectos verifica intentos de
desbordamiento de búfer, inyección de SQL, inyección de HTML, desbordamiento de
enteros y otras entradas maliciosas?
Recursos adicionales
cc2e.com/0875 Eche un vistazo a los siguientes recursos de programación defensiva:
Seguridad
Howard, Michael y David Le Blanc.Escribir código seguro, 2ª ed. Redmond, WA: Microsoft Press, 2003.
Howard y LeBlanc cubren las implicaciones de seguridad de confiar en la entrada. El libro es
revelador porque ilustra cuántas formas en que se puede infringir un programa, algunas de las
cuales tienen que ver con las prácticas de construcción y muchas otras no. El libro abarca una gama
completa de requisitos, diseño, código y problemas de prueba.
afirmaciones
Maguire, Steve.Escribir código sólido. Redmond, WA: Microsoft Press, 1993. El Capítulo 2
contiene una excelente discusión sobre el uso de aserciones, incluidos varios ejemplos
interesantes de aserciones en productos de Microsoft conocidos.
MEYER, Bertrand.Construcción de software orientada a objetos, 2ª ed. New York, NY: Prentice
Hall PTR, 1997. Este libro contiene la discusión definitiva de las condiciones previas y
posteriores.
Excepciones
MEYER, Bertrand.Construcción de software orientada a objetos, 2ª ed. New York, NY: Prentice Hall
PTR, 1997. El Capítulo 12 contiene una discusión detallada sobre el manejo de excepciones.
Puntos clave 213
Stroustrup, Bjarne.El lenguaje de programación C++, 3d ed. Reading, MA: Addison-Wesley, 1997. El
Capítulo 14 contiene una discusión detallada del manejo de excepciones en C++. La Sección 14.11
contiene un excelente resumen de 21 consejos para manejar las excepciones de C++.
Meyers, Scott.C++ más eficaz: 35 nuevas formas de mejorar sus programas y diseños.
Reading, MA: Addison-Wesley, 1996. Los puntos 9 a 15 describen numerosos matices del
manejo de excepciones en C++.
Arnold, Ken, James Gosling y David Holmes.El lenguaje de programación Java, 3d ed. Boston,
MA: Addison-Wesley, 2000. El Capítulo 8 contiene una discusión sobre el manejo de
excepciones en Java.
Bloch, Joshua.Guía efectiva del lenguaje de programación Java. Boston, MA: Addison-Wesley, 2001.
Los puntos 39 a 47 describen los matices del manejo de excepciones en Java.
Foxall, James.Estándares prácticos para Microsoft Visual Basic .NET. Redmond, WA:
Microsoft Press, 2003. El Capítulo 10 describe el manejo de excepciones en Visual Basic.
Puntos clave
- El código de producción debe manejar los errores de una manera más sofisticada que "basura que
entra, basura que sale".
- Las técnicas de programación defensiva hacen que los errores sean más fáciles de encontrar, más fáciles de corregir y menos
- Las aserciones pueden ayudar a detectar errores de manera temprana, especialmente en sistemas grandes, sistemas de alta
- La decisión sobre cómo manejar entradas incorrectas es una decisión clave para el manejo de errores y
- Las excepciones proporcionan un medio para manejar errores que opera en una dimensión
diferente del flujo normal del código. Son una valiosa adición a la caja de herramientas
intelectual del programador cuando se usan con cuidado, y deben sopesarse frente a otras
técnicas de procesamiento de errores.
El pseudocódigo
Proceso de programación
cc2e.com/0936 Contenido
Temas relacionados
Aunque podría ver todo este libro como una descripción ampliada del proceso de
programación para crear clases y rutinas, este capítulo pone los pasos en contexto. Este
capítulo se centra en la programación en pequeño: en los pasos específicos para construir una
clase individual y sus rutinas, los pasos que son críticos en proyectos de todos los tamaños. El
capítulo también describe el Proceso de programación de pseudocódigo (PPP), que reduce el
trabajo requerido durante el diseño y la documentación y mejora la calidad de ambos.
El PPP no es el único procedimiento para crear clases y rutinas. La Sección 9.4, al final de este
capítulo, describe las alternativas más populares, incluido el desarrollo de prueba primero y el
diseño por contrato.
215
216 Capítulo 9: El proceso de programación del pseudocódigo
Empezar
Crear un
diseño general
para la clase
Revisión y Construye el
probar la clase como rutinas dentro
entero la clase
Hecho
Figura 9-1Los detalles de la construcción de clases varían, pero las actividades generalmente ocurren en el orden que se
muestra aquí.
Crear un diseño general para la clase.El diseño de la clase incluye numerosos temas
específicos. Defina las responsabilidades específicas de la clase, defina qué "secretos" ocultará
la clase y defina exactamente qué abstracción capturará la interfaz de la clase. Determine si la
clase se derivará de otra clase y si se permitirá que otras clases se deriven de ella. Identifique
los métodos públicos clave de la clase e identifique y diseñe cualquier miembro de datos no
trivial utilizado por la clase. Repita estos temas tantas veces como sea necesario para crear un
diseño sencillo para la rutina. Estas consideraciones y muchas otras se analizan con más detalle
en el Capítulo 6, “Clases trabajadoras”.
9.1 Resumen de pasos en la construcción de clases y rutinas 217
Construir cada rutina dentro de la clase.Una vez que haya identificado las principales rutinas de la
clase en el primer paso, debe construir cada rutina específica. La construcción de cada rutina
generalmente descubre la necesidad de rutinas adicionales, tanto menores como mayores, y los
problemas que surgen de la creación de esas rutinas adicionales a menudo se reflejan en el diseño
general de la clase.
Revisar y probar la clase como un todo.Normalmente, cada rutina se prueba a medida que se crea. Una
vez que la clase en su conjunto se vuelve operativa, la clase en su conjunto debe revisarse y probarse para
detectar cualquier problema que no se pueda probar en el nivel de rutina individual.
Muchas de las rutinas de una clase serán simples y directas de implementar: rutinas de acceso,
transferencias a las rutinas de otros objetos y similares. La implementación de otras rutinas
será más complicada y la creación de esas rutinas se beneficia de un enfoque sistemático. Las
principales actividades involucradas en la creación de una rutina (diseñar la rutina, verificar el
diseño, codificar la rutina y verificar el código) generalmente se realizan en el orden que se
muestra en la figura 9-2.
Empezar
Diseña el Comprobar el
rutina diseño
repetir si
necesario
Revisión y codificar el
probar el código rutina
Hecho
Figura 9-2Estas son las principales actividades que intervienen en la construcción de una rutina. Por lo general, se
realizan en el orden que se muestra.
Los expertos han desarrollado numerosos enfoques para crear rutinas, y mi enfoque favorito es el
Proceso de programación de pseudocódigo, que se describe en la siguiente sección.
218 Capítulo 9: El proceso de programación del pseudocódigo
El término "pseudocódigo" se refiere a una notación informal similar al inglés para describir cómo
funcionará un algoritmo, una rutina, una clase o un programa. El proceso de programación de
pseudocódigo define un enfoque específico para usar pseudocódigo para agilizar la creación de
código dentro de las rutinas.
Debido a que el pseudocódigo se parece al inglés, es natural suponer que cualquier descripción similar al
inglés que recopile sus pensamientos tendrá aproximadamente el mismo efecto que cualquier otra. En la
práctica, encontrará que algunos estilos de pseudocódigo son más útiles que otros. Aquí hay pautas para
usar pseudocódigo de manera efectiva:
- Use declaraciones similares al inglés que describan con precisión operaciones específicas.
Referencia cruzadaPara obtener - Escribir pseudocódigo en el nivel de intención. Describa el significado del enfoque
detalles sobre los comentarios a nivel
en lugar de cómo se implementará el enfoque en el idioma de destino.
de intención, consulte "Tipos de
He aquí un ejemplo de un diseño en pseudocódigo que viola prácticamente todos los principios que
acabamos de describir:
¿Cuál es la intención de este bloque de pseudocódigo? Debido a que está mal escrito, es difícil
saberlo. Este llamado pseudocódigo es malo porque incluye detalles de codificación del idioma de
destino, como*hRsrcPtr(en notación de puntero de lenguaje C específico) ymalloc()(un espe-
9.2 Pseudocódigo para profesionales 219
función específica del lenguaje C). Este bloque de pseudocódigo se enfoca en cómo se
escribirá el código más que en el significado del diseño. Entra en detalles de codificación, ya
sea que la rutina devuelva un1o un0. Si piensa en este pseudocódigo desde el punto de vista
de si se convertirá en buenos comentarios, comenzará a comprender que no es de mucha
ayuda.
Este pseudocódigo es mejor que el primero porque está escrito completamente en inglés; no
utiliza ningún elemento sintáctico del idioma de destino. En el primer ejemplo, el
pseudocódigo podría haberse implementado solo en C. En el segundo ejemplo, el
pseudocódigo no restringe la elección de idiomas. El segundo bloque de pseudocódigo
también está escrito en el nivel de intención. ¿Qué significa el segundo bloque de
pseudocódigo? Probablemente sea más fácil de entender para usted que el primer bloque.
Aunque está escrito en un inglés claro, el segundo bloque de pseudocódigo es lo suficientemente preciso y
detallado como para que pueda usarse fácilmente como base para el código del lenguaje de programación.
Cuando las declaraciones de pseudocódigo se conviertan en comentarios, serán una buena explicación de la
intención del código.
Estos son los beneficios que puede esperar al usar este estilo de pseudocódigo:
- El pseudocódigo facilita las revisiones. Puede revisar diseños detallados sin examinar el
código fuente. El pseudocódigo facilita las revisiones de diseño de bajo nivel y reduce la
necesidad de revisar el código en sí.
- El pseudocódigo apoya la idea del refinamiento iterativo. Comienza con un diseño de alto
nivel, refina el diseño a pseudocódigo y luego refina el pseudocódigo a código fuente. Este
refinamiento sucesivo en pequeños pasos le permite verificar su diseño a medida que lo lleva
a niveles más bajos de detalle. El resultado es que detecta errores de nivel alto en el nivel más
alto, errores de nivel medio en el nivel medio y errores de nivel bajo en el nivel más bajo,
antes de que cualquiera de ellos se convierta en un problema o contamine el trabajo en
niveles más detallados.
220 Capítulo 9: El proceso de programación del pseudocódigo
Otras lecturasPara obtener más - El pseudocódigo facilita los cambios. Unas pocas líneas de pseudocódigo son más fáciles de
información sobre las ventajas de
cambiar que una página de código. ¿Preferirías cambiar una línea en un plano o arrancar una
realizar cambios en la etapa de
menor valor, consulte Andy
pared y clavar los dos por cuatro en otro lugar? Los efectos no son tan dramáticos físicamente
Grove'sGestión de alto en el software, pero el principio de cambiar el producto cuando es más maleable es el mismo.
rendimiento(Grove 1983).
Una de las claves del éxito de un proyecto es detectar errores en la “etapa de menor valor”, la
etapa en la que se ha invertido el menor esfuerzo.
Se ha invertido mucho menos en la etapa de pseudocódigo que después de la codificación, prueba y
depuración completas, por lo que tiene sentido económico detectar los errores temprano.
Como herramienta para el diseño detallado, el pseudocódigo es difícil de superar. Una encuesta encontró
que los programadores prefieren el pseudocódigo por la forma en que facilita la construcción en un lenguaje
de programación, por su capacidad para ayudarlos a detectar diseños insuficientemente detallados y por la
PUNTO CLAVE
facilidad de documentación y modificación que proporciona (Ramsey, Atwood y Van Doren 1983). ). El
pseudocódigo no es la única herramienta para el diseño detallado, pero el pseudocódigo y el PPP son
herramientas útiles para tener en la caja de herramientas de su programador. Pruébalos. La siguiente
sección le muestra cómo hacerlo.
- Diseña la rutina.
- Codifica la rutina.
- Revisa el código.
Diseña la rutina
Referencia cruzadaPara obtener Una vez que haya identificado las rutinas de una clase, el primer paso para construir cualquiera de
detalles sobre otros aspectos del
las rutinas más complicadas de la clase es diseñarla. Suponga que desea escribir una rutina para
diseño, consulte los Capítulos 5 a 8.
9.3 Construcción de rutinas usando el PPP 221
generar un mensaje de error dependiendo de un código de error, y suponga que llama a la rutina
ReportErrorMessage(). Aquí hay una especificación informal paraReportErrorMessage():
El resto del capítulo utiliza esta rutina como ejemplo de ejecución. El resto de esta
sección describe cómo diseñar la rutina.
Referencia cruzadaPara obtener detalles Verifique los requisitos previosAntes de realizar cualquier trabajo en la rutina en sí, verifique que el
sobre la verificación de los requisitos
trabajo de la rutina esté bien definido y encaje perfectamente en el diseño general. Verifique para
previos, consulte el Capítulo 3, "Medir dos
Definir el problema que resolverá la rutina.Indique el problema que resolverá la rutina con
suficiente detalle para permitir la creación de la rutina. Si el diseño de alto nivel es lo suficientemente
detallado, es posible que el trabajo ya esté hecho. El diseño de alto nivel debe indicar al menos lo
siguiente:
- Entradas a la rutina
- Salidas de la rutina
Referencia cruzadaPara obtener - Las condiciones previas que se garantiza que son verdaderas antes de que se llame a la rutina
detalles sobre las condiciones previas
(valores de entrada dentro de ciertos rangos, flujos inicializados, archivos abiertos o cerrados,
y posteriores, consulte "Usar
Referencia cruzadaPara obtener detalles Nombra la rutinaNombrar la rutina puede parecer trivial, pero los buenos nombres de rutina
sobre cómo nombrar rutinas, consulte la
son una señal de un programa superior y no son fáciles de encontrar. En general, una rutina
Sección 7.3, “Nombres correctos de
rutinas”.
debe tener un nombre claro e inequívoco. Si tiene problemas para crear un buen nombre, eso
generalmente indica que el propósito de la rutina no está claro. Un nombre vago e insípido es
como un político en la campaña electoral. Suena como si estuviera diciendo algo, pero cuando
miras detenidamente, no puedes entender lo que significa. Si puede aclarar el nombre, hágalo.
Si el nombre insípido resulta de un diseño insípido, preste atención a la señal de advertencia.
Copia de seguridad y mejorar el diseño.
Otras lecturasPara un enfoque Decidir cómo probar la rutinaMientras escribe la rutina, piense en cómo puede probarla.
diferente a la construcción que se
Esto es útil para usted cuando realiza pruebas unitarias y para el probador que prueba su
enfoca en escribir casos de prueba
Piense en el manejo de erroresPiense en todas las cosas que podrían salir mal en la
rutina. Piense en valores de entrada incorrectos, valores no válidos devueltos por otras
rutinas, etc.
Las rutinas pueden manejar los errores de muchas maneras, y debe elegir conscientemente cómo
manejar los errores. Si la arquitectura del programa define la estrategia de manejo de errores del
programa, simplemente puede planear seguir esa estrategia. En otros casos, debe decidir qué
enfoque funcionará mejor para la rutina específica.
Referencia cruzadaPara obtener En la segunda situación, en la minoría de los sistemas, el rendimiento es fundamental. El problema
detalles sobre la eficiencia, consulte el
de rendimiento puede estar relacionado con conexiones de base de datos escasas, memoria
Capítulo 25, "Estrategias de ajuste de
Diseñe su rutina para que cumpla con sus objetivos de recursos y velocidad. Si los recursos o la velocidad
parecen más críticos, diseñe de manera que intercambie recursos por velocidad o viceversa. Es aceptable
durante la construcción inicial de la rutina ajustarla lo suficiente para cumplir con sus presupuestos de
recursos y velocidad.
Además de adoptar los enfoques sugeridos para estas dos situaciones generales, suele ser una
pérdida de esfuerzo trabajar en la eficiencia a nivel de rutinas individuales. Las grandes
optimizaciones provienen de refinar el diseño de alto nivel, no de las rutinas individuales. Por
lo general, utiliza microoptimizaciones solo cuando el diseño de alto nivel resulta no ser
compatible con los objetivos de rendimiento del sistema, y no lo sabrá hasta que todo el
programa esté terminado. No pierda el tiempo buscando mejoras incrementales hasta que
sepa que son necesarias.
Investigar los algoritmos y tipos de datos.Si la funcionalidad no está disponible en las bibliotecas
disponibles, aún podría estar descrita en un libro de algoritmos. Antes de lanzarse a escribir código
complicado desde cero, consulte un libro de algoritmos para ver lo que ya está disponible. Si utiliza
un algoritmo predefinido, asegúrese de adaptarlo correctamente a su lenguaje de programación.
Escribe el pseudocódigoEs posible que no tenga mucho por escrito después de terminar los
pasos anteriores. El propósito principal de los pasos es establecer una orientación mental que
sea útil cuando escribas la rutina.
Referencia cruzadaEsta discusión Con los pasos preliminares completados, puede comenzar a escribir la rutina como
asume que se utilizan buenas técnicas
pseudocódigo de alto nivel. Continúe y use su editor de programación o su entorno integrado
de diseño para crear la versión en
construcción". Comience con lo general y trabaje hacia algo más específico. La parte más general de una
rutina es un comentario de encabezado que describe lo que se supone que debe hacer la
rutina, así que primero escriba una declaración concisa del propósito de la rutina. Escribir la
declaración le ayudará a aclarar su comprensión de la rutina. La dificultad para escribir el
comentario general es una advertencia de que necesita comprender mejor el papel de la
rutina en el programa. En general, si es difícil resumir el rol de la rutina, probablemente
debería asumir que algo anda mal. Aquí hay un ejemplo de un comentario de encabezado
conciso que describe una rutina:
V413HAV
224 Capítulo 9: El proceso de programación del pseudocódigo
Después de escribir el comentario general, complete el pseudocódigo de alto nivel para la rutina.
Aquí está el pseudocódigo para este ejemplo:
Nuevamente, tenga en cuenta que el pseudocódigo está escrito en un nivel bastante alto. Ciertamente no
está escrito en un lenguaje de programación. En cambio, expresa en un inglés preciso lo que debe hacer la
rutina.
Referencia cruzadaPara obtener detalles Piensa en los datosPuede diseñar los datos de la rutina en varios puntos diferentes del
sobre el uso eficaz de las variables,
proceso. En este ejemplo, los datos son simples y la manipulación de datos no es una parte
consulte los capítulos 10 a 13.
destacada de la rutina. Si la manipulación de datos es una parte importante de la rutina, vale la
pena pensar en los principales datos antes de pensar en la lógica de la rutina. Es útil tener
definiciones de tipos de datos clave cuando se diseña la lógica de una rutina.
Referencia cruzadaPara obtener Revisa el pseudocódigoUna vez que haya escrito el pseudocódigo y diseñado los datos,
detalles sobre las técnicas de
tómese un minuto para revisar el pseudocódigo que ha escrito. Aléjese de él y piense en
revisión, consulte el Capítulo 21,
“Construcción colaborativa”.
cómo se lo explicaría a otra persona.
Pídele a otra persona que lo mire o que te escuche explicarlo. Puede pensar que es una tontería que
alguien mire 11 líneas de pseudocódigo, pero se sorprenderá. El pseudocódigo puede hacer que sus
suposiciones y errores de alto nivel sean más obvios que el código del lenguaje de programación.
Las personas también están más dispuestas a revisar unas pocas líneas de pseudocódigo que a
revisar 35 líneas de C++ o Java.
9.3 Construcción de rutinas usando el PPP 225
Asegúrese de tener una comprensión fácil y cómoda de lo que hace la rutina y cómo
lo hace. Si no lo entiendes conceptualmente, a nivel de pseudocódigo, ¿qué
posibilidades tienes de entenderlo a nivel de lenguaje de programación? Y si tú no lo
entiendes, ¿quién más lo hará?
Referencia cruzadaPara obtener más Pruebe algunas ideas en pseudocódigo y quédese con la mejor (iterar)Pruebe tantas ideas como pueda
información sobre la iteración, consulte
en pseudocódigo antes de comenzar a codificar. Una vez que comienza a codificar, se involucra
la Sección 34.8, “Iterar, repetidamente,
La idea general es iterar la rutina en pseudocódigo hasta que las declaraciones en pseudocódigo se
vuelvan lo suficientemente simples como para que pueda completar el código debajo de cada
declaración y dejar el pseudocódigo original como documentación. Parte del pseudocódigo de su
primer intento puede tener un nivel lo suficientemente alto como para necesitar descomponerlo aún
más. Asegúrate de descomponerlo aún más. Si no está seguro de cómo codificar algo, siga
trabajando con el pseudocódigo hasta que esté seguro. Siga refinando y descomponiendo el
pseudocódigo hasta que parezca una pérdida de tiempo escribirlo en lugar del código real.
Codifica la rutina
Una vez que haya diseñado la rutina, constrúyala. Puede realizar los pasos de construcción en un
orden casi estándar, pero siéntase libre de variarlos según lo necesite. La figura 9-3 muestra los
pasos para construir una rutina.
Verifica el código
Hecho
Figura 9-3Realizará todos estos pasos a medida que diseñe una rutina, pero no necesariamente en
un orden particular.
226 Capítulo 9: El proceso de programación del pseudocódigo
Este es un buen momento para tomar notas sobre cualquier suposición de interfaz. En este caso, la
variable de interfazerror a informares sencillo y está escrito para su propósito específico, por lo que
no necesita ser documentado.
En este punto, el carácter de la rutina es evidente. El trabajo de diseño está completo y puede
sentir cómo funciona la rutina incluso sin ver ningún código. Debería sentir que convertir el
pseudocódigo en código de lenguaje de programación será mecánico, natural y fácil. Si no lo
hace, continúe diseñando en pseudocódigo hasta que el diseño se sienta sólido.
Referencia cruzadaEste es un caso en Completa el código debajo de cada comentarioComplete el código debajo de cada línea de
el que la metáfora de la escritura
comentario de pseudocódigo. El proceso es muy parecido a escribir un trabajo final. Primero escribe
funciona bien, en lo pequeño. Para
En este ejemplo, los dos primeros comentarios de pseudocódigo dan lugar a dos líneas de código:
Cada comentario ha dado lugar a una o más líneas de código. Cada bloque de código forma un
pensamiento completo basado en el comentario. Los comentarios se han retenido para proporcionar una
explicación de mayor nivel del código. Todas las variables han sido declaradas y definidas cerca del punto en
que se usaron por primera vez. Cada comentario normalmente debería expandirse a alrededor de 2 a 10
líneas de código. (Debido a que este ejemplo es solo para fines ilustrativos, la expansión del código está en
el lado bajo de lo que normalmente debería experimentar en la práctica).
Ahora mire nuevamente la especificación en la página 221 y el pseudocódigo inicial en la página 224. La
especificación original de cinco oraciones se expandió a 15 líneas de pseudocódigo (dependiendo de cómo
cuente las líneas), que a su vez se expandió en una rutina de una página. Aunque la especificación fue
detallada, la creación de la rutina requirió un trabajo de diseño sustancial en pseudocódigo y código. Ese
diseño de bajo nivel es una de las razones por las que "codificar" no es una tarea trivial y por qué el tema de
este libro es importante.
Compruebe si el código debe factorizarse másEn algunos casos, verá una explosión de
código debajo de una de las líneas iniciales de pseudocódigo. En este caso, debe considerar
tomar uno de los dos cursos de acción:
Referencia cruzadaPara obtener más - Factorice el código debajo del comentario en una nueva rutina. Si encuentra una línea de
información sobre la refactorización, consulte
pseudocódigo que se expande en más código del que esperaba, incluya el código en su propia
el Capítulo 24, “Refactorización”.
rutina. Escriba el código para llamar a la rutina, incluido el nombre de la rutina. Si ha usado
bien el PPP, el nombre de la nueva rutina debería desaparecer fácilmente del pseudocódigo.
Una vez que haya completado la rutina que estaba creando originalmente, puede sumergirse
en la nueva rutina y aplicar el PPP nuevamente a esa rutina.
- Aplicar el PPP recursivamente. En lugar de escribir un par de docenas de líneas de código debajo de
una línea de pseudocódigo, tómese el tiempo para descomponer la línea original de pseudocódigo
en varias líneas más de pseudocódigo. Luego continúe completando el código debajo de cada una
de las nuevas líneas de pseudocódigo.
230 Capítulo 9: El proceso de programación del pseudocódigo
Revisa el código
Después de diseñar e implementar la rutina, el tercer gran paso para construirla es verificar que lo
que ha construido sea correcto. Cualquier error que pase por alto en esta etapa no se encontrará
hasta pruebas posteriores. Son más costosos de encontrar y corregir entonces, por lo que debe
encontrar todo lo que pueda en esta etapa.
Referencia cruzadaPara obtener Es posible que no aparezca un problema hasta que la rutina esté completamente codificada por
detalles sobre la verificación de
varias razones. Un error en el pseudocódigo podría volverse más evidente en la lógica de
errores en la arquitectura y los
Revisa mentalmente la rutina en busca de errores.La primera verificación formal de una rutina es
mental. Los pasos de limpieza y control informal mencionados anteriormente son dos tipos de
controles mentales. Otro es ejecutar cada camino mentalmente. Ejecutar mentalmente una rutina es
difícil, y esa dificultad es una de las razones para mantener sus rutinas pequeñas. Asegúrese de
verificar las rutas nominales y los puntos finales y todas las condiciones de excepción. Haga esto
usted mismo, lo que se denomina "verificación de escritorio", y con uno o más compañeros, lo que se
denomina "revisión por pares", "recorrido" o "inspección", según cómo lo haga.
3 Una de las mayores diferencias entre los programadores aficionados y los profesionales es la
2
1
diferencia que surge al pasar de la superstición a la comprensión. La palabra "superstición" en
DATOS DUROS
este contexto no se refiere a un programa que te da escalofríos o genera errores adicionales
cuando hay luna llena. Significa sustituir los sentimientos sobre el código por la comprensión.
Si a menudo sospecha que el compilador o el hardware cometieron un error, todavía se
encuentra en el reino de la superstición. Un estudio realizado hace muchos años encontró que
solo alrededor del cinco por ciento de todos los errores son errores de hardware, compilador o
sistema operativo (Ostrand y Weyuker 1984). Hoy, ese porcentaje probablemente sería aún
más bajo. Los programadores que se han trasladado al ámbito de la comprensión siempre
sospechan primero de su propio trabajo porque saben que causan el 95 por ciento de los
errores. Comprenda la función de cada línea de código y por qué es necesaria. Nada está bien
solo porque parece funcionar. Si no sabe por qué funciona, probablemente no lo haga,
simplemente no lo sabe todavía.
En pocas palabras: una rutina de trabajo no es suficiente. Si no sabe por qué funciona, estúdielo,
discútalo y experimente con diseños alternativos hasta que lo sepa.
PUNTO CLAVE
Compilar la rutinaDespués de revisar la rutina, compílela. Puede parecer ineficiente esperar
tanto tiempo para compilar ya que el código se completó hace varias páginas. Es cierto que
podría haberse ahorrado algo de trabajo al compilar la rutina antes y dejar que la
computadora verifique las variables no declaradas, los conflictos de nombres, etc.
9.3 Construcción de rutinas usando el PPP 231
Sin embargo, se beneficiará de varias maneras si no compila hasta el final del proceso. La razón
principal es que cuando compila código nuevo, un cronómetro interno comienza a funcionar.
Después de la primera compilación, aumenta la presión: "Lo haré bien con solo una compilación
más". El síndrome de "Solo una compilación más" conduce a cambios apresurados y propensos a
errores que toman más tiempo a largo plazo. Evite las prisas por completarlo no compilando hasta
que se haya convencido de que la rutina es correcta.
El objetivo de este libro es mostrar cómo superar el ciclo de piratear algo y ejecutarlo para ver
si funciona. Compilar antes de estar seguro de que su programa funciona es a menudo un
síntoma de la mentalidad de los piratas informáticos. Si no está atrapado en el ciclo de hackear
y compilar, compile cuando lo considere apropiado. Pero sea consciente del tirón que la
mayoría de la gente siente por "piratear, compilar y arreglar" su camino hacia un programa
que funcione.
- Establezca el nivel de advertencia del compilador en el nivel más exigente posible. Puede detectar una
cantidad asombrosa de errores sutiles simplemente permitiendo que el compilador los detecte.
- Usa validadores. La verificación del compilador realizada por lenguajes como C se puede
complementar con el uso de herramientas como lint. Incluso el código que no está compilado,
como HTML y JavaScript, puede verificarse mediante herramientas de validación.
- Elimine las causas de todos los mensajes de error y advertencias. Preste atención a lo que le
dicen los mensajes sobre su código. Una gran cantidad de advertencias a menudo indica un
código de baja calidad y debe tratar de comprender cada advertencia que recibe.
En la práctica, las advertencias que ha visto una y otra vez tienen uno de dos efectos posibles:
las ignora y camuflan otras advertencias más importantes, o simplemente se vuelven
molestas. Por lo general, es más seguro y menos doloroso volver a escribir el código para
resolver el problema subyacente y eliminar las advertencias.
Referencia cruzadaPara obtener más Probar el códigoPruebe el código utilizando los casos de prueba que planificó o creó mientras
información, consulte el Capítulo 22, "Pruebas
desarrollaba la rutina. Es posible que deba desarrollar scaffolding para respaldar sus casos de
del desarrollador". Consulte también
Referencia cruzadaPara obtener más información, Eliminar errores de la rutina.Una vez que se ha detectado un error, debe eliminarse. Si la rutina
que está desarrollando tiene errores en este punto, es muy probable que siga teniendo errores. Si
consulte el Capítulo 23, “Depuración”.
- Compruebe la interfaz de la rutina. Asegúrese de que se tengan en cuenta todos los datos de entrada y
salida y de que se utilicen todos los parámetros. Para obtener más detalles, consulte la Sección 7.5, “Cómo
usar los parámetros de rutina”.
- Compruebe la calidad general del diseño. Asegúrese de que la rutina haga una cosa y la haga bien,
que esté poco acoplada a otras rutinas y que esté diseñada a la defensiva. Para obtener más
información, consulte el Capítulo 7, “Rutinas de alta calidad”.
- Verifique las instrucciones y la lógica de la rutina. Compruebe si hay errores de uno en uno, bucles
infinitos, anidamiento inadecuado y fugas de recursos. Para más detalles, consulte los capítulos
sobre declaraciones, Capítulos 14 a 19.
- Compruebe el diseño de la rutina. Asegúrese de haber utilizado espacios en blanco para aclarar la estructura
lógica de las listas de rutinas, expresiones y parámetros. Para obtener más información, consulte el Capítulo
31, “Diseño y estilo”.
Desarrollo de prueba primeroTest-first es un estilo de desarrollo popular en el que los casos de prueba
se escriben antes de escribir cualquier código. Este enfoque se describe con más detalle en "¿Probar
primero o probar último?" en la Sección 22.2. Un buen libro sobre programación basada en pruebas es el
de Kent Beck.Desarrollo basado en pruebas: por ejemplo(Beck 2003).
Diseño por contratoEl diseño por contrato es un enfoque de desarrollo en el que se considera
que cada rutina tiene condiciones previas y posteriores. Este enfoque se describe en "Usar
aserciones para documentar y verificar condiciones previas y posteriores" en la Sección 8.2. La
mejor fuente de información sobre diseño por contrato es Bertrand Meyers'sConstrucción de
software orientada a objetos(Mayer 1997).
si siguió un buen conjunto de pasos - ¿Ha definido el problema que resolverá la clase?
para crear una rutina. Para obtener
"Rutinas de alta calidad" en el - ¿Has pensado en cómo probar la clase y cada una de sus rutinas?
Capítulo 7, página 185.
- ¿Ha pensado en la eficiencia principalmente en términos de interfaces estables e
implementaciones legibles o principalmente en términos de cumplir con los presupuestos de
recursos y velocidad?
- ¿Ha revisado las bibliotecas estándar y otras bibliotecas de códigos para las
rutinas o componentes aplicables?
- ¿Aplicó el PPP recursivamente, dividiendo las rutinas en rutinas más pequeñas cuando
fue necesario?
Puntos clave
- La construcción de clases y la construcción de rutinas tiende a ser un proceso iterativo. Los
conocimientos adquiridos durante la construcción de rutinas específicas tienden a reflejarse en el
diseño de la clase.
- Revise su trabajo en cada paso y anime a otros a revisarlo también. De esa forma,
detectará los errores al nivel más económico, cuando haya invertido la menor cantidad
de esfuerzo.
Parte III
Variables
En esta parte:
cc2e.com/1085 Contenido
Temas relacionados
La información de este capítulo debería ser particularmente valiosa para usted si es un programador
experimentado. Es fácil comenzar a usar prácticas peligrosas antes de que esté completamente consciente
de sus alternativas y luego continuar usándolas por costumbre incluso después de haber aprendido formas
de evitarlas. Un programador experimentado podría encontrar interesantes las discusiones sobre el tiempo
de vinculación en la Sección 10.6 y sobre el uso de cada variable para un propósito en la Sección 10.8. Si no
está seguro de si califica como un "programador experimentado", realice la "Prueba de alfabetización de
datos" en la siguiente sección y descúbralo.
237
238 Capítulo 10: Problemas generales en el uso de variables
A lo largo de este capítulo utilizo la palabra "variable" para referirme a objetos, así como a tipos de datos
integrados como enteros y matrices. La frase "tipo de datos" generalmente se refiere a tipos de datos
integrados, mientras que la palabra "datos" se refiere a objetos o tipos integrados.
El primer paso para crear datos efectivos es saber qué tipo de datos crear. Un buen repertorio de
tipos de datos es una parte clave de la caja de herramientas de un programador. Un tutorial sobre
tipos de datos está más allá del alcance de este libro, pero la "Prueba de alfabetización de datos" lo
PUNTO CLAVE
ayudará a determinar cuánto más necesita aprender sobre ellos.
Poner un1junto a cada término que le resulte familiar. Si cree que sabe lo que significa un término
pero no está seguro, dése una0.5. Sume los puntos cuando haya terminado e interprete su puntaje
de acuerdo con la tabla de puntaje a continuación.
______Puntaje total
10.2 Facilitando las declaraciones de variables 239
32.5.
Declaraciones implícitas
Algunos lenguajes tienen declaraciones de variables implícitas. Por ejemplo, si usa una
variable en Microsoft Visual Basic sin declararla, el compilador la declara
automáticamente (según la configuración del compilador).
240 Capítulo 10: Problemas generales en el uso de variables
Si estás programando en un lenguaje que requiere que declares variables, debes cometer dos
errores antes de que tu programa te muerda. primero hay que poner los dosnúmero de cuenta
yNo. de cuentaen el cuerpo de la rutina. Entonces tienes que declarar ambas variables en la
PUNTO CLAVE
rutina. Este es un error más difícil de cometer y virtualmente elimina el problema de las
variables sinónimas. Los lenguajes que requieren que declare datos explícitamente, en esencia,
requieren que use los datos con más cuidado, lo cual es una de sus principales ventajas. ¿Qué
haces si programas en un lenguaje con declaraciones implícitas? Aquí hay algunas sugerencias:
Declarar todas las variablesA medida que escribe una nueva variable, declárela, aunque el
compilador no lo requiera. Esto no detectará todos los errores, pero detectará algunos de ellos.
Referencia cruzadaPara obtener Usar convenciones de nomenclaturaEstablezca una convención de nomenclatura para sufijos
detalles sobre la estandarización de
comunes como númeroyNopara que no use dos variables cuando quiera usar una.
las abreviaturas, consulte "Pautas
generales de abreviaturas" en la
Comprobar nombres de variablesUtilice la lista de referencias cruzadas generada por su compilador u
Sección 11.6.
otro programa de utilidad. Muchos compiladores enumeran todas las variables en una rutina, lo que le
permite detectar ambasnumcuentayNo. de cuenta. También señalan las variables que ha declarado y no
ha utilizado.
Los problemas con la inicialización incorrecta provienen de una variable que contiene un valor
inicial que no espera que contenga. Esto puede suceder por cualquiera de varias razones:
Referencia cruzadaPara un enfoque - A la variable nunca se le ha asignado un valor. Su valor es cualquier bit que
de prueba basado en la inicialización
haya en su área de memoria cuando se inició el programa.
de datos y patrones de uso, consulte
Este último tema tiene varias variaciones. Puede inicializar algunos de los miembros de un objeto,
pero no todos. Puede olvidarse de asignar memoria y luego inicializar la "variable" a la que apunta el
puntero no inicializado. Esto significa que realmente está seleccionando una porción aleatoria de la
memoria de la computadora y asignándole algún valor. Podría ser la memoria que contiene datos.
Podría ser memoria que contiene código. Puede que sea el sistema operativo. El síntoma del
problema del puntero puede manifestarse de maneras completamente sorprendentes que son
diferentes cada vez; eso es lo que hace que la depuración de errores de puntero sea más difícil que la
depuración de otros errores.
Inicializar cada variable como se declaraInicializar variables tal como se declaran es una
forma económica de programación defensiva. Es una buena póliza de seguro contra errores
de inicialización. El siguiente ejemplo asegura queestudianteCalificacionesse reinicializará cada
vez que llame a la rutina que lo contiene.
Referencia cruzadaVerificar los Inicializa cada variable cerca de donde se usa por primera vezAlgunos lenguajes, incluido Visual
parámetros de entrada es una forma
Basic, no admiten la inicialización de variables a medida que se declaran. Eso puede conducir a
de programación defensiva. Para
Una mejor práctica es inicializar las variables lo más cerca posible de donde se usaron por primera vez:
Referencia cruzadaPara obtener más Este es un ejemplo del Principio de Proximidad: mantener juntas las acciones relacionadas. El mismo
detalles sobre cómo mantener juntas las
principio se aplica a mantener los comentarios cerca del código que describen, mantener el código
acciones relacionadas, consulte la Sección
10.4, “Alcance”.
de configuración del bucle cerca del bucle, agrupar declaraciones en código de línea recta y muchas
otras áreas.
Idealmente, declare y defina cada variable cerca de donde se usa por primera vez.Una declaración
establece el tipo de una variable. Una definición asigna a la variable un valor específico. En los lenguajes que
lo admiten, como C++ y Java, las variables deben declararse y definirse cerca de donde se usaron por
primera vez. Idealmente, cada variable debe definirse al mismo tiempo que se declara, como se muestra a
continuación:
Referencia cruzadaPara obtener más Usarfinaloconstantecuando sea posibleAl declarar una variable comofinalen Java oconstante en C+
detalles sobre cómo mantener juntas las
+, puede evitar que se asigne un valor a la variable después de inicializarla. losfinalyconstanteLas
acciones relacionadas, consulte la Sección
Inicializar los datos de los miembros de una clase en su constructorAsí como las variables de una
rutina deben inicializarse dentro de cada rutina, los datos de una clase deben inicializarse dentro de
su constructor. Si se asigna memoria en el constructor, debe liberarse en el destructor.
Inicialice las constantes con nombre una vez; inicializar variables con código ejecutableSi usa
variables para emular constantes con nombre, está bien escribir código que las inicialice una vez, al
comienzo del programa. Para hacer esto, inicialícelos en unPuesta en marcha()rutina. Inicialice las
variables verdaderas en código ejecutable cerca de donde se usan. Una de las modificaciones de
programa más comunes es cambiar una rutina que originalmente se llamó una vez para que la llame
varias veces. Variables que se inicializan en un nivel de programa Puesta en marcha()la rutina no se
reinicializa la segunda vez a través de la rutina.
Use la configuración del compilador que inicializa automáticamente todas las variablesSi su
compilador admite dicha opción, tener el compilador configurado para inicializar automáticamente todas las
variables es una variación fácil del tema de confiar en su compilador. Sin embargo, confiar en la
configuración específica del compilador puede causar problemas cuando mueve el código a otra máquina y
otro compilador. Asegúrese de documentar su uso de la configuración del compilador; las suposiciones que
se basan en configuraciones específicas del compilador son difíciles de descubrir de otra manera.
Referencia cruzadaPara obtener más Comprobar la validez de los parámetros de entradaOtra forma valiosa de inicialización es verificar la
información sobre cómo verificar los
validez de los parámetros de entrada. Antes de asignar valores de entrada a cualquier cosa, asegúrese de
parámetros de entrada, consulte la
Capítulo 8, “Programación defensiva”. Use un verificador de acceso a la memoria para verificar si hay punteros incorrectosEn algunos
sistemas operativos, el código del sistema operativo busca referencias de puntero no válidas. En otros,
estás solo. Sin embargo, no tiene que quedarse solo, porque puede comprar verificadores de acceso a la
memoria que verifican las operaciones del puntero de su programa.
- Puede usar un relleno de memoria preprogramado para llenar la memoria con un valor
predecible. El valor0es bueno para algunos propósitos porque asegura que los punteros no
inicializados apunten a poca memoria, lo que hace que sea relativamente fácil detectarlos
cuando se usan. En los procesadores Intel,0xCCes un buen valor para usar porque es el
código de máquina para una interrupción de punto de interrupción; si está ejecutando código
en un depurador e intenta ejecutar sus datos en lugar de su código, estará inundado de
puntos de interrupción. Otra virtud del valor0xCCes que es fácil de reconocer en volcados de
memoria y rara vez se usa por razones legítimas. Alternativamente, Brian Kernighan y Rob
Pike sugieren usar la constante0x CARNE MUERTAcomo relleno de memoria que es fácil de
reconocer en un depurador (1999).
- Si está usando un relleno de memoria, puede cambiar el valor que usa para llenar la
memoria de vez en cuando. Sacudir el programa a veces descubre problemas que
permanecen ocultos si el entorno ambiental nunca cambia.
- Puede hacer que su programa inicialice su memoria de trabajo en el momento del inicio.
Mientras que el propósito de usar un relleno de memoria de preprograma es exponer los
defectos, el propósito de esta técnica es ocultarlos. Al llenar la memoria de trabajo con el
mismo valor cada vez, garantiza que su programa no se verá afectado por variaciones
aleatorias en la memoria de inicio.
10.4 Alcance
“Alcance” es una forma de pensar sobre el estatus de celebridad de una variable: ¿qué tan famoso es? El
alcance, o visibilidad, se refiere a la medida en que sus variables son conocidas y pueden ser referenciadas a
lo largo de un programa. Una variable con alcance limitado o pequeño se conoce solo en una pequeña área
de un programa, por ejemplo, un índice de bucle utilizado en solo un bucle pequeño. Una variable de gran
alcance se conoce en muchos lugares de un programa; por ejemplo, una tabla de información de los
empleados que se usa en todo el programa.
Diferentes lenguajes manejan el alcance de diferentes maneras. En algunos lenguajes primitivos, todas las
variables son globales. Por lo tanto, no tiene ningún control sobre el alcance de una variable,
10.4 Alcance 245
y eso puede crear muchos problemas. En C++ y lenguajes similares, una variable puede ser visible
para un bloque (una sección de código encerrada entre corchetes), una rutina, una clase (y
posiblemente sus clases derivadas) o el programa completo. En Java y C#, una variable también
puede ser visible para un paquete o espacio de nombres (una colección de clases).
La idea de localizar referencias a una variable es bastante evidente, pero es una idea que
se presta a la medición formal. Un método para medir qué tan juntas están las
referencias a una variable es calcular el "intervalo" de una variable. Aquí hay un ejemplo:
En este caso, dos líneas se interponen entre la primera referencia aay el segundo, entoncesatiene un
lapso de dos. Una línea se interpone entre las dos referencias ab, asi quebtiene un lapso de uno, yC
tiene un lapso de cero. Aquí hay otro ejemplo:
Otras lecturasPara obtener más En este caso, hay una línea entre la primera referencia aby el segundo, por un lapso de
información sobre el intervalo
uno. No hay líneas entre la segunda referencia aby el tercero, para un lapso de cero.
variable, consulteMétricas y
Un concepto que está relacionado con el intervalo variable es el "tiempo de vida" de la variable, el número total de
sentencias sobre las que una variable está activa. La vida de una variable comienza en la primera declaración en la
que se hace referencia a ella; su vida termina en la última declaración en la que se hace referencia.
A diferencia del intervalo, el tiempo de vida no se ve afectado por la cantidad de veces que se usa la variable
entre la primera y la última vez que se hace referencia a ella. Si la variable se hace referencia por primera
vez en la línea 1 y se hace referencia por última vez en la línea 25, tiene un tiempo de vida de 25
declaraciones. Si esas son las únicas dos líneas en las que se usa, tiene un promedio de 23 declaraciones. Si
la variable se usara en cada línea desde la línea 1 hasta la línea 25, tendría un intervalo promedio de 0
declaraciones, pero aún tendría un tiempo de vida de 25 declaraciones. La figura 10-1 ilustra tanto el
intervalo como el tiempo de vida.
Corto Largo
se extiende se extiende
corta vida
tiempo
Lapsos cortos
Figura 10-1“Long live time” significa que una variable está activa en el transcurso de muchas declaraciones. “Tiempo
de vida corto” significa que está en vivo solo por unas pocas declaraciones. “Span” se refiere a qué tan juntas están
las referencias a una variable.
Al igual que con el intervalo, el objetivo con respecto al tiempo de vida es mantener el número bajo, para
mantener una variable viva durante el menor tiempo posible. Y al igual que con el intervalo, la ventaja
básica de mantener un número bajo es que reduce la ventana de vulnerabilidad. usted reduce
10.4 Alcance 247
la posibilidad de alterar incorrecta o inadvertidamente una variable entre los lugares en los
que pretende alterarla.
Una segunda ventaja de mantener el tiempo en vivo corto es que le brinda una imagen precisa
de su código. Si a una variable se le asigna un valor en la línea 10 y no se vuelve a usar hasta la
línea 45, el mismo espacio entre las dos referencias implica que la variable se usa entre las
líneas 10 y 45. Si a la variable se le asigna un valor en la línea 44 y se usa en línea 45, no se
implican otros usos de la variable, y puede concentrarse en una sección más pequeña de
código cuando esté pensando en esa variable.
Un tiempo de vida corto también reduce la posibilidad de errores de inicialización. A medida que
modifica un programa, el código de línea recta tiende a convertirse en bucles y tiende a olvidar las
inicializaciones que se realizaron lejos del bucle. Al mantener el código de inicialización y el código de
bucle más juntos, reduce la posibilidad de que las modificaciones introduzcan errores de
inicialización.
Un tiempo de vida corto hace que su código sea más legible. Cuantas menos líneas de código tenga que
tener en cuenta un lector a la vez, más fácil será entender su código. Del mismo modo, cuanto más corto
sea el tiempo de vida, menos código tendrá que mantener en su pantalla cuando desee ver todas las
referencias a una variable durante la edición y la depuración.
Finalmente, los tiempos de vida cortos son útiles cuando se divide una rutina grande en rutinas más
pequeñas. Si las referencias a las variables se mantienen juntas, es más fácil refactorizar secciones de
código relacionadas en rutinas propias.
Puede formalizar el concepto de tiempo de vida contando el número de líneas entre la primera y la
última referencia a una variable (incluidas la primera y la última línea). Aquí hay un ejemplo con
tiempos de vida que son demasiado largos:
64 mientras (! hecho) {
...
última referencia atotal. 69 if ( total > total proyectado ) {
70 hecho = verdadero;
última referencia ahecho.
248 Capítulo 10: Problemas generales en el uso de variables
Estos son los tiempos en vivo para las variables en este ejemplo:
El ejemplo se ha reescrito a continuación para que las referencias de las variables estén más
juntas:
Estos son los tiempos en vivo para las variables en este ejemplo:
Otras lecturasPara obtener más Intuitivamente, el segundo ejemplo parece mejor que el primero porque las inicializaciones de
información sobre las variables
las variables se realizan más cerca de donde se utilizan las variables. La diferencia medida en
"vivas", consulteMétricas y
modelos de ingeniería de
el tiempo de vida promedio entre los dos ejemplos es significativa: un promedio de 54 frente a
software (Conte, Dunsmore y un promedio de 7 brinda un buen respaldo cuantitativo para la preferencia intuitiva por la
Shen 1986).
segunda pieza de código.
¿Un número duro separa un buen tiempo en vivo de uno malo? ¿Un buen lapso de uno malo? Los
investigadores aún no han producido esos datos cuantitativos, pero es seguro asumir que
minimizar tanto la duración como el tiempo de vida es una buena idea.
Si intenta aplicar las ideas de lapso y tiempo de vida a las variables globales, encontrará que las variables
globales tienen lapsos y tiempos de vida enormes, una de las muchas buenas razones para evitar las
variables globales.
10.4 Alcance 249
Referencia cruzadaPara obtener Inicialice las variables utilizadas en un bucle inmediatamente antes del bucle en lugar de
detalles sobre cómo inicializar
volver al principio de la rutina que contiene el bucle.Hacer esto mejora la posibilidad de
variables cerca de donde se usan,
Referencia cruzadaPara obtener más No asigne un valor a una variable hasta justo antes de que se use el valorEs posible que haya
información sobre este estilo de
experimentado la frustración de tratar de averiguar dónde se asignó su valor a una variable. Cuanto
declaración y definición de variables,
Referencia cruzadaPara obtener más Declaraciones relacionadas con el grupoLos siguientes ejemplos muestran una rutina para
detalles sobre cómo mantener juntas las
resumir recibos diarios e ilustran cómo juntar referencias a variables para que sean más
declaraciones relacionadas, consulte la
...
}
Tenga en cuenta que, en este ejemplo, debe realizar un seguimiento dedatos antiguos,nuevos datos,
numOldData, numNewData,totalOldData, ytotalNewDatatodo a la vez: seis variables solo para esto
250 Capítulo 10: Problemas generales en el uso de variables
fragmento corto. El siguiente ejemplo muestra cómo reducir ese número a solo tres elementos
dentro de cada bloque de código:
Ejemplo de C++ del uso de dos conjuntos de variables de manera más comprensible
void SummarizeData(...) {
Declaraciones usandodatos antiguos. GetOldData( oldData, &numOldData ); totalDatosViejos = Sum(datosViejos,
numDatosViejos); PrintOldDataSummary( oldData, totalOldData,
numOldData ); SaveOldDataSummary( totalOldData, numOldData );
...
Declaraciones usandonuevos datos. ObtenerNuevosDatos(nuevosDatos, &numNuevoDatos); totalNewData =
Sum( newData, numNewData ); PrintNewDataSummary( newData,
totalNewData, numNewData ); SaveNewDataSummary( totalNewData,
numNewData );
...
}
Cuando se divide el código, los dos bloques son cada uno más cortos que el bloque original e
individualmente contienen menos variables. Son más fáciles de entender y si necesita dividir
este código en rutinas separadas, los bloques más cortos con menos variables promoverán
rutinas mejor definidas.
Dividir grupos de sentencias relacionadas en rutinas separadasEn igualdad de condiciones, una variable
en una rutina más corta tenderá a tener un lapso y un tiempo de vida más pequeños que una variable en
una rutina más larga. Al dividir las declaraciones relacionadas en rutinas separadas más pequeñas, reduce
el alcance que puede tener la variable.
Referencia cruzadaPara obtener más Comience con la visibilidad más restringida y amplíe el alcance de la variable solo si es
necesarioParte de minimizar el alcance de una variable es mantenerla lo más local posible. Es
información sobre las variables globales,
mucho más difícil reducir el alcance de una variable que ha tenido un alcance grande que expandir el
alcance de una variable que ha tenido un alcance pequeño; en otras palabras, es más difícil convertir
una variable global en una variable de clase que es convertir una variable de clase en una variable
global. Es más difícil convertir un miembro de datos protegidos en un miembro de datos privados
que viceversa. Por esa razón, en caso de duda, favorezca el alcance más pequeño posible para una
variable: local para un bucle específico, local para una rutina individual, luego privada para una clase,
luego protegida, luego empaquetada (si su lenguaje de programación lo admite) y mundial sólo
como último recurso.
El enfoque de muchos programadores para minimizar el alcance de las variables depende de sus
puntos de vista sobre los temas de "conveniencia" y "manejabilidad intelectual". Algunos
programadores hacen que muchas de sus variables sean globales porque el alcance global facilita el
acceso a las variables y los programadores no tienen que perder el tiempo con listas de parámetros y
reglas de alcance de clase. En su opinión, la conveniencia de poder acceder a las variables en
cualquier momento supera los riesgos involucrados.
10.5 Persistencia 251
Referencia cruzadaLa idea de Otros programadores prefieren mantener sus variables lo más locales posible porque el alcance
minimizar el alcance está relacionada
local ayuda a la capacidad de gestión intelectual. Cuanta más información pueda ocultar, menos
con la idea de ocultar información.
Referencia cruzadaPara obtener detalles En consecuencia, debe declarar que cada variable sea visible para el segmento de código más
sobre el uso de rutinas de acceso, consulte
pequeño que necesite verla. Si puede limitar el alcance de la variable a un solo ciclo oa una sola
“Uso de rutinas de acceso
10.5 Persistencia
"Persistencia" es otra palabra para la vida útil de una pieza de datos. La persistencia toma varias
formas. Algunas variables persisten
- siempre y cuando les permitas. En Java, las variables creadas connuevopersisten hasta que se
recolectan como basura. En C++, las variables creadas connuevopersistir hasta que usted Eliminara
ellos.
- durante la vida de un programa. Las variables globales en la mayoría de los idiomas se ajustan a esta
- Siempre. Estas variables pueden incluir valores que almacena en una base de datos entre
ejecuciones de un programa. Por ejemplo, si tiene un programa interactivo en el que los
usuarios pueden personalizar el color de la pantalla, puede almacenar sus colores en un
archivo y luego volver a leerlos cada vez que se carga el programa.
El principal problema con la persistencia surge cuando asumes que una variable tiene una
persistencia más larga de lo que realmente tiene. La variable es como esa jarra de leche en tu
refrigerador. Se supone que dura una semana. A veces dura un mes, y a veces
252 Capítulo 10: Problemas generales en el uso de variables
se vuelve agrio después de cinco días. Una variable puede ser igual de impredecible. Si intenta
usar el valor de una variable después de que finaliza su vida útil normal, ¿habrá conservado su
valor? A veces, el valor de la variable es agrio y sabes que tienes un error. Otras veces, la
computadora deja el valor anterior en la variable, dejándote imaginar que lo has usado
correctamente.
Aquí hay algunos pasos que puede seguir para evitar este tipo de problema:
Referencia cruzadaDepurar - Use código de depuración o aserciones en su programa para verificar las variables críticas en busca
El código es fácil de incluir en
de valores razonables. Si los valores no son razonables, muestra una advertencia que le indica que
las rutinas de acceso y se
discute más en "Ventajas de busque una inicialización incorrecta.
las rutinas de acceso" en
- Establezca las variables en "valores irrazonables" cuando haya terminado con ellos. Por ejemplo,
Sección 13.3.
podría establecer un puntero en nulo después de eliminarlo.
- Escriba código que suponga que los datos no son persistentes. Por ejemplo, si una variable
tiene un cierto valor cuando sale de una rutina, no asuma que tiene el mismo valor la próxima
vez que ingrese a la rutina. Esto no se aplica si está utilizando funciones específicas del idioma
que garantizan que el valor seguirá siendo el mismo, comoestáticoen C++ y Java.
- Desarrolle el hábito de declarar e inicializar todos los datos justo antes de usarlos.
Si ve datos que se usan sin una inicialización cercana, ¡sospeche!
Puede resultar ventajoso utilizar el último tiempo de vinculación posible. En general, cuanto más
tarde establezca el tiempo de enlace, más flexibilidad incorporará a su código. El siguiente ejemplo
muestra el enlace en el momento más temprano posible, cuando se escribe el código:
Ejemplo de Java de una variable que está vinculada en el momento de escribir código
Aquí hay un ejemplo de vinculación en un momento ligeramente posterior, cuando se compila el código:
10.6 Tiempo de vinculación 253
int final estático privado COLOR_BLUE = 0xFF; int final estático privado
TITLE_BAR_COLOR = COLOR_BLUE; . . .
titleBar.color = TITLE_BAR_COLOR;
TITLE_BAR_COLORes una constante con nombre, una expresión por la cual el compilador sustituye
un valor en tiempo de compilación. Esto casi siempre es mejor que la codificación fija, si su idioma lo
admite. Aumenta la legibilidad porqueTITLE_BAR_COLORle dice más sobre lo que se está
representando que0xFFlo hace. Hace que cambiar el color de la barra de título sea más fácil porque
un cambio tiene en cuenta todas las ocurrencias. Y no incurre en una penalización de rendimiento en
tiempo de ejecución.
titleBar.color = ReadTitleBarColor();
LeerTitleBarColor()es una rutina que lee un valor mientras se ejecuta un programa, quizás desde el
archivo de registro de Microsoft Windows o un archivo de propiedades de Java.
El código es más legible y flexible de lo que sería si un valor estuviera codificado de forma
rígida. No necesitas cambiar el programa para cambiartitleBar.color; simplemente cambia el
contenido de la fuente que leeReadTitleBarColor(). Este enfoque se usa comúnmente para
aplicaciones interactivas en las que un usuario puede personalizar el entorno de la aplicación.
Todavía hay otra variación en el tiempo de enlace, que tiene que ver con cuando elLeer-
TitleBarColor()se llama rutina. Esa rutina podría llamarse una vez en el momento de carga del
programa, cada vez que se crea la ventana o cada vez que se dibuja la ventana; cada alternativa
representa tiempos de vinculación sucesivamente posteriores.
Para resumir, a continuación se muestran las veces que una variable se puede vincular a un valor en este
ejemplo. (Los detalles pueden variar un poco en otros casos).
- Tiempo de carga (lectura de un valor de una fuente externa, como el archivo de registro de Windows
o un archivo de propiedades de Java)
- Tiempo de creación de instancias de objetos (como leer el valor cada vez que se crea una
ventana)
- Justo a tiempo (como leer el valor cada vez que se dibuja la ventana)
254 Capítulo 10: Problemas generales en el uso de variables
En general, cuanto antes sea el tiempo de enlace, menor será la flexibilidad y menor la complejidad. Para las
dos primeras opciones, usar constantes con nombre es preferible a usar números mágicos por muchas
razones, por lo que puede obtener la flexibilidad que proporcionan las constantes con nombre simplemente
usando buenas prácticas de programación. Más allá de eso, cuanto mayor sea la flexibilidad deseada, mayor
será la complejidad del código necesario para admitir esa flexibilidad y más propenso a errores será el
código. Debido a que la programación exitosa depende de minimizar la complejidad, un programador hábil
incorporará tanta flexibilidad como sea necesario para cumplir con los requisitos del software, pero no
agregará flexibilidad, ni la complejidad relacionada, más allá de lo que se requiere.
Jackson establece conexiones entre tres tipos de datos y las estructuras de control
correspondientes:
Referencia cruzadaPara obtener detalles Los datos secuenciales se traducen en instrucciones secuenciales en un programaLas
sobre las secuencias, consulte el Capítulo
secuencias consisten en grupos de datos que se usan juntos en un cierto orden, como lo sugiere la
14, "Organización del código de línea
recta".
figura 10-2. Si tiene cinco sentencias seguidas que manejan cinco valores diferentes, son sentencias
secuenciales. Si lee el nombre, el número de seguro social, la dirección, el número de teléfono y la
edad de un empleado de un archivo, tendrá sentencias secuenciales en su programa para leer datos
secuenciales del archivo.
Figura 10-2Los datos secuenciales son datos que se manejan en un orden definido.
Referencia cruzadaPara obtener detalles Los datos selectivos se traducen ensiycasosentencias en un programaEn general, los datos selectivos son una
sobre los condicionales, consulte el
recopilación en la que se utiliza una de varias piezas de datos en un momento determinado, pero solo una, como se
Capítulo 15, "Uso de condicionales".
muestra en la figura 10-3. Las declaraciones de programa correspondientes deben hacer la selección real, y
consisten ensi-entonces-otroocasodeclaraciones. Si tuviera un programa de nómina de empleados, podría procesar
a los empleados de manera diferente dependiendo de si se les paga por hora o como asalariados. Nuevamente, los
patrones en el código coinciden con los patrones en los datos.
10.8 Usando cada variable para exactamente un propósito 255
Figura 10-3Los datos selectivos le permiten usar una pieza u otra, pero no ambas.
Referencia cruzadaPara obtener detalles Los datos iterativos se traducen enpor,repetir, ytiempobucles de estructuras en un programa Los
sobre los bucles, consulte el Capítulo 16,
datos iterativos son el mismo tipo de datos repetidos varias veces, como sugiere la figura 10-4.
“Control de bucles”.
Normalmente, los datos iterativos se almacenan como elementos en un contenedor, registros en un archivo
o elementos en una matriz. Es posible que tenga una lista de Números de Seguro Social que lea de un
archivo. Los datos iterativos coincidirían con el bucle de código iterativo utilizado para leer los datos.
Sus datos reales pueden ser combinaciones de tipos de datos secuenciales, selectivos e iterativos.
Puede combinar los bloques de construcción simples para describir tipos de datos más complicados.
PUNTO CLAVE Use cada variable para un solo propósitoA veces es tentador usar una variable en dos lugares
diferentes para dos actividades diferentes. Por lo general, la variable se nombra de manera
inapropiada para uno de sus usos o se utiliza una variable “temporal” en ambos casos (con el
256 Capítulo 10: Problemas generales en el uso de variables
nombre poco útil habitualXotemperatura). Aquí hay un ejemplo que muestra una variable temporal
que se usa para dos propósitos:
Ejemplo de C++ del uso de una variable para dos propósitos: mala práctica
// Calcula raíces de una ecuación cuadrática.
// Este código asume que (b*b-4*a*c) es positivo. temperatura =
CODIFICACIÓN Sqrt( b*b - 4*a*c );
HORROR
root[O] = ( -b + temp ) / ( 2 * a ); root[1] = ( -b -
temp ) / ( 2 * a ); . . .
Referencia cruzadaLos parámetros de Pregunta: ¿Cuál es la relación entretemperaturaen las primeras líneas ytemperaturaen los ultimos?
rutina también deben usarse para un solo
Respuesta: Los dostemperaturas no tienen ninguna relación. El uso de la misma variable en ambos
propósito. Para obtener más información
Parámetros.”
Ejemplo de C++ del uso de dos variables para dos propósitos: buena práctica
// Calcula raíces de una ecuación cuadrática.
// Este código asume que (b*b-4*a*c) es positivo. discriminante =
Sqrt( b*b - 4*a*c );
root[0] = ( -b + discriminante ) / ( 2 * a ); root[1] = ( -b -
discriminante ) / ( 2 * a ); . . .
Evite variables con significados ocultosOtra forma en que una variable se puede usar para
más de un propósito es hacer que diferentes valores de la variable signifiquen cosas
diferentes. Por ejemplo:
Evite las variables con este tipo de significados ocultos. El nombre técnico para este tipo de abuso es
“acoplamiento híbrido” (Page-Jones 1988). La variable se extiende sobre dos trabajos, lo que significa
que la variable es del tipo incorrecto para uno de los trabajos. En elnúmero de páginasejemplo,
número de páginasnormalmente indica el número de páginas; es un entero Cuandonúmero de
páginases-1, sin embargo, indica que se ha producido un error; ¡el entero está pluriempleado como
booleano!
Incluso si el doble uso es claro para ti, no lo será para otra persona. La claridad adicional que
logrará al usar dos variables para contener dos tipos de información lo sorprenderá. Y nadie le
envidiará el almacenamiento adicional.
3 Asegúrese de que se utilicen todas las variables declaradasLo contrario de usar una variable para más
2
1
de un propósito es no usarla en absoluto. Un estudio de Card, Church y Agresti encontró que las variables
DATOS DUROS
no referenciadas estaban correlacionadas con índices más altos de fallas (1986). Acostúmbrese a verificar
para asegurarse de que se utilicen todas las variables declaradas. Algunos compiladores y utilidades (como
lint) notifican las variables no utilizadas como advertencia.
denominación de variables, consulte la - ¿El código inicializa las variables cerca de donde se usaron por primera vez, si no
lista de verificación en el Capítulo 11, “El
es posible declararlas e inicializarlas al mismo tiempo?
poder de los nombres de ” en la página
- ¿Se compila el código sin advertencias del compilador? (¿Y has activado
todas las advertencias disponibles?)
- ¿Las referencias a las variables están lo más juntas posible, tanto de cada referencia
a una variable a la siguiente referencia como en el tiempo total en vivo?
- ¿Están todas las variables vinculadas en los momentos apropiados, es decir, está
logrando un equilibrio consciente entre la flexibilidad de la vinculación tardía y la mayor
complejidad asociada con la vinculación tardía?
Puntos clave
- La inicialización de datos es propensa a errores, así que utilice las técnicas de inicialización descritas
en este capítulo para evitar los problemas causados por valores iniciales inesperados.
- Minimice el alcance de cada variable. Mantenga las referencias a una variable juntas.
Manténgalo local a una rutina o clase. Evite los datos globales.
- Mantenga las declaraciones que funcionan con las mismas variables lo más juntas posible.
Temas relacionados
Tan importante como el tema de los buenos nombres es para la programación efectiva, nunca he leído una
discusión que cubriera más que un puñado de las docenas de consideraciones que intervienen en la
creación de buenos nombres. Muchos textos de programación dedican algunos párrafos a la elección de
abreviaturas, sueltan algunas perogrulladas y esperan que te las arregles por ti mismo. Tengo la intención
de ser culpable de lo contrario: ¡inundarte con más información sobre buenos nombres de la que jamás
podrás usar!
Las pautas de este capítulo se aplican principalmente a la denominación de variables: objetos y datos primitivos.
Pero también se aplican a clases de nombres, paquetes, archivos y otras entidades de programación. Para obtener
detalles sobre cómo nombrar rutinas, consulte la Sección 7.3, “Nombres correctos de rutinas”.
259
260 Capítulo 11: El poder de los nombres de variables
x = x - xx;
xxx = fido + Impuesto sobre las ventas( fido ); x
CODIFICACIÓN = x + Recargo por pago atrasado( x1, x ) + xxx;
HORROR
x = x + interés (x1, x);
¿Qué está pasando en este fragmento de código? Qué hacerx1,XX, yxxx¿significar? Que hace fido
¿significar? Suponga que alguien le dice que el código calculó la factura total de un cliente en
función de un saldo pendiente y un nuevo conjunto de compras. ¿Qué variable usaría para imprimir
la factura del cliente solo por el nuevo conjunto de compras?
Aquí hay una versión del mismo código que hace que estas preguntas sean más fáciles de responder:
En vista del contraste entre estas dos piezas de código, un buen nombre de variable es
legible, memorable y apropiado. Puede utilizar varias reglas generales para lograr estos
objetivos.
Para una variable que represente el número de personas en el equipo olímpico de EE. UU., crearía el
nombrenumberOfPeopleOnTheUsOlympicEquipo. Una variable que representa el número de
asientos en un estadio seríanúmeroDeAsientosEnElEstadio. Una variable que representa el número
máximo de puntos anotados por el equipo de un país en cualquier Olimpiada moderna seríaNúmero
Máximo De Puntos En Los Juegos Olímpicos Modernos. Una variable que contiene la tasa de interés
actual es mejor nombradaVelocidadotasa de interésqueroX. Entiendes la idea.
11.1 Consideraciones en la elección de buenos nombres 261
Tenga en cuenta dos características de estos nombres. Primero, son fáciles de descifrar. De hecho, no es
necesario descifrarlos en absoluto porque simplemente puede leerlos. Pero segundo, algunos de los
nombres son largos, demasiado largos para ser prácticos. En breve abordaré la cuestión de la longitud del
nombre de la variable.
Los nombresfecha actualyFechason buenos nombres porque describen de forma completa y precisa
la idea de "fecha actual". De hecho, usan las palabras obvias. Los programadores a veces pasan por
alto el uso de palabras comunes, que a menudo es la solución más fácil. Porque son demasiado
breves y nada descriptivos,discos compactosyCson nombres pobres.Actuales pobre porque no te dice
lo que es actual.fechaes casi un buen nombre, pero es un mal nombre en el análisis final porque la
fecha involucrada no es cualquier fecha, sino la fecha actual;fechapor sí mismo no da tal indicación.X,
x1, yx2son nombres pobres porque siempre son nombres pobres—Xrepresenta tradicionalmente una
cantidad desconocida; si no quiere que sus variables sean cantidades desconocidas, piense en
mejores nombres.
Los nombres deben ser lo más específicos posible. nombres comoX,temperatura, yique son lo suficientemente
generales como para usarse para más de un propósito no son tan informativos como podrían ser y generalmente
Orientación al Problema
La longitud óptima de un nombre parece estar entre las longitudes deXy Número Máximo De Puntos
En Los Juegos Olímpicos Modernos. Los nombres que son demasiado cortos no transmiten suficiente
significado. El problema con nombres comox1yx2es que aunque puedas descubrir lo queXes que no
sabrás nada de la relación entrex1yx2. Los nombres que son demasiado largos son difíciles de
escribir y pueden oscurecer la estructura visual de un programa.
3 Gorla, Benander y Benander encontraron que el esfuerzo requerido para depurar un programa se
2
1
minimizaba cuando las variables tenían nombres que tenían un promedio de 10 a 16 caracteres
DATOS DUROS
(1990). Los programas con nombres con un promedio de 8 a 20 caracteres eran casi tan fáciles de
depurar. La directriz no significa que deba intentar que todos los nombres de las variables tengan
entre 9 y 15 o entre 10 y 16 caracteres. Significa que si revisa su código y ve muchos nombres que
son más cortos, debe verificar para asegurarse de que los nombres sean tan claros como deben ser.
Probablemente saldrá ganando si adopta el enfoque de Ricitos de oro y los tres osos
para nombrar las variables, como ilustra la tabla 11-2.
Tabla 11-2 Nombres de variables que son demasiado largos, demasiado cortos o correctos
norte,ns,nsisd
metro,diputado,máximo,puntos
numSeatsInStadium,número de asientos
teamPointsMax,puntosRegistrar
Referencia cruzadaEl alcance ¿Los nombres cortos de variables siempre son malos? No, no siempre. Cuando le das a una variable
se analiza con más detalle en la
un nombre corto comoi, la longitud misma dice algo sobre la variable, a saber, que la variable es un
Sección 10.4, “Alcance”.
valor inicial con un alcance de operación limitado.
Un programador que lea una variable de este tipo debería poder suponer que su valor no se usa
fuera de unas pocas líneas de código. Cuando nombras una variablei, está diciendo: "Esta variable es
un contador de bucle o un índice de matriz común y corriente y no tiene ningún significado fuera de
estas pocas líneas de código".
Un estudio realizado por WJ Hansen descubrió que los nombres más largos son mejores para las variables que se usan con poca
frecuencia o las variables globales y los nombres más cortos son mejores para las variables locales o las variables de bucle.
11.1 Consideraciones en la elección de buenos nombres 263
(Shneiderman 1980). Sin embargo, los nombres cortos están sujetos a muchos problemas y algunos programadores
cuidadosos los evitan por completo como una cuestión de política de programación defensiva.
Use calificadores en los nombres que están en el espacio de nombres globalSi tiene variables que
están en el espacio de nombres global (constantes con nombre, nombres de clases, etc.), considere si
necesita adoptar una convención para particionar el espacio de nombres global y evitar conflictos de
nombres. En C++ y C#, puede usar elespacio de nombrespalabra clave para particionar el espacio de
nombres global.
C++ Ejemplo de uso de laespacio de nombresPalabra clave para particionar el espacio de nombres global
espacio de nombres UserInterfaceSubsystem {
...
// muchas declaraciones. . .
...
// muchas declaraciones. . .
En los idiomas que no admiten espacios de nombres o paquetes, aún puede usar convenciones de
nomenclatura para particionar el espacio de nombres global. Una convención es exigir que las clases visibles
globalmente tengan el prefijo mnemotécnico de subsistema. La clase de empleado de la interfaz de usuario
podría convertirseuiEmpleado, y la clase de empleado de la base de datos podría convertirse endbEmpleado.
Esto minimiza el riesgo de colisiones de espacios de nombres globales.
Muchos programas tienen variables que contienen valores calculados: totales, promedios,
máximos, etc. Si modifica un nombre con un calificador comoTotal,Suma,Promedio,máx.,
mínimo,Registro,Cuerda, oPuntero, pon el modificador al final del nombre.
Esta práctica ofrece varias ventajas. Primero, la parte más significativa del nombre de la variable, la
parte que le da a la variable la mayor parte de su significado, está al frente, por lo que es más
prominente y se lee primero. En segundo lugar, al establecer esta convención, evita la confusión que
podría crear si usara amboslos ingresos totalesyingresosTotalen el mismo programa. Los nombres
son semánticamente equivalentes y la convención impediría que se usaran como si fueran
diferentes. En tercer lugar, un conjunto de nombres comoingresosTotal, gastototal,Promedio de
ingresos, ygastopromediotiene una agradable simetría. un conjunto de nombres
Traducido del inglés al español - www.onlinedoctranslator.com
Una excepción a la regla de que los valores calculados van al final del nombre es la posición
habitual delnúmeroCalificatorio. Colocado al comienzo de un nombre de variable,número se
refiere a un total:numClienteses el número total de clientes. Colocado al final del nombre de la
variable,númerose refiere a un índice:númeroclientees el número del cliente actual. lossal final
denumClienteses otro consejo sobre la diferencia de significado. Pero, debido a que usar
númerotan a menudo crea confusión, probablemente sea mejor eludir todo el problema
usandoContaroTotalpara referirse a un número total de clientes yÍndicepara referirse a un
cliente específico. De este modo,número de clienteses el número total de clientes y
índiceclientese refiere a un cliente específico.
- empezar/finalizar
- primero último
- bloqueado/desbloqueado
- mínimo máximo
- siguiente anterior
- viejo nuevo
- abierto/cerrado
- visible invisible
- origen Destino
- origen Destino
- arriba abajo
Referencia cruzadaPara obtener detalles Las pautas para nombrar variables en bucles han surgido porque los bucles son una característica
sobre los bucles, consulte el Capítulo 16,
común de la programación de computadoras. Los nombresi,j, ykson habituales:
“Control de bucles”.
Si se va a usar una variable fuera del ciclo, se le debe dar un nombre más significativo
quei,j, ok. Por ejemplo, si está leyendo registros de un archivo y necesita recordar
cuántos registros ha leído, un nombre comonúmero de registrossería apropiado:
Si el bucle es más largo que unas pocas líneas, es fácil olvidar quéise supone que representa y
es mejor que le dé al índice de bucle un nombre más significativo. Debido a que el código se
cambia, expande y copia a menudo en otros programas, muchos programadores
experimentados evitan nombres comoien total.
Una razón común por la que los bucles crecen más es porque están anidados. Si tiene varios bucles
anidados, asigne nombres más largos a las variables de bucle para mejorar la legibilidad.
Los nombres cuidadosamente elegidos para las variables de índice de bucle evitan el problema común de la diafonía
de índice: deciricuando quieres decirjyjcuando quieres deciri. También hacen que los accesos a la matriz sean más
claros:puntuación[ teamIndex ][ eventIndex ]es más informativo quepuntuación[ i ][ j ].
Si tienes que usari,j, yk, no los use para otra cosa que no sean índices de bucle para bucles
simples: la convención está demasiado bien establecida y romperla para usarlos de otras
formas es confuso. La forma más sencilla de evitar tales problemas es simplemente pensar en
nombres más descriptivos quei,j, yk.
266 Capítulo 11: El poder de los nombres de variables
Las variables de estado describen el estado de su programa. Aquí hay una guía de nombres:
Piensa en un nombre mejor quebanderapara variables de estadoEs mejor pensar en las banderas como
variables de estado. Una bandera nunca debería tenerbanderaen su nombre porque eso no te da ninguna
pista sobre lo que hace la bandera. Para mayor claridad, a las banderas se les deben asignar valores y sus
valores se deben probar con tipos enumerados, constantes con nombre o variables globales que actúan
como constantes con nombre. Estos son algunos ejemplos de banderas con nombres incorrectos:
bandera = 0x1;
bandera de estado = 0x80;
imprimirBandera = dieciséis;
computar bandera = 0;
Declaraciones comobandera de estado = 0x80no le dará ninguna pista sobre lo que hace el código a menos
que usted haya escrito el código o tenga documentación que le diga québandera de estadoes y que0x80
representa. Aquí hay ejemplos de código equivalente que son más claros:
Tenga cuidado con las variables “temporales”A menudo es necesario preservar los valores
temporalmente. Pero de una forma u otra, la mayoría de las variables en su programa son
temporales. Llamar a algunos de ellos temporales puede indicar que no está seguro de sus
propósitos reales. Considere el siguiente ejemplo:
Está bien almacenar el valor de la expresión.sqrt( b^2 - 4 * a * c )en una variable, especialmente
porque se usa en dos lugares más adelante. pero el nombretemperaturano te dice nada sobre lo que
hace la variable. Un mejor enfoque se muestra en este ejemplo:
Ejemplo de C++ con un nombre de variable "temporal" reemplazado por una variable real
// Calcular soluciones de una ecuación cuadrática. // Esto
supone que (b^2-4*a*c) es positivo. discriminante = sqrt( b^2 -
4*a*c );
solucion[0] = ( -b + discriminante ) / ( 2 * a ); solución[1] = ( -b -
discriminante ) / ( 2 * a );
268 Capítulo 11: El poder de los nombres de variables
Tenga en cuenta los nombres booleanos típicosAquí hay algunos nombres de variables booleanas particularmente
útiles:
A algunos programadores les gusta ponerEsdelante de sus nombres booleanos. Entonces el nombre de la
variable se convierte en una pregunta:está hecho?esError?es encontrado?isProcessingComplete?
Respondiendo a la pregunta converdaderoofalsoproporciona el valor de la variable. Un beneficio de este
enfoque es que no funcionará con nombres vagos:esEstado? no tiene ningún sentido. Un inconveniente es
que hace que las expresiones lógicas simples sean menos legibles:si (se encuentra)es un poco menos
legible quesi se encuentra ).
11.2 Nombrar tipos específicos de datos 269
si no no encontrado
Ejemplo de Visual Basic del uso de una convención de nomenclatura de prefijos para tipos enumerados
Color de enumeración pública
Color rojo
Color verde
Color azul
Enumeración final
Además, el propio tipo de enumeración (Color,Planeta, oMes) se puede identificar de varias maneras, incluyendo
mayúsculas o prefijos (e_Color,e_Planeta, oe_Mes). Una persona podría argumentar que una enumeración es
esencialmente un tipo definido por el usuario y, por lo tanto, el nombre de la enumeración debe tener el mismo
formato que otros tipos definidos por el usuario, como las clases. Un argumento diferente sería que las
enumeraciones son tipos, pero también son constantes, por lo que el nombre del tipo de enumeración debe
formatearse como constantes. Este libro utiliza la convención de mayúsculas y minúsculas para los nombres de tipos
enumerados.
En algunos idiomas, los tipos enumerados se tratan más como clases, y los miembros de la
enumeración siempre tienen el prefijo del nombre de la enumeración, comoColor.Color_Rojoo
Planeta.Planeta_Tierra. Si está trabajando en ese tipo de lenguaje, no tiene mucho sentido repetir el
prefijo, por lo que puede tratar el nombre del tipo de enumeración como prefijo y simplificar los
nombres paraColor rojoyPlaneta Tierra.
270 Capítulo 11: El poder de los nombres de variables
Constantes de nomenclatura
Referencia cruzadaPara obtener detalles Al nombrar constantes, nombre la entidad abstracta que representa la constante en lugar del
número al que se refiere la constante.CINCOes un mal nombre para una constante
sobre el uso de constantes con nombre,
nombre”.
(independientemente de si el valor que representa es5.0).CICLOS_NECESARIOSes un buen
nombre CICLOS_NECESARIOSpuede igualar5.0o6.0.CINCO = 6.0sería ridículo. De la misma
manera,DOCENA DEL PANADEROes un pobre nombre constante;DONUTS_MAXes un buen
nombre constante.
- Te permiten dar más por sentado. Al tomar una decisión global en lugar de
muchas locales, puede concentrarse en las características más importantes del
código.
- Compensan las debilidades del lenguaje. Puede usar convenciones para emular
constantes con nombre y tipos enumerados. Las convenciones pueden diferenciar entre
datos locales, de clase y globales y pueden incorporar información de tipo para tipos
que no son compatibles con el compilador.
11.3 El poder de las convenciones de nomenclatura 271
- Enfatizan las relaciones entre elementos relacionados. Si usa datos de objetos, el compilador
se encarga de esto automáticamente. Si su idioma no admite objetos, puede complementarlo
con una convención de nomenclatura. nombres comoDirección, teléfono, ynombreno indican
que las variables están relacionadas. Pero suponga que decide que todas las variables de
datos de empleados deben comenzar con unEmpleadoprefijo. dirección del empleado,
empleadoTeléfono, ynombre de empleadono deje ninguna duda de que las variables están
relacionadas. Las convenciones de programación pueden compensar la debilidad del lenguaje
que está utilizando.
La clave es que cualquier convención es a menudo mejor que ninguna convención. La convención
puede ser arbitraria. El poder de las convenciones de nomenclatura no proviene de la convención
específica elegida, sino del hecho de que existe una convención, que agrega estructura al código y le
PUNTO CLAVE
brinda menos cosas de las que preocuparse.
No existen reglas estrictas sobre cuándo debe establecer una convención de nomenclatura, pero
aquí hay algunos casos en los que las convenciones valen la pena:
- Cuando su programa es tan grande que no puede retenerlo todo en su cerebro de una
sola vez y debe pensar en él por partes
- Cuándo el programa durará lo suficiente como para dejarlo de lado durante algunas
semanas o meses antes de volver a trabajar en él.
- Cuando tiene muchos términos inusuales que son comunes en un proyecto y desea tener
términos estándar o abreviaturas para usar en la codificación
Grados de Formalidad
Referencia cruzadaPara obtener detalles Diferentes convenciones tienen diferentes grados de formalidad. Una convención informal podría ser
sobre las diferencias en la formalidad en
tan simple como "Usar nombres significativos". Otras convenciones informales se describen en la
proyectos pequeños y grandes, consulte
esta sección.
Diferenciar entre nombres de variables y nombres de rutinasLa convención que utiliza este libro es
comenzar los nombres de variables y objetos con minúsculas y los nombres de rutinas con mayúsculas:
nombre de la variablecontranombre de rutina().
Opción 3: diferenciación de tipos y variables a través del prefijo "t_" para tipos
t_Widget Widget;
t_LongerWidget Más largoWidget;
Opción 4: diferenciación de tipos y variables a través del prefijo "a" para variables
Widget un Widget;
Widget más largo a Widget más largo;
Opción 5: diferenciación de tipos y variables mediante el uso de nombres más específicos para las
variables
Widget empleadoWidget;
Widget más largo fullEmployeeWidget;
Cada una de estas opciones tiene fortalezas y debilidades. La opción 1 es una convención común en
lenguajes que distinguen entre mayúsculas y minúsculas, incluidos C++ y Java, pero algunos programadores
se sienten incómodos al diferenciar nombres únicamente en función de las mayúsculas. De hecho, la
creación de nombres que difieren solo en la mayúscula de la primera letra del nombre parece proporcionar
una "distancia psicológica" demasiado pequeña y una distinción visual demasiado pequeña entre los dos
nombres.
11.4 Convenciones de nombres informales 273
El enfoque de la Opción 1 no se puede aplicar de forma coherente en entornos de idiomas mixtos si alguno
de los idiomas no distingue entre mayúsculas y minúsculas. En Microsoft Visual Basic, por ejemplo,Dim
widget como Widgetgenerará un error de sintaxis porqueartilugioyWidgetse tratan como la misma ficha.
La opción 2 crea una distinción más obvia entre el nombre del tipo y el nombre de la variable.
Sin embargo, por razones históricas, todas las mayúsculas se usan para indicar constantes en
C++ y Java, y el enfoque está sujeto a los mismos problemas en entornos de lenguaje mixto a
los que está sujeta la Opción 1.
La opción 3 funciona adecuadamente en todos los idiomas, pero a algunos programadores no les gusta la idea de
La opción 5 requiere más reflexión sobre una base de variable por variable. En la mayoría de los
casos, verse obligado a pensar en un nombre específico para una variable da como resultado un
código más legible. Pero a veces unartilugiorealmente es solo un genericoartilugio, y en esos casos
te encontrarás con nombres menos que obvios, comowidget genérico, que posiblemente son menos
legibles.
En resumen, cada una de las opciones disponibles implica compensaciones. El código de este libro utiliza la
opción 5 porque es la más comprensible en situaciones en las que la persona que lee el código no está
necesariamente familiarizada con una convención de nomenclatura menos intuitiva.
Identificar variables globalesUn problema común de programación es el mal uso de las variables globales.
Si le da a todos los nombres de variables globales ungramo_prefijo, por ejemplo, un programador viendo la
variableg_Total en ejecuciónsabrá que es una variable global y la tratará como tal.
Identificar variables miembroIdentificar los datos de los miembros de una clase. Deje en claro que la
variable no es una variable local y que tampoco es una variable global. Por ejemplo, puede identificar
variables miembro de clase con unmetro_prefijo para indicar que se trata de datos de miembros.
Identificar definiciones de tipoLas convenciones de nomenclatura para tipos tienen dos propósitos:
identifican explícitamente un nombre como un nombre de tipo y evitan conflictos de nombres con variables.
Para cumplir con esas consideraciones, un prefijo o sufijo es un buen enfoque. En C++, el enfoque habitual
es usar todas las letras mayúsculas para un nombre de tipo, por ejemplo, COLORyMENÚ. (Esta convención
se aplica adefinición de tipoarenaestructuras, no nombres de clases). Pero esto crea la posibilidad de
confusión con constantes de preprocesador con nombre. Para evitar confusiones, puede prefijar los
nombres de los tipos cont_, comot_Coloryt_Menú.
Identificar constantes con nombreLas constantes con nombre deben identificarse para que
pueda saber si está asignando a una variable un valor de otra variable (cuyo valor puede
cambiar) o de una constante con nombre. En Visual Basic, tiene la posibilidad adicional de que
el valor sea de una función. Visual Basic no requiere nombres de funciones para usar
paréntesis, mientras que en C++ incluso una función sin parámetros usa paréntesis.
274 Capítulo 11: El poder de los nombres de variables
Un enfoque para nombrar constantes es usar un prefijo comoC_para nombres constantes. Eso
te daría nombres comoc_RecsMaxoc_LinesPerPageMax. En C++ y Java, la convención es usar
todas las letras mayúsculas, posiblemente con guiones bajos para separar palabras, RECSMAXo
RECS_MAXyLÍNEAS POR PÁGINA MÁX.oLÍNEAS_POR_PÁGINA_ MÁX..
Identifique parámetros de solo entrada en idiomas que no los aplicanA veces, los
parámetros de entrada se modifican accidentalmente. En lenguajes como C++ y Visual
Basic, debe indicar explícitamente si desea que un valor modificado se devuelva a la
rutina de llamada. Esto se indica con el*,&, yconstantecalificadores en C++ oPorRefy
PorValen Visual Basic.
En otros idiomas, si modifica una variable de entrada, se devuelve, le guste o no. Esto es
especialmente cierto al pasar objetos. En Java, por ejemplo, todos los objetos se pasan "por
valor", de modo que cuando pasa un objeto a una rutina, el contenido del objeto se puede
cambiar dentro de la rutina llamada (Arnold, Gosling, Holmes 2000).
Referencia cruzadaMejorar un En esos lenguajes, si establece una convención de nomenclatura en la que los parámetros de
lenguaje con una convención de
solo entrada reciben unconstanteprefijo (ofinal,no modificable, o algo similar), sabrá que se ha
nomenclatura para compensar las
limitaciones del lenguaje mismo
producido un error cuando vea algo con unconstanteprefijo en el lado izquierdo de un signo
es un ejemplo de programación. igual. Si tú vesconstMax.SetNewMax( ... ), sabrás que es una tontería porque elconstanteEl
dentroun lenguaje en lugar de
prefijo indica que no se supone que la variable se modifique.
simplemente programar en él.
Para más detalles sobre la
Dar formato a los nombres para mejorar la legibilidadDos técnicas comunes para
programacióndentro un idioma,
consulte la Sección 34.4,
aumentar la legibilidad son el uso de mayúsculas y espaciado entre caracteres para separar
“Programe en su idioma, no en palabras. Por ejemplo, TOTAL DE PUNTOS DE GIMNASIAes menos legible que
él”.
gimnasiaPuntoTotalo gymnastics_point_total. C++, Java, Visual Basic y otros lenguajes
permiten caracteres mixtos en mayúsculas y minúsculas. C++, Java, Visual Basic y otros
lenguajes también permiten el uso del separador de guión bajo (_).
Trate de no mezclar estas técnicas; eso hace que el código sea difícil de leer. Sin embargo, si hace un
intento honesto de usar cualquiera de estas técnicas de legibilidad de manera consistente, mejorará
su código. La gente se las ha arreglado para tener debates entusiastas y apasionantes sobre puntos
finos, como si el primer carácter de un nombre debe escribirse en mayúscula (Puntos totales contra
puntos totales), pero mientras usted y su equipo sean consistentes, no habrá mucha diferencia. Este
libro utiliza minúsculas iniciales debido a la fuerza de la práctica de Java y para facilitar la similitud de
estilo entre varios idiomas.
11.4 Convenciones de nombres informales 275
C Convenciones
Otras lecturasEl libro clásico Varias convenciones de nomenclatura se aplican específicamente al lenguaje de programación C:
sobre el estilo de programación C
- pagses un puntero.
- Las macros del preprocesador están enTODAS_MAYÚSCULAS. Esto generalmente se extiende para incluir
también typedefs.
Estas son las convenciones para la programación C genérica, estilo UNIX y estilo Linux, pero las
convenciones C son diferentes en diferentes entornos. En Microsoft Windows, los programadores de C
tienden a usar una forma de la convención de nomenclatura húngara y letras mayúsculas y minúsculas
mixtas para nombres de variables. En Macintosh, los programadores de C tienden a usar nombres de
mayúsculas y minúsculas para las rutinas porque la caja de herramientas y las rutinas del sistema operativo
de Macintosh se diseñaron originalmente para una interfaz Pascal.
Convenciones de C++
Otras lecturasPara obtener más Estas son las convenciones que han surgido en torno a la programación en C++:
información sobre el estilo de
- Los nombres de variables y funciones usan minúsculas para la primera palabra, con la primera letra de cada
- El guión bajo no se usa como separador dentro de los nombres, a excepción de los nombres en
mayúsculas y ciertos tipos de prefijos (como los que se usan para identificar variables globales).
276 Capítulo 11: El poder de los nombres de variables
Al igual que con la programación en C, esta convención está lejos de ser estándar y diferentes entornos se
han estandarizado en diferentes detalles de la convención.
Convenciones de Java
Otras lecturasPara obtener más A diferencia de C y C++, las convenciones de estilo de Java se han establecido desde el
información sobre el estilo de
comienzo del lenguaje:
programación Java, consulteLos
- Los nombres de clase e interfaz escriben en mayúscula la primera letra de cada palabra, incluida la
primera palabra, por ejemplo,ClassOrInterfaceName.
- Los nombres de variables y métodos usan minúsculas para la primera palabra, con la primera letra
de cada palabra siguiente en mayúscula, por ejemplo,nombre de la variable o de la rutina.
- El guión bajo no se usa como separador dentro de los nombres, excepto para los nombres en mayúsculas.
En este libro, por ejemplo, todos los nombres de las variables comienzan con minúsculas, lo cual es
consistente con la práctica de programación Java convencional y algunas pero no todas las convenciones de
C++. Este libro da formato a todos los nombres de rutinas con una letra mayúscula inicial, que sigue la
convención de C++. La convención de Java sería comenzar los nombres de los métodos con minúsculas, pero
este libro usa nombres de rutinas que comienzan con mayúsculas en todos los lenguajes por el bien de la
legibilidad general.
Las convenciones estándar anteriores tienden a ignorar varios aspectos importantes de los nombres
que se discutieron en las últimas páginas, incluido el ámbito de las variables (privado, de clase o
global), la diferenciación entre nombres de clases, objetos, rutinas y variables, y otros temas.
11.4 Convenciones de nombres informales 277
Las pautas de la convención de nomenclatura pueden parecer complicadas cuando están encadenadas en
varias páginas. Sin embargo, no es necesario que sean terriblemente complejos y puede adaptarlos a sus
necesidades. Los nombres de variables incluyen tres tipos de información:
- El tipo de datos (constante con nombre, variable primitiva, tipo definido por el usuario o clase)
Las tablas 11-3, 11-4 y 11-5 proporcionan convenciones de nomenclatura para C, C++, Java y Visual
Basic que se han adaptado de las pautas presentadas anteriormente. Estas convenciones específicas
no se recomiendan necesariamente, pero le dan una idea de lo que incluye una convención de
nomenclatura informal.
Entidad Descripción
Nombre de la clase Los nombres de las clases se mezclan en mayúsculas y minúsculas con
una letra mayúscula inicial.
Escribe un nombre Las definiciones de tipo, incluidos los tipos enumerados y las
definiciones de tipo, usan mayúsculas y minúsculas mixtas con una
letra mayúscula inicial.
nombre de rutina() Las rutinas están en mayúsculas y minúsculas mixtas. (Los buenos
nombres de rutinas se analizan en la Sección 7.3.)
m_ClassVariable Las variables miembro que están disponibles para varias rutinas dentro
de una clase, pero solo dentro de una clase, tienen el prefijo metro_.
MACRO TODAS_MAYÚSCULAS.
Entidad Descripción
Escribe un nombre Las definiciones de tipo utilizan mayúsculas y minúsculas mixtas con una
letra mayúscula inicial.
Nombre de la rutina global() Las rutinas públicas están en mayúsculas y minúsculas mixtas.
f_FileRoutineName() Las rutinas que son privadas para un solo módulo (archivo)
tienen el prefijoF_.
Variable local Las variables locales están en mayúsculas y minúsculas mixtas.
El nombre debe ser independiente del tipo de datos
subyacente y debe hacer referencia a lo que represente la
variable.
Parámetro de rutina Los parámetros de rutina tienen el mismo formato que las variables
locales.
Dado que Visual Basic no distingue entre mayúsculas y minúsculas, se aplican reglas especiales para
diferenciar entre nombres de tipos y nombres de variables. Eche un vistazo a la tabla 11-5.
Entidad Descripción
C_ClassName Los nombres de las clases están en mayúsculas y minúsculas mixtas
con una letra mayúscula inicial y unC_prefijo.
Entidad Descripción
variable local Las variables locales están en mayúsculas y minúsculas mixtas
con una letra minúscula inicial. El nombre debe ser
independiente del tipo de datos subyacente y debe hacer
referencia a lo que represente la variable.
parámetro de rutina Los parámetros de rutina tienen el mismo formato que las variables
locales.
nombre de rutina() Las rutinas están en mayúsculas y minúsculas mixtas. (Los buenos
nombres de rutinas se analizan en la Sección 7.3.)
m_ClassVariable Las variables miembro que están disponibles para varias rutinas dentro
de una clase, pero solo dentro de una clase, tienen el prefijo metro_.
Los prefijos estandarizados se componen de dos partes: la abreviatura de tipo definido por el usuario
(UDT) y el prefijo semántico.
La abreviatura UDT identifica el tipo de datos del objeto o variable que se nombra. Las abreviaturas
UDT pueden hacer referencia a entidades como ventanas, regiones de pantalla y fuentes. Una
abreviatura UDT generalmente no se refiere a ninguno de los tipos de datos predefinidos que ofrece
el lenguaje de programación.
Los UDT se describen con códigos cortos que usted crea para un programa específico y luego los estandariza
para usarlos en ese programa. Los códigos son mnemotécnicos comownpara ventanas yscrpara las regiones
de la pantalla. La tabla 11-6 ofrece una lista de ejemplo de UDT que puede usar en un programa para un
procesador de texto.
280 Capítulo 11: El poder de los nombres de variables
UDT
Abreviatura Sentido
ch Carácter (un carácter no en el sentido de C++, sino en el sentido del tipo de
datos que usaría un programa de procesamiento de texto para representar un
carácter en un documento)
doc Documento
Pensilvania Párrafo
scr región de la pantalla
sel Selección
wn Ventana
Cuando usa UDT, también define tipos de datos de lenguaje de programación que usan las mismas
abreviaturas que los UDT. Por lo tanto, si tuviera los UDT en la Tabla 11-6, vería declaraciones de
datos como estas:
DOC docActivo
Pensilvania primerPaActivoDocumento;
Pensilvania últimoPaActiveDocument;
WN wnPrincipal;
Nuevamente, estos ejemplos se relacionan con un procesador de textos. Para usar en sus propios
proyectos, crearía abreviaturas de UDT para los UDT que se usan más comúnmente dentro de su
entorno.
Prefijos semánticos
Los prefijos semánticos van un paso más allá del UDT y describen cómo se usa la variable u objeto. A
diferencia de los UDT, que varían de un proyecto a otro, los prefijos semánticos son algo estándar en
todos los proyectos. La tabla 11-7 muestra una lista de prefijos semánticos estándar.
Semántico
Prefijo Sentido
C Recuento (como en el número de registros, caracteres, etc.)
primero El primer elemento que necesita ser tratado en una matriz.primeroes parecido a
minpero en relación con la operación actual en lugar de la matriz en sí.
gramo Variable global
i Indexar en una matriz
Semántico
Prefijo Sentido
límite El límite superior de los elementos que deben tratarse en una matriz.límiteno es un índice
válido. Me gustaultimo,límitese utiliza como contrapartida deprimero. A diferencia deultimo,
límiterepresenta un límite superior no inclusivo en la matriz;ultimorepresenta un elemento
legal final. En general,límitees igualúltimo + 1.
máximo El último elemento absoluto en una matriz u otro tipo de lista.máximose refiere a la
matriz en sí en lugar de a las operaciones en la matriz.
Los prefijos estandarizados agregan precisión a varias áreas de nombres que tienden a ser
imprecisas. Las distinciones precisas entremin,primero,ultimo, ymáximoson particularmente útiles.
Los prefijos estandarizados hacen que los nombres sean más compactos. Por ejemplo, puedes usar
cpapara el recuento de párrafos en lugar detotalParagraphs. Puedes usaripapara identificar un
índice en una matriz de párrafos en lugar deindexParagraphsoíndice de párrafos.
Finalmente, los prefijos estandarizados le permiten verificar los tipos con precisión cuando usa tipos de
datos abstractos que su compilador no necesariamente puede verificar:paReformat = docReformat
probablemente esté mal porquePensilvaniaydocson UDT diferentes.
Si las circunstancias requieren que cree nombres cortos, tenga en cuenta que algunos métodos para
acortar nombres son mejores que otros. Puede crear buenos nombres cortos de variables eliminando
palabras innecesarias, usando sinónimos cortos y usando cualquiera de varias estrategias de
abreviatura. Es una buena idea estar familiarizado con múltiples técnicas para abreviar porque
ninguna técnica única funciona bien en todos los casos.
Aquí hay varias pautas para crear abreviaturas. Algunos de ellos contradicen a otros, así que
no intentes usarlos todos al mismo tiempo.
- Eliminar todas las vocales no principales. (computadorase conviertecmptr, ypantallase convierte pantalla.
manzanase convierteaplicación, yenterose convierteintegral.)
- Trunca consistentemente después de la primera, segunda o tercera (la que sea apropiada)
letra de cada palabra.
- Utilice todas las palabras significativas del nombre, hasta un máximo de tres palabras.
- Repita estas técnicas hasta que abrevie cada nombre de variable entre 8 y 20
caracteres o el número de caracteres a los que su idioma limita los nombres
de variables.
11.6 Crear nombres cortos que sean legibles 283
Abreviaturas fonéticas
Algunas personas abogan por crear abreviaturas basadas en el sonido de las palabras en lugar de su
ortografía. De este modoPatinajese conviertepatinando,destacarse conviertehilita,antes de se
convierteb4,ejecutarse conviertexqt, y así. Esto se parece demasiado a pedirle a la gente que
descubra matrículas personalizadas para mí, y no lo recomiendo. Como ejercicio, averigua qué
significan estos nombres:
Crea nombres que puedas pronunciarUsarpos xmás bien quexPstnynecesidadesComp más bien
quendsCmptg. Aplique la prueba del teléfono: si no puede leer su código a alguien por teléfono,
cambie el nombre de sus variables para que sean más distintivos (Kernighan y Plauger 1978).
Una manera fácil de evitar las colisiones de nombres es usar una palabra diferente con el mismo
significado, por lo que un diccionario de sinónimos es útil. En este ejemplo,despedidopodría ser sustituido
por encendidoydesembolso completo de ingresospodría ser sustituido pordesembolso total de ingresos.
Las abreviaturas de tres letras se convierten endsmyCrd, eliminando la colisión de nombres.
284 Capítulo 11: El poder de los nombres de variables
Documente nombres extremadamente cortos con tablas de traducción en el códigoEn los idiomas que
solo permiten nombres muy cortos, incluya una tabla de traducción para proporcionar un recordatorio del
contenido mnemotécnico de las variables. Incluya la tabla como comentarios al comienzo de un bloque de
código. Aquí hay un ejemplo:
Puede pensar que esta técnica está desactualizada, pero a mediados de 2003 trabajé con un
cliente que tenía cientos de miles de líneas de código escritas en RPG que estaban sujetas a
una limitación de nombre de variable de 6 caracteres. Estos problemas todavía surgen de vez
en cuando.
Documente todas las abreviaturas en un documento de "abreviaturas estándar" a nivel de proyecto Las
abreviaturas en el código crean dos riesgos generales:
Para abordar estos dos problemas potenciales, puede crear un documento de "Abreviaturas
estándar" que capture todas las abreviaturas de codificación utilizadas en su proyecto. El documento
puede ser un documento de procesador de texto o una hoja de cálculo. En un proyecto muy grande,
podría ser una base de datos. El documento se registra en el control de versiones y se desprotege
cada vez que alguien crea una nueva abreviatura en el código. Las entradas en el documento deben
ordenarse por palabra completa, no por abreviatura.
Esto puede parecer una gran cantidad de gastos generales, pero aparte de una pequeña cantidad de gastos
generales de inicio, en realidad solo establece un mecanismo que ayuda al proyecto a usar abreviaturas de
manera efectiva. Aborda el primero de los dos riesgos generales descritos anteriormente al documentar
todas las abreviaturas en uso. El hecho de que un programador no pueda crear una nueva abreviatura sin la
sobrecarga de revisar el documento de abreviaturas estándar fuera de ver-
11.7 Tipos de nombres a evitar 285
El problema general ilustrado por esta guía es la diferencia entre la conveniencia del tiempo de escritura y la
conveniencia del tiempo de lectura. Este enfoque crea claramente un tiempo de escriturainconveniencia,
pero los programadores durante la vida útil de un sistema pasan mucho más tiempo leyendo código que
PUNTO CLAVE
escribiendo código. Este enfoque aumenta la conveniencia del tiempo de lectura. Para cuando todo el polvo
se asiente en un proyecto, también podría haber mejorado la conveniencia del tiempo de escritura.
Recuerde que los nombres son más importantes para el lector del código que para el
escritor.Lea su propio código que no haya visto durante al menos seis meses y observe dónde
debe trabajar para comprender el significado de los nombres. Resolver cambiar las prácticas
que causan tal confusión.
Evite nombres con significados similaresSi puede cambiar los nombres de dos variables sin dañar
el programa, debe cambiar el nombre de ambas variables. Por ejemplo,aporte yvalor de entrada,
número de registroynumRegistros, ynúmero de expedienteyíndice de archivoson tan similares
semánticamente que si los usa en la misma pieza de código, los confundirá fácilmente e instalará
algunos errores sutiles y difíciles de encontrar.
Referencia cruzadaEl término técnico Evite variables con diferentes significados pero nombres similaresSi tiene dos variables con
para diferencias como esta entre
nombres similares y significados diferentes, intente cambiar el nombre de una de ellas o cambie sus
nombres de variables similares es
¿Qué?
GOL - - - ¡DONANTE!
Recuerde que la prueba del teléfono se aplica a nombres que suenan similares al igual que a
nombres extrañamente abreviados.
Evite los números en los nombresSi los números en un nombre son realmente significativos,
use una matriz en lugar de variables separadas. Si una matriz es inapropiada, los números son
aún más inapropiados. Por ejemplo, evitararchivo1yarchivo2, ototal1ytotal2. Casi siempre
puede pensar en una mejor manera de diferenciar entre dos variables que tachando una1o un
2 al final del nombre. no puedo decirnuncautiliza numerales. Algunas entidades del mundo
real (como la Ruta 66 o la Interestatal 405) tienen números incrustados. Pero considere si
existen mejores alternativas antes de crear un nombre que incluya números.
Evite las palabras mal escritas en los nombresYa es bastante difícil recordar cómo se supone que
se escriben las palabras. Requerir que las personas recuerden las faltas de ortografía "correctas" es
demasiado pedir. Por ejemplo, falta de ortografíadestacarcomohilitasalvar a tres personajes hace que
sea diabólicamente difícil para un lector recordar cómodestacarestaba mal escrito. Era queresaltar?
hilita?resaltar?hilito?jai-a-lai-t? ¿Quién sabe?
Un problema más sutil ocurre en las variaciones del inglés. Si un proyecto se lleva
a cabo en varios países de habla inglesa, estandarice una versión del inglés para
que no se pregunte constantemente si el código debe decir "color" o "color",
"check" o "cheque", etc. .
Evite los nombres de tipos estándar, variables y rutinasTodas las guías de lenguajes de
programación contienen listas de nombres reservados y predefinidos del lenguaje. Lea la lista
de vez en cuando para asegurarse de no pisar los dedos del idioma que está usando. Por
ejemplo, el siguiente fragmento de código es legal en PL/I, pero sería un idiota certificable si lo
usara:
si si = entonces entonces
entonces = más;
más más = si;
CODIFICACIÓN
HORROR
No use nombres que no tengan ninguna relación con lo que representan las variables primavera-
Evite nombres que contengan caracteres difíciles de leerTenga en cuenta que algunos personajes
se parecen tanto que es difícil diferenciarlos. Si la única diferencia entre dos nombres es uno de estos
caracteres, es posible que le resulte difícil diferenciar los nombres. Por ejemplo, intente encerrar en
un círculo el nombre que no pertenece a cada uno de los siguientes conjuntos:
Los pares que son difíciles de distinguir incluyen (1 y l), (1 e I), (. y ,), (0 y O), (2 y
Z), (; y :), (S y 5), y (G y 6).
Referencia cruzadaPara conocer las ¿De verdad importan detalles como estos? ¡Por cierto! Gerald Weinberg informa que en la década de 1970,
consideraciones sobre el uso de datos,
se usó una coma en un FortranFORMATOdeclaración en la que se debería haber utilizado un punto. El
consulte la lista de verificación en la página
- ¿El nombre se refiere al problema del mundo real en lugar de a la solución del
lenguaje de programación?
- ¿Se nombran las variables booleanas de modo que sus significados cuando severdadero¿están
claros?
- ¿Las constantes nombradas se nombran por las entidades abstractas que representan en lugar de
Convenciones de nombres
- ¿La convención distingue entre datos locales, de clase y globales?
Nombres cortos
- ¿El código usa nombres largos (a menos que sea necesario usar nombres cortos)?
- ¿Se evitan los nombres que podrían leerse mal o pronunciarse mal?
- . . . nombres que entran en conflicto con nombres de rutinas de biblioteca estándar o con
Puntos clave
- Los buenos nombres de variables son un elemento clave de la legibilidad del programa. Los tipos específicos
de variables, como los índices de bucle y las variables de estado, requieren consideraciones específicas.
- Los nombres deben ser lo más específicos posible. Los nombres que son lo suficientemente vagos o
generales como para usarse para más de un propósito suelen ser malos nombres.
- Independientemente del tipo de proyecto en el que esté trabajando, debe adoptar una convención
de nomenclatura de variables. El tipo de convención que adopte dependerá del tamaño de su
programa y del número de personas que trabajen en él.
- Rara vez se necesitan abreviaturas con los lenguajes de programación modernos. Si usa
abreviaturas, realice un seguimiento de las abreviaturas en un diccionario del proyecto o use
el enfoque de prefijos estandarizados.
- El código se lee muchas más veces de las que se escribe. Asegúrese de que los nombres que elija favorezcan la
Temas relacionados
Los tipos de datos fundamentales son los bloques de construcción básicos para todos los demás tipos de
datos. Este capítulo contiene sugerencias para el uso de números (en general), enteros, números de coma
flotante, caracteres y cadenas, variables booleanas, tipos enumerados, constantes con nombre y matrices.
La sección final de este capítulo describe cómo crear sus propios tipos.
Este capítulo cubre la solución de problemas básicos para los tipos fundamentales de datos. Si ha
cubierto sus bases de datos fundamentales, salte al final del capítulo, revise la lista de verificación de
problemas a evitar y continúe con la discusión de tipos de datos inusuales en el Capítulo 13.
291
292 Capítulo 12: Tipos de datos fundamentales
Referencia cruzadaPara obtener más Evita los “números mágicos”Los números mágicos son números literales, como100o47524,
detalles sobre el uso de constantes con
que aparecen en medio de un programa sin explicación. Si programa en un lenguaje que
nombre en lugar de números mágicos,
- Los cambios se pueden hacer de forma más fiable. Si usa constantes con nombre, no pasará
por alto una de las100s o cambiar un100que se refiere a otra cosa.
- Los cambios se pueden hacer más fácilmente. Cuando el número máximo de entradas
cambia de100a200, si estás usando números mágicos tienes que encontrar todos los
100s y cambiarlos a200s. Si utiliza100+1o100-1, también tendrás que encontrar todos
los101arena99s y cambiarlos a201arena199s. Si está utilizando una constante con
nombre, simplemente cambie la definición de la constante de100a200en un lugar.
no deja dudas. Incluso si está seguro de que un número nunca cambiará, obtiene un beneficio
de legibilidad si usa una constante con nombre.
está bien, y el 1 en
total = total + 1
está bien Una buena regla general es que los únicos literales que deben aparecer en el cuerpo
de un programa son0y1. Cualquier otro literal debe reemplazarse con algo más descriptivo.
Anticipar errores de división por ceroCada vez que uses el símbolo de división (/ en la
mayoría de los idiomas), piensa si es posible que el denominador de la expresión sea0. Si
existe la posibilidad, escriba código para evitar un error de división por cero.
12.2 Enteros 293
Hacer que las conversiones de tipo sean obviasAsegúrese de que alguien que lea su código lo sepa
cuando se produzca una conversión entre diferentes tipos de datos. En C++ se podría decir
y = x + (flotante) yo
y = x + CSng( yo )
Esta práctica también ayuda a garantizar que la conversión sea la que desea que ocurra. Los
diferentes compiladores realizan diferentes conversiones, por lo que se arriesga de lo contrario.
Referencia cruzadaPara ver una Evite las comparaciones de tipo mixtoSiXes un número de punto flotante yies un entero, la prueba
variación de este ejemplo, consulte
en la Sección 12.3.
si (i = x) entonces...
está casi garantizado que no funcionará. Para cuando el compilador descubra qué tipo quiere
usar para la comparación, convierta uno de los tipos en el otro, haga un montón de redondeos
y determine la respuesta, tendrá suerte si su programa se ejecuta. Realice la conversión
manualmente para que el compilador pueda comparar dos números del mismo tipo y sepa
exactamente qué se está comparando.
Preste atención a las advertencias de su compiladorMuchos compiladores modernos le informan cuando tiene
diferentes tipos numéricos en la misma expresión. ¡Presta atención! A todos los programadores se les ha pedido en
un momento u otro que ayuden a alguien a rastrear un error molesto, solo para descubrir que el compilador había
PUNTO CLAVE
advertido sobre el error todo el tiempo. Los mejores programadores corrigen su código para eliminar todas las
advertencias del compilador. Es más fácil dejar que el compilador haga el trabajo que hacerlo usted mismo.
12.2 Enteros
Tenga en cuenta estas consideraciones cuando utilice números enteros:
Comprueba la división de enterosCuando usa números enteros, 7/10 no es igual a 0,7. Por lo
general, es igual a 0, o menos infinito, o el número entero más cercano, o... te haces una idea. Lo que
equivale varía de un idioma a otro. Esto se aplica igualmente a los resultados intermedios. En el
mundo real 10 * (7/10) = (10*7) / 10 = 7. No así en el mundo de la aritmética de enteros. 10 * (7/10) es
igual a 0 porque la división de enteros (7/10) es igual a 0. La forma más fácil de remediar este
problema es reordenar la expresión para que las divisiones se hagan al final: (10*7) / 10.
ejemplo, si multiplicas 250 * 300, la respuesta correcta es 75,000. Pero si el entero máximo es 65
535, la respuesta que obtendrá probablemente sea 9464 debido al desbordamiento de enteros (75
000 - 65 536 = 9464). La tabla 12-1 muestra los rangos de los tipos de enteros comunes.
La forma más sencilla de evitar el desbordamiento de enteros es pensar en cada uno de los términos
de la expresión aritmética y tratar de imaginar el valor más grande que cada uno puede asumir. Por
ejemplo, si en la expresión enteram = j * k, el mayor valor esperado parajes 200 y el mayor valor
esperado parakes 25, el valor más grande que puede esperar parametroes200
* 25 = 5000. Esto está bien en una máquina de 32 bits ya que el entero más grande es
2,147,483,647. Por otro lado, si el mayor valor esperado parajes 200,000 y el mayor valor
esperado parakes 100.000, el valor más grande que puede esperar parametroes200 000 * 100
000 = 20 000 000 000. Esto no está bien ya que 20,000,000,000 es mayor que
2.147.483.647. En este caso, tendría que usar números enteros de 64 bits o números de
punto flotante para acomodar el mayor valor esperado demetro.
Considere también futuras extensiones del programa. Simetronunca será más grande que 5,000, eso es
genial. pero si esperasmetrocrecer de manera constante durante varios años, tenga eso en cuenta.
Si sus números enteros llegan solo a 2 147 483 647, el resultado intermedio es demasiado grande para el
tipo de datos enteros. En este caso, el resultado intermedio que debe ser1,000,000,000,000es
- 727.379.968, entonces cuando divides por1,000,000, usted obtiene -727, más bien que1,000,000.
Puede manejar el desbordamiento en los resultados intermedios de la misma manera que maneja el
Las siguientes son algunas pautas específicas para el uso de números de coma flotante:
Referencia cruzadaPara Evite sumas y restas en números que tienen magnitudes muy diferentes Con una variable de
libros de algoritmos que
coma flotante de 32 bits, 1.000.000,00 + 0,1 probablemente produzca una respuesta de 1.000.000,00
describen formas de resolver
estos problemas, consulte porque 32 bits no proporcionan suficientes dígitos significativos para abarcar el rango entre
Recursos sobre tipos de datos” en la 1.000.000 y 0,1. Del mismo modo, 5.000.000,02 - 5.000.000,01 es probablemente 0,0.
Sección 10.1.
¿Soluciones? Si tiene que agregar una secuencia de números que contiene grandes diferencias como
esta, ordene los números primero y luego agréguelos comenzando con los valores más pequeños.
Del mismo modo, si necesita sumar una serie infinita, comience con el término más pequeño;
básicamente, sume los términos al revés. Esto no elimina los problemas de redondeo, pero los
minimiza. Muchos libros de algoritmos tienen sugerencias para tratar casos como este.
1 es igual a 2 para valores Evite las comparaciones de igualdadLos números de coma flotante que deberían ser iguales no siempre
suficientemente grandes de 1.
son iguales. El principal problema es que dos caminos diferentes hacia el mismo número no siempre
—Anónimo
conducen al mismo número. Por ejemplo, 0,1 sumado 10 veces rara vez es igual a 1,0. El siguiente ejemplo
muestra dos variables,nominalysuma, que deberían ser iguales pero no lo son.
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
Por lo tanto, es una buena idea encontrar una alternativa al uso de una comparación de igualdad
para números de coma flotante. Un enfoque efectivo es determinar un rango de precisión que sea
aceptable y luego usar una función booleana para determinar si los valores están lo suficientemente
cerca. Por lo general, escribiría unIguales()función que devuelveverdaderosi los valores son lo
suficientemente cercanos yfalsode lo contrario. En Java, una función de este tipo se vería así:
Referencia cruzadaEste ejemplo es Ejemplo Java de una rutina para comparar números de coma flotante
una prueba de la máxima de que hay
final doble ACCEPTABLE_DELTA = 0.00001; booleano
una excepción a cada regla. Las
Equals( doble Término1, doble Término2 ) {
variables en este ejemplo realista
if (Math.abs(Término1 - Término2) <ACEPTABLE_DELTA) {
tienen dígitos en sus nombres. por la
devolver verdadero;
regla contrautilizando dígitos en
}
nombres de variables, consulte la
más {
Sección 11.7, “Tipos de nombres que
devolver falso;
se deben evitar”.
}
}
Anticipar errores de redondeoLos problemas de error de redondeo no son diferentes del problema
de los números con magnitudes muy diferentes. Se trata del mismo problema, y muchas de las
mismas técnicas ayudan a resolver problemas de redondeo. Además, aquí hay soluciones específicas
comunes para problemas de redondeo:
- Cambie a un tipo de variable que tenga mayor precisión. Si está utilizando punto flotante de
precisión simple, cambie a punto flotante de precisión doble, y así sucesivamente.
Referencia cruzadaPor lo - Cambie a variables decimales codificadas en binario (BCD). El esquema BCD suele ser más
general, el impacto en el
lento y ocupa más espacio de almacenamiento, pero evita muchos errores de redondeo. Esto
rendimiento de la conversión a
BCD será mínimo. Si le es particularmente valioso si las variables que está utilizando representan dólares y centavos
preocupa el rendimiento u otras cantidades que deben equilibrarse con precisión.
impacto, consulte la Sección 25.6,
"Resumen del enfoque para el ajuste - Cambiar de punto flotante a variables enteras. Este es un enfoque personalizado para las variables BCD.
de código". Probablemente tendrá que usar números enteros de 64 bits para obtener la precisión que desea. Esta
técnica requiere que usted mismo realice un seguimiento de la parte fraccionaria de sus números. Suponga
que originalmente estaba realizando un seguimiento de los dólares utilizando un punto flotante con
centavos expresados como partes fraccionarias de dólares. Esta es una forma normal de manejar dólares y
centavos. Cuando cambia a números enteros, debe realizar un seguimiento de los centavos usando
números enteros y de los dólares usando múltiplos de 100 centavos.
En otras palabras, multiplicas dólares por 100 y mantienes los centavos en el rango de 0
a 99 de la variable. Esto puede parecer absurdo a primera vista, pero es una solución
efectiva en términos de velocidad y precisión. Puede facilitar estas manipulaciones
creando unDólares Y Centavosclase que oculta la representación de enteros y admite las
operaciones numéricas necesarias.
idiomas.
Referencia cruzadaLos problemas Evite los caracteres mágicos y las cadenasLos caracteres mágicos son caracteres literales (como 'A'
relacionados con el uso de cadenas y
) y las cadenas mágicas son cadenas literales (como"Programa de Contabilidad Gigamática") que
caracteres mágicos son similares a
- Para las cadenas que ocurren comúnmente, como el nombre de su programa, los nombres de los
comandos, los títulos de los informes, etc., es posible que en algún momento necesite cambiar el
contenido de la cadena. Por ejemplo,"Programa de Contabilidad Gigamática"podría cambiar a
"¡Nuevo y mejorado! Programa de contabilidad Gigamatic"para una versión posterior.
298 Capítulo 12: Tipos de datos fundamentales
- Los mercados internacionales se están volviendo cada vez más importantes y es más fácil traducir
cadenas que están agrupadas en un archivo de recursos de cadenas que traducirlas a ellos.en el
lugara lo largo de un programa.
- Los literales de cadena tienden a ocupar mucho espacio. Se utilizan para menús, mensajes, pantallas
de ayuda, formularios de entrada, etc. Si tiene demasiados, crecen sin control y causan problemas de
memoria. El espacio de cadenas no es una preocupación en muchos entornos, pero en la
programación de sistemas integrados y otras aplicaciones en las que el espacio de almacenamiento
es escaso, las soluciones a los problemas de espacio de cadenas son más fáciles de implementar si las
cadenas son relativamente independientes del código fuente.
- Los literales de caracteres y cadenas son crípticos. Los comentarios o las constantes con
nombre aclaran sus intenciones. En el siguiente ejemplo, el significado de0x1Bno está claro el
uso de laESCAPARconstante hace que el significado sea más obvio.
Esté atento a los errores de uno en unoDebido a que las subcadenas se pueden indexar tanto como las matrices,
esté atento a los errores de error que leen o escriben más allá del final de una cadena.
cc2e.com/1285 Sepa cómo su idioma y entorno son compatibles con UnicodeEn algunos lenguajes como
Java, todas las cadenas son Unicode. En otros, como C y C++, el manejo de cadenas Unicode
requiere su propio conjunto de funciones. A menudo se requiere la conversión entre Unicode y
otros conjuntos de caracteres para la comunicación con bibliotecas estándar y de terceros. Si
algunas cadenas no estarán en Unicode (por ejemplo, en C o C++), decida con anticipación si
usará el juego de caracteres Unicode. Si decide usar cadenas Unicode, decida dónde y cuándo
usarlas.
cc2e.com/1292 Si sabe que solo necesita admitir un solo idioma alfabético, considere usar un
conjunto de caracteres ISO 8859Para las aplicaciones que necesitan admitir solo un
único idioma alfabético (como el inglés) y que no necesitan admitir varios idiomas o un
idioma ideográfico (como el chino escrito), el estándar de tipo ASCII extendido ISO 8859
es una buena alternativa a Unicode. .
12.4 Caracteres y cadenas 299
Decidir sobre una estrategia de conversión coherente entre los tipos de cadenaSi usa varios
tipos de cadenas, un enfoque común que ayuda a mantener los tipos de cadenas distintos es
mantener todas las cadenas en un solo formato dentro del programa y convertir las cadenas a otros
formatos lo más cerca posible de las operaciones de entrada y salida.
Cuerdas en C
La clase de cadena de la biblioteca de plantillas estándar de C++ ha eliminado la mayoría de los problemas
tradicionales con las cadenas en C. Para aquellos programadores que trabajan directamente con cadenas de C, aquí
hay algunas formas de evitar errores comunes:
Tenga en cuenta la diferencia entre punteros de cadena y matrices de caracteresEl problema con los
punteros de cadena y las matrices de caracteres surge debido a la forma en que C maneja las cadenas.
Esté alerta a la diferencia entre ellos de dos maneras:
- Sospeche de cualquier expresión que contenga una cadena que involucre un signo igual. Las
operaciones con cadenas en C casi siempre se realizan constrcmp(),strcpy(),strlen()y rutinas
relacionadas. Los signos de igual a menudo implican algún tipo de error de puntero. En C, las
asignaciones no copian literales de cadena a una variable de cadena. Supongamos que tiene una
declaración como
- Utilice una convención de nomenclatura para indicar si las variables son matrices de
caracteres o punteros a cadenas. Una convención común es usarPDcomo prefijo para
indicar unpunteroa uncuerdayachcomo prefijo de unformacióndecaracteres. Aunque no
siempre están equivocados, debe considerar las expresiones que involucran tantoPDy
achprefijos con sospecha.
Declare que las cadenas de estilo C tienen longitudCONSTANTE+1En C y C++, los errores de uno en uno
con cadenas de estilo C son comunes porque es fácil olvidar que una cadena de longitudnorterequierenorte
+ 1bytes de almacenamiento y olvidarse de dejar espacio para el terminador nulo (el byte establecido en 0 al
final de la cadena). Una forma eficaz de evitar este tipo de problemas es utilizar constantes con nombre para
declarar todas las cadenas. Una clave en este enfoque es que usa la constante nombrada de la misma
manera cada vez. Declarar que la cadena tiene longitudCONSTANTE+1y luego usarCONSTANTEpara referirse
a la longitud de una cadena en el resto del código. Aquí hay un ejemplo:
300 Capítulo 12: Tipos de datos fundamentales
...
/* Ejemplo 1: Establecer la cadena en todas las 'A's usando la constante,
NAME_LENGTH, como el número de 'A' que se pueden copiar. Tenga en cuenta que
se utiliza NAME_LENGTH en lugar de NAME_LENGTH + 1. */ for ( i = 0; i <
Operaciones en la cadena NOMBRE_LONGITUD; i++ )
usandoNOMBRE_LONGITUD nombre[ i ] = 'A';
aquí… ...
Si no tiene una convención para manejar esto, a veces declarará que la cadena tiene una
longitudNOMBRE_LONGITUDy tener operaciones en él conNOMBRE_ LONGITUD-1; en otras
ocasiones, declarará que la cadena tiene una longitudNOMBRE_LONGITUD+1y hacer que las
operaciones funcionen con longitudNOMBRE_LONGITUD. Cada vez que use una cadena,
deberá recordar de qué manera la declaró.
Cuando usa cadenas de la misma manera cada vez, no tiene que recordar cómo trató
cada cadena individualmente y elimina los errores causados por olvidar los detalles de
una cadena individual. Tener una convención minimiza la sobrecarga mental y los
errores de programación.
Referencia cruzadaPara obtener Inicialice cadenas a nulo para evitar cadenas interminablesC determina el final de una cadena
más detalles sobre la inicialización de
encontrando un terminador nulo, un byte establecido en 0 al final de la cadena. No importa qué tan
datos, consulte la Sección 10.3,
Puede evitar cadenas interminables de dos maneras. Primero, inicialice las matrices de caracteres para0
cuando los declaras:
Referencia cruzadaPara obtener más Use matrices de caracteres en lugar de punteros en CSi la memoria no es una restricción, ya
información sobre matrices, lea la Sección
menudo no lo es, declare todas sus variables de cadena como matrices de caracteres. Esto ayuda a
12.8, “Matrices”, más adelante en este
capítulo.
evitar problemas con los punteros y el compilador le dará más advertencias cuando haga algo mal.
Referencia cruzadaPara obtener Use variables booleanas para documentar su programaEn lugar de simplemente probar una
detalles sobre el uso de comentarios
expresión booleana, puede asignar la expresión a una variable que hace que la implicación de la
para documentar su programa,
Referencia cruzadaPor un Ejemplo de Java de prueba booleana en la que el propósito no está claro
ejemplo del uso de una función if ( ( índiceelemento < 0 ) || ( MAX_ELEMENTS < índiceelemento ) ||
booleana para documentar su ( índiceelemento == últimoíndiceelemento ) ) {
programa, consulte "Hacer
simples las expresiones ...
complicadas" en la Sección 19.1. }
En el siguiente fragmento, el uso de variables booleanas hace que el propósito de lasiprueba más
clara:
Use variables booleanas para simplificar pruebas complicadasA menudo, cuando tiene que
codificar una prueba complicada, se necesitan varios intentos para hacerlo bien. Cuando más tarde
intente modificar la prueba, puede ser difícil entender qué estaba haciendo la prueba en primer
lugar. Las variables lógicas pueden simplificar la prueba. En el ejemplo anterior, el programa
realmente está probando dos condiciones: si la rutina ha terminado y si está funcionando en una
entrada repetida. Al crear las variables booleanasacabadoyentrada repetida, haces elsiprueba más
simple: más fácil de leer, menos propenso a errores y más fácil de modificar.
302 Capítulo 12: Tipos de datos fundamentales
Terminara si
La prueba en el ejemplo es bastante complicada, pero no es raro que lo sea. Supone una pesada
carga mental para el lector. Mi suposición es que ni siquiera tratarás de entender elsi prueba, pero lo
mirará y dirá: "Lo resolveré más tarde si realmente lo necesito". Preste atención a ese pensamiento
porque eso es exactamente lo mismo que hacen otras personas cuando leen su código y contiene
pruebas como esta.
Aquí hay una reescritura del código con variables booleanas agregadas para simplificar la prueba:
Terminara si
Esta segunda versión es más sencilla. Supongo que leerá la expresión booleana en elsi
prueba sin ninguna dificultad.
Verdadero=1,
Falso=(!Verdadero)
};
12.6 Tipos enumerados 303
Declarar variables comoBOOLEANOmás bien queEn thace que su uso previsto sea más obvio
y hace que su programa sea un poco más autodocumentado.
Color rojo
Color verde
Color azul
Enumeración final
Enumeración final
Los tipos enumerados son una poderosa alternativa a los esquemas desgastados en los que se
dice explícitamente: "1 representa rojo, 2 representa verde, 3 representa azul...". Esta capacidad
sugiere varias pautas para el uso de tipos enumerados:
Use tipos enumerados para mejorar la legibilidadEn lugar de escribir declaraciones como
si es elegidoColor = 1
Cada vez que vea un literal numérico, pregunte si tiene sentido reemplazarlo con un
tipo enumerado.
Los tipos enumerados son especialmente útiles para definir parámetros de rutina. ¿Quién sabe
cuáles son los parámetros de esta llamada de función?
304 Capítulo 12: Tipos de datos fundamentales
Ejemplos en C++ de una llamada de rutina que sería mejor con tipos enumerados
resultado int = RetrievePayrollData (datos, verdadero, falso, falso, verdadero);
CODIFICACIÓN
HORROR
Por el contrario, los parámetros de esta llamada de función son más comprensibles:
Ejemplos de C++ de una llamada de rutina que usa tipos enumerados para mejorar la legibilidad
resultado int = Recuperar datos de nómina (
datos,
Estado de empleo_Empleado actual, Tipo de
nómina_Asalariado,
SavingsPlan_NoDeduction,
MedicalCoverage_IncludeDependents
);
Use tipos enumerados para confiabilidadCon algunos lenguajes (Ada en particular), un tipo
enumerado le permite al compilador realizar una verificación de tipo más exhaustiva que con
valores enteros y constantes. Con constantes con nombre, el compilador no tiene forma de
saber que los únicos valores legales sonColor rojo,Color verde, yColor azul. El compilador no se
opondrá a declaraciones comocolor = País_Inglaterraopaís = Salida_Impresora. Si usa un tipo
enumerado, declarar una variable comoColor, el compilador permitirá que a la variable se le
asignen solo los valoresColor rojo,Color verde, y Color azul.
Usar tipos enumerados para la modificabilidadLos tipos enumerados hacen que su código
sea fácil de modificar. Si descubre una falla en su esquema "1 significa rojo, 2 significa verde, 3
significa azul", debe revisar su código y cambiar todos los1s,2s,3s, y así sucesivamente. Si usa
un tipo enumerado, puede continuar agregando elementos a la lista simplemente
colocándolos en la definición de tipo y recompilando.
Utilice tipos enumerados como alternativa a las variables booleanasA menudo, una
variable booleana no es lo suficientemente rica para expresar los significados que necesita. Por
ejemplo, suponga que tiene un retorno de rutinaverdaderosi ha realizado con éxito su tarea y
Falsode lo contrario. Más tarde puede descubrir que realmente tiene dos tipos deFalso. El
primer tipo significa que la tarea falló y los efectos se limitan a la rutina misma; el segundo tipo
significa que la tarea falló y provocó un error fatal que deberá propagarse al resto del
programa. En este caso, un tipo enumerado con los valoresEstado_Éxito, Estado_Advertencia, y
Estado_FatalErrorsería más útil que un booleano con los valoresverdaderoyfalso. Este esquema
se puede expandir fácilmente para manejar distinciones adicionales en los tipos de éxito o
fracaso.
Defina la primera y la última entrada de una enumeración para usar como límites de
bucleDefinir el primer y último elemento en una enumeración para serColor_primero,
Color_Último, País_primero,País_Último, etc. le permite escribir un ciclo que recorre los
elementos de una enumeración. Configura el tipo enumerado usando valores explícitos, como
se muestra aquí:
Ejemplo de Visual Basic para declarar que el primer valor de una enumeración no es válido
País de enumeración pública
Country_InvalidFirst =0
País_primero = 1
País_China = 1
País_Inglaterra = 2
País_Francia = 3
País_Alemania = 4
País_India = 5
País_Japón = 6
País_Estados Unidos = 7
País_Último = 7
Enumeración final
Tenga cuidado con las trampas de asignar valores explícitos a los elementos de una enumeraciónAlgunos
lenguajes le permiten asignar valores específicos a elementos dentro de una enumeración, como se muestra en
este ejemplo de C++:
Color_InvalidFirst = 0,
Color_primero = 1,
Color_Rojo = 1,
Color verde = 2,
Color azul = 4,
De color negro = 8,
Color_Último = 8
};
Estos tipos enumerados hacen que su programa sea más legible porque puede usar los
miembros de la clase pública comoPaís.InglaterrayPantalla de salidaen lugar de constantes
con nombre. Este método particular de crear tipos enumerados también es seguro para tipos;
debido a que cada tipo se declara como una clase, el compilador buscará asignaciones no
válidas comoSalida salida = País.Inglaterra(Bloch 2001).
En idiomas que no admiten clases, puede lograr el mismo efecto básico mediante el uso
disciplinado de variables globales para cada uno de los elementos de la enumeración.
El uso de una constante con nombre es una forma de "parametrizar" su programa, colocando un
aspecto de su programa que podría cambiar en un parámetro que puede cambiar en un lugar en
lugar de tener que hacer cambios a lo largo del programa. Si alguna vez ha declarado que una matriz
es tan grande como cree que tendrá que ser y luego se quedó sin espacio porque no era lo
suficientemente grande, puede apreciar el valor de las constantes con nombre.
308 Capítulo 12: Tipos de datos fundamentales
Cuando cambia el tamaño de una matriz, solo cambia la definición de la constante que utilizó para declarar
la matriz. Este "control de punto único" contribuye en gran medida a hacer que el software sea
verdaderamente "suave": fácil de trabajar y cambiar.
Usar constantes con nombre en declaraciones de datosEl uso de constantes con nombre ayuda a la
legibilidad y mantenimiento del programa en declaraciones de datos y en declaraciones que necesitan
saber el tamaño de los datos con los que están trabajando. En el siguiente ejemplo, se utiliza
LOCAL_NUMBER_LENGTHpara describir la longitud de los números de teléfono de los empleados en lugar
del literal 7.
Buen ejemplo de Visual Basic del uso de una constante con nombre en una declaración de datos
Constante AREA_CODE_LENGTH = 3
LOCAL_NUMBER_LENGTH Constante LOCAL_NUMBER_LENGTH = 7 . . .
se declara como una constante
' asegúrese de que todos los caracteres en el número de teléfono sean dígitos
También se usa aquí. Para iDigit = 1 Para LOCAL_NUMBER_LENGTH
Si ( phoneNumber.localNumber( iDigit ) < "0" ) O _
( "9" < phoneNumber.localNumber( iDigit ) ) Luego ' realice un
procesamiento de errores
...
Este es un ejemplo simple, pero probablemente puedas imaginar un programa en el que la información
sobre la longitud del número de teléfono sea necesaria en muchos lugares.
En el momento de crear el programa, todos los empleados viven en un país, por lo que solo
necesita siete dígitos para sus números de teléfono. A medida que la empresa se expande y se
establecen sucursales en diferentes países, necesitará números de teléfono más largos. Si ha
parametrizado, puede realizar el cambio en un solo lugar: en la definición de la constante
nombradaLOCAL_NUMBER_LENGTH.
Otras lecturasPara obtener más Como era de esperar, se ha demostrado que el uso de constantes con nombre ayuda mucho al
detalles sobre el valor del control de
mantenimiento del programa. Como regla general, cualquier técnica que centralice el control sobre las
un solo punto, consulte las páginas
57 a 60 deConflicto de software(
cosas que pueden cambiar es una buena técnica para reducir los esfuerzos de mantenimiento (Glass 1991).
Vidrio 1991).
Evite los literales, incluso los "seguros".En el siguiente bucle, ¿qué crees que12
representa?
Debido a la naturaleza específica del código, parece que el código probablemente está
recorriendo los 12 meses del año. Pero eres tuPor supuesto? ¿Apostarías tu colección de
Monty Python a ello?
En este caso, no necesita usar una constante con nombre para respaldar la flexibilidad futura:
no es muy probable que la cantidad de meses en un año cambie pronto. Pero si la forma en
que está escrito el código deja alguna sombra de duda sobre su propósito, aclárelo con una
constante bien nombrada, como esta:
Esto es mejor, pero, para completar el ejemplo, el índice del bucle también debería llamarse
algo más informativo:
Este ejemplo parece bastante bueno, pero podemos llevarlo un paso más allá usando un tipo
enumerado:
Con este ejemplo final, no puede haber dudas sobre el propósito del ciclo. Incluso si cree que
un literal es seguro, use constantes con nombre en su lugar. Sea un fanático de eliminar
literales en su código. Utilice un editor de texto para buscar2,3,4,5,6,7,8, y9para asegurarse de
que no los ha usado accidentalmente.
Referencia cruzadaPara obtener Simule constantes con nombre con variables o clases de ámbito adecuadoSi su idioma no admite
detalles sobre la simulación de
constantes con nombre, puede crear las suyas propias. Al utilizar un enfoque similar al enfoque
tipos enumerados, consulte "Si su
Usar constantes nombradas consistentementeEs peligroso usar una constante con nombre
en un lugar y un literal en otro para representar la misma entidad. Algunas prácticas de
programación piden errores; este es como llamar a un número 800 y recibir errores
310 Capítulo 12: Tipos de datos fundamentales
12.8 Arreglos
Las matrices son el tipo de datos estructurados más simple y común. En algunos idiomas, las matrices son el
único tipo de datos estructurados. Una matriz contiene un grupo de elementos que son todos del mismo
tipo y a los que se accede directamente mediante el uso de un índice de matriz. Aquí hay algunos consejos
sobre el uso de matrices.
Asegúrese de que todos los índices de la matriz estén dentro de los límites de la matriz.De una forma u
otra, todos los problemas con las matrices se deben al hecho de que se puede acceder a los elementos de la
matriz aleatoriamente. El problema más común surge cuando un programa intenta acceder a un elemento
PUNTO CLAVE
de matriz que está fuera de los límites. En algunos idiomas, esto produce un error; en otros, simplemente
produce resultados extraños e inesperados.
Considere usar contenedores en lugar de arreglos, o piense en los arreglos como estructuras
secuencialesAlgunas de las personas más brillantes en ciencias de la computación han sugerido que nunca
se debe acceder a los arreglos al azar, sino solo secuencialmente (Mills y Linger 1986). Su argumento es que
los accesos aleatorios en matrices son similares a los accesos aleatoriosirs en un programa: dichos accesos
tienden a ser indisciplinados, propensos a errores y difíciles de demostrar que son correctos. Sugieren usar
conjuntos, pilas y colas, a cuyos elementos se accede secuencialmente, en lugar de usar arreglos.
3 En un pequeño experimento, Mills y Linger encontraron que los diseños creados de esta manera resultaron
2
1
en menos variables y menos referencias de variables. Los diseños fueron relativamente eficientes y
DATOS DUROS
condujeron a un software altamente confiable.
Considere usar clases contenedoras a las que pueda acceder secuencialmente (conjuntos,
pilas, colas, etc.) como alternativas antes de elegir automáticamente una matriz.
Referencia cruzadaLos problemas relacionados Comprobar los puntos finales de las matricesAsí como es útil pensar en los puntos finales en una
con el uso de matrices y bucles son similares y
estructura de bucle, puede detectar muchos errores al verificar los puntos finales de las matrices.
están relacionados. Para obtener detalles sobre
Cuidado con el índice de diafoníaSi usa bucles anidados, es fácil escribirmatriz[ j ] cuando
quieres decirmatriz[ yo ]. El cambio de índices de bucle se denomina “diafonía de índices”.
Verifique este problema. Mejor aún, use nombres de índice más significativos queiyjpara que
sea más difícil cometer errores de diafonía en primer lugar.
Cuando usa operaciones en una matriz, en lugar de usar una constante con nombre para el límite
superior del tamaño de la matriz, use elARRAY_LENGTH()macro. Aquí hay un ejemplo:
Esta técnica es particularmente útil para arreglos adimensionales como el de este ejemplo. Si
agrega o resta entradas, no tiene que acordarse de cambiar una constante con nombre que
describe el tamaño de la matriz. Por supuesto, la técnica también funciona con matrices
dimensionadas, pero si usa este enfoque, no siempre necesita configurar una constante
adicional con nombre para la definición de la matriz.
Referencia cruzadaEn muchos casos, Para apreciar el poder de la creación de tipos, suponga que está escribiendo un programa para
es mejor crear una clase que crear un
convertir coordenadas en unX,y,zsistema a latitud, longitud y elevación. Usted piensa que podrían
tipo de datos simple. Para obtener
Esta definición de tipo declara un nuevo tipo,Coordinar, eso es funcionalmente lo mismo que
el tipoflotar. Para usar el nuevo tipo, declara variables con él tal como lo haría con un tipo
predefinido comoflotar. Aquí hay un ejemplo:
Rutina2( ... ) {
Coordenada x; // coordenada x en metros //
Coordenada y; coordenada y en metros //
Coordenada z; coordenada z en metros
...
}
Ahora suponga que el programa cambia y descubre que, después de todo, necesita usar variables de
doble precisión para las coordenadas. Debido a que definió un tipo específicamente para datos de
coordenadas, todo lo que tiene que cambiar es la definición del tipo. Y tienes que cambiarlo en un
solo lugar: en eldefinición de tipodeclaración. Aquí está la definición de tipo modificada:
He aquí un segundo ejemplo, éste en Pascal. Suponga que está creando un sistema de nómina en el
que los nombres de los empleados tienen un máximo de 30 caracteres. Tus usuarios te han dicho
que nadiealguna veztiene un nombre de más de 30 caracteres. ¿Codificas el número?30a lo largo de
su programa? Si lo hace, ¡confía en sus usuarios mucho más de lo que yo confío en los míos! Un
mejor enfoque es definir un tipo para los nombres de los empleados:
Cuando se trata de una cadena o una matriz, generalmente es conveniente definir una constante con
nombre que indique la longitud de la cadena o matriz y luego usar la constante con nombre en la
definición de tipo. Encontrará muchos lugares en su programa en los que usar la constante; este es
solo el primer lugar en el que la usará. Así es como se ve:
Un ejemplo más poderoso combinaría la idea de crear sus propios tipos con la
idea de ocultar información. En algunos casos, la información que desea ocultar es
información sobre el tipo de datos.
Otros lenguajes, como Ada, van un paso más allá y admiten la ocultación de información
literal. Así es como elCoordinarel fragmento de código se vería en un paquete de Ada que lo
declara:
V413HAV
314 Capítulo 12: Tipos de datos fundamentales
Estos ejemplos han ilustrado varias razones para crear sus propios tipos:
- Para evitar la distribución excesiva de informaciónLa escritura dura distribuye los detalles
de escritura de datos en su programa en lugar de centralizarlos en un solo lugar. Este es un
ejemplo del principio de ocultación de información de centralización discutido en la Sección
6.2.
- Para aumentar la confiabilidadEn Ada, puede definir tipos comotipo Edad es rango 0..99. Luego, el
compilador genera verificaciones en tiempo de ejecución para verificar que cualquier variable de tipo
Añossiempre está dentro del rango0..99.
¿Por qué los ejemplos de creación de sus propios tipos están en Pascal y Ada?
Pascal y Ada han seguido el camino del estegosaurio y, en general, los lenguajes que los han
reemplazado son más usables. Sin embargo, en el área de las definiciones de tipos simples,
creo que C++, Java y Visual Basic representan un caso de tres pasos hacia adelante y uno hacia
atrás. Una declaración de Ada como
temperatura interna;
permite al compilador asegurarse de quetemperatura actualse asigna sólo a otras variables con elLa
temperaturatipo, y se requiere muy poca codificación adicional para proporcionar ese margen de
seguridad adicional.
Por supuesto, un programador podría crear unLa temperaturaclass para aplicar la misma
semántica que aplicaba automáticamente el lenguaje Ada, pero el paso de crear un tipo
de datos simple en una línea de código a crear una clase es un gran paso. En muchas
situaciones, un programador crearía el tipo simple pero no haría el esfuerzo adicional de
crear una clase.
Tenga cuidado al crear nombres de tipo que hagan referencia a tipos predefinidos. Escribe nombres
comoEntero grandeocadena largareferirse a los datos de la computadora en lugar del problema del
mundo real. La gran ventaja de crear su propio tipo es que proporciona una capa de aislamiento
entre su programa y el lenguaje de implementación. Los nombres de tipo que se refieren a los tipos
de lenguaje de programación subyacentes hacen agujeros en ese aislamiento. No le dan mucha
ventaja sobre el uso de un tipo predefinido. Los nombres orientados a problemas, por otro lado, le
ofrecen fácil modificabilidad y declaraciones de datos que se autodocumentan.
Evitar tipos predefinidosSi existe alguna posibilidad de que un tipo pueda cambiar, evite usar tipos
predefinidos en cualquier lugar menos endefinición de tipooescribedefiniciones Es fácil crear nuevos
tipos orientados funcionalmente y es difícil cambiar datos en un programa que usa tipos cableados.
Además, el uso de declaraciones de tipo orientadas funcionalmente documenta parcialmente las
variables declaradas con ellas. Una declaración comocoordenada xte dice mucho más sobreXque una
declaración comoflotar x. Utilice sus propios tipos tanto como pueda.
Definir tipos de sustitución para la portabilidadEn contraste con el consejo de que no cambie la
definición de un tipo estándar, es posible que desee definir sustitutos para los tipos estándar para
que en diferentes plataformas de hardware pueda hacer que las variables representen exactamente
las mismas entidades. Por ejemplo, puede definir un tipoINT32y utilízalo
316 Capítulo 12: Tipos de datos fundamentales
en vez deEn t, o un tipoLARGO64en vez delargo. Originalmente, la única diferencia entre los
dos tipos sería su capitalización. Pero cuando movió el programa a una nueva plataforma de
hardware, podría redefinir las versiones en mayúsculas para que pudieran coincidir con los
tipos de datos en el hardware original.
Asegúrese de no definir tipos que se confundan fácilmente con tipos predefinidos. Sería
posible definirEN Tmás bien queINT32, pero es mejor que cree una distinción clara entre
los tipos que define y los tipos proporcionados por el idioma.
Considere la posibilidad de crear una clase en lugar de utilizar undefinición de tipoSimpledefiniciones de tipo
puede contribuir en gran medida a ocultar información sobre el tipo subyacente de una variable. En algunos casos,
sin embargo, es posible que desee la flexibilidad y el control adicionales que obtendrá al crear una clase. Para más
detalles, consulte el Capítulo 6, “Clases trabajadoras”.
problemas con tipos específicos de datos, - ¿El código anticipa errores de división por cero?
consulte la lista de verificación en la página
enteros
- ¿Las expresiones que usan la división de enteros funcionan de la forma en que deben hacerlo?
Caracteres y cadenas
- ¿El código evita cadenas y caracteres mágicos?
- ¿El código C trata los punteros de cadena y las matrices de caracteres de manera diferente?
- ¿El código C sigue la convención de declarar que las cadenas tienen una longitud
CONSTANTE+1?
Variables booleanas
- ¿El programa utiliza variables booleanas adicionales para documentar las pruebas
condicionales?
Tipos enumerados
- ¿El programa utiliza tipos enumerados en lugar de constantes con nombre para
mejorar su legibilidad, confiabilidad y modificabilidad?
- ¿El programa utiliza tipos enumerados en lugar de variables booleanas cuando el uso
de una variable no se puede capturar completamente converdaderoyfalso?
- ¿La primera entrada en un tipo enumerado está reservada para "no válido"?
- ¿El programa utiliza constantes con nombre para declaraciones de datos y límites de
bucle en lugar de números mágicos?
- ¿Se han usado constantemente constantes con nombre, no como constantes con
nombre en algunos lugares y como literales en otros?
arreglos
- En los bucles anidados, ¿se usa la variable correcta como el subíndice de la matriz, evitando
la diafonía del índice de bucle?
318 Capítulo 12: Tipos de datos fundamentales
Creación de tipos
- ¿El programa utiliza un tipo diferente para cada tipo de datos que podrían
cambiar?
- ¿Los nombres de tipos están orientados hacia las entidades del mundo real que representan los
tipos en lugar de hacia los tipos de lenguaje de programación?
- ¿Los nombres de los tipos son lo suficientemente descriptivos para ayudar a documentar las declaraciones de datos?
Puntos clave
- Trabajar con tipos de datos específicos significa recordar muchas reglas individuales para cada tipo.
Utilice la lista de verificación de este capítulo para asegurarse de haber considerado los problemas
comunes.
- La creación de sus propios tipos hace que sus programas sean más fáciles de modificar y más
autodocumentados, si su idioma es compatible con esa capacidad.
Temas relacionados
Algunos lenguajes admiten tipos exóticos de datos además de los tipos de datos que se
analizan en el Capítulo 12, “Tipos de datos fundamentales”. La Sección 13.1 describe cuándo
podría seguir usando estructuras en lugar de clases en algunas circunstancias. La sección 13.2
describe los entresijos del uso de punteros. Si alguna vez ha tenido problemas asociados con
el uso de datos globales, la Sección 13.3 explica cómo evitar tales dificultades. Si cree que los
tipos de datos descritos en este capítulo no son los tipos que normalmente lee en los libros
modernos de programación orientada a objetos, tiene razón. Por eso el capítulo se llama “
InusualTipos de datos."
13.1 Estructuras
El término "estructura" se refiere a los datos que se construyen a partir de otros tipos. Debido a que las
matrices son un caso especial, se tratan por separado en el Capítulo 12. Esta sección trata de los datos
estructurados creados por el usuario:estructuras en C y C++ yEstructurasen Microsoft Visual Basic. En Java y
C++, las clases a veces también se comportan como estructuras (cuando la clase consta completamente de
miembros de datos públicos sin rutinas públicas).
Por lo general, querrá crear clases en lugar de estructuras para poder aprovechar la privacidad
y la funcionalidad que ofrecen las clases además de los datos públicos compatibles con las
estructuras. Pero a veces puede ser útil manipular directamente bloques de datos, así que aquí
hay algunas razones para usar estructuras:
319
320 Capítulo 13: Tipos de datos inusuales
Usar estructuras para aclarar las relaciones de datosLas estructuras agrupan grupos de
elementos relacionados. A veces, la parte más difícil de descifrar un programa es averiguar qué datos
van con qué otros datos. Es como ir a un pueblo pequeño y preguntar quién es pariente de quién.
Llegas a descubrir que todo el mundo está relacionado con todo el mundo, pero no realmente, y
nunca obtienes una buena respuesta.
Si los datos se han estructurado cuidadosamente, averiguar qué va con qué es mucho más fácil.
Aquí hay un ejemplo de datos que no han sido estructurados:
Debido a que estos datos no están estructurados, parece como si todas las instrucciones de
asignación estuvieran juntas. Realmente,nombre,Dirección, yteléfonoson variables asociadas con
empleados individuales, ytítulo,Departamento, yprimason variables asociadas a un supervisor. El
fragmento de código no proporciona ningún indicio de que haya dos tipos de datos en
funcionamiento. En el siguiente fragmento de código, el uso de estructuras aclara las relaciones:
En el código que usa variables estructuradas, está claro que algunos de los datos están asociados
con un empleado, otros datos con un supervisor.
Suponga que tiene un grupo de elementos de datos que pertenecen juntos, por ejemplo, datos sobre un
empleado en una base de datos de personal. Si los datos no se combinan en una estructura, simplemente
copiar el grupo de datos puede implicar muchas declaraciones. Aquí hay un ejemplo en Visual Basic:
13.1 Estructuras 321
Cada vez que desee transferir información sobre un empleado, debe tener todo este grupo de
declaraciones. Si alguna vez agrega una nueva pieza de información del empleado, por
ejemplo,númRetenciones—tienes que encontrar cada lugar en el que tienes un bloque de
tareas y agregar una tarea paranewNumWithholdings = oldNumWithholdings.
Imagínese lo horrible que sería intercambiar datos entre dos empleados. No tienes
que usar tu imaginación, aquí está:
antiguoSsn = nuevoSsn
viejoGénero = nuevoGénero
antiguosalario = nuevoSalario
nuevoNombre = anteriorAntiguoNombre
nueva direccion = dirección anterior anterior
teléfono nuevo = anteriorTeléfono antiguo
nuevoSsn = anteriorOldSsn
nuevoGénero = anteriorAntiguoGénero
nuevoSalario = anteriorAntiguoSalario
Una forma más fácil de abordar el problema es declarar una variable estructurada:
Estructura final
Dim newEmployee como empleado
Dim oldEmployee como empleado
Dim anteriorOldEmployee como empleado
Ahora puede cambiar todos los elementos en las estructuras de empleados antiguas y nuevas con tres
declaraciones:
Ejemplo de Visual Basic de una forma más fácil de intercambiar dos grupos de datos
anteriorAntiguoEmpleado = antiguo empleado
viejoempleado = nuevo empleado
nuevo empleado = anteriorAntiguoEmpleado
Referencia cruzadaPara obtener detalles Utilice estructuras para simplificar las listas de parámetrosPuede simplificar las listas de parámetros de
sobre la cantidad de datos que se deben
rutina mediante el uso de variables estructuradas. La técnica es similar a la que se acaba de mostrar. En
compartir entre las rutinas, consulte
Y este es un ejemplo de la manera fácil de llamar a una rutina usando una variable estructurada que
contiene los elementos de la primera lista de parámetros:
Ejemplo de Visual Basic de una llamada de rutina elegante con una estructura
EasyWayRoutine (empleado)
Referencia cruzadaPara obtener detalles Puede llevar esta técnica al extremo, poniendo todas las variables en su programa en una variable
sobre los peligros de pasar demasiados
grande y jugosa y luego pasándola a todas partes. Los programadores cuidadosos evitan agrupar
datos, consulte "Mantenga el
Utilizar estructuras para reducir el mantenimientoDebido a que agrupa datos relacionados cuando usa
estructuras, cambiar una estructura requiere menos cambios a lo largo de un programa. Esto es
especialmente cierto en secciones de código que no están relacionadas lógicamente con el cambio en la
estructura. Dado que los cambios tienden a producir errores, menos cambios significan menos errores. Si tu
Empleadoestructura tiene untítuloy decide eliminarlo, no necesita cambiar ninguna de las listas de
parámetros o declaraciones de asignación que usan la estructura completa. Por supuesto, debe cambiar
cualquier código que se ocupe específicamente de los títulos de los empleados, pero eso está relacionado
conceptualmente con la eliminación deltítulocampo y es difícil pasarlo por alto.
La gran ventaja de estructurar los datos se encuentra en secciones de código que no guardan una
relación lógica con eltítulocampo. A veces, los programas tienen declaraciones que se refieren
conceptualmente a una colección de datos en lugar de componentes individuales. En tales casos, los
componentes individuales, como eltítulose hace referencia a ellos simplemente porque son parte de
la colección. Tales secciones de código no tienen ninguna razón lógica para trabajar con eltítulo
campo específicamente, y esas secciones son fáciles de pasar por alto cuando cambia título. Si usa
una estructura, está bien pasar por alto dichas secciones porque el código se refiere a la recopilación
de datos relacionados en lugar de a cada componente individualmente.
13.2 Punteros
El uso de punteros es una de las áreas más propensas a errores de la programación moderna, hasta
el punto de que los lenguajes modernos como Java, C# y Visual Basic no proporcionan un tipo de
datos de puntero. El uso de punteros es intrínsecamente complicado, y usarlos correctamente
PUNTO CLAVE
requiere que tenga una excelente comprensión del esquema de administración de memoria de su
compilador. Muchos problemas comunes de seguridad, especialmente los desbordamientos del
búfer, pueden atribuirse al uso erróneo de punteros (Howard y LeBlanc 2003).
Incluso si su lenguaje no requiere que use punteros, una buena comprensión de los
punteros lo ayudará a comprender cómo funciona su lenguaje de programación. Una
dosis liberal de prácticas de programación defensiva ayudará aún más.
Ubicación en la memoria
El tipo base del puntero proporciona el conocimiento de cómo interpretar el contenido de una ubicación en
la memoria. Si un puntero apunta a un número entero, lo que realmente significa es que el compilador
interpreta la ubicación de memoria dada por el puntero como un número entero. Por supuesto, puede
tener un puntero de número entero, un puntero de cadena y un puntero de punto flotante, todos
apuntando a la misma ubicación de memoria. Pero solo uno de los punteros interpreta correctamente el
contenido en esa ubicación.
La figura 13-1 muestra varias vistas de la misma ubicación en la memoria, interpretadas de varias maneras
diferentes.
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
Visto como: Contenido de la memoria sin procesar utilizado para más ejemplos (en hexadecimal)
Interpretado como: No es posible la interpretación sin la variable de puntero asociada
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
Visto como: String[10] (en formato Visual Basic con el byte de longitud primero)
Interpretado como: abcdefghij
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco
Figura 13-1 La cantidad de memoria utilizada por cada tipo de datos se muestra mediante líneas dobles.
En cada uno de los casos de la Figura 13-1, el puntero apunta a la ubicación que contiene el valor
hexadecimal0x0A. El número de bytes utilizados más allá del0Adepende de cómo se interprete la memoria.
La forma en que se utilizan los contenidos de la memoria también depende de cómo se utilice la memoria.
13.2 Punteros 325
interpretado. (También depende del procesador que esté utilizando, así que tenga esto en cuenta si intenta
duplicar estos resultados en su Desktop Cray). El mismo contenido de memoria sin procesar se puede
interpretar como una cadena, un número entero, un punto flotante o cualquier otra cosa, todo depende del
tipo base del puntero que apunta a la memoria.
Con muchos tipos de defectos, localizar el error es la parte más fácil de manejar el error y corregirlo es la
parte difícil. Los errores de puntero son diferentes. Un error de puntero suele ser el resultado de que un
puntero apunta a un lugar al que no debería apuntar. Cuando asigna un valor a una variable de puntero
incorrecta, escribe datos en un área de la memoria que no debería. Esto se llama "corrupción de la
memoria". A veces, la corrupción de la memoria produce fallas horribles y ardientes del sistema; a veces
altera los resultados de un cálculo en otra parte del programa; a veces hace que su programa se salte rutinas
de forma impredecible; y a veces no hace nada en absoluto. En el último caso, el error del puntero es una
bomba de tiempo que espera arruinar su programa cinco minutos antes de mostrárselo a su cliente más
importante. Los síntomas de los errores de puntero tienden a no estar relacionados con las causas de los
errores de puntero. Por lo tanto, la mayor parte del trabajo para corregir un error de puntero consiste en
localizar la causa.
Trabajar con punteros con éxito requiere una estrategia doble. Primero, evite instalar errores de puntero en
primer lugar. Los errores de puntero son tan difíciles de encontrar que se justifican medidas preventivas
adicionales. En segundo lugar, detecte los errores de puntero tan pronto como sea posible después de
PUNTO CLAVE
codificarlos. Los síntomas de los errores de puntero son tan erráticos que se justifican medidas adicionales
para hacerlos más predecibles. Aquí le mostramos cómo lograr estos objetivos clave:
Aislar operaciones de puntero en rutinas o clasesSuponga que utiliza una lista enlazada en varios lugares
de un programa. En lugar de recorrer la lista manualmente en cada lugar que se usa, escriba rutinas de
acceso comoSiguienteEnlace(),Enlace anterior(),Insertar el link(), yEliminarEnlace(). Al minimizar la cantidad
de lugares en los que se accede a los punteros, minimiza la posibilidad de cometer errores por descuido que
se esparcen por todo el programa y tardan una eternidad en encontrarlos. Debido a que el código es
relativamente independiente de los detalles de implementación de datos, también aumenta la posibilidad
de que pueda reutilizarlo en otros programas. Escribir rutinas para la asignación de punteros es otra forma
de centralizar el control sobre sus datos.
Declarar y definir punteros al mismo tiempoAsignar a una variable su valor inicial cerca de
donde se declara es generalmente una buena práctica de programación, y es aún más
valiosa cuando se trabaja con punteros. Aquí hay un ejemplo de lo que no se debe hacer:
Verifique los punteros antes de usarlosAntes de usar un puntero en una parte crítica de su
programa, asegúrese de que la ubicación de memoria a la que apunta sea razonable. Por ejemplo, si
espera que las ubicaciones de memoria estén entredatos de inicioyEndData, debería tener una visión
sospechosa de un puntero que apunta antesdatos de inicioo despuésEndData. Tendrá que
determinar cuáles son los valores dedatos de inicioyEndDataestán en su entorno. Puede configurar
esto para que funcione automáticamente si usa punteros a través de rutinas de acceso en lugar de
manipularlos directamente.
Verifique la variable a la que hace referencia el puntero antes de usarlaA veces, puede
realizar comprobaciones de razonabilidad en el valor al que apunta el puntero. Por ejemplo, si
se supone que debe apuntar a un valor entero entre 0 y 1000, debe sospechar de valores
superiores a 1000. Si está apuntando a una cadena de estilo C++, puede sospechar de cadenas
con longitudes mayores de 100. Esto también se puede hacer automáticamente si trabaja con
punteros a través de rutinas de acceso.
Use campos de etiquetas de identificación para verificar si hay memoria dañadaUn "campo de etiqueta" o
"etiqueta de perro" es un campo que agrega a una estructura únicamente con el propósito de verificar errores.
Cuando asigne una variable, coloque un valor que debe permanecer sin cambios en su campo de etiqueta. Cuando
utilice la estructura, especialmente cuando elimine la memoria, verifique el valor del campo de la etiqueta. Si el
campo de la etiqueta no tiene el valor esperado, los datos se han dañado.
Cuando elimine el puntero, dañe el campo para que, si accidentalmente intenta liberar el
mismo puntero nuevamente, detecte la corrupción. Por ejemplo, supongamos que necesita
asignar 100 bytes:
104 bytes
13.2 Punteros 327
2.Establezca los primeros 4 bytes en un valor de etiqueta de perro y luego devuelva un puntero a la memoria que
etiqueta
etiqueta
4.Si la etiqueta está bien, configúrela en0o algún otro valor que usted y su programa reconozcan
como un valor de etiqueta no válido. No desea que el valor se confunda con una etiqueta
válida después de que se haya liberado la memoria. Establezca los datos para0,0xCC, o algún
otro valor no aleatorio por la misma razón.
Poner una placa de identificación al comienzo del bloque de memoria que ha asignado le permite
comprobar si hay intentos redundantes de desasignar el bloque de memoria sin necesidad de
mantener una lista de todos los bloques de memoria que ha asignado. Poner la placa de
identificación al final del bloque de memoria le permite verificar si se sobrescribe la memoria más
allá de la ubicación que se suponía que debía usarse. Puede usar etiquetas al principio y al final del
bloque para lograr ambos objetivos.
Puede usar este enfoque junto con la verificación de razonabilidad sugerida anteriormente:
verificar que los punteros estén entredatos de inicioyEndData. Para asegurarse de que un
puntero apunte a una ubicación razonable, en lugar de verificar un rango probable de
memoria, verifique que el puntero esté en la lista de punteros asignados.
Puede verificar el campo de etiqueta solo una vez antes de eliminar la variable. Una etiqueta
dañada le diría que en algún momento durante la vida de esa variable, su contenido se
corrompió. Sin embargo, cuanto más a menudo revise el campo de la etiqueta, más cerca de la
raíz del problema detectará la corrupción.
Use variables de puntero adicionales para mayor claridadPor todos los medios, no escatime en variables
de puntero. El punto se señala en otra parte que una variable no debe usarse para más de un propósito.
Esto es especialmente cierto para las variables de puntero. Ya es bastante difícil darse cuenta
328 Capítulo 13: Tipos de datos inusuales
lo que alguien está haciendo con una lista enlazada sin tener que averiguar por qué genericLink
variable se usa una y otra vez o quépuntero->siguiente->último->siguienteestá apuntando a.
Considere este fragmento de código:
nodoactual insertarNodo
Aquí hay un código que hace referencia explícita a los tres objetos involucrados:
Este fragmento de código tiene una línea adicional de código, pero sin el primer fragmento.nodo-actual-
>siguiente->anterior, es más fácil de seguir.
Con esta simplificación, no solo obtiene una mejora en la legibilidad, sino que también puede
obtener un aumento en el rendimiento al simplificar la operación del puntero dentro del bucle.
Como de costumbre, tendría que medir el beneficio de rendimiento antes de apostar dinero en él.
Hacer un dibujoLas descripciones de código de los punteros pueden resultar confusas. Por lo general,
ayuda hacer un dibujo. Por ejemplo, una imagen del problema de inserción de la lista enlazada podría
parecerse a la que se muestra en la figura 13-2.
nuevoNodoMedio->anterior nuevoNodoMedio->siguiente
nuevoNodoMedio
Figura 13-2Un ejemplo de una imagen que nos ayuda a pensar en los pasos necesarios para volver a
vincular punteros.
330 Capítulo 13: Tipos de datos inusuales
Otras lecturasPara una excelente tritura tu basuraLos errores de puntero son difíciles de depurar porque el punto en el que la
discusión sobre enfoques seguros
memoria a la que apunta el puntero deja de ser válido no es determinista. A veces, el contenido de la
para el manejo
punteros en C, verEscribir memoria parecerá válido mucho después de que se libere el puntero. Otras veces, la memoria
código sólido(Maguire 1993). cambiará de inmediato.
En C, puede forzar errores relacionados con el uso de punteros desasignados para que sean más
consistentes sobrescribiendo bloques de memoria con datos no deseados justo antes de desasignarlos.
Como con muchas otras operaciones, puede hacer esto automáticamente si usa rutinas de acceso. En C,
cada vez que elimina un puntero, podría usar un código como este:
Por supuesto, esta técnica no funcionará en C++, donde el puntero apunta a un objeto, y
requiere que implemente una rutina Establecer contenido en basura para cada objeto.
Establecer punteros en nulo después de eliminarlos o liberarlosUn tipo común de error de puntero es el
"puntero colgante", el uso de un puntero que ha sidoEliminar'd olibre'd. Una de las razones por las que los
errores de puntero son difíciles de detectar es que a veces el error no produce ningún síntoma. Al configurar
los punteros como nulos después de liberarlos, no cambia el hecho de que puede leer los datos a los que
apunta un puntero colgante. Pero se asegura de que escribir datos en un puntero colgante produzca un
error. Probablemente será un error feo, desagradable y desastroso, pero al menos lo encontrará en lugar de
que lo encuentre otra persona.
El código que precede alEliminarLa operación en el ejemplo anterior podría aumentarse para
manejar esto también:
Compruebe si hay punteros incorrectos antes de eliminar una variableUna de las mejores maneras de
arruinar un programa esEliminar()olibre()un puntero después de que ya ha sidoEliminar'd olibre'd.
Desafortunadamente, pocos idiomas detectan este tipo de problema.
Establecer punteros liberados en nulo también le permite verificar si un puntero está configurado en
nulo antes de usarlo o intentar eliminarlo nuevamente; si no configura los punteros liberados como
nulos, no tendrá esa opción. Eso sugiere otra adición al código de eliminación del puntero:
Realizar un seguimiento de las asignaciones de punterosMantenga una lista de los punteros que ha
asignado. Esto le permite verificar si un puntero está en la lista antes de deshacerse de él. Aquí hay un
ejemplo de cómo el código de eliminación de puntero estándar podría modificarse para incluir eso:
Escriba rutinas de cobertura para centralizar su estrategia para evitar problemas de puntero
Como puede ver en este ejemplo, puede terminar con una gran cantidad de código extra cada vez
que un puntero esnuevo'd oEliminar'd. Algunas de las técnicas descritas en esta sección son
mutuamente excluyentes o redundantes, y no querrá tener varias estrategias conflictivas en uso en la
misma base de código. Por ejemplo, no necesita crear y verificar valores de etiquetas de identificación
si está manteniendo su propia lista de punteros válidos.
- SEGURO_NUEVOEsta rutina llama a new para asignar el puntero, agrega el nuevo puntero a una lista
de punteros asignados y devuelve el puntero recién asignado a la rutina de llamada. También se
puede verificar si hay una excepción o un retorno nulo de nuevo (también conocido como un error
de "memoria insuficiente") solo en este lugar, lo que simplifica el procesamiento de errores en otras
partes de su programa.
332 Capítulo 13: Tipos de datos inusuales
Referencia cruzadaPara obtener detalles En C++, esta rutina eliminará los punteros individuales, pero también deberá implementar un
sobre la planificación para eliminar el
procedimiento similar.SAFE_DELETE_ARRAYRutina para eliminar arreglos.
código utilizado para la depuración,
Puede adaptar fácilmente este esquema allamarylibreen C y a otros lenguajes que usan
punteros.
Usar una técnica sin punteroLos punteros son más difíciles de entender que el promedio, son propensos a
errores y tienden a requerir un código no portátil que depende de la máquina. Si puede pensar en una
alternativa al uso de un puntero que funcione razonablemente, ahórrese algunos dolores de cabeza y
utilícelo en su lugar.
Punteros de C++-Pointer
Otras lecturasPara obtener más C++ presenta algunas arrugas específicas relacionadas con el uso de punteros y referencias. Las
sugerencias sobre el uso de
siguientes subsecciones describen las pautas que se aplican al uso de punteros en C++:
punteros en C++, consulteC++
efectivo, 2ª ed. (Meyers 1998) yC++
Comprender la diferencia entre punteros y referencias.En C++, ambos punteros (*) y las
más efectivo(Meyers 1996).
referencias (&) se refieren indirectamente a un objeto. Para los no iniciados, la única diferencia
parece ser una distinción puramente cosmética entre referirse a campos como
13.2 Punteros 333
objeto->campocontraobjeto.campo. Las diferencias más significativas son que una referencia siempre debe
hacer referencia a un objeto, mientras que un puntero puede apuntar a un valor nulo, y a qué se refiere una
referencia no se puede cambiar después de inicializar la referencia.
A veces, sin embargo, le gustaría tener lasemánticade un pase por valor, es decir, que el
objeto pasado no debe ser alterado, con elimplementaciónde un pase por referencia, es
decir, pasar el objeto real en lugar de una copia.
En C++, la solución a este problema es que utiliza punteros para pasar por referencia y, por
extraña que parezca la terminología: “constantereferencias” para pasar por valor! Aquí hay un
ejemplo:
Este enfoque brinda el beneficio adicional de proporcionar una diferenciación sintáctica dentro
de la rutina llamada entre los objetos que se supone que deben tratarse como modificables y
los que no lo son. En un objeto modificable, las referencias a los miembros utilizarán elobjeto-
>miembronotación, mientras que para objetos no modificables las referencias a miembros
usaránobjeto.miembronotación.
Sea inteligente con los punteros inteligentesLos punteros inteligentes reemplazan a los punteros
regulares o punteros "tontos" (Meyers 1996). Funcionan de manera similar a los punteros normales, pero
brindan más control sobre la administración de recursos, las operaciones de copia, las operaciones de
asignación, la construcción de objetos y la destrucción de objetos. Los problemas involucrados son
específicos de C++.C++ más efectivo, Artículo #28, contiene una discusión completa.
Punteros de puntero C
Aquí hay algunos consejos sobre el uso de punteros que se aplican específicamente al lenguaje C:
Use tipos de puntero explícitos en lugar del tipo predeterminadoC te permite usarcarbonizarse
ovacío punteros para cualquier tipo de variable. Siempre que el puntero apunte, al lenguaje
realmente no le importa a qué apunta. Sin embargo, si usa tipos explícitos para sus punteros, el
compilador puede advertirle sobre tipos de punteros que no coinciden y desreferencias
inapropiadas. Si no lo hace, no puede. Utilice el tipo de puntero específico siempre que pueda.
El corolario de esta regla es utilizar la conversión de tipo explícita cuando se tiene que realizar una
conversión de tipo. Por ejemplo, en este fragmento, está claro que una variable de tipoNODO_PTR se
está asignando:
Evite la conversión de tiposEvitar el tipo de casting no tiene nada que ver con ir a la escuela de
actuación o dejar de jugar siempre "el pesado". Tiene que ver con evitar apretar una variable de un
tipo en el espacio para una variable de otro tipo. La conversión de tipos desactiva la capacidad de su
compilador para verificar las discrepancias de tipos y, por lo tanto, crea un agujero en su armadura
de programación defensiva. Un programa que requiere muchos moldes tipográficos probablemente
tenga algunos vacíos arquitectónicos que deben revisarse. Rediseñar si eso es posible; de lo
contrario, intente evitar las conversiones de tipos tanto como pueda.
Siga la regla del asterisco para pasar parámetrosPuede devolver un argumento desde
una rutina en C solo si tiene un asterisco (*) delante del argumento en la instrucción de
asignación. Muchos programadores de C tienen dificultades para determinar cuándo C
permite que un valor se devuelva a una rutina de llamada. Es fácil recordar que, siempre
que tenga un asterisco delante del parámetro cuando le asigne un valor, el valor se
devuelve a la rutina de llamada. Independientemente de cuántos asteriscos acumule en
la declaración, debe tener al menos uno en la declaración de asignación si desea
devolver un valor. Por ejemplo, en el siguiente fragmento, el valor asignado a parámetro
no se devuelve a la rutina de llamada porque la declaración de asignación no usa un
asterisco:
13.3 Datos globales 335
La mayoría de los programadores experimentados han llegado a la conclusión de que usar datos globales es más riesgoso
que usar datos locales. Los programadores más experimentados también han llegado a la conclusión de que el acceso a los
Sin embargo, incluso si las variables globales no siempre producen errores, casi nunca son la mejor manera
de programar. El resto de esta sección explora completamente los temas involucrados.
PUNTO CLAVE
Las personas citan numerosos problemas en el uso de datos globales, pero los problemas se reducen a una
Cambios involuntarios en los datos globalesPuede cambiar el valor de una variable global en un
lugar y pensar erróneamente que se ha mantenido sin cambios en otro lugar. Tal problema se
conoce como un "efecto secundario". Por ejemplo, en este ejemplo,la respuestaes una variable
global:
la respuestaes una variable global. Ejemplo de Visual Basic de un problema de efectos secundarios
la respuesta = ObtenerLaRespuesta()
ObtenerOtraRespuesta()cambios la otra respuesta = ObtenerOtraRespuesta()
respuesta. respuesta promedio = (la respuesta + otra respuesta) / 2
promedioRespuestaEstá Mal.
Podría suponer que la llamada aObtenerOtraRespuesta()no cambia el valor dela respuesta; si lo
hace, el promedio en la tercera línea será incorrecto. Y de hecho,ObtenerOtraRespuesta()
cambia el valor dela respuesta, por lo que el programa tiene un error que corregir.
Ejemplo de Visual Basic de una rutina que está madura para un problema de aliasing
Sub WriteGlobal( ByRef inputVar As Integer )
variable de entrada = 0
Aquí está el código que llama a la rutina con la variable global como argumento:
"alias" por dos nombres diferentes. El efecto de laMsjBox()líneas es, por lo tanto, bastante
diferente de la que se pretendía: muestran la misma variable dos veces, aunque se refieren a
dos nombres diferentes.
Problemas de código reentrante con datos globalesEl código que puede ser ingresado por
más de un hilo de control es cada vez más común. El código de subprocesos múltiples crea la
posibilidad de que los datos globales se compartan no solo entre rutinas, sino entre diferentes
PUNTO CLAVE
copias del mismo programa. En tal entorno, debe asegurarse de que los datos globales
mantengan su significado incluso cuando se ejecutan varias copias de un programa. Este es un
problema importante y puede evitarlo utilizando las técnicas sugeridas más adelante en esta
sección.
Los datos globales complican el panorama. Si la clase que desea reutilizar lee o escribe datos
globales, no puede simplemente conectarla al nuevo programa. Tienes que modificar el programa
nuevo o la clase antigua para que sean compatibles. Si toma el camino correcto, modificará la clase
anterior para que no use datos globales. Si lo hace, la próxima vez que necesite reutilizar la clase
podrá conectarla sin problemas adicionales. Si toma el camino sencillo, modificará el nuevo programa
para crear los datos globales que necesita usar la clase anterior. Esto es como un virus; los datos
globales no solo afectan al programa original, sino que también se propagan a nuevos programas
que usan cualquiera de las clases del programa anterior.
Problemas de orden de inicialización inciertos con datos globalesEl orden en el que se inicializan los datos entre
diferentes "unidades de traducción" (archivos) no está definido en algunos lenguajes, especialmente en C++. Si la
inicialización de una variable global en un archivo usa una variable global que se inicializó en un archivo diferente,
todas las apuestas están canceladas en el valor de la segunda variable a menos que tome medidas explícitas para
Este problema se puede resolver con una solución que Scott Meyers describe enC++ efectivo, Artículo
#47 (Meyers 1998). Pero la complejidad de la solución es representativa de la complejidad adicional
que presenta el uso de datos globales.
Modularidad y manejabilidad intelectual dañadas por los datos globalesLa esencia de crear
programas que son más grandes que unos pocos cientos de líneas de código es administrar la
complejidad. La única forma en que puede administrar intelectualmente un programa grande es
dividirlo en partes para que solo tenga que pensar en una parte a la vez. La modularización es la
herramienta más poderosa a su disposición para dividir un programa en partes.
Los datos globales abren brechas en su capacidad de modularización. Si usa datos globales,
¿puede concentrarse en una rutina a la vez? No. Debe concentrarse en una rutina y en
cualquier otra rutina que use los mismos datos globales. Aunque los datos globales no
338 Capítulo 13: Tipos de datos inusuales
destruye por completo la modularidad de un programa, lo debilita, y eso es razón suficiente para tratar de
encontrar mejores soluciones a sus problemas.
Los puristas de datos a veces argumentan que los programadores nunca deberían usar datos globales, pero la mayoría de los
programas usan "datos globales" cuando el término se interpreta de manera amplia. Los datos de una base de datos son
datos globales, al igual que los datos de los archivos de configuración, como el registro de Windows. Las constantes con
Usadas con disciplina, las variables globales son útiles en varias situaciones:
Preservación de los valores globalesA veces tiene datos que se aplican conceptualmente a todo su
programa. Puede ser una variable que refleje el estado de un programa, por ejemplo, modo
interactivo frente a línea de comandos, o modo normal frente a recuperación de errores. O puede
ser información que se necesita a lo largo de un programa, por ejemplo, una tabla de datos que
utiliza cada rutina del programa.
Referencia cruzadaPara obtener más Emulación de constantes nombradasAunque C++, Java, Visual Basic y la mayoría de los
detalles sobre las constantes con nombre,
lenguajes modernos admiten constantes con nombre, algunos lenguajes como Python, Perl,
consulte la Sección 12.7, “Constantes con
nombre”.
Awk y UNIX shell script todavía no lo hacen. Puede usar variables globales como sustitutos de
constantes con nombre cuando su idioma no las admita. Por ejemplo, puede reemplazar los
valores literales1y0con las variables globalesCIERTOyFALSOajustado a1y0, o puede reemplazar
66como el número de líneas por página conLÍNEAS_POR_PÁGINA = 66. Es más fácil cambiar el
código más tarde cuando se usa este enfoque y el código tiende a ser más fácil de leer. Este
uso disciplinado de datos globales es un excelente ejemplo de la distinción entre
programaciónencontra la programacióndentroun lenguaje, que se analiza con más detalle en
la Sección 34.4, “Programe en su idioma, no en él”.
Emulación de tipos enumeradosTambién puede usar variables globales para emular tipos
enumerados en lenguajes como Python que no admiten tipos enumerados directamente.
Racionalización del uso de datos extremadamente comunesA veces tienes tantas referencias a una
variable que aparece en la lista de parámetros de cada rutina que escribes. En lugar de incluirlo en cada lista
de parámetros, puede convertirlo en una variable global. Sin embargo, en los casos en los que parece que
se accede a una variable desde cualquier lugar, rara vez lo es. Por lo general, se accede mediante un
conjunto limitado de rutinas que puede empaquetar en una clase con los datos en los que trabajan. Más
sobre esto más adelante.
Eliminación de datos vagabundosA veces pasa datos a una rutina o clase simplemente para que
puedan pasarse a otra rutina o clase. Por ejemplo, podría tener un objeto de procesamiento de
errores que se usa en cada rutina. Cuando la rutina en el medio de la cadena de llamadas no usa el
objeto, el objeto se llama "datos vagabundos". El uso de variables globales puede eliminar los datos
de trampa.
13.3 Datos globales 339
Comience por hacer que cada variable sea local y haga que las variables sean globales solo cuando sea
necesario. Haga que todas las variables sean locales para las rutinas individuales inicialmente. Si encuentra
que son necesarios en otro lugar, conviértalos en variables de clase privadas o protegidas antes de llegar a
hacerlos globales. Si finalmente descubre que tiene que hacerlos globales, hágalo, pero solo cuando esté
seguro de que tiene que hacerlo. Si comienza por hacer que una variable sea global, nunca la hará local,
mientras que si comienza por hacerla local, es posible que nunca necesite hacerla global.
Usar rutinas de accesoLa creación de rutinas de acceso es el enfoque de caballo de batalla para
solucionar los problemas con los datos globales. Más sobre eso en la siguiente sección.
- Obtiene un control centralizado sobre los datos. Si descubre una implementación más
adecuada de la estructura más adelante, no tiene que cambiar el código en todos los lugares
donde se hace referencia a los datos. Los cambios no se propagan por todo el programa. Se
mantienen dentro de las rutinas de acceso.
Referencia cruzadaPara obtener - Puede asegurarse de que todas las referencias a la variable estén bloqueadas. Si empuja
más detalles sobre la protección,
elementos a la pila con declaraciones comostack.array[ stack.top ] = nuevoElemento, puede
consulte la Sección 8.5, “Proteja su
Referencia cruzadaPara obtener detalles - Obtiene los beneficios generales de ocultar información automáticamente. Las rutinas
sobre la ocultación de información,
de acceso son un ejemplo de ocultación de información, incluso si no las diseña por ese
consulte "Ocultar secretos (Ocultar
- Las rutinas de acceso son fáciles de convertir a un tipo de datos abstracto. Una ventaja de las
rutinas de acceso es que puede crear un nivel de abstracción que es más difícil de lograr
cuando trabaja directamente con datos globales. Por ejemplo, en lugar de escribir código que
digasi lineCount > MAX_LINES, una rutina de acceso le permite escribir código que dice si
Página Llena(). Este pequeño cambio documenta la intención de lasi prueba lineCount, y lo
hace en el código. Es una pequeña ganancia en la legibilidad, pero la atención constante a
esos detalles marca la diferencia entre un software bellamente diseñado y un código que
simplemente se piratea.
Aquí está la versión corta de la teoría y práctica de las rutinas de acceso: Ocultar datos en una clase.
Declare que los datos mediante elestáticopalabra clave o su equivalente para garantizar que solo
exista una única instancia de los datos. Escriba rutinas que le permitan ver los datos y cambiarlos.
Requerir código fuera de la clase para usar las rutinas de acceso en lugar de trabajar directamente
con los datos.
Referencia cruzadaRestringir el Si su idioma no admite clases, aún puede crear rutinas de acceso para manipular los datos
acceso a las variables globales,
globales, pero tendrá que imponer restricciones en el uso de los datos globales a través de
incluso cuando su idioma no lo
admite directamente, es un
estándares de codificación en lugar de la aplicación del lenguaje de programación integrado.
ejemplo de programación.dentro
un lenguaje vs programaciónenun Aquí hay algunas pautas detalladas para usar rutinas de acceso para ocultar variables
idioma. Para obtener más globales cuando su idioma no tiene soporte incorporado:
detalles, consulte la Sección 34.4,
“Programe en su idioma, no en Requerir que todo el código pase por las rutinas de acceso a los datosUna buena convención es requerir
él”.
que todos los datos globales comiencen con elgramo_prefijo, y para exigir además que ningún código
acceda a una variable con elgramo_prefijo excepto las rutinas de acceso de esa variable. El resto del código
llega a los datos a través de las rutinas de acceso.
No arroje todos sus datos globales en el mismo barrilSi arroja todos sus datos globales en
una gran pila y escribe rutinas de acceso para ellos, elimina los problemas de los datos
globales pero pierde algunas de las ventajas de ocultar información y tipos de datos
abstractos. Siempre que esté escribiendo rutinas de acceso, tómese un momento para pensar
13.3 Datos globales 341
sobre a qué clase pertenece cada variable global y luego empaquetar los datos y sus
rutinas de acceso con los otros datos y rutinas en esa clase.
Utilice el bloqueo para controlar el acceso a las variables globalesDe manera similar al control de
concurrencia en un entorno de base de datos multiusuario, el bloqueo requiere que antes de que el valor de
una variable global pueda usarse o actualizarse, la variable debe estar "retirada". Después de usar la
variable, se vuelve a registrar. Durante el tiempo que está en uso (retirada), si alguna otra parte del
programa intenta retirarla, la rutina de bloqueo/desbloqueo muestra un mensaje de error o activa una
afirmación.
Referencia cruzadaPara obtener Esta descripción de bloqueo ignora muchas de las sutilezas de escribir código para admitir
detalles sobre la planificación de las
completamente la concurrencia. Por esa razón, los esquemas de bloqueo simplificados como este son
diferencias entre las versiones de
desarrollo y producción de un
más útiles durante la etapa de desarrollo. A menos que el esquema esté muy bien pensado,
programa, consulte "Plan para probablemente no será lo suficientemente confiable como para ponerlo en producción. Cuando el
eliminar las ayudas de depuración" en
programa se pone en producción, el código se modifica para hacer algo más seguro y elegante que
la Sección 8.6 y la Sección 8.7,
"Determinación de cuánta
mostrar mensajes de error. Por ejemplo, podría registrar un mensaje de error en un archivo cuando
programación defensiva dejar en el detecta varias partes del programa que intentan bloquear la misma variable global.
código de producción".
Este tipo de protección en tiempo de desarrollo es bastante fácil de implementar cuando usa
rutinas de acceso para datos globales, pero sería difícil de implementar si estuviera usando datos
globales directamente.
Uso directo de datos globales Uso de datos globales a través de rutinas de acceso
En los primeros tres ejemplos, el punto es que una rutina de acceso abstracta te dice mucho más que
una estructura genérica. Si usa la estructura directamente, hace demasiado a la vez: muestra tanto lo
que está haciendo la estructura en sí misma (moverse al siguiente enlace en una lista enlazada)
como lo que se está haciendo con respecto a la entidad que representa (obtener una cuenta,
siguiente empleado, o nivel de tarifa). Esta es una gran carga para poner en una asignación de
estructura de datos simple. Ocultar la información detrás de rutinas de acceso abstractas permite
que el código hable por sí mismo y hace que el código se lea al nivel del dominio del problema, en
lugar de al nivel de los detalles de implementación.
342 Capítulo 13: Tipos de datos inusuales
Mantenga todos los accesos a los datos en el mismo nivel de abstracciónSi usa una rutina
de acceso para hacer una cosa en una estructura, debe usar una rutina de acceso para hacer
todo lo demás también. Si lee de la estructura con una rutina de acceso, escriba en ella con
una rutina de acceso. si llamasPilaInicial()para inicializar una pila yPushStack()para insertar un
elemento en la pila, ha creado una vista coherente de los datos. Si revientas la pila escribiendo
valor = matriz[ pila.superior ], ha creado una vista incoherente de los datos. La inconsistencia
hace que sea más difícil para otros entender el código. Crear un PopStack()rutina en lugar de
escribirvalor = matriz [parte superior de la pila].
Referencia cruzadaUsando En los pares de sentencias de ejemplo de la tabla 13-1, las dos operaciones de cola de eventos
Las rutinas de acceso para una cola
ocurrieron en paralelo. Insertar un evento en la cola sería más complicado que cualquiera de las dos
de eventos sugieren la necesidad de
Aunque podría pensar que estas pautas se aplican solo a programas grandes, las rutinas de acceso
han demostrado ser una forma productiva de evitar los problemas de los datos globales. Como
beneficio adicional, hacen que el código sea más legible y agregan flexibilidad.
Referencia cruzadaPara obtener detalles Desarrollar una convención de nomenclatura que haga que las variables globales sean obvias
sobre las convenciones de nomenclatura
Puede evitar algunos errores simplemente dejando claro que está trabajando con datos globales. Si
para las variables globales, consulte
Cree una lista bien anotada de todas sus variables globalesUna vez que su convención de
nomenclatura indica que una variable es global, es útil indicar qué hace la variable. Una lista de
variables globales es una de las herramientas más útiles que puede tener alguien que trabaje con su
programa.
No finjas que no estás usando datos globales colocando todos tus datos en un objeto monstruo
y pasándolos a todas partes.Poner todo en un objeto enorme podría satisfacer la letra de la ley al
evitar las variables globales, pero es pura sobrecarga, que no produce ninguno de los beneficios de
la verdadera encapsulación. Si usa datos globales, hágalo abiertamente. No intentes disimularlo con
objetos obesos.
Recursos adicionales
cc2e.com/1385 Los siguientes son más recursos que cubren tipos de datos inusuales:
Maguire, Steve.Escribir código sólido. Redmond, WA: Microsoft Press, 1993. El Capítulo 3 contiene
una excelente discusión sobre los peligros del uso de punteros y numerosos consejos específicos
para evitar problemas con los punteros.
Meyers, Scott.C++ efectivo, 2ª ed. Lectura, MA: Addison-Wesley, 1998; Meyers, Scott, C++ más efectivo
. Reading, MA: Addison-Wesley, 1996. Como sugieren los títulos, estos libros contienen numerosos
consejos específicos para mejorar los programas de C++, incluidas pautas para usar punteros de
forma segura y eficaz.C++ más efectivoen particular, contiene una excelente discusión sobre los
problemas de administración de memoria de C++.
Estructuras
- ¿Ha utilizado estructuras en lugar de variables desnudas para organizar y
manipular grupos de datos relacionados?
- ¿Ha considerado crear una clase como alternativa al uso de una estructura?
Datos globales
- ¿Son todas las variables locales o de alcance de clase a menos que sea absolutamente necesario que sean
globales?
- ¿El código está libre de datos pseudoglobales: objetos gigantescos que contienen
una mezcla de datos que se pasan a cada rutina?
- ¿Las rutinas de acceso proporcionan un nivel de abstracción más allá de las implementaciones de
tipos de datos subyacentes?
Punteros
- ¿Las operaciones de puntero están aisladas en las rutinas?
- ¿El código utiliza todas las variables de puntero necesarias para facilitar la lectura?
- ¿Los punteros se usan solo como último recurso, cuando no hay otro método disponible?
Puntos clave
- Las estructuras pueden ayudar a que los programas sean menos complicados, más fáciles de entender y
- Siempre que considere usar una estructura, considere si una clase funcionaría
mejor.
- Los punteros son propensos a errores. Protéjase utilizando rutinas o clases de acceso y
prácticas de programación defensiva.
- Evite las variables globales, no solo porque son peligrosas, sino porque puede
reemplazarlas con algo mejor.
- Si no puede evitar las variables globales, trabaje con ellas mediante rutinas de acceso. Las
rutinas de acceso le brindan todo lo que le brindan las variables globales y más.
Parte IV
Declaraciones
En esta parte:
cc2e.com/1465 Contenido
Temas relacionados
Este capítulo pasa de una visión de la programación centrada en los datos a una visión centrada en
las declaraciones. Introduce el tipo más simple de flujo de control: colocar sentencias y bloques de
sentencias en orden secuencial.
Aunque organizar el código de línea recta es una tarea relativamente simple, algunas sutilezas
organizativas influyen en la calidad, corrección, legibilidad y mantenimiento del código.
A menos que suceda algo misterioso con este fragmento de código, la declaración debe ejecutarse
en el orden que se muestra. Los datos se deben leer antes de que se puedan calcular los resultados,
y los resultados se deben calcular antes de que se puedan imprimir.
347
348 Capítulo 14: Organización del código de línea recta
instrucción depende de otra es obvio a partir de los nombres de las rutinas. En el siguiente
fragmento de código, las dependencias son menos obvias:
En este caso, el cálculo de los ingresos trimestrales supone que ya se han calculado los
ingresos mensuales. La familiaridad con la contabilidad, o incluso el sentido común, podría
indicarle que los ingresos trimestrales deben calcularse antes que los ingresos anuales. Hay
una dependencia, pero no es obvia simplemente al leer el código. Y aquí, las dependencias no
son obvias, están literalmente ocultas:
Ejemplo de Visual Basic de instrucciones en las que se ocultan las dependencias de orden
ComputeMarketingExpense
ComputeSalesExpense
Calcular gastos de viaje
ComputePersonnelExpense
DisplayExpenseSummary
Cuando las declaraciones tienen dependencias que requieren que las coloque en un orden
determinado, tome medidas para aclarar las dependencias. Aquí hay algunas pautas simples para
ordenar declaraciones:
PUNTO CLAVE
Organice el código para que las dependencias sean obviasEn el ejemplo de Microsoft Visual
Basic recién presentado,Calcular gastos de marketing()no debe inicializar las variables
miembro de la clase. Los nombres de las rutinas sugieren queCalcular gastos de marketing()es
parecido a Calcular gastos de ventas (),Calcular gastos de viaje (), y las demás rutinas, excepto
que funciona con datos de marketing en lugar de con datos de ventas u otros datos. Tener
Calcular gastos de marketing()inicializar la variable miembro es una práctica arbitraria que
debe evitar. ¿Por qué debería hacerse la inicialización en esa rutina en lugar de en una de las
otras dos? A menos que pueda pensar en una buena razón, debe escribir otra rutina,
InitializeExpenseData(), para inicializar la variable miembro. El nombre de la rutina es una clara
indicación de que debe llamarse antes que las otras rutinas de gastos.
Nombre las rutinas para que las dependencias sean obviasEn el ejemplo de Visual Basic,Calcular
gastos de marketing()tiene un nombre incorrecto porque hace más que calcular los gastos de
marketing; también inicializa los datos de los miembros. Si se opone a crear una rutina adicional para
inicializar los datos, al menos déCalcular gastos de marketing()un nombre que
14.1 Declaraciones que deben estar en un orden específico 349
Referencia cruzadaPara obtener Use parámetros de rutina para hacer que las dependencias sean obviasNuevamente en el
detalles sobre el uso de rutinas y sus
ejemplo de Visual Basic, dado que no se pasan datos entre rutinas, no sabe si alguna de las
parámetros, consulte el Capítulo 5,
"Diseño en construcción".
rutinas usa los mismos datos. Al reescribir el código para que los datos pasen entre las
rutinas, establece una pista de que el orden de ejecución es importante. El nuevo código se
vería así:
Porque todas las rutinas usandatos de gastos, tiene una pista de que podrían estar trabajando
en los mismos datos y que el orden de las declaraciones podría ser importante.
En este ejemplo particular, un mejor enfoque podría ser convertir las rutinas en funciones que
tomandatos de gastoscomo entradas y retorno actualizadodatos de gastoscomo salidas, lo que
deja aún más claro que el código incluye dependencias de orden.
Ejemplo de Visual Basic de datos y llamadas de rutina que sugieren una dependencia de orden
datos de gastos = Inicializar datos de gastos (datos de gastos) datos de
gastos = calcular gastos de marketing (datos de gastos) datos de gastos
= calcular gastos de ventas (datos de gastos) datos de gastos = calcular
gastos de viaje (datos de gastos) datos de gastos = calcular gastos de
personal (datos de gastos) Mostrar resumen de gastos (datos de gastos)
Los datos también pueden indicar que el orden de ejecución no es importante, como en este caso:
Dado que las rutinas en las primeras cuatro líneas no tienen datos en común, el código
implica que el orden en que se llaman no importa. Debido a que la rutina en la quinta línea
usa datos de cada una de las primeras cuatro rutinas, puede suponer que debe ejecutarse
después de las primeras cuatro rutinas.
350 Capítulo 14: Organización del código de línea recta
Documentar dependencias poco claras con comentariosIntente primero escribir código sin
dependencias de orden. Intente escribir código que haga que las dependencias sean obvias. Si aún le
preocupa que la dependencia de un pedido no sea lo suficientemente explícita, documéntelo. La
PUNTO CLAVE
documentación de dependencias poco claras es un aspecto de la documentación de suposiciones de
codificación, que es fundamental para escribir código modificable y mantenible. En el ejemplo de Visual
Basic, los comentarios a lo largo de estas líneas serían útiles:
Ejemplo de Visual Basic de declaraciones en las que las dependencias de orden están ocultas pero
aclaradas con comentarios
' Calcular datos de gastos. Cada una de las rutinas accede a los ' datos del
miembro expensasDatos. Mostrar resumen de gastos
' debe llamarse en último lugar porque depende de los datos calculados ' por las
otras rutinas.
InitializeExpenseData
ComputeMarketingExpense
ComputeSalesExpense
Calcular gastos de viaje
ComputePersonnelExpense
DisplayExpenseSummary
Este código no usa las técnicas para hacer obvias las dependencias de orden. Es mejor confiar
en tales técnicas que en los comentarios, pero si mantiene un código estrictamente controlado
o no puede mejorar el código en sí por alguna otra razón, use la documentación para
compensar las debilidades del código.
Esta técnica crea nuevas variables, nuevo código de inicialización y nuevo código de
verificación de errores, todo lo cual crea posibilidades adicionales de error. Los beneficios de
esta técnica deben sopesarse frente a la complejidad adicional y la mayor posibilidad de
errores secundarios que crea esta técnica.
14.2 Declaraciones cuyo orden no importa 351
travelData.ComputeQuarterly();
salesData.ComputeQuarterly();
marketingData.ComputeQuarterly();
salesData.ComputeAnnual();
marketingData.ComputeAnnual();
travelData.ComputeAnnual();
datosventas.Imprimir();
travelData.Print();
marketingData.Print();
Suponga que desea determinar cómodatos de marketinges calculado. Debe comenzar en la última
línea y realizar un seguimiento de todas las referencias adatos de marketingvolver a la primera línea.
datos de marketingse usa solo en algunos otros lugares, pero debe tener en cuenta cómodatos de
marketingse usa en todas partes entre la primera y la última referencia a él. En otras palabras, tienes
que mirar y pensar en cada línea de código en este fragmento para descubrir cómodatos de
marketinges calculado. Y, por supuesto, este ejemplo es más simple que el código que se ve en los
sistemas de tamaño real. Aquí está el mismo código con una mejor organización:
salesData.ComputeQuarterly();
salesData.ComputeAnnual();
datosventas.Imprimir();
Referencia cruzadaun mas Este código es mejor de varias maneras. Las referencias a cada objeto se mantienen juntas;
La definición técnica de las
están "localizados". El número de líneas de código en las que los objetos están "vivos" es
variables “vivas” se da en
detalles sobre el proceso, consulte el su rutina y luego dibujar cuadros alrededor de las declaraciones relacionadas. Si las declaraciones están
Capítulo 9, "La programación del bien ordenadas, obtendrá una imagen como la que se muestra en la figura 14-1, en la que los cuadros no se
pseudocódigo".
superponen.
Proceso."
Figura 14-1Si el código está bien organizado en grupos, los cuadros dibujados alrededor de las secciones relacionadas no se
superponen. Pueden estar anidados.
Referencia cruzadaPara obtener más Si las declaraciones no están bien ordenadas, obtendrá una imagen similar a la que se muestra en la figura
información sobre cómo mantener
14-2, en la que los cuadros se superponen. Si encuentra que sus cuadros se superponen, reorganice su
juntas las operaciones con variables,
Figura 14-2Si el código está mal organizado, los recuadros dibujados alrededor de las secciones relacionadas se superponen.
Una vez que haya agrupado declaraciones relacionadas, es posible que encuentre que están fuertemente
relacionadas y no tienen una relación significativa con las declaraciones que las preceden o las siguen. En tal
caso, es posible que desee refactorizar las declaraciones fuertemente relacionadas en su propia rutina.
Puntos clave
- El principio más sólido para organizar el código de línea recta es ordenar las dependencias.
- Las dependencias deben hacerse evidentes mediante el uso de buenos nombres de rutinas, listas de
parámetros, comentarios y, si el código es lo suficientemente crítico, variables de mantenimiento.
Uso de condicionales
cc2e.com/1538 Contenido
Temas relacionados
15.1siDeclaraciones
Dependiendo del idioma que esté usando, es posible que pueda usar cualquiera de varios tipos desi
declaraciones. El más simple es el llano.siosi-entoncesdeclaración. lossi-entonces-otroes un poco más
complejo, y las cadenas desi-entonces-otro-sison los más complejos.
Sencillosi-entoncesDeclaraciones
Escriba primero la ruta nominal a través del código; luego escribe los casos inusualesEscriba su
código para que la ruta normal a través del código sea clara. Asegúrese de que los casos raros no
oscurezcan la ruta normal de ejecución. Esto es importante tanto para la legibilidad como para el
PUNTO CLAVE
rendimiento.
Asegúrate de bifurcar correctamente en igualdadUsar > en lugar de >= o < en lugar de <=
es análogo a cometer un error de uno en uno al acceder a una matriz o calcular un índice de
bucle. En un ciclo, piense en los puntos finales para evitar un error de uno por uno. En una
declaración condicional, piense en el caso de igualdad para evitar uno.
355
356 Capítulo 15: Uso de condicionales
Referencia cruzadaPara conocer otras Pon lo normalcasodespués de lasien lugar de después de lamásPonga el caso que normalmente
formas de manejar el código de
espera procesar primero. Esto está en línea con el principio general de colocar el código que resulta
procesamiento de errores, consulte
Ejemplo de Visual Basic de código que procesa una gran cantidad de errores al azar
Abrir archivo (archivo de entrada, estado) si
(estado = error de estado) entonces
Caso de error. tipo de error = FileOpenError Else
tipoError = TipoError_Ninguno
Terminar si
Terminara si
Más
errorType = ErrorType_FileReadError End If
Terminara si
Este código es difícil de seguir porque los casos nominales y los casos de error están todos
mezclados. Es difícil encontrar la ruta que normalmente se toma a través del código. Además,
debido a que las condiciones de error a veces se procesan en elsicláusula en lugar de lamás
cláusula, es difícil averiguar cuálsiprueba el caso normal va con. En el siguiente código
reescrito, la ruta normal se codifica en primer lugar y todos los casos de error se codifican en
último lugar. Esto hace que sea más fácil encontrar y leer el caso nominal.
Ejemplo de Visual Basic de código que procesa una gran cantidad de errores sistemáticamente
Abrir archivo (archivo de entrada, estado) si
(estado = estado_éxito) entonces
caso nominal. Leer archivo (archivo de entrada, datos de archivo,
estado) si (estado = estado_éxito) entonces
caso nominal. SummarizeFileData(fileData, summaryData, status) If (status =
Status_Success) Entonces
caso nominal. PrintSummary(datos resumidos)
SaveSummaryData(datos resumidos, estado) If
(estado = Status_Success) Entonces
caso nominal. ActualizarTodasLasCuentas()
BorrarDeshacerArchivo()
15.1 Declaraciones if 357
Más
Caso de error. tipo de error = ErrorType_DataSummaryError
Terminara si
Más
Caso de error. tipo de error = ErrorType_FileReadError
Terminara si
Más
Caso de error. tipo de error = ErrorType_FileOpenError
Terminara si
En el ejemplo revisado, puede leer el flujo principal delsiPruebas para encontrar el caso
normal. La revisión se enfoca en leer el flujo principal en lugar de leer los casos excepcionales,
por lo que el código es más fácil de leer en general. La pila de condiciones de error en la parte
inferior del nido es un signo de código de procesamiento de errores bien escrito.
Este ejemplo ilustra un enfoque sistemático para manejar casos normales y casos de error. A lo largo
de este libro se analiza una variedad de otras soluciones a este problema, incluido el uso de cláusulas
de guarda, la conversión a despacho polimórfico y la extracción de la parte interna de la prueba en
una rutina separada. Para obtener una lista completa de los enfoques disponibles, consulte
"Resumen de técnicas para reducir el anidamiento profundo" en la Sección 19.4.
Siga elsicláusula con una declaración significativaA veces verá un código como el
siguiente ejemplo, en el que elsicláusula es nula:
Referencia cruzadaUna clave La mayoría de los programadores experimentados evitarían un código como este aunque solo fuera
para construir unsi declaración
para evitar el trabajo de codificar la línea nula adicional y elmáslínea. Parece tonto y se mejora
está escribiendo la expresión
DATOS DUROS
encontró que del 50 al 80 por ciento desiLas declaraciones deberían haber tenido unmáscláusula
(Elshoff 1976).
Una opción es codificar elmáscláusula, con una declaración nula si es necesario, para mostrar
que elmásel caso ha sido considerado. Codificación nulamáss solo para mostrar que ese caso
ha sido considerado podría ser excesivo, pero al menos, tome lamáscaso en cuenta. cuando
tienes unsiprueba sin unmás, a menos que la razón sea obvia, use comentarios para explicar
por qué lamáscláusula no es necesaria, así:
cadenas desi-entonces-otroDeclaraciones
Referencia cruzadaPara obtener C++ Ejemplo de uso de unsi-entonces-otroCadena para categorizar un personaje
más detalles sobre la simplificación
if (caracter de entrada < ESPACIO) {
de expresiones complicadas,
characterType = CharacterType_ControlCharacter;
consulte la Sección 19.1, “Boolean
}
Expresiones.” más si (
carácter de entrada == ' ' ||
carácter de entrada == ',' ||
carácter de entrada == '.' ||
carácter de entrada == '!' ||
carácter de entrada == '(' ||
carácter de entrada == ')' ||
15.1 Declaraciones if 359
Simplifique las pruebas complicadas con llamadas a funciones booleanasUna de las razones por las que
el código del ejemplo anterior es difícil de leer es que las pruebas que clasifican al personaje son
complicadas. Para mejorar la legibilidad, puede reemplazarlos con llamadas a funciones booleanas. Así es
como se ve el código del ejemplo cuando las pruebas se reemplazan con funciones booleanas:
Ponga los casos más comunes primeroAl poner primero los casos más comunes, minimiza la
cantidad de código de manejo de casos de excepción que alguien tiene que leer para encontrar los
casos habituales. Mejora la eficiencia porque minimiza la cantidad de pruebas que realiza el código
para encontrar los casos más comunes. En el ejemplo que se acaba de mostrar, las letras serían más
comunes que la puntuación, pero la prueba de puntuación se realiza primero. Aquí está el código
revisado para que primero pruebe las letras:
Asegúrese de que todos los casos estén cubiertoscodificar una finalmáscláusula con un mensaje de
error o aserción para detectar casos que no planeó. Este mensaje de error está destinado a usted y
no al usuario, así que expréselo apropiadamente. Así es como puede modificar el ejemplo de
clasificación de caracteres para realizar una prueba de "otros casos":
Referencia cruzadaEste es también un Ejemplo de C++ del uso del caso predeterminado para atrapar errores
buen ejemplo de cómo se puede utilizar
if (EsLetra(CaracterEntrada)) {
una cadena desi-entoncespruebas en
tipoCaracter = TipoCaracter_Letra;
lugar de código profundamente anidado.
}
Para obtener detalles sobre esta técnica,
más si (EsPuntuación(caracter de entrada)) {
consulte la Sección 19.4, “Domar el
characterType = CharacterType_Punctuation;
anidamiento peligrosamente profundo”.
}
de lo contrario si (EsDígito(CarácterEntrada)) {
tipo de caracter = tipo de caracter_digit;
}
más si (IsControl(inputCharacter)) {
characterType = CharacterType_ControlCharacter;
}
más {
DisplayInternalError("Se detectó un tipo de carácter inesperado.");
}
15.2casoDeclaraciones
loscasoocambiarLa declaración es una construcción que varía mucho de un idioma a otro.
Compatibilidad con C++ y Javacasosolo para tipos ordinales tomados un valor a la vez. Compatibilidad
con Visual Basiccasopara tipos ordinales y tiene poderosas notaciones abreviadas para expresar
rangos y combinaciones de valores. Muchos lenguajes de secuencias de comandos no admitencaso
declaraciones en absoluto.
Ordenar los casos por orden alfabético o numéricoSi los casos son igualmente importantes,
ponerlos en orden ABC mejora la legibilidad. De esa manera, un caso específico es fácil de
seleccionar del grupo.
Ponga el caso normal primeroSi tiene un caso normal y varias excepciones, ponga
primero el caso normal. Indique con comentarios que es el caso normal y que los
demás son inusuales.
Ordenar casos por frecuenciaPonga los casos que se ejecutan con más frecuencia primero y los que se
ejecutan con menos frecuencia al final. Este enfoque tiene dos ventajas. Primero, los lectores humanos
pueden encontrar fácilmente los casos más comunes. Es probable que los lectores que buscan en la lista un
caso específico se interesen en uno de los casos más comunes, y poner los comunes en la parte superior del
código hace que la búsqueda sea más rápida.
Referencia cruzadaPara obtener otros consejos Mantenga las acciones de cada caso simplesMantenga breve el código asociado con cada
caso. El código corto que sigue a cada caso ayuda a hacer la estructura delcasodeclaración
sobre cómo simplificar el código, consulte el
clara. Si las acciones realizadas para un caso son complicadas, escriba una rutina y llame a la
rutina desde el caso en lugar de poner el código en el caso mismo.
caso 'd':
EliminarCarácter();
descanso;
caso 'f':
Formato();
descanso;
caso 'h':
Ayuda();
descanso;
...
defecto:
HandleUserInputError(ErrorType.InvalidUserCommand);
}
Referencia cruzadaA diferencia Este código problemático proviene del lado equivocado de la ciudad e invita a los problemas.
de este consejo, a veces puede
En general, cuando fabrica una variable para usarla en uncasodeclaración, los datos reales
mejorar la legibilidad asignando
una expresión complicada a una
podrían no corresponder con lacasodeclaración de la manera que desee. En este ejemplo, si el
variable o función booleana bien usuario escribeCopiar, lacasodeclaración quita la primera "c" y llama correctamente a la
nombrada. Para obtener más
Copiar()rutina. Por otro lado, si el usuario escribechanclos de cemento,bizcocho de almejas, o
información, consulte "Hacer
simples las expresiones
celulitis, lacasodeclaración también quita la "c" y llamaCopiar(). La prueba de un comando
complicadas" en la Sección 19.1. erróneo en elcasodeclaracionesmásLa cláusula no funcionará muy bien porque solo perderá
las primeras letras erróneas en lugar de los comandos erróneos.
En lugar de inventar una variable falsa, este código debería usar una cadena desi-entonces-otro-si
pruebas para verificar toda la cadena. Una reescritura virtuosa del código se ve así:
Use la cláusula predeterminada solo para detectar valores predeterminados legítimosEn ocasiones, es
posible que solo le quede un caso y decida codificar ese caso como la cláusula predeterminada. Aunque a
veces es tentador, eso es tonto. Pierdes la documentación automática proporcionada por caso-etiquetas de
declaración, y pierde la capacidad de detectar errores con la cláusula predeterminada.
Usar la cláusula por defecto para detectar erroresSi la cláusula por defecto en uncasodeclaración no se
está utilizando para otro procesamiento y no se supone que debe ocurrir, coloque un mensaje de
diagnóstico en ella:
Ejemplo de Java del uso del caso predeterminado para detectar errores: buena práctica
cambiar (comandoShortcutLetter) {
caso 'a':
ImprimirInformeAnual();
descanso;
caso 'p':
// no se requiere acción, pero el caso se consideró
interrumpido;
caso 'q':
ImprimirInformeTrimestral();
descanso;
casos':
ImprimirInformeResumen();
descanso;
defecto:
DisplayInternalError("Error interno 905: Llame al servicio de atención al cliente.");
}
Mensajes como este son útiles tanto en la depuración como en el código de producción. La mayoría de los usuarios
prefieren un mensaje como "Error interno: llame a atención al cliente" a un bloqueo del sistema o, peor aún, a
resultados sutilmente incorrectos que parecen correctos hasta que el jefe del usuario los verifica.
Si la cláusula predeterminada se usa para algún otro propósito que no sea la detección de errores, la
implicación es que todos los selectores de casos son correctos. Vuelva a verificar para asegurarse de que
todos los valores que posiblemente podrían ingresar en elcasodeclaración sería legítima. Si se le ocurren
algunas que no serían legítimas, reescriba las declaraciones para que la cláusula predeterminada verifique si
hay errores.
El programa cae hasta el final y ejecuta el código para el siguiente caso. Esto puede conducir a
algunas prácticas de codificación particularmente atroces, incluido el siguiente ejemplo horrible:
HORROR
// declaración 1
// declaración 2
Referencia cruzadaEl formato de este
caso 'B': // declaración 3
código hace que se vea mejor de lo
// declaración 4
que es. Para obtener detalles sobre
...
cómo usar el formato para hacer que
}
el código bueno se vea bien y el
...
código malo se vea mal, consulte
descanso;
"Diseño de línea final" en la Sección
...
31.3 y el resto del Capítulo
}
31, “Disposición y estilo”.
En C++, identifique de forma clara e inequívoca los flujos directos al final de uncaso
declaraciónSi escribe código intencionalmente para pasar al final de un caso, comente
claramente el lugar en el que sucede y explique por qué debe codificarse de esa manera.
caso DocumentationLevel_Summary:
Mostrar resumen de errores (número de error);
// FALLTHROUGH -- La documentación resumida también imprime el número de error
caso DocumentationLevel_NumberOnly:
DisplayErrorNumber( errorNumber break; ) ;
defecto:
DisplayInternalError("Error interno 905: Llame al servicio de atención al cliente.");
}
15.2
1 caso Declaraciones 365
Esta técnica es útil siempre que encuentre a alguien que prefiera tener un Pontiac
Aztek usado que un Corvette nuevo. En general, el código que falla de un caso a
otro es una invitación a cometer errores a medida que se modifica el código, y debe
evitarse.
si-entoncesDeclaraciones
- ¿Está clara la ruta nominal a través del código?
- Es elmáscláusula correcta?
si-entonces-otro-siCadenas
- ¿Las pruebas complicadas están encapsuladas en llamadas a funciones booleanas?
casoDeclaraciones
- ¿Los casos están ordenados de manera significativa?
- ¿Son simples las acciones para cada caso, llamando a otras rutinas si es necesario?
- ¿Se utiliza la cláusula por defecto para detectar y reportar casos inesperados?
Puntos clave
- por simplesi-másdeclaraciones, preste atención al orden de lassiymáscláusulas,
especialmente si procesan muchos errores. Asegúrese de que el caso nominal sea claro.
- Para atrapar errores, use la cláusula predeterminada en uncasodeclaración o la últimamásen una cadena de
si-entonces-otrodeclaraciones.
- Todas las construcciones de control no son iguales. Elija la construcción de control que sea
más apropiada para cada sección de código.
capitulo 16
Bucles de control
cc2e.com/1609 Contenido
Temas relacionados
"Bucle" es un término informal que se refiere a cualquier tipo de estructura de control iterativo,
cualquier estructura que haga que un programa ejecute repetidamente un bloque de código. Los
tipos de bucle comunes sonpor,tiempo, yhacer mientrasen C++ y Java, yPara-Siguiente,Mientras-
Wend, y Do-Loop-Mientrasen Microsoft Visual Basic. El uso de bucles es uno de los aspectos más
complejos de la programación; saber cómo y cuándo usar cada tipo de bucle es un factor decisivo en
la construcción de software de alta calidad.
- El bucle contado se realiza un número específico de veces, quizás una vez para
cada empleado.
- El ciclo sin fin se ejecuta para siempre una vez que ha comenzado. Es del tipo que se encuentra en
sistemas integrados como marcapasos, hornos de microondas y controles de crucero.
- El ciclo iterador realiza su acción una vez para cada elemento en una clase de contenedor.
367
368 Capítulo 16: Lazos de control
Los tipos de bucles se diferencian primero por la flexibilidad, ya sea que el bucle se ejecute
una cantidad específica de veces o que pruebe que se complete en cada iteración.
Los tipos de bucles también se diferencian por la ubicación de la prueba para su finalización.
Puede poner la prueba al principio, en el medio o al final del bucle. Esta característica te dice si
el bucle se ejecuta al menos una vez. Si el bucle se prueba al principio, su cuerpo no se ejecuta
necesariamente. Si el bucle se prueba al final, su cuerpo se ejecuta al menos una vez. Si el bucle
se prueba en el medio, la parte del bucle que precede a la prueba se ejecuta al menos una vez,
pero la parte del bucle que sigue a la prueba no se ejecuta necesariamente en absoluto.
Un bucle con salida normalmente consta del comienzo del bucle, el cuerpo del bucle (incluida una
condición de salida) y el final del bucle, como en este ejemplo de Visual Basic:
Declaraciones. ...
Si (alguna condición de salida) Entonces Exit Do. . .
Más declaraciones.
Círculo
El uso típico de un bucle con salida es para el caso en el que la prueba al principio o al
final del bucle requiere codificar un bucle y medio. Aquí hay un ejemplo en C++ de un
caso que justifica un bucle con salida pero no lo usa:
}
370 Capítulo 16: Lazos de control
Las dos líneas de código en la parte superior del ejemplo se repiten en las últimas dos líneas de código del
tiempocírculo. Durante la modificación, puede olvidarse fácilmente de mantener los dos conjuntos de líneas
paralelas. Otro programador que modifique el código probablemente ni siquiera se dé cuenta de que se
supone que los dos conjuntos de líneas se modifican en paralelo. De cualquier manera, el resultado serán
errores derivados de modificaciones incompletas. Así es como puede reescribir el código más claramente:
mientras (verdadero) {
GetNextRating( &ratingIncrement ); rating =
rating + ratingIncrement;
Esta es la condición de salida del if ( !( (puntuación <puntuación objetivo ) && (incremento de calificación != 0 ) ) ) {
bucle (y ahora podría simplificarse descanso;
GetNextRating(ratingIncrement) rating =
rating + ratingIncrement
Considere estos puntos más finos cuando use este tipo de bucle:
Referencia cruzadaLos detalles sobre las Ponga todas las condiciones de salida en un solo lugar. Repartirlos prácticamente garantiza
condiciones de salida se presentan más
que una condición de salida u otra se pasará por alto durante la depuración, modificación o
adelante en este capítulo. Para obtener
comentarios" en la Sección 32.5. Utilice los comentarios para aclarar. Si usa la técnica de bucle con salida en un idioma que
no la admite directamente, use comentarios para aclarar lo que está haciendo.
16.1 Selección del tipo de bucle 371
3 El bucle con salida es una construcción de control estructurado de una entrada y una salida, y es el
2
1 tipo preferido de control de bucle (Software Productivity Consortium 1989). Se ha demostrado que es
más fácil de entender que otros tipos de bucles. Un estudio de estudiantes de programación
DATOS DUROS
comparó este tipo de bucle con los que salían por arriba o por abajo (Soloway, Bonar y Ehrlich 1983).
Los estudiantes obtuvieron un puntaje 25 por ciento más alto en una prueba de comprensión
cuando se usaron bucles de bucle con salida, y los autores del estudio concluyeron que la estructura
de bucle con salida modela más de cerca la forma en que las personas piensan sobre el control
iterativo que otras estructuras de bucle. .
En la práctica común, el ciclo de bucle con salida aún no se usa mucho. El jurado todavía está encerrado en
una habitación llena de humo discutiendo si es una buena práctica para el código de producción. Hasta que
el jurado esté listo, el bucle con salida es una buena técnica para tener en la caja de herramientas de su
programador, siempre que lo use con cuidado.
Aquí se muestra otro tipo de bucle con salida que se usa para evitar un bucle y medio:
Comienzo:
A primera vista, esto parece ser similar a los ejemplos anteriores de bucle con salida. Se utiliza en
simulaciones en las que //hacer algono necesita ser ejecutado en el primer paso por el ciclo pero //
hacer algomás lo hace. Es una construcción de control de una entrada, una salida: la única forma de
entrar en el ciclo es a través deliren la parte superior, y la única forma de salir del bucle es a través del
tiempoprueba. Este enfoque tiene dos problemas: utiliza unir, y es lo suficientemente inusual como
para ser confuso.
En C++, puede lograr el mismo efecto sin usar unir, como se demuestra en el siguiente
ejemplo. Si el idioma que está utilizando no es compatible con undescansocomando,
puede emular uno con unir.
si ( !( expresión ) ) {
descanso;
// hacer algo
...
}
Maguire 1993).
Usarporbucles para actividades simples que no requieren controles de bucle internos. Úselos
cuando el control de bucle implique incrementos simples o decrementos simples, como iterar a
través de los elementos en un contenedor. el punto de unporbucle es que lo configura en la
parte superior del bucle y luego lo olvida. No tienes que hacer nada dentro del ciclo para
controlarlo. Si tiene una condición en la que la ejecución tiene que salirse de un bucle, use un
tiempobucle en su lugar.
Del mismo modo, no cambie explícitamente el valor de índice de unporbucle para obligarlo a terminar.
Utilizar unatiempobucle en su lugar. losporloop es para usos simples. Las tareas de bucle más complicadas
se manejan mejor con untiempocírculo.
// cuenta el número de números pares e impares en una secuencia de Fibonacci foreach ( int
fibonacciNumber in fibonacciSequence ) {
si ( número de fibonacci % 2 ) == 0 ) {
inclusoNúmerosFibonacci++;
}
más {
números impares de Fibonacci++;
}
}
Puede prevenir estos problemas observando dos prácticas. Primero, minimice la cantidad de factores
que afectan el ciclo. ¡Simplificar! ¡Simplificar! ¡Simplificar! En segundo lugar, trate el interior del bucle
como si fuera una rutina: mantenga la mayor parte del control posible fuera del bucle. Indique
PUNTO CLAVE
explícitamente las condiciones bajo las cuales se ejecutará el cuerpo del ciclo. No haga que el lector
mire dentro del ciclo para comprender el control del ciclo. Piense en un bucle como una caja negra: el
programa que lo rodea conoce las condiciones de control pero no el contenido.
Referencia cruzadaSi usas el Ejemplo de C++ de tratar un bucle como una caja negra
mientras (verdadero)-descanso while ( !inputFile.EndOfFile() && moreDataAvailable ) {
técnica descrita anteriormente, la
¿Cuáles son las condiciones bajo las cuales termina este bucle? Claramente, todo lo que sabes es
que o bieninputFile.EndOfFile()se vuelve verdad oMás datos disponiblesse vuelve falso.
Entrar en el bucle
Use estas pautas al ingresar a un bucle:
Ingrese al circuito desde una sola ubicaciónUna variedad de estructuras de control de bucle le permite probar al
principio, en el medio o al final de un bucle. Estas estructuras son lo suficientemente ricas como para permitirle
ingresar al bucle desde la parte superior cada vez. No necesita ingresar en múltiples ubicaciones.
Ponga el código de inicialización directamente antes del cicloEl Principio de Proximidad aboga por
juntar declaraciones relacionadas. Si las declaraciones relacionadas están dispersas en una rutina, es fácil
pasarlas por alto durante la modificación y hacer las modificaciones incorrectamente. Si las declaraciones
relacionadas se mantienen juntas, es más fácil evitar errores durante la modificación.
Referencia cruzadaPara obtener más Mantenga las declaraciones de inicialización de bucle con el bucle con el que están relacionadas. Si no
información sobre cómo limitar el alcance
lo hace, es más probable que cause errores cuando generalice el ciclo en un ciclo más grande y olvide
de las variables de bucle, consulte "Limitar
Usarmientras (verdadero)para bucles infinitosEs posible que tenga un bucle que se ejecuta
sin terminar, por ejemplo, un bucle en el firmware, como un marcapasos o un horno de
microondas. O podría tener un ciclo que termina solo en respuesta a un evento, un "bucle de
evento". Podría codificar un bucle tan infinito de varias maneras. Fingir un ciclo infinito con
una declaración comopara i = 1 a 99999es una mala elección porque el bucle específico limita
la intención del bucle:99999podría ser un valor legítimo. Tal bucle infinito falso también puede
fallar durante el mantenimiento.
reserva elporencabezado de bucle para sentencias de control de bucle: sentencias que inicializan el
bucle, lo terminan o lo mueven hacia la terminación. En el ejemplo que se acaba de mostrar, el
archivo de entrada.GetRecord()declaración en el cuerpo del bucle mueve el bucle hacia la
terminación, pero elnúmero de registroslas declaraciones no; son declaraciones de limpieza que no
controlan el progreso del ciclo. Poniendo elnúmero de registrosdeclaraciones en el encabezado del
bucle y dejando elarchivo de entrada.GetRecord()declaración es engañosa; crea la falsa impresión de
quenúmero de registroscontrola el bucle.
Si desea utilizar elporbucle en lugar de latiempobucle en este caso, coloque las declaraciones de
control de bucle en el encabezado del bucle y omita todo lo demás. Esta es la forma correcta de usar
el encabezado del bucle:
16.2 Control del bucle 375
El contenido del encabezado del ciclo en este ejemplo está relacionado con el control del ciclo. los
inputFile.MoveToStart()instrucción inicializa el ciclo, el!inputFile.EndOfFile() declaración comprueba si
el bucle ha terminado, y elarchivo de entrada.GetRecord()instrucción mueve el ciclo hacia la
terminación. Las declaraciones que afectannúmero de registrosno mueven directamente el ciclo
hacia la terminación y no se incluyen apropiadamente en el encabezado del ciclo. lostiempoloop es
probablemente aún más apropiado para este trabajo, pero al menos este código usa el encabezado
de loop lógicamente. Para que conste, así es como se ve el código cuando usa untiempocírculo:
Usar{y}para encerrar las sentencias en un bucleUtilice corchetes de código cada vez. No cuestan
nada en velocidad o espacio en tiempo de ejecución, ayudan a la legibilidad y ayudan a prevenir
errores a medida que se modifica el código. Son una buena práctica de programación defensiva.
Evite bucles vacíosEn C++ y Java, es posible crear un bucle vacío, uno en el que el
trabajo que realiza el bucle se codifica en la misma línea que la prueba que
comprueba si el trabajo ha finalizado. Aquí hay un ejemplo:
En este ejemplo, el bucle está vacío porque eltiempoexpresión incluye dos cosas: el
trabajo del ciclo—inputChar = archivo de datos.GetChar()—y una prueba de si el bucle
debe terminar—inputChar != CharType_Eof. El ciclo sería más claro si se recodificara para
que el trabajo que hace sea evidente para el lector:
El nuevo código ocupa tres líneas completas en lugar de una línea y un punto y coma, lo cual
es apropiado ya que hace el trabajo de tres líneas en lugar de una línea y un punto y coma.
Mantenga las tareas de limpieza del bucle al principio o al final del bucle.Las
tareas domésticas son expresiones comoyo = yo + 1oj++, expresiones cuyo objetivo
principal no es hacer el trabajo del bucle sino controlarlo. El mantenimiento se
realiza al final del bucle en este ejemplo:
Como regla general, las variables que inicializa antes del bucle son las variables que
manipulará en la parte de limpieza del bucle.
Referencia cruzadaPara obtener más Haz que cada bucle realice solo una funciónEl mero hecho de que un bucle pueda usarse para
información sobre la optimización,
hacer dos cosas a la vez no es justificación suficiente para hacerlas juntas. Los bucles deben ser como
consulte el Capítulo 25, "Estrategias de
Haga que las condiciones de terminación de bucle sean obviasSi usas unporbucle y no
juegue con el índice de bucle y no use unirodescansopara salir del bucle, la condición de
terminación será obvia. Del mismo modo, si utiliza untiempoorepetir hastabucle y poner todo
el control en eltiempoorepetir hastacláusula, la condición de rescisión será evidente. La clave
es poner el control en un solo lugar.
No juegues con el índice de bucle de unporbucle para hacer que el bucle termineAlgunos
programadores manipulan el valor de unporíndice de bucle para que el bucle termine antes de tiempo.
Aquí hay un ejemplo:
// más código
...
}
La intención en este ejemplo es terminar el ciclo bajo alguna condición configurandoi a100, un valor que es
mayor que el final de laporrango de bucle de0mediante99. Prácticamente todos los buenos programadores
evitan esta práctica; es el signo de un aficionado. Cuando configuras unporloop, el contador de loops está
fuera de los límites. Utilizar unatiempobucle para proporcionar más control sobre las condiciones de salida
del bucle.
Evite el código que depende del valor final del índice de bucleEs una mala forma
usar el valor del índice de bucle después del bucle. El valor terminal del índice de
bucle varía de un idioma a otro y de una implementación a otra. El valor es diferente
cuando el ciclo termina normalmente y cuando termina de manera anormal. Incluso
si sabe cuál es el valor final sin detenerse a pensarlo, la próxima persona que lea el
código probablemente tendrá que pensarlo. Es mejor forma y más
autodocumentado si asigna el valor final a una variable en el punto apropiado
dentro del ciclo.
378 Capítulo 16: Lazos de control
Este código hace un mal uso del valor final del índice:
Ejemplo de C++ de código que hace un mal uso del valor terminal de un índice de bucle
for (contador de registros = 0; contador de registros < MAX_REGISTROS; contador de registros++) {
if (entrada[recordCount] == testValue) {
descanso;
}
}
// mucho código
...
Aquí está el mal uso del valor if (contador de registros < MAX_REGISTROS) {
terminal del índice de bucle. devolver( verdadero );
}
más {
devolver( falso );
}
Ejemplo de C++ de código que no hace un mal uso del valor terminal de un índice de bucle
encontrado = falso;
for (contador de registros = 0; contador de registros < MAX_REGISTROS; contador de registros++) {
if (entrada[recordCount] == testValue) {
encontrado = verdadero;
descanso;
}
}
// mucho código
...
devolver (encontrado);
Este segundo fragmento de código usa una variable extra y mantiene referencias anúmero de registros más
localizado. Como suele ocurrir cuando se utiliza una variable booleana adicional, el código resultante es más
claro.
Considere el uso de contadores de seguridadUn contador de seguridad es una variable que incrementa cada paso
a través de un bucle para determinar si un bucle se ha ejecutado demasiadas veces. Si tiene un programa en el que
un error sería catastrófico, puede usar contadores de seguridad para asegurarse de que finalicen todos los bucles.
Los contadores de seguridad no son una panacea. Introducidos en el código uno a la vez, los contadores de
seguridad aumentan la complejidad y pueden generar errores adicionales. Debido a que no se utilizan en todos los
bucles, es posible que se olvide de mantener el código de contador de seguridad cuando modifique bucles en partes
del programa que sí los utilizan. Sin embargo, si los contadores de seguridad se instituyen como un estándar en
todo el proyecto para los bucles críticos, aprenderá a esperarlos y el código del contador de seguridad no será más
Muchos lenguajes proporcionan un medio para hacer que un ciclo termine de alguna forma que no
sea completar elporotiempocondición. En esta discusión,descansoes un término genérico para
descanso en C++, C y Java; porSalir-HacerySalir paraen Visual Basic; y para construcciones similares,
incluidas las simuladas conirs en idiomas que no admitendescansodirectamente. los descanso
declaración (o equivalente) hace que un ciclo termine a través del canal de salida normal; el
programa reanuda la ejecución en la primera instrucción que sigue al bucle.
Tenga cuidado con un bucle con muchodescansoestá disperso a través de élUn bucle contiene una gran
cantidad dedescansos puede indicar un pensamiento poco claro sobre la estructura del bucle o su papel en
el código circundante. Una proliferación dedescansos plantea la posibilidad de que el ciclo pueda expresarse
más claramente como una serie de ciclos en lugar de como un ciclo con muchas salidas.
Según un artículo enNotas de ingeniería de software, el error de software que detuvo los sistemas
telefónicos de la ciudad de Nueva York durante 9 horas el 15 de enero de 1990 se debió a un extra
descansodeclaración (SEN 1990):
UsarSeguirpara pruebas en la parte superior de un bucleun buen uso deSeguires para mover la
ejecución más allá del cuerpo del bucle después de probar una condición en la parte superior. Por
ejemplo, si el ciclo lee registros, descarta registros de un tipo y procesa registros de otro tipo, puede
colocar una prueba como esta en la parte superior del ciclo:
Aquí hay una posible solución al problema del código telefónico de la ciudad de Nueva York, con el cambio
del lenguaje de programación de C++ a Java para mostrar la ruptura etiquetada:
Un solo ciclo generalmente tiene tres casos de interés: el primer caso, un caso intermedio
seleccionado arbitrariamente y el último caso. Cuando crees un bucle, repasa mentalmente el
primer, el medio y el último caso para asegurarte de que el bucle no tenga errores de uno en
uno. Si tiene casos especiales que son diferentes del primer o último caso, verifíquelos
también. Si el bucle contiene cálculos complejos, saque su calculadora y verifique
manualmente los cálculos.
382 Capítulo 16: Lazos de control
La voluntad de realizar este tipo de verificación es una diferencia clave entre programadores
eficientes e ineficientes. Los programadores eficientes hacen el trabajo de simulaciones mentales y
cálculos manuales porque saben que tales medidas les ayudan a encontrar errores.
PUNTO CLAVE
Los programadores ineficientes tienden a experimentar al azar hasta que encuentran una
combinación que parece funcionar. Si un bucle no funciona como se supone que debe hacerlo, el
programador ineficiente cambia el signo < por un signo <=. Si eso falla, el programador ineficiente
cambia el índice del ciclo sumando o restando 1. Eventualmente, el programador que usa este
enfoque podría tropezar con la combinación correcta o simplemente reemplazar el error original con
uno más sutil. Incluso si este proceso aleatorio da como resultado un programa correcto, no significa
que el programador sepa por qué el programa es correcto.
Puede esperar varios beneficios de las simulaciones mentales y los cálculos manuales. La
disciplina mental da como resultado menos errores durante la codificación inicial, una
detección más rápida de errores durante la depuración y una mejor comprensión general del
programa. El ejercicio mental significa que comprendes cómo funciona tu código en lugar de
adivinarlo.
Referencia cruzadaPara obtener detalles Use tipos ordinales o enumerados para límites tanto en matrices como en buclesGeneralmente, los
sobre cómo nombrar variables de bucle,
contadores de bucle deben ser valores enteros. Los valores de punto flotante no se incrementan bien. Por
consulte "Nombrar índices de bucle" en la
Sección 11.2.
ejemplo, podría agregar 1,0 a 26 742 897,0 y obtener 26 742 897,0 en lugar de 26 742 898,0. Si este valor
incrementado fuera un contador de bucle, tendría un bucle infinito.
Use nombres de variables significativos para hacer que los bucles anidados sean legiblesLas matrices a
menudo se indexan con las mismas variables que se utilizan para los índices de bucle. Si tiene una matriz
unidimensional, es posible que pueda salirse con la suya usandoi,j, okpara indexarlo. Pero si tiene una
PUNTO CLAVE
matriz con dos o más dimensiones, debe usar nombres de índice significativos para aclarar lo que está
haciendo. Los nombres de índice de matriz significativos aclaran tanto el propósito del bucle como la parte
de la matriz a la que pretende acceder.
Aquí hay un código que no pone en práctica este principio; utiliza los nombres sin sentidoi,j, yk
en cambio:
¿Qué crees que indexa la matriz entransacción¿significar? Haceri,j, ykdecirle nada sobre
el contenido detransacción? Si tuviera la declaración detransacción, ¿podría determinar
fácilmente si los índices estaban en el orden correcto? Aquí está el mismo bucle con
nombres de variables de bucle más legibles:
¿Qué crees que indexa la matriz entransacciónsignifica esta vez? En este caso, la respuesta es más
fácil de encontrar porque los nombres de las variablespayCodeIdx,mes, ydivisiónIdxdecirte mucho
más quei,j, ykhizo. La computadora puede leer las dos versiones del bucle con la misma facilidad. Sin
embargo, las personas pueden leer la segunda versión más fácilmente que la primera, y la segunda
versión es mejor ya que su audiencia principal está compuesta por humanos, no por computadoras.
Utilice nombres significativos para evitar la diafonía entre bucles e índicesuso habitual dei,j, ykpuede dar
lugar a diafonía de índices, ya que se utiliza el mismo nombre de índice para dos propósitos diferentes. Echale un
vistazo a éste ejemplo:
El uso deies tan habitual que se utiliza dos veces en la misma estructura de nido. El
segundoporbucle controlado porientra en conflicto con el primero, y eso es diafonía de
índice. Usar nombres más significativos quei,j, ykhabría evitado el problema. En general,
si el cuerpo de un bucle tiene más de un par de líneas, si puede crecer o si está en un
grupo de bucles anidados, evitei,j, yk.
Limite el alcance de las variables de índice de bucle al propio bucleLa diafonía del índice de
bucle y otros usos de los índices de bucle fuera de sus bucles es un problema tan importante que el
Traducido del inglés al español - www.onlinedoctranslator.com
diseñadores de Ada decidieron hacerporíndices de bucle no válidos fuera de sus bucles; tratando de
usar uno fuera de suporloop genera un error en tiempo de compilación.
C++ y Java implementan la misma idea hasta cierto punto: permiten declarar índices de
bucle dentro de un bucle, pero no lo requieren. En el ejemplo de la página 378, el
número de registrosvariable podría declararse dentro de lapordeclaración, que limitaría
su alcance a laporbucle, así:
Ejemplo de C++ de declaración de índices de bucle dentroporbucles y reutilizarlos de forma segura, ¡tal
vez!
por (En tcuentaregistro = 0; recordCount < MAX_REGISTROS; cuentaregistro++ ) {
// código de bucle que usa recordCount
}
// código intermedio
por (En tcuentaregistro = 0; recordCount < MAX_REGISTROS; cuentaregistro++ ) {
// código de bucle adicional que usa un recordCount diferente
}
Esta técnica es útil para documentar el propósito de lanúmero de registrosvariable; sin embargo, no
confíe en su compilador para hacer cumplirnúmero de registrosel alcance de Sección 6.3.3.1 de El
lenguaje de programación C++(Stroustrup 1997) dice quenúmero de registrosdebe tener un alcance
limitado a su ciclo. Sin embargo, cuando verifiqué esta funcionalidad con tres compiladores de C++
diferentes, obtuve tres resultados diferentes:
- El tercer compilador permitió ambos usos denúmero de registrosy no permitía que ninguno
de los dos fuera utilizado fuera delporbucle en el que se declaró.
Como suele ser el caso con características de lenguaje más esotéricas, las implementaciones del compilador
pueden variar.
16.3 Creación de bucles fácilmente: de adentro hacia afuera 385
Haga que sus bucles sean lo suficientemente cortos para verlos todos a la vezSi normalmente
observa bucles en su monitor y su monitor muestra 50 líneas, eso le impone una restricción de 50
líneas. Los expertos han sugerido un límite de longitud de bucle de una página. Sin embargo, cuando
comience a apreciar el principio de escribir código simple, rara vez escribirá bucles de más de 15 o 20
líneas.
Referencia cruzadaPara obtener detalles Limite el anidamiento a tres nivelesLos estudios han demostrado que la capacidad de los
sobre cómo simplificar el anidamiento,
programadores para comprender un ciclo se deteriora significativamente más allá de los tres niveles
consulte la Sección 19.4, “Domar el
Mueva las entrañas de bucle de bucles largos a rutinasSi el ciclo está bien diseñado, el
código en el interior de un ciclo a menudo se puede mover a una o más rutinas que se llaman
desde dentro del ciclo.
Hacer bucles largos especialmente clarosLa longitud añade complejidad. Si escribe un ciclo corto,
puede usar estructuras de control más riesgosas comodescansoySeguir, salidas múltiples,
condiciones de terminación complicadas, etc. Si escribe un ciclo más largo y siente alguna
preocupación por su lector, le dará al ciclo una sola salida y hará que la condición de salida sea
inequívocamente clara.
Referencia cruzadaCodificar un Suponga que está escribiendo un programa para una compañía de seguros. Tiene tarifas de seguro
bucle de adentro hacia afuera es
de vida que varían según la edad y el sexo de la persona. Su trabajo es escribir una rutina que calcule
similar al proceso descrito en el
Primero, en los comentarios, escriba los pasos que debe realizar el cuerpo del bucle. Es más fácil
anotar lo que debe hacerse cuando no está pensando en los detalles de la sintaxis, los índices de
bucle, los índices de matrices, etc.
Segundo, convierta los comentarios en el cuerpo del bucle en código, tanto como pueda sin
tener que escribir todo el bucle. En este caso, obtenga la tarifa para una persona y súmela al
total general. Utilice datos concretos y específicos en lugar de abstracciones.
El ejemplo supone quemesaes una matriz que contiene los datos de tasa. No tiene que preocuparse por los
índices de matriz al principio.Velocidades la variable que contiene los datos de tasa seleccionados de la tabla
de tasas. Igualmente,tasa totales una variable que contiene el total de las tasas.
A la matriz se accede por edad y sexo, por lo quecenso.Edadycenso.Génerose utilizan para indexar la
matriz. El ejemplo supone quecensoes una estructura que contiene información sobre las personas
del grupo a calificar.
El siguiente paso es construir un ciclo alrededor de las declaraciones existentes. Dado que se supone
que el ciclo calcula las tasas de cada persona en un grupo, el ciclo debe indexarse por persona.
Todo lo que tienes que hacer aquí es poner elporhaga un bucle alrededor del código existente y
luego sangre el código existente y colóquelo dentro de unprincipio-finpar. Finalmente, verifique que
las variables que dependen delpersonaíndice de bucle se han generalizado. En este caso, el censo
variable varía conpersona, por lo que debe generalizarse adecuadamente.
16.4 Correspondencia entre lazos y arreglos 387
Finalmente, escriba las inicializaciones que sean necesarias. En este caso, eltasa totalla variable
necesita ser inicializada.
Si tuvieras que poner otro lazo alrededor delpersonabucle, se procedería de la misma manera. No es
necesario seguir los pasos de forma rígida. La idea es comenzar con algo concreto, preocuparse solo
por una cosa a la vez y construir el ciclo a partir de componentes simples. Tome pasos pequeños y
comprensibles a medida que hace que el ciclo sea más general y complejo. De esa manera, minimiza
la cantidad de código en el que tiene que concentrarse en un momento dado y, por lo tanto, minimiza
la posibilidad de error.
En Java, se necesita un bucle para esta operación de matriz. Pero vale la pena señalar que las
estructuras de bucle y las matrices no están inherentemente conectadas. Algunos lenguajes,
especialmente APL y Fortran 90 y posteriores, brindan potentes operaciones de matriz que eliminan
la necesidad de bucles como el que se muestra. Aquí hay un fragmento de código APL que realiza la
misma operación:
El APL es más simple y menos propenso a errores. Utiliza solo tres operandos, mientras que el fragmento de
Java usa 17. No tiene variables de bucle, índices de matriz o estructuras de control para codificar
incorrectamente.
Un punto de este ejemplo es que haces algo de programación para resolver un problema y algo para
resolverlo en un lenguaje en particular. El lenguaje que usa para resolver un problema afecta
sustancialmente su solución.
Entrar en el bucle
- ¿Se ingresa al bucle desde la parte superior?
- Si el bucle es C++, C o Javaporbucle, ¿el encabezado del bucle está reservado para el código de control
de bucle?
- ¿El cuerpo del bucle tiene algo dentro? ¿No está vacío?
- ¿El bucle realiza una y sólo una función, como lo hace una rutina bien
definida?
- ¿Se han movido los contenidos de los bucles largos a su propia rutina?
Índices de bucle
- Si el bucle es unporbucle, ¿el código que contiene evita jugar con el índice del
bucle?
- ¿Se usa una variable para guardar valores importantes de índice de bucle en lugar de usar
el índice de bucle fuera del bucle?
Puntos clave
- Los bucles son complicados. Mantenerlos simples ayuda a los lectores de su código.
- Las técnicas para mantener los bucles simples incluyen evitar tipos exóticos de bucles, minimizar el
anidamiento, hacer que las entradas y salidas sean claras y mantener el código de limpieza en un
solo lugar.
- Los índices de bucle están sujetos a una gran cantidad de abusos. Nómbrelos claramente y
utilícelos para un solo propósito.
Temas relacionados
Varias construcciones de control existen en una zona de penumbra nebulosa en algún lugar entre ser de
vanguardia y ser desacreditado y refutado, ¡a menudo en ambos lugares al mismo tiempo! Estas
construcciones no están disponibles en todos los idiomas, pero pueden ser útiles cuando se usan con
cuidado en aquellos idiomas que las ofrecen.
Utilizar unadevolvercuando mejora la legibilidadEn ciertas rutinas, una vez que conoce la
respuesta, desea devolverla a la rutina de llamada inmediatamente. Si la rutina está definida
de tal manera que no requiere más limpieza una vez que detecta un error, no regresar
PUNTO CLAVE
inmediatamente significa que tiene que escribir más código.
391
392 Capítulo 17: Estructuras de control inusuales
Utilice cláusulas de protección (retornos o salidas anticipadas) para simplificar el procesamiento de errores complejos
El código que tiene que verificar numerosas condiciones de error antes de realizar sus acciones nominales puede resultar en
un código profundamente sangrado y puede oscurecer el caso nominal, como se muestra aquí:
Si archivo.Abrir() Después
Si encryptionKey.valid() Entonces
Si archivo.Descifrar (clave de cifrado) Entonces
Este es el código para el 'mucho código
caso nominal. ...
Terminara si
Final Si
Final Si
Final Si
Código simple de Visual Basic que utiliza cláusulas de protección para aclarar el caso nominal
' configurar, rescatar si se encuentran errores If Not
file.validName() Then Exit Sub
Si no es un archivo. Abrir (), luego salga de Sub
Si no escryptionKey.valid (), luego salga de Sub
Si no es un archivo. Descifrar (clave de cifrado) y luego salir de Sub
'mucho código
...
17.2 Recursividad 393
Este código simple hace que esta técnica parezca una solución ordenada, pero el código de
producción a menudo requiere una limpieza o limpieza más extensa cuando se detecta una
condición de error. Aquí hay un ejemplo más realista:
Código de Visual Basic más realista que utiliza cláusulas de protección para aclarar el caso nominal
' configurar, rescatar si se encuentran errores If Not
file.validName() Then
errorStatus = FileError_InvalidFileName Salir de Sub
Terminara si
Si no es archivo.Abrir() Entonces
errorStatus = FileError_CantOpenFile Salir Sub
Terminara si
Terminara si
Terminara si
Con el código de tamaño de producción, elSalir de SubEl enfoque crea una cantidad notable de
código antes de que se maneje el caso nominal. losSalir de SubSin embargo, el enfoque evita el
anidamiento profundo del primer ejemplo y, si el código del primer ejemplo se expandiera para
mostrar la configuración de unestado de errorvariable, laSalir de SubEl enfoque haría un mejor
trabajo al mantener juntas las declaraciones relacionadas. Cuando todo el polvo se asiente, elSalir de
SubEl enfoque parece más legible y mantenible, pero no por un margen muy amplio.
Minimizar el número de devoluciones en cada rutinaEs más difícil entender una rutina cuando, al
leerla en la parte inferior, no te das cuenta de la posibilidad de que haya regresado a algún lugar
arriba. Por esa razón, use las devoluciones con prudencia, solo cuando mejoren la legibilidad.
17.2 Recursividad
En la recursividad, una rutina resuelve una pequeña parte de un problema por sí misma, divide el problema en
partes más pequeñas y luego se llama a sí misma para resolver cada una de las partes más pequeñas. La
recursividad generalmente entra en juego cuando una pequeña parte del problema es fácil de resolver y una gran
La recursividad no suele ser útil, pero cuando se usa juiciosamente produce soluciones elegantes, como en
este ejemplo en el que un algoritmo de clasificación hace un excelente uso de la recursividad:
PUNTO CLAVE
394 Capítulo 17: Estructuras de control inusuales
En este caso, el algoritmo de clasificación corta una matriz en dos y luego se llama a sí mismo para clasificar cada
mitad de la matriz. Cuando se llama a sí mismo con un subarreglo que es demasiado pequeño para ordenar, como(
últimoíndice <= primeríndice )—deja de llamarse a sí mismo.
Para un pequeño grupo de problemas, la recursividad puede producir soluciones simples y elegantes. Para
un grupo de problemas un poco más grande, puede producir soluciones simples, elegantes y difíciles de
entender. Para la mayoría de los problemas, produce soluciones enormemente complicadas; en esos casos,
la iteración simple suele ser más comprensible. Utilice la recursividad de forma selectiva.
Ejemplo de recursividad
Suponga que tiene un tipo de datos que representa un laberinto. Un laberinto es básicamente una
cuadrícula, y en cada punto de la cuadrícula puede girar a la izquierda, girar a la derecha, moverse hacia
arriba o hacia abajo. A menudo podrá moverse en más de una dirección.
¿Cómo se escribe un programa para encontrar su camino a través del laberinto, como se muestra en
la Figura 17-1? Si usa la recursividad, la respuesta es bastante sencilla. Empiezas por el principio y
luego pruebas todos los caminos posibles hasta que encuentras la salida del laberinto. La primera vez
que visita un punto, intenta moverse hacia la izquierda. Si no puedes moverte a la izquierda, intentas
subir o bajar, y si no puedes subir o bajar, intentas ir a la derecha. No tienes que preocuparte por
perderte porque dejas caer algunas migas de pan en cada lugar que visitas y no visitas el mismo
lugar dos veces.
Arriba
Derecha
Izquierda
Abajo
Figura 17-1La recursividad puede ser una herramienta valiosa en la batalla contra la complejidad, cuando se usa
para atacar problemas adecuados.
17.2 Recursividad 395
}
}
}
}
}
}
Las líneas restantes de la rutina intentan encontrar un camino hacia la izquierda, hacia arriba, hacia abajo y hacia la
derecha. El código detiene la recursividad si la rutina alguna vez regresaverdadero—es decir, cuando la rutina
encuentra un camino a través del laberinto.
La lógica utilizada en esta rutina es bastante sencilla. La mayoría de las personas experimentan
cierta incomodidad inicial al usar la recursividad porque es autorreferencial. En este caso, sin
embargo, una solución alternativa sería mucho más complicada y la recursividad funciona bien.
Asegúrese de que la recursividad se detengaVerifique la rutina para asegurarse de que incluya una ruta
no recursiva. Eso generalmente significa que la rutina tiene una prueba que detiene la repetición adicional
cuando no es necesaria. En el ejemplo del laberinto, las pruebas paraYa probado()y EstaEsLaSalida()
asegúrese de que la recursividad se detenga.
Utilice contadores de seguridad para evitar la repetición infinitaSi está usando la recursividad en una
situación que no permite una prueba simple como la que se acaba de describir, use un contador de
seguridad para evitar la recurrencia infinita. El contador de seguridad tiene que ser una variable que no se
vuelve a crear cada vez que llama a la rutina. Utilice una variable miembro de clase o pase el contador de
seguridad como parámetro. Aquí hay un ejemplo:
Ejemplo de Visual Basic del uso de un contador de seguridad para evitar la recursividad infinita
La rutina recursiva debe ser capaz Public Sub RecursiveProc (ByRef contador de seguridad como entero)
de cambiar el valor deContador Si ( contador de seguridad > LÍMITE DE SEGURIDAD ) Entonces
de seguridad, por lo que en Visual Salir de Sub
Basic es unPorRefparámetro. Terminara si
contadordeseguridad = contadordeseguridad +
1...
RecursiveProc(contadordeseguridad) End
Sub
sola rutina. Si no puede y aún piensa que la recursividad es el mejor enfoque, use contadores de
seguridad como una póliza de seguro recursiva.
Vigila la pilaCon la recursividad, no tiene garantías sobre cuánto espacio de pila usa su programa y
es difícil predecir de antemano cómo se comportará el programa en tiempo de ejecución. Sin
embargo, puede tomar un par de pasos para controlar su comportamiento en tiempo de ejecución.
En primer lugar, si usa un contador de seguridad, una de las consideraciones al establecer un límite debe ser la
cantidad de pila que está dispuesto a asignar a la rutina recursiva. Establezca el límite de seguridad lo
suficientemente bajo para evitar un desbordamiento de la pila.
En segundo lugar, observe la asignación de variables locales en funciones recursivas, especialmente objetos
que requieren mucha memoria. En otras palabras, usanuevopara crear objetos en el montón en lugar de
dejar que el compilador creeautoobjetos en la pila.
No use recursividad para factoriales o números de FibonacciUn problema con los libros de texto
de ciencias de la computación es que presentan ejemplos tontos de recursividad. Los ejemplos
típicos son calcular un factorial o calcular una secuencia de Fibonacci. La recursividad es una
herramienta poderosa, y es realmente tonto usarla en cualquiera de esos casos. Si un programador
que trabajara para mí usara la recursividad para calcular un factorial, contrataría a otra persona.
Aquí está la versión recursiva de la rutina factorial:
Ejemplo en Java de una solución inapropiada: uso de la recursividad para calcular un factorial
factorial int (número int) {
si ( número == 1 ) {
CODIFICACIÓN devolver 1;
HORROR
}
más {
número de retorno * Factorial (número - 1);
}
}
Además de ser lenta y hacer que el uso de la memoria en tiempo de ejecución sea
impredecible, la versión recursiva de esta rutina es más difícil de entender que la versión
iterativa, que sigue:
Ejemplo de Java de una solución adecuada: uso de la iteración para calcular un factorial
factorial int (número int) {
int resultado intermedio = 1;
for ( int factor = 2; factor <= número; factor++ ) {
resultado intermedio = resultado intermedio * factor;
}
devolver resultado intermedio;
}
398 Capítulo 17: Estructuras de control inusuales
Puedes sacar tres lecciones de este ejemplo. En primer lugar, los libros de texto de informática no le
hacen ningún favor al mundo con sus ejemplos de recursividad. En segundo lugar, y más importante,
la recursividad es una herramienta mucho más poderosa de lo que sugeriría su uso confuso en el
cálculo de factoriales o números de Fibonacci. En tercer lugar, y lo más importante, debe considerar
alternativas a la recursividad antes de usarla. Puede hacer cualquier cosa con las pilas y la iteración
que puede hacer con la recursividad. A veces, un enfoque funciona mejor; a veces el otro lo hace.
Considere ambos antes de elegir cualquiera de ellos.
17.3ir
cc2e.com/1785 Se podría pensar que el debate está relacionado conirs está extinto, pero un viaje rápido a través de los
repositorios de código fuente modernos comoSourceForge.netmuestra que elirtodavía está vivo y bien y
vive en lo profundo del servidor de su empresa. Además, los equivalentes modernos delir el debate sigue
surgiendo de diversas formas, incluidos los debates sobre devoluciones múltiples, salidas de bucles
múltiples, salidas de bucles con nombre, procesamiento de errores y manejo de excepciones.
El argumento en contrairs
El argumento general en contrairs es ese código sinirs es un código de mayor calidad. La
famosa carta que desató la controversia original fue la "Declaración Ir a Considerada Dañina"
de Edsger Dijkstra en marzo de 1968.Comunicaciones de la ACM. Dijkstra observó que la
calidad del código era inversamente proporcional al número de irs el programador utilizado.
En trabajos posteriores, Dijkstra argumentó que el código que no contieneirs puede
demostrarse más fácilmente que es correcto.
Código que contieneirs es difícil de formatear. La sangría debe usarse para mostrar la
estructura lógica, yirs tienen un efecto sobre la estructura lógica. Uso de la sangría para
mostrar la estructura lógica de uniry su objetivo, sin embargo, es difícil o imposible.
Uso deirs derrota las optimizaciones del compilador. Algunas optimizaciones dependen del flujo de
control de un programa que reside en unas pocas declaraciones. un incondicionalirhace que el flujo
sea más difícil de analizar y reduce la capacidad del compilador para optimizar el código. Así, incluso
si se introduce unirproduce una eficiencia en el nivel del lenguaje de origen, bien puede reducir la
eficiencia general al frustrar las optimizaciones del compilador.
Los defensores deirLos s a veces argumentan que hacen que el código sea más rápido o más pequeño. Pero
el código que contieneirs rara vez es el más rápido o el más pequeño posible. El maravilloso y clásico
artículo de Donald Knuth “Programación estructurada con sentencias go to” brinda varios ejemplos de casos
en los que usarirs hace que el código sea más lento y más grande (Knuth 1974).
En la práctica, el uso deirs conduce a la violación del principio de que el código debe fluir estrictamente de
arriba hacia abajo. Incluso siirs no son confusos cuando se usan con cuidado, una vezirSe introducen s, se
propagan a través del código como termitas a través de una casa en descomposición. Si algunairLos malos
están permitidos, los malos se meten con los buenos, así que es mejor no permitir ninguno de ellos.
17..3 ir a 399
El argumento a favorirs
El argumento de lairse caracteriza por una defensa de su uso cuidadoso en circunstancias específicas
en lugar de su uso indiscriminado. La mayoría de los argumentos en contrairs hablar en contra del
uso indiscriminado. losirla controversia estalló cuando Fortran era el idioma más popular. Fortran no
tenía estructuras de bucle presentables, y en ausencia de buenos consejos sobre la programación de
bucles conirs, los programadores escribieron una gran cantidad de código de espagueti. Sin duda,
dicho código se correlacionó con la producción de programas de baja calidad, pero tiene poco que
ver con el uso cuidadoso de unirpara compensar una brecha en las capacidades de un lenguaje
moderno.
Referencia cruzadaPara obtener detalles losires útil en una rutina que asigna recursos, realiza operaciones en esos recursos
sobre el usoirs en el código que asigna
y luego desasigna los recursos. Con unir, puede limpiar en una sección de código.
recursos, consulte “Procesamiento de
“Excepciones”. En algunos casos, elirpuede dar como resultado un código más rápido y más pequeño. El artículo de Knuth de 1974
citó algunos casos en los que elirprodujo una ganancia legítima.
La evidencia sugiere solo que la Décadas de investigación conirs no pudo demostrar su nocividad. En un estudio de la literatura, BA
estructura de control
Sheil concluyó que las condiciones de prueba poco realistas, el análisis deficiente de los datos y los
deliberadamente caótica degrada
el falsoirDebate
Una característica principal de la mayoríairdiscusiones es un enfoque superficial de la
pregunta. El argumentador sobre el “irs are evil” presenta un fragmento de código trivial que
utilizairs y luego muestra lo fácil que es reescribir el fragmento sinirs. Esto prueba
principalmente que es fácil escribir código trivial sinirs.
El argumentador sobre el "No puedo vivir sinirlado s” por lo general presenta un caso en el que la
eliminación de unirda como resultado una comparación adicional o la duplicación de una línea de código.
Esto prueba principalmente que hay un caso en el que usar unirda como resultado una comparación menos,
no una ganancia significativa en las computadoras de hoy.
La mayoría de los libros de texto no ayudan. Proporcionan un ejemplo trivial de reescribir algún código sin
unircomo si eso cubriera el tema. Aquí hay un ejemplo disfrazado de una pieza de código trivial de un libro
de texto de este tipo:
Este ejemplo llamado "trivial" contiene un error. En el caso en quedatoses igual-1 Al entrar en
el bucle, el código traducido detecta el-1y sale del bucle antes de ejecutar Hacer algo(). El
código original se ejecutaHacer algo()antes de-1es detectado. El libro de programación que
intenta mostrar lo fácil que es codificar sinirs tradujo su propio ejemplo incorrectamente. Pero
el autor de ese libro no debería sentirse tan mal; otros libros cometen errores similares.
Incluso los profesionales tienen dificultades para traducir el código que utilizairs.
Incluso con una traducción correcta del código, el ejemplo sigue siendo falso porque muestra
un uso trivial delir. Tales casos no son aquellos para los cuales los programadores sensatos
eligen unircomo su forma preferida de control.
Sería difícil en esta fecha tardía agregar algo que valga la pena a la teoríair debate. Sin
embargo, lo que generalmente no se aborda es la situación en la que un programador es
plenamente consciente de lair-menos alternativas elige usar unirpara mejorar la legibilidad y la
mantenibilidad.
Las siguientes secciones presentan casos en los que algunos programadores experimentados
han defendido el uso deirs. Las discusiones proporcionan ejemplos de código conirs y código
reescrito sinirs y evaluar las ventajas y desventajas entre las versiones.
MakePurgeFileList(fileList, numFilesToPurge)
Final Tiempo
Esta rutina es típica de circunstancias en las que los programadores experimentados deciden
utilizar unir. Casos similares surgen cuando una rutina necesita asignar y limpiar recursos
como conexiones de base de datos, memoria o archivos temporales. La alternativa a irs en
esos casos suele duplicar código para limpiar los recursos. En tales casos, un programador
podría equilibrar el mal de laircontra el dolor de cabeza del mantenimiento de código
duplicado y decidir que elires el mal menor.
Puede reescribir la rutina anterior en un par de formas para evitarirs, y ambas formas implican
compensaciones. Las posibles estrategias de reescritura son las siguientes:
Referencia cruzadaEsta rutina Código de Visual Basic que evitairs usando anidadosis
también podría reescribirse con
' Esta rutina purga un grupo de archivos.
descansoy noirs. Para obtener Sub PurgeFiles (ByRef errorState As Error_Code)
detalles sobre ese enfoque, consulte
Dim fileIndex As Integer Dim fileToPurge
"Salir temprano de los bucles" en la
As Data_File Dim fileList As File_List Dim
Sección 16.2.
numFilesToPurge As Integer
MakePurgeFileList(fileList, numFilesToPurge)
Para gente acostumbrada a programar sinirs, este código podría ser más fácil de leer
que elirversión, y si la usas, no tendrás que enfrentar una inquisición delir escuadrón de
matones.
Referencia cruzadaPara obtener más La principal desventaja de este anidado-sienfoque es que el nivel de anidamiento es profundo, muy
detalles sobre la sangría y otros
profundo. Para comprender el código, debe mantener todo el conjunto de anidadossis en su mente a
problemas de diseño de codificación,
Con elirversión, ninguna declaración está a más de cuatro líneas de la condición que la
profundo”.
Reescribir con una variable de estadoPara reescribir con una variable de estado (también
llamada variable de estado), cree una variable que indique si la rutina está en un estado de error.
En este caso, la rutina ya utiliza elestado de errorvariable de estado, por lo que puede usar eso.
Código de Visual Basic que evitairs mediante el uso de una variable de estado
' Esta rutina purga un grupo de archivos.
Sub PurgeFiles (ByRef errorState As Error_Code)
Dim fileIndex As Integer Dim fileToPurge
As Data_File Dim fileList As File_List Dim
numFilesToPurge As Integer
MakePurgeFileList(fileList, numFilesToPurge)
Terminara si
Terminara si
Terminara si
Final Tiempo
DeletePurgeFileList(fileList, numFilesToPurge)
Final Sub
La desventaja de este enfoque es que el uso de variables de estado no es una práctica tan común
como debería ser. Documente su uso completamente, o algunos programadores podrían no
entender lo que está haciendo. En este ejemplo, el uso de tipos enumerados bien nombrados
ayuda significativamente.
Para reescribir usando elintentar-finalmenteenfoque, incluya el código que, de otro modo, tendría
que comprobar si hay errores dentro de unprobarbloque y coloque el código de limpieza dentro de
unfinalmentebloquear. losprobarEl bloque especifica el alcance del manejo de excepciones y el
finalmentebloque realiza cualquier limpieza de recursos. losfinalmentebloque siempre se llamará
independientemente de si se lanza una excepción y sin importar si elPurgar archivos ()rutina capturas
cualquier excepción que se lanza.
17..3 ir a 405
índice de archivo = 0
Mientras que (fileIndex < numFilesToPurge )
índicearchivo = índicearchivo + 1
FindFile(fileList(fileIndex), fileToPurge) OpenFile(fileToPurge
)
Archivo de sobrescritura( archivo para purgar)
Borrar( archivo para purgar)
Terminar mientras
Finalmente
DeletePurgeFileList(fileList, numFilesToPurge) Finalizar intento
Final Sub
Este enfoque asume que todas las llamadas a funciones arrojan excepciones por fallas en lugar de devolver
códigos de error.
Descargar desde Guau! Libro electrónico <www.wowebook.com>
Cualquiera de estas técnicas funciona bien cuando se aplica de forma coherente a todo el código de un
proyecto. Considere todas las compensaciones y luego tome una decisión en todo el proyecto sobre qué
método favorecer.
406 Capítulo 17: Estructuras de control inusuales
BUCLE_MEDIO:
// mucho código
...
}
Este es un buen ejemplo porque es lógicamente tortuoso: es casi imposible leerlo tal
como está, y es difícil reescribirlo correctamente sin unir. Si cree que puede reescribirlo
fácilmente sinirs, ¡pídele a alguien que revise tu código! Varios programadores expertos
lo han reescrito incorrectamente.
Puede reescribir el código de varias maneras. Puede duplicar el código, poner el código común en una
rutina y llamarlo desde dos lugares, o volver a probar las condiciones. En la mayoría de los idiomas, la
reescritura será un poquito más grande y más lenta que la original, pero será extremadamente parecida. A
menos que el código esté en un bucle muy activo, reescríbalo sin pensar en la eficiencia.
La mejor reescritura sería poner el//un montón de códigoparte en su propia rutina. Luego
puede llamar a la rutina desde los lugares que de otro modo habría utilizado como orígenes o
destinos deirs y preservar la estructura original del condicional. Así es como se ve:
Normalmente, escribir una nueva rutina es el mejor enfoque. A veces, sin embargo, no es
práctico poner código duplicado en su propia rutina. En este caso, puede evitar la
solución poco práctica reestructurando el condicional para mantener el código en la
misma rutina en lugar de colocarlo en una nueva rutina:
// mucho código
...
}
Referencia cruzadaOtro enfoque para Esta es una traducción fiel y mecánica de la lógica en elirversión. pruebaestadoOKdos veces extra y
este problema es utilizar una tabla de
datos disponiblesuna vez, pero el código es equivalente. Si te molesta volver a probar los
decisiones. Para obtener más
En el caso restante de 100 en el que unires una solución legítima al problema, documéntelo
claramente y utilícelo. Si tiene puestas las botas de lluvia, no vale la pena caminar alrededor de
la cuadra para evitar un charco de lodo. Pero mantén tu mente abierta air-menos enfoques
sugeridos por otros programadores. Es posible que vean algo que tú no.
falta un código, es decir, el código que va a las etiquetas. Si las etiquetas no se utilizan, elimínelas.
- Habilidad para llamar a una rutina con un número de línea o etiqueta que permitió que la
ejecución comenzara en algún lugar en medio de la rutina
- Capacidad para que el programa genere código sobre la marcha y luego ejecute el código que acaba
de escribir
En un momento, cada una de estas ideas se consideró aceptable o incluso deseable, aunque ahora todas
parecen irremediablemente pintorescas, anticuadas o peligrosas. El campo del desarrollo de software ha
avanzado en gran medida a través derestringiendolo que los programadores pueden hacer con su código.
En consecuencia, veo las estructuras de control no convencionales con gran escepticismo. Sospecho que la
mayoría de las construcciones en este capítulo eventualmente encontrarán su camino en el montón de
chatarra del programador junto con computadoiretiquetas, puntos de entrada de rutinas variables, código
automodificable y otras estructuras que favorecían la flexibilidad y la comodidad sobre la estructura y la
capacidad de gestionar la complejidad.
Recursos adicionales
cc2e.com/1792 Los siguientes recursos también abordan estructuras de control inusuales:
Devoluciones
Fowler, Martín.Refactorización: mejora del diseño del código existente. Reading, MA: Addison-
Wesley, 1999. En la descripción de la refactorización llamada "Reemplazar condicional anidado con
cláusulas de protección", Fowler sugiere usar múltiplesdevolverdeclaraciones
Recursos adicionales 409
de una rutina para reducir la anidación en un conjunto desideclaraciones. Fowler argumenta que múltiples
devolverLas s son un medio apropiado para lograr una mayor claridad y que no se produce ningún daño
por tener múltiples retornos de una rutina.
irs
Estos artículos contienen la totalidadirdebate. Aparece de vez en cuando en la mayoría de los lugares
de trabajo, libros de texto y revistas, pero no escuchará nada que no haya sido explorado por
completo hace 20 años.
Wulf, WA "Un caso contra GOTO".Actas de la 25ª Conferencia Nacional ACM, agosto de 1972:
791–97. Este trabajo fue otro argumento contra el uso indiscriminado deirs. Wulf argumentó
que si los lenguajes de programación proporcionaran estructuras de control adecuadas,irs se
volvería en gran medida innecesario. Desde 1972, cuando se escribió el artículo, lenguajes
como C++, Java y Visual Basic han demostrado que Wulf tenía razón.
La respuesta que generó la carta de Rubin fue más interesante que la carta misma. Durante
cinco meses,Comunicaciones de la ACM (CACM)publicó cartas que ofrecían diferentes
versiones del programa original de siete líneas de Rubin. Las cartas se dividieron
equitativamente entre los que defendíanirs y los que los castigan. Los lectores sugirieron
aproximadamente 17 reescrituras diferentes, y el código reescrito cubrió completamente el
espectro de enfoques para evitarirs. el redactor deMCCAseñaló que la carta había generado
más respuesta que cualquier otro tema considerado en las páginas deMCCA.
devolver
recursividad
- ¿La rutina recursiva incluye código para detener la recursividad?
- ¿La rutina utiliza un contador de seguridad para garantizar que la rutina se detenga?
- ¿Está la profundidad de recursión de la rutina dentro de los límites impuestos por el tamaño
de la pila del programa?
- ¿Es la recursividad la mejor manera de implementar la rutina? ¿Es mejor que una simple
iteración?
ir
- Sonir¿Se usa solo como último recurso, y luego solo para hacer que el código sea más
legible y mantenible?
Puntos clave
- MúltipledevolverLos s pueden mejorar la legibilidad y el mantenimiento de una rutina, y
ayudan a evitar una lógica profundamente anidada. Sin embargo, deben usarse con cuidado.
- La recursividad proporciona soluciones elegantes a un pequeño conjunto de problemas. Úselo con cuidado, también.
- En algunos casos,irLos s son la mejor manera de escribir código que sea legible y mantenible.
Tales casos son raros. Usarirs sólo como último recurso.
capitulo 18
cc2e.com/1865 Contenido
- 18.1 Consideraciones generales sobre el uso de métodos controlados por tablas: página 411
Temas relacionados
Un método basado en tablas es un esquema que le permite buscar información en una tabla en lugar de
usar declaraciones lógicas (siycaso) para averiguarlo. Prácticamente cualquier cosa que pueda seleccionar
con declaraciones lógicas, puede seleccionar con tablas en su lugar. En casos simples, las declaraciones
lógicas son más fáciles y directas. A medida que la cadena lógica se vuelve más compleja, las tablas se
vuelven cada vez más atractivas.
Si ya está familiarizado con los métodos basados en tablas, este capítulo podría ser solo una revisión. En
ese caso, puede examinar el "Ejemplo de formato de mensaje flexible" en la Sección 18.2 para obtener un
buen ejemplo de cómo un diseño orientado a objetos no es necesariamente mejor que cualquier otro tipo
de diseño simplemente porque está orientado a objetos, y luego podría pasar a la discusión de los
problemas generales de control en el Capítulo 19.
Usado en circunstancias apropiadas, el código basado en tablas es más simple que la lógica
complicada, más fácil de modificar y más eficiente. Suponga que desea clasificar los caracteres
en letras, signos de puntuación y dígitos; podrías usar una cadena lógica complicada como
PUNTO CLAVE
esta:
411
412 Capítulo 18: Métodos controlados por tablas
Si usara una tabla de búsqueda en su lugar, almacenaría el tipo de cada carácter en una matriz a la que se
accede mediante el código de carácter. El fragmento de código complicado que se acaba de mostrar sería
reemplazado por esto:
Ejemplo de Java del uso de una tabla de búsqueda para clasificar un personaje
charType = charTypeTable[ inputChar ];
Este fragmento asume que elcharTypeTableLa matriz se ha configurado anteriormente. Usted pone
el conocimiento de su programa en sus datos en lugar de en su lógica, en la tabla en lugar de en elsi
pruebas
Cuando utiliza métodos basados en tablas, debe abordar dos problemas. Primero debe
abordar la cuestión de cómo buscar entradas en la tabla. Puede utilizar algunos datos para
acceder a una tabla directamente. Si necesita clasificar los datos por mes, por ejemplo,
PUNTO CLAVE
ingresar una tabla mensual es sencillo. Puede utilizar una matriz con índices del 1 al 12.
Otros datos son demasiado incómodos para buscar una entrada de tabla directamente. Si necesita clasificar
los datos por Número de Seguro Social, por ejemplo, no puede usar el Número de Seguro Social para
ingresar la tabla directamente a menos que pueda permitirse almacenar 999-99-9999 entradas en su tabla.
Estás obligado a utilizar un enfoque más complicado. Aquí hay una lista de formas de buscar una entrada en
una tabla:
- Acceso directo
- Acceso indexado
Cada uno de estos tipos de acceso se describe con más detalle en las subsecciones más adelante en este
capítulo.
18.2 Tablas de acceso directo 413
El segundo problema que debe abordar si está utilizando un método basado en tablas es lo que debe
almacenar en la tabla. En algunos casos, el resultado de una búsqueda en una tabla son datos. Si ese
es el caso, puede almacenar los datos en la tabla. En otros casos, el resultado de una búsqueda en
PUNTO CLAVE
una tabla es una acción. En tal caso, puede almacenar un código que describa la acción o, en algunos
lenguajes, puede almacenar una referencia a la rutina que implementa la acción. En cualquiera de
estos casos, las tablas se vuelven más complicadas.
(aedad
, ya
mri )
Tabla de búsqueda
Figura 18-1Como sugiere el nombre, una tabla de acceso directo le permite acceder directamente al
elemento de la tabla que le interesa.
Ejemplo de Visual Basic de una forma torpe de determinar el número de días en un mes
Si (mes = 1) Entonces
días = 31
ElseIf ( mes = 2 ) Entonces
días = 28
ElseIf ( mes = 3 ) Entonces
días = 31
ElseIf ( mes = 4 ) Entonces
días = 30
ElseIf ( mes = 5 ) Entonces
días = 31
ElseIf ( mes = 6 ) Entonces
días = 30
ElseIf ( mes = 7 ) Entonces
días = 31
Traducido del inglés al español - www.onlinedoctranslator.com
Una forma más fácil y modificable de realizar la misma función es colocar los datos en una
tabla. En Microsoft Visual Basic, primero configuraría la tabla:
Ahora, en lugar de usar el largosideclaración, puede usar un acceso de matriz simple para
averiguar la cantidad de días en un mes:
Si quisiera tener en cuenta el año bisiesto en la versión de búsqueda de tabla, el código seguiría siendo
simple, asumiendoÍndice de año bisiesto()tiene un valor de cualquiera0o1:
Determinar el número de días por mes es un ejemplo conveniente porque puede usar elmesvariable
para buscar una entrada en la tabla. A menudo puede utilizar los datos que habrían controlado una
gran cantidad desideclaraciones para acceder a una tabla directamente.
18.2 Tablas de acceso directo 415
La versión abreviada de la estructura lógica debería ser suficiente para darle una idea de lo
complicado que puede llegar a ser este tipo de cosas. No muestra mujeres casadas, ningún
hombre o la mayoría de las edades entre 18 y 65 años. Puede imaginar lo complicado que se
volvería si programara toda la tabla de tarifas.
Podrías decir: “Sí, pero ¿por qué hiciste una prueba para cada edad? ¿Por qué no pones
las tasas en matrices para cada edad?”. Esa es una buena pregunta, y una mejora obvia
sería colocar las tasas en matrices separadas para cada edad.
416 Capítulo 18: Métodos controlados por tablas
Sin embargo, una mejor solución es poner las tasas en matrices para todos los factores, no solo para
la edad. Así es como declararía la matriz en Visual Basic:
Ejemplo de Visual Basic de declaración de datos para configurar una tabla de tarifas de seguros
Enumeración pública SmokingStatus
SmokingStatus_First = 0
FumarEstado_Fumar =0
SmokingStatus_NonSmoking =1
SmokingStatus_Last = 1
Enumeración final
Referencia cruzadaUna Una vez que declara la matriz, debe encontrar alguna forma de poner datos en ella. Puede usar
La ventaja de un enfoque basado en
declaraciones de asignación, leer los datos de un archivo de disco, calcular los datos o hacer lo que
tablas es que puede colocar los datos
información sobre la idea, consulte la Ejemplo de Visual Basic de una forma elegante de determinar una tasa de seguro
Sección 10.6, “Tiempo de vinculación”.
rate = rateTable(estado de fumador, sexo, estado civil, edad)
Este enfoque tiene las ventajas generales de reemplazar la lógica complicada con una tabla de
búsqueda. La búsqueda en la tabla es más legible y más fácil de cambiar.
lo necesitabas Sin embargo, en algunos casos, los datos son demasiado complicados para describirlos con
código fijo.sideclaraciones.
Si cree que tiene una idea de cómo funcionan las tablas de acceso directo, es posible que desee
omitir el siguiente ejemplo. Sin embargo, es un poco más complicado que los ejemplos anteriores y
demuestra aún más el poder de los enfoques basados en tablas.
Suponga que está escribiendo una rutina para imprimir mensajes que están almacenados en un archivo.
El archivo suele tener alrededor de 500 mensajes y cada archivo tiene alrededor de 20 tipos de mensajes.
Los mensajes provienen originalmente de una boya y dan la temperatura del agua, la ubicación de la
boya, etc.
Cada uno de los mensajes tiene varios campos, y cada mensaje comienza con un encabezado que tiene una
identificación que le permite saber con cuál de los 20 o más tipos de mensajes está tratando. La figura 18-2
ilustra cómo se almacenan los mensajes.
ID para boya
Mensaje de temperatura
ID para boya
Mensaje de deriva
ID para boya
Mensaje de ubicación
Figura 18-2 Los mensajes se almacenan sin ningún orden en particular y cada uno se identifica con un
identificador de mensaje
Tiempo de medición
(hora del día)
Figura 18-3Además del ID del mensaje, cada tipo de mensaje tiene su propio formato.
Cada vez que cambia el formato de cualquier mensaje, debe cambiar la lógica en la
rutina o clase responsable de ese mensaje. En el mensaje detallado anterior, si el campo
de temperatura promedio cambió de un punto flotante a otra cosa, tendría que cambiar
la lógica deImprimirBoyaTemperatureMessage(). (Si la boya misma cambiara de un
"punto flotante" a otra cosa, ¡tendría que comprar una boya nueva!)
En el enfoque basado en la lógica, la rutina de lectura de mensajes consta de un bucle para leer cada
mensaje, decodificar la identificación y luego llamar a una de las 20 rutinas según la identificación del
mensaje. Aquí está el pseudocódigo para el enfoque basado en la lógica:
El pseudocódigo está abreviado porque puede hacerse una idea sin ver los 20 casos.
Este es el código para un solo tipo de mensaje. Cada uno de los otros 19 tipos de mensajes
requeriría un código similar. Y si se agregó un tipo de mensaje 21, se necesitaría agregar una
rutina 21 o una subclase 21; de cualquier manera, un nuevo tipo de mensaje requeriría que se
cambiara el código.
420 Capítulo 18: Métodos controlados por tablas
El enfoque basado en tablas es más económico que el enfoque anterior. La rutina de lectura de
mensajes consta de un bucle que lee el encabezado de cada mensaje, decodifica el ID, busca la
descripción del mensaje en elMensajematriz, y luego llama a la misma rutina cada vez para
decodificar el mensaje. Con un enfoque basado en tablas, puede describir el formato de cada
mensaje en una tabla en lugar de codificarlo en la lógica del programa. Esto facilita la
codificación original, genera menos código y facilita el mantenimiento sin cambiar el código.
Para usar este enfoque, comienza enumerando los tipos de mensajes y los tipos de campos. En C++,
podría definir los tipos de todos los campos posibles de esta manera:
En lugar de codificar rutinas de impresión para cada uno de los 20 tipos de mensajes, puede
crear un puñado de rutinas que impriman cada uno de los tipos de datos primarios: punto
flotante, entero, cadena de caracteres, etc. Puede describir el contenido de cada tipo de
mensaje en una tabla (incluido el nombre de cada campo) y luego decodificar cada mensaje
según la descripción de la tabla. Una entrada de tabla para describir un tipo de mensaje podría
verse así:
Campos numéricos 5
Esta tabla podría estar codificada en el programa (en cuyo caso, cada uno de los elementos que se
muestran se asignaría a las variables), o podría leerse desde un archivo al iniciar el programa o más
tarde.
Una vez que las definiciones de mensajes se leen en el programa, en lugar de tener toda la información
incrustada en la lógica de un programa, la tiene incrustada en los datos. Los datos tienden a ser
18.2 Tablas de acceso directo 421
más flexible que la lógica. Los datos son fáciles de cambiar cuando cambia el formato de un mensaje. Si tiene
que agregar un nuevo tipo de mensaje, simplemente puede agregar otro elemento a la tabla de datos.
Aquí está el pseudocódigo para el bucle de nivel superior en el enfoque basado en tablas:
Las primeras tres líneas aquí son Mientras más mensajes para leer
las mismas que en el enfoque Leer un encabezado de mensaje
basado en la lógica. Decodificar el ID del mensaje del encabezado del mensaje
Busque la descripción del mensaje en la tabla de descripción del mensaje. Lea los campos del
mensaje e imprímalos en función de la descripción del mensaje. Finalizar mientras
de (entero)
leer un valor entero imprimir la
etiqueta del campo imprimir el
valor entero
de (cadena de caracteres)
leer una cadena de caracteres
imprimir la etiqueta del campo
imprimir la cadena de caracteres
de (booleano)
leer una sola bandera imprimir
la etiqueta del campo imprimir
la sola bandera
de (campo de bits)
leer un campo de bits imprimir
la etiqueta del campo imprimir
el campo de bits Caso
Final
Final Tiempo
422 Capítulo 18: Métodos controlados por tablas
Es cierto que esta rutina con sus seis casos es más larga que la única rutina necesaria para
imprimir el mensaje de temperatura de la boya. Pero esta es la única rutina que necesitas. No
necesita otras 19 rutinas para los otros 19 tipos de mensajes. Esta rutina maneja los seis tipos
de campos y se ocupa de todos los tipos de mensajes.
Esta rutina también muestra la forma más complicada de implementar este tipo de búsqueda
de tabla porque utiliza uncasodeclaración. Otro enfoque sería crear una clase abstracta.
ResumenCampoy luego crea subclases para cada tipo de campo. no necesitarás uncaso
declaración; puede llamar a la rutina miembro del tipo de objeto apropiado.
}
};
Este fragmento de código declara una rutina miembro para cada clase que tiene un parámetro de cadena y
unEstado del archivoparámetro.
El siguiente paso es declarar una matriz para contener el conjunto de objetos. La matriz es la tabla
de búsqueda, y así es como se ve:
Ejemplo de C++ de configuración de una mesa para contener un objeto de cada tipo
CampoAbstracto* campo[ Campo_Último+1];
El paso final requerido para configurar la tabla de objetos es asignar los nombres de objetos
específicos a laCampoformación:
Este fragmento de código asume queCampo de punto flotantey los otros identificadores en el lado
derecho de las declaraciones de asignación son nombres de objetos de tipoResumenCampo. Asignar
los objetos a los elementos de la matriz en la matriz significa que puede llamar a la correctaLeer e
imprimir ()rutina haciendo referencia a un elemento de matriz en lugar de usar un tipo específico de
objeto directamente.
Una vez configurada la tabla de rutinas, puede manejar un campo en el mensaje simplemente
accediendo a la tabla de objetos y llamando a una de las rutinas miembro de la tabla. El código
se ve así:
objetos.
Recuerde las 34 líneas originales del pseudocódigo de búsqueda de tabla que contiene elcaso¿declaración?
Si reemplaza elcasodeclaración con una tabla de objetos, este es todo el código que necesitaría para
proporcionar la misma funcionalidad. Increíblemente, también es todo el código necesario para reemplazar
las 20 rutinas individuales en el enfoque basado en la lógica. Además, si las descripciones de los mensajes se
leen desde un archivo, los nuevos tipos de mensajes no requerirán cambios de código a menos que haya un
nuevo tipo de campo.
Puede utilizar este enfoque en cualquier lenguaje orientado a objetos. Es menos propenso a errores,
más fácil de mantener y más eficiente que largossideclaraciones,casodeclaraciones, o subclases
copiosas.
El hecho de que un diseño use herencia y polimorfismo no lo convierte en un buen diseño. El diseño
orientado a objetos de memoria descrito anteriormente en la sección "Enfoque orientado a objetos"
requeriría tanto código como un diseño funcional de memoria, o más. Ese enfoque hizo que el
espacio de soluciones fuera más complicado, en lugar de menos. La idea clave del diseño en este
caso no es la orientación a objetos ni la orientación funcional: es el uso de una tabla de búsqueda
bien pensada.
En cada uno de los tres ejemplos anteriores, podría usar los datos para ingresar la tabla
directamente. Es decir, podrías usarID de mensajecomo una llave sin alteración, como podrías
usar mesen el ejemplo de días por mes ygénero,Estado civil, yfumandoEstadoen el ejemplo de
las tasas de seguros.
Siempre le gustaría ingresar una tabla directamente porque es simple y rápido. A veces, sin
embargo, los datos no son cooperativos. En el ejemplo de las tasas de seguros,añosno se portó bien.
La lógica original tenía una tarifa para menores de 18 años, tarifas individuales por edades
V413HAV
424 Capítulo 18: Métodos controlados por tablas
18 a 65, y una tasa para personas mayores de 65 años. Esto significaba que para las edades de 0 a 17 y 66 y
mayores, no podía usar la edad para ingresar directamente en una tabla que almacenaba solo un conjunto
de tasas para varias edades.
Esto lleva al tema de falsificar claves de búsqueda de tablas. Puede falsificar teclas de varias maneras:
Información duplicada para que la llave funcione directamenteUna forma sencilla de haceraños
trabajar como una clave en la tabla de tarifas es duplicar las tarifas para menores de 18 años para
cada una de las edades de 0 a 17 y luego usar la edad para ingresar directamente en la tabla. Puede
hacer lo mismo para las personas mayores de 66 años. Los beneficios de este enfoque son que la
estructura de la tabla en sí misma es sencilla y los accesos a la tabla también son sencillos. Si necesita
agregar tarifas específicas por edad para menores de 17 años, simplemente puede cambiar la tabla.
Los inconvenientes son que la duplicación desperdiciaría espacio para información redundante y
aumentaría la posibilidad de errores en la tabla, aunque solo sea porque la tabla contendría datos
redundantes.
Transforma la clave para que funcione directamente.Una segunda forma de hacerAñostrabajar como
una tecla directa es aplicar una función aAñospara que funcione bien. En este caso, la función tendría que
cambiar todas las edades de 0 a 17 a una clave, digamos 17, y todas las edades mayores de 66 a otra clave,
digamos 66. Este rango en particular se comporta lo suficientemente bien como para que pueda usar min()y
máx()funciones para hacer la transformación. Por ejemplo, podrías usar la expresión
Aislar la transformación clave en su propia rutinaSi tiene que modificar los datos para que
funcionen como una clave de tabla, coloque la operación que cambia los datos a una clave en
su propia rutina. Una rutina elimina la posibilidad de usar diferentes transformaciones en
diferentes lugares. Facilita las modificaciones cuando cambia la transformación. Un buen
nombre para la rutina, comoClaveDeEdad(), también aclara y documenta el propósito de las
maquinaciones matemáticas.
Si su entorno proporciona transformaciones clave listas para usar, utilícelas. Por ejemplo, Java
proporcionamapa hash, que se puede utilizar para asociar pares clave/valor.
18.3 Tablas de acceso indexado 425
Cuando usa índices, usa los datos primarios para buscar una clave en una tabla de índice y
luego usa el valor de la tabla de índice para buscar los datos principales que le interesan.
Suponga que administra un almacén y tiene un inventario de alrededor de 100 artículos. Suponga
además que cada elemento tiene un número de pieza de cuatro dígitos que va desde 0000 hasta
9999. En este caso, si desea usar el número de pieza para ingresar directamente en una tabla que
describe algún aspecto de cada elemento, configura un índice matriz con 10.000 entradas (de 0 a
9999). La matriz está vacía excepto por las 100 entradas que corresponden a los números de pieza de
los 100 artículos en su almacén. Como muestra la figura 18-4, esas entradas apuntan a una tabla de
descripción de elementos que tiene mucho menos de 10 000 entradas.
Matriz de índices en
Tabla de búsqueda
(principalmente vacío)
Tabla de búsqueda
(mayormente lleno)
Figura 18-4En lugar de acceder directamente, se accede a una tabla de acceso indexada a través de un
índice intermedio.
Los esquemas de acceso indexado ofrecen dos ventajas principales. Primero, si cada una de las entradas en
la tabla de búsqueda principal es grande, se necesita mucho menos espacio para crear una matriz de índice
con mucho espacio desperdiciado que para crear una tabla de búsqueda principal con mucho espacio
desperdiciado. Por ejemplo, suponga que la tabla principal ocupa 100 bytes por entrada y que el índice
426 Capítulo 18: Métodos controlados por tablas
matriz toma 2 bytes por entrada. Suponga que la tabla principal tiene 100 entradas y que los datos
utilizados para acceder a ella tienen 10 000 valores posibles. En tal caso, la elección es entre tener un
índice con 10 000 entradas o un miembro de datos principal con 10 000 entradas. Si usa un índice, su
uso total de memoria es de 30,000 bytes. Si renuncia a la estructura del índice y desperdicia espacio
en la tabla principal, el uso total de la memoria es de 1 000 000 de bytes.
La segunda ventaja, incluso si no ahorra espacio usando un índice, es que a veces es más económico
manipular las entradas en un índice que las entradas en una tabla principal. Por ejemplo, si tiene una
tabla con nombres de empleados, fechas de contratación y salarios, puede crear un índice que
acceda a la tabla por nombre de empleado, otro que acceda a la tabla por fecha de contratación y un
tercero que acceda a la tabla por salario.
Una ventaja final de un esquema de acceso a índices es la ventaja general de la búsqueda en tablas de la
capacidad de mantenimiento. Los datos codificados en tablas son más fáciles de mantener que los datos
incrustados en el código. Para maximizar la flexibilidad, coloque el código de acceso al índice en su propia
rutina y llame a la rutina cuando necesite obtener una clave de tabla de un número de pieza. Cuando llegue
el momento de cambiar la tabla, puede decidir cambiar el esquema de acceso al índice o cambiar a otro
esquema de búsqueda de tablas por completo. El esquema de acceso será más fácil de cambiar si no
distribuye los accesos de índice a lo largo de su programa.
La idea general de las estructuras escalonadas, ilustrada en la figura 18-5, es que las entradas en una tabla
son válidas para rangos de datos en lugar de puntos de datos distintos.
Figura 18-5El enfoque de escalones clasifica cada entrada determinando el nivel en el que llega a
una "escalera". El "paso" que golpea determina su categoría.
Por ejemplo, si está escribiendo un programa de calificación, el rango de entrada "B" podría ser del 75 al 90
por ciento. Aquí hay una variedad de grados que podría tener que programar algún día:
≥90,0% A
< 90,0% B
<75,0% C
<65,0% D
<50,0% F
18.4 Mesas de acceso para escalones 427
Este es un rango feo para una búsqueda de tabla porque no puede usar una función de
transformación de datos simple para ingresar las letrasAmedianteF. Un esquema de índice
sería incómodo porque los números son de coma flotante. Podría considerar convertir los
números de punto flotante en enteros y, en este caso, sería una opción de diseño válida, pero
a modo de ilustración, este ejemplo se mantendrá con el punto flotante.
Para usar el método de escalones, coloca el extremo superior de cada rango en una tabla y luego escribe un
bucle para comparar una puntuación con el extremo superior de cada rango. Cuando encuentra el punto en
el que la puntuación supera por primera vez la parte superior de un rango, sabe cuál es la calificación. Con
la técnica de escalones, debe tener cuidado de manejar correctamente los extremos de los rangos. Aquí está
el código en Visual Basic que asigna calificaciones a un grupo de estudiantes basado en este ejemplo:
Aunque este es un ejemplo simple, puede generalizarlo fácilmente para manejar varios estudiantes,
múltiples esquemas de calificación (por ejemplo, diferentes calificaciones para diferentes niveles de
puntos en diferentes tareas) y cambios en el esquema de calificación.
La ventaja de este enfoque sobre otros métodos basados en tablas es que funciona bien con datos
irregulares. El ejemplo de calificación es simple en el sentido de que, aunque las calificaciones se
asignan a intervalos irregulares, los números son "redondos" y terminan en 5 y 0. El enfoque
escalonado se adapta igualmente bien a los datos que no terminan perfectamente en 5 y 0. Puede
usar el enfoque de escalón en el trabajo estadístico para distribuciones de probabilidad con números
como este:
0.458747 $0.00
0.547651 $254.32
0.627764 $514.77
0.776883 $747.82
0.893211 $1,042.65
428 Capítulo 18: Métodos controlados por tablas
0.957665 $5,887.55
0.976544 $12,836.98
0.987889 $27,234.12
...
Números feos como estos desafían cualquier intento de crear una función para transformarlos
perfectamente en claves de tabla. El enfoque escalonado es la respuesta.
Este enfoque también disfruta de las ventajas generales de los enfoques basados en tablas: es
flexible y modificable. Si los rangos de calificación en el ejemplo de calificación cambiaran, el
programa podría adaptarse fácilmente modificando las entradas en elLímite de rangoformación.
Puede generalizar fácilmente la parte del programa de asignación de calificaciones para que acepte
una tabla de calificaciones y las puntuaciones de corte correspondientes. La parte del programa de
asignación de calificaciones no tendría que usar puntajes expresados como porcentajes; podría usar
puntos brutos en lugar de porcentajes, y el programa no tendría que cambiar mucho.
Mira los puntos finalesAsegúrese de haber cubierto la caja en el extremo superior de cada rango de
peldaños. Ejecute la búsqueda escalonada para que encuentre elementos que se asignen a cualquier
rango que no sea el rango superior y luego haga que el resto caiga en el rango superior. A veces, esto
requiere crear un valor artificial para la parte superior del rango superior.
Tenga cuidado de no confundir < con <=. Asegúrese de que el ciclo termine correctamente con valores que
se encuentren en los rangos superiores y que los límites del rango se manejen correctamente.
Considere usar una búsqueda binaria en lugar de una búsqueda secuencialEn el ejemplo de
calificación, el bucle que asigna la calificación busca secuencialmente en la lista de límites de
calificación. Si tuviera una lista más grande, el costo de la búsqueda secuencial podría volverse
prohibitivo. Si es así, puede reemplazarlo con una búsqueda cuasi-binaria. Es una búsqueda binaria
“cuasi” porque el objetivo de la mayoría de las búsquedas binarias es encontrar un valor. En este
caso, no espera encontrar el valor; espera encontrar la categoría correcta para el valor. El algoritmo
de búsqueda binaria debe determinar correctamente dónde debe ir el valor. Recuerde también
tratar el punto final como un caso especial.
Considere usar el acceso indexado en lugar de la técnica de escalonesUn esquema de acceso a índices
como los descritos en la Sección 18.3 podría ser una buena alternativa a la técnica de escalones. La
búsqueda requerida en el método escalonado puede sumar, y si la velocidad de ejecución es una
preocupación, es posible que esté dispuesto a cambiar el espacio que ocupa una estructura de índice
adicional por la ventaja de tiempo que obtiene con un método de acceso más directo.
Obviamente, esta alternativa no es una buena elección en todos los casos. En el ejemplo de
calificación, probablemente podría usarlo; si tuviera solo 100 puntos porcentuales discretos, el costo
de memoria de configurar una matriz de índice no sería prohibitivo. Si, por el contrario, tuvieras la
18.5 Otros ejemplos de búsquedas en tablas 429
Referencia cruzadaPara obtener más En algunos casos, cualquiera de las varias opciones podría funcionar. El objetivo del diseño es
información sobre buenos enfoques para
elegir una de las varias buenas opciones para su caso. No te preocupes demasiado por elegir el
elegir alternativas de diseño, consulte el
- Búsqueda de tasas en una tabla de seguros: Sección 16.3, “Creación de bucles fácilmente: de
adentro hacia afuera”
- Uso de tablas de decisiones para reemplazar lógica complicada: “Usar tablas de decisiones
para reemplazar condiciones complicadas” en la Sección 19.1.
- Costo de la paginación de memoria durante una consulta de tabla: Sección 25.3, “Tipos de grasa y
melaza”
- ¿Ha considerado los métodos basados en tablas como una alternativa a la lógica
complicada?
- ¿Ha considerado los métodos basados en tablas como una alternativa a las
estructuras de herencia complicadas?
Puntos clave
- Las tablas proporcionan una alternativa a la lógica complicada y las estructuras de herencia. Si
encuentra que está confundido por la lógica o el árbol de herencia de un programa,
pregúntese si podría simplificar usando una tabla de búsqueda.
- Una consideración clave al usar una tabla es decidir cómo acceder a la tabla. Puede
acceder a las tablas mediante acceso directo, acceso indexado o acceso escalonado.
- Otra consideración clave al usar una tabla es decidir qué poner exactamente en la
tabla.
capitulo 19
cc2e.com/1978 Contenido
Temas relacionados
Ninguna discusión sobre el control estaría completa a menos que aborde varios temas generales
que surgen cuando se piensa en las construcciones de control. La mayor parte de la información de
este capítulo es detallada y pragmática. Si está leyendo sobre la teoría de las estructuras de control
en lugar de los detalles ásperos, concéntrese en la perspectiva histórica de la programación
estructurada en la Sección 19.5 y en las relaciones entre las estructuras de control en la Sección 19.6.
Usa los identificadoresverdaderoyfalsoen expresiones booleanas en lugar de usar valores como 0y1.
La mayoría de los lenguajes modernos tienen un tipo de datos booleano y proporcionan
identificadores predefinidos para verdadero y falso. Lo hacen fácil, ni siquiera le permiten asignar
431
432 Capítulo 19: Problemas generales de control
valores distintos averdaderoofalsoa las variables booleanas. Los idiomas que no tienen un tipo de
datos booleano requieren que tenga más disciplina para que las expresiones booleanas sean
legibles. He aquí un ejemplo del problema:
Ejemplos de Visual Basic del uso de indicadores ambiguos para valores booleanos
Dim printerError As Integer Dim
reportSelected As Integer Dim
CODIFICACIÓN summarySelected As Integer . . .
HORROR
Si usa banderas como0y1es una práctica común, ¿qué tiene de malo? No está claro al leer el
código si las llamadas a funciones se ejecutan cuando las pruebas son verdaderas o cuando
son falsas. Nada en el fragmento de código en sí te dice si1representa la verdad y0falso o si lo
contrario es cierto. Ni siquiera está claro que los valores1y 0se utilizan para representar
verdadero y falso. por ejemplo, en elSi informeSeleccionado = 1línea, la1podría representar
fácilmente el primer informe, un2el segundo, un3El tercero; nada en el código te dice eso1
representa verdadero o falso. También es fácil de escribir.0cuando quieres decir1y viceversa.
Uso de laVerdaderoyFalsoconstantes hace que la intención sea más clara. No tienes que
recordar lo que1y0representar, y no los invertirá accidentalmente. Además, en el
19.1 Expresiones booleanas 433
código reescrito, ahora está claro que algunos de los1arena0s en el ejemplo original de Visual Basic
no se usaban como indicadores booleanos. losSi informeSeleccionado = 1line no era una prueba
booleana en absoluto; probó si el primer informe había sido seleccionado.
Este enfoque le dice al lector que está haciendo una prueba booleana. También es más difícil
escribir.verdaderocuando quieres decirfalsode lo que es escribir1cuando quieres decir0, y evitas
difundir los números mágicos0y1a lo largo de su código. Estos son algunos consejos para definir
verdaderoyfalsoen pruebas booleanas:
El uso de comparaciones implícitas reduce la cantidad de términos que alguien que lee su
código debe tener en cuenta, y las expresiones resultantes se parecen más al inglés
conversacional. El ejemplo anterior podría reescribirse con un estilo aún mejor como este:
Referencia cruzadaPara obtener detalles, Si su idioma no admite variables booleanas y tiene que emularlas, es posible que no
pueda usar esta técnica porque las emulaciones deverdaderoyfalsono siempre se puede
consulte la Sección 12.5, “Variables
booleanas”.
probar con declaraciones comomientras (no hecho).
Dividir pruebas complicadas en pruebas parciales con nuevas variables booleanasEn lugar de crear una
prueba monstruosa con media docena de términos, asigne valores intermedios a los términos que le
permitan realizar una prueba más simple.
434 Capítulo 19: Problemas generales de control
Terminara si
Esta es una prueba fea para tener que leer si no está interesado en la prueba en sí. Al ponerlo
en una función booleana, puede aislar la prueba y permitir que el lector se olvide de ella a
menos que sea importante. Así es como podría poner elsiprobar en una función:
Referencia cruzadaPara obtener Ejemplo de Visual Basic de una prueba complicada trasladada a una función booleana, con
detalles sobre la técnica de usar nuevas variables intermedias para hacer la prueba más clara
variables intermedias para aclarar
Función DocumentoIsValid( _
una prueba booleana, consulte "Use
ByRef documentToCheck As Document, _
variables booleanas para
lineCount As Integer, _
documentar su programa" en la
inputError como booleano _ )
Sección 12.5.
como booleano
Las variables intermedias se introducen allDataRead = ( documentToCheck.AtEndOfStream ) And ( Not inputError ) legalLineCount = ( MIN_LINES <=
aquí para aclarar la prueba en la línea lineCount ) And ( lineCount <= MAX_LINES ) DocumentIsValid = allDataRead And legalLineCount And ( Not
final, a continuación. ErrorProcessing() )
función final
Este ejemplo supone queProcesamiento de errores ()es una función booleana que indica
el estado de procesamiento actual. Ahora, cuando lea el flujo principal del código, no
tiene que leer la prueba complicada:
Ejemplo de Visual Basic del flujo principal del código sin la prueba complicada
Si (DocumentoIsValid(documento, lineCount, inputError)) Entonces
' hacer una cosa u otra . . .
Terminara si
Si usa la prueba solo una vez, es posible que no crea que vale la pena incluirla en una
rutina. Pero poner la prueba en una función bien nombrada mejora la legibilidad y le
facilita ver lo que está haciendo su código, y esa es una razón suficiente para hacerlo.
PUNTO CLAVE
19.1 Expresiones booleanas 435
Referencia cruzadaPara obtener detalles Use tablas de decisión para reemplazar condiciones complicadasA veces tienes una prueba
sobre el uso de tablas como sustitutos de
complicada que involucra varias variables. Puede ser útil usar una tabla de decisiones para realizar la
la lógica complicada, consulte el Capítulo
La prueba en esta línea Ejemplo de Java de una prueba booleana positiva más clara
se ha invertido. si ( estado OK ) {
// hacer algo más . . .
El código en este bloque ha
sido cambiado... }
más {
. . . con el código en este bloque. // hacer algo
...
}
436 Capítulo 19: Problemas generales de control
Referencia cruzadaLa El segundo fragmento de código es lógicamente el mismo que el primero, pero es más
recomendación de enmarcar
fácil de leer porque la expresión negativa se ha cambiado a positiva.
expresiones booleanas
positivamente a veces contradice
Alternativamente, puede elegir un nombre de variable diferente, uno que invierta el valor de
la recomendación de
codifique el caso nominal verdad de la prueba. En el ejemplo, podría reemplazarestadoOKconError detectado, lo cual
después delsien lugar de la sería cierto cuandoestadoOKera falso
más. (Consulte la Sección
15.1, “siDeclaraciones”). En Aplique los teoremas de DeMorgan para simplificar las pruebas booleanas con negativosLos
tal caso, debe pensar en los
teoremas de DeMorgan te permiten explotar la relación lógica entre una expresión y una versión de
beneficios de cada enfoque y
decidir cuál es mejor para su la expresión que significa lo mismo porque es doblemente negada. Por ejemplo, podría tener un
situación. fragmento de código que contenga la siguiente prueba:
Aquí no hay que flip-flopsiymáscláusulas; las expresiones en los dos últimos fragmentos
de código son lógicamente equivalentes. Para aplicar los teoremas de DeMorgan al
operador lógicoyo el operador lógicooy un par de operandos, usted niega cada uno de
los operandos, cambia elyarenaos, y niega toda la expresión. La tabla 19-1 resume las
posibles transformaciones según los teoremas de DeMorgan.
expresiones, consulte
paréntesis exige menos a su lector, que podría no entender las sutilezas de cómo su
"Paréntesis" en la Sección 31.2. lenguaje evalúa las expresiones booleanas. Si es inteligente, no dependerá de su propia
memorización profunda o de la precedencia de evaluación de su lector, especialmente
cuando tenga que cambiar entre dos o más idiomas. Usar paréntesis no es como enviar
un telegrama: no se le cobra por cada carácter, los caracteres adicionales son gratuitos.
Para empezar, esta es una expresión confusa, y es aún más confusa porque no está claro si el
codificador tiene la intención de probar(un < segundo ) == ( c == re )o( (un < segundo ) == c ) == re. La
siguiente versión de la expresión todavía es un poco confusa, pero los paréntesis ayudan:
En este caso, los paréntesis ayudan a la legibilidad y la corrección del programa; el compilador no
habría interpretado el primer fragmento de código de esta manera. En caso de duda, entre
paréntesis.
Referencia cruzadaMuchos editores Utilice una técnica de conteo simple para equilibrar los paréntesisSi tiene problemas para saber
de texto orientados a programadores
si los paréntesis se equilibran, aquí hay un truco de conteo simple que lo ayudará. Comience
tienen comandos que coinciden con
En este ejemplo, terminó con un 0, por lo que los paréntesis están equilibrados. En el siguiente
ejemplo, los paréntesis no están balanceados:
Expresiones lógicas completamente entre paréntesisLos paréntesis son baratos y ayudan a la legibilidad.
Poner entre paréntesis las expresiones lógicas por costumbre es una buena práctica.
Si se evalúa toda esta expresión, obtendrá un error en el último paso por el bucle. La variablei
es igualmaxElements, por lo que la expresiónelemento[ yo ]es equivalente a
artículo[ maxElements ], que es un error de índice de matriz. Puede argumentar que no
importa ya que solo está mirando el valor, no cambiándolo. Pero es una práctica de
programación descuidada y podría confundir a alguien que lea el código. En muchos entornos,
también generará un error de tiempo de ejecución o una infracción de protección.
Muchos lenguajes modernos brindan facilidades que evitan que ocurra este tipo de error
en primer lugar. Por ejemplo, C++ usa evaluación de cortocircuito: si el primer operando
delyes falso, el segundo no se evalúa porque la expresión completa sería falsa de todos
modos. En otras palabras, en C++ la única parte de
que se evalúa esalgo falso. La evaluación se detiene tan pronto comoalgo falsose identifica
como falso.
La evaluación se cortocircuita de manera similar con elooperador. En C++ y Java, la única parte
de
que se evalúa esalgo verdadero. La evaluación se detiene tan pronto comoalgo verdaderose identifica como
verdadera porque la expresión siempre es verdadera si alguna parte de ella es verdadera. Como resultado
de este método de evaluación, la siguiente declaración es una buena declaración legal.
Por otra parte, porque el&& (y)se evalúa de izquierda a derecha, la siguiente declaración
lógicamente equivalente no funciona:
Java complica aún más esta imagen al proporcionar operadores "lógicos". lógica de Java& y|Los
operadores garantizan que todos los términos serán completamente evaluados sin importar si la
verdad o falsedad de la expresión podría determinarse sin una evaluación completa. En otras
palabras, en Java, esto es seguro:
Ejemplo de Java de una prueba que funciona debido a la evaluación de cortocircuito (condicional)
if ( ( denominador != 0 ) && ( ( elemento / denominador ) > MIN_VALUE ) ) ...
440 Capítulo 19: Problemas generales de control
Ejemplo Java de una prueba que no funciona porque la evaluación de cortocircuito no está
garantizada
if ( ( denominador != 0 ) & ( ( item / denominador ) > MIN_VALUE ) ) ...
Diferentes lenguajes usan diferentes tipos de evaluación, y los implementadores de lenguaje tienden
a tomarse libertades con la evaluación de expresiones, así que consulte el manual de la versión
específica del lenguaje que está usando para averiguar qué tipo de evaluación usa su lenguaje. Mejor
PUNTO CLAVE
aún, dado que un lector de su código puede no ser tan inteligente como usted, use pruebas anidadas
para aclarar sus intenciones en lugar de depender del orden de evaluación y la evaluación de
cortocircuito.
MIN_ELEMENTOS MAX_ELEMENTOS
Figura 19-1Ejemplos del uso de la ordenación de líneas numéricas para pruebas booleanas.
Pero siise supone que es más grande, tendrás una prueba como esta:
mientras (!hecho)...
Esta comparación implícita con0es apropiado porque la comparación está en una expresión
lógica.
mientras (equilibrio)...
mientras (*charPtr)...
Ejemplo en C++ de colocar una constante en el lado izquierdo de una expresión: un error que
detectará el compilador
si ( MIN_ELEMENTOS = i ) ...
En esta expresión, el compilador debe marcar el único=como un error ya que asignar cualquier cosa a una
constante no es válido. Por el contrario, en la siguiente expresión, el compilador marcará esto solo como una
advertencia, y solo si tiene las advertencias del compilador completamente activadas:
Ejemplo de C++ de colocar una constante en el lado derecho de una expresión: un error que el
compilador podría no detectar
si ( i = MIN_ELEMENTS ) ...
Muchos programadores experimentados ven este enfoque como una ayuda para la legibilidad del
programador que no puede mantener los detalles del lenguaje de programación correctos, pero
como una legibilidad degradante para el programador que domina el lenguaje. Además, la mayoría
de los compiladores proporcionarán advertencias de error para usos de asignación y bit a bit.
19.2 Sentencias Compuestas (Bloques) 443
operadores que parecen errores. Activar las advertencias completas del compilador suele ser una
mejor opción que crear macros no estándar.
Referencia cruzadaMuchos editores Escribe pares de llaves juntasComplete el medio después de escribir las partes de apertura y cierre
de texto orientados a programadores
de un bloque. Las personas a menudo se quejan de lo difícil que es hacer coincidir los pares de
tienen comandos que coinciden con
Esto se aplica a todas las estructuras de bloqueo, incluidassi,por, ytiempoen C++ y Java y elSi-
entonces-más,Para-Siguiente, yMientras-Wendcombinaciones en Visual Basic.
Use llaves para aclarar condicionalesLos condicionales son lo suficientemente difíciles de leer sin tener
que determinar qué declaraciones van con elsiprueba. Poner una sola declaración después de unasitest a
veces es atractivo estéticamente, pero bajo mantenimiento, tales declaraciones tienden a convertirse en
bloques más complicados, y las declaraciones individuales son propensas a errores cuando eso sucede.
Use bloques para aclarar sus intenciones sin importar si el código dentro del bloque es de 1 o
20 líneas.
Traducido del inglés al español - www.onlinedoctranslator.com
lostiempoen C++ requiere que siga una declaración, pero puede ser una declaración nula. El punto y
coma en una línea por sí mismo es una declaración nula. Aquí hay pautas para manejar
declaraciones nulas en C++:
Referencia cruzadaLa mejor manera Llamar la atención sobre sentencias nulasLas declaraciones nulas son poco comunes, así que hágalas
de manejar declaraciones nulas es
obvias. Una forma es darle al punto y coma de una declaración nula una línea propia. Ponle sangría, tal
probablemente evitarlas. Para obtener
Considere si el código sería más claro con un cuerpo de bucle no nuloLa mayor parte del código que da como
resultado bucles con cuerpos vacíos se basa en efectos secundarios en el código de control de bucle. En la mayoría
de los casos, el código es más legible cuando los efectos secundarios se hacen explícitos, como se muestra aquí:
Ejemplos de C++ de reescritura de código más claramente con un cuerpo de bucle no nulo
RecordType record = recordArray.Read(índice); índice++;
Este enfoque introduce una variable de control de bucle adicional y requiere más líneas de
código, pero enfatiza la práctica de programación directa en lugar del uso inteligente de los
efectos secundarios. Tal énfasis es preferible en el código de producción.
DATOS DUROS
Chomsky y Gerald Weinberg sugieren que pocas personas pueden comprender más de tres niveles
de anidado.sis (Yourdon 1986a), y muchos investigadores recomiendan evitar la anidación a más de
tres o cuatro niveles (Myers 1976, Marca 1981 y Ledgard y Tauer 1987a). El anidamiento profundo va
en contra de lo que el Capítulo 5, “Diseño en construcción”, describe como el imperativo técnico
principal del software: administrar la complejidad. Esa es razón suficiente para evitar el anidamiento
profundo.
No es difícil evitar el anidamiento profundo. Si tiene un anidamiento profundo, puede rediseñar las pruebas
realizadas en elsiymáscláusulas o puede refactorizar el código en rutinas más simples. Las siguientes
subsecciones presentan varias formas de reducir la profundidad de anidamiento:
PUNTO CLAVE
Simplificar un anidadosial volver a probar parte de la condiciónSi el anidamiento es demasiado profundo, puede
disminuir la cantidad de niveles de anidamiento volviendo a probar algunas de las condiciones. Este ejemplo de código
Este ejemplo está ideado para mostrar los niveles de anidamiento. Los //un montón de códigolas partes
están destinadas a sugerir que la rutina tiene suficiente código para abarcar varias pantallas o el límite de la
página de una lista de códigos impresos. Aquí está el código revisado para usar la repetición de pruebas en
lugar de la anidación:
if (estadoEntrada == EstadoEntrada_Éxito) {
// mucho código
...
if (rutina de impresora!= NULL) {
// mucho código
...
}
}
...
if ( AllocMem( &printData ) ) {
// mucho código
...
}
}
Este es un ejemplo particularmente realista porque muestra que no puede reducir el nivel de
anidamiento de forma gratuita; tiene que soportar una prueba más complicada a cambio del nivel
reducido de anidamiento. Sin embargo, una reducción de cuatro niveles a dos es una gran mejora en
la legibilidad y vale la pena considerarla.
// mucho código
...
if (impresoraRutina == NULL) {
descanso; // salir del bloque
}
// mucho código
...
if ( !PáginaConfiguración() ) {
descanso; // salir del bloque
}
// mucho código
...
if ( !AllocMem( &printData ) ) {
descanso; // salir del bloque
}
// mucho código
...
} mientras (FALSO); // fin del bloque de ruptura
Esta técnica es tan poco común que debe usarse solo cuando todo el equipo esté
familiarizado con ella y cuando el equipo la haya adoptado como una práctica de
codificación aceptada.
Esta prueba está mal organizada de varias maneras, una de las cuales es que las pruebas son
redundantes. Cuando prueba sicantidades mayor que1000, tampoco es necesario probar si es
mayor que100y mayor que10. En consecuencia, puede reorganizar el código:
448 Capítulo 19: Problemas generales de control
Esta solución es más fácil que algunas porque los números aumentan ordenadamente. Así es como
podría volver a trabajar el anidadosisi los números no fueran tan ordenados:
La principal diferencia entre este código y el código anterior es que las expresiones en elsi no
Las cláusulas no se basan en pruebas anteriores. Este código no necesita elmás cláusulas para
trabajar, y las pruebas en realidad podrían realizarse en cualquier orden. El código podría
constar de cuatrosis y nomáss. La única razón por la quemásLa versión preferida es que evita
repetir pruebas innecesariamente.
Este ejemplo se lee como un libro. Cuando lo compara con los dos ejemplos de sangrías
múltiples de unas pocas páginas antes, parece una solución particularmente clara.
de transacción. transacción.SourceAccountType,
transacción.TargetAccountType,
transacción.AccountNum,
cantidad de transacción
);
}
más {
// procesar tipo desconocido de transacción LogTransactionError( "Tipo de transacción
desconocido", transacción );
}
}
Aunque es complicado, este no es el peor código que verás. Está anidado a solo cuatro niveles,
está comentado, está sangrado lógicamente y la descomposición funcional es adecuada,
especialmente para elTransactionType_Transfertipo de transacción. Sin embargo, a pesar de
su adecuación, puede mejorarlo rompiendo el contenido del interior.sipruebas en sus propias
rutinas.
Referencia cruzadaEste tipo de Ejemplo de C++ de buen código anidado después de la descomposición en rutinas
descomposición funcional es while ( !Transacciones Completas() ) {
especialmente fácil si inicialmente // leer registro de transacción transacción =
creó la rutina siguiendo los pasos ReadTransaction();
descritos en el Capítulo 9, "El
caso (TransactionType_Withdrawal):
ProcesarRetiro(
transacción.TipoCuenta,
transacción.NúmeroCuenta,
transacción.Importe
);
descanso;
caso (TransactionType_Transfer):
HacerTransferenciaDeFondos(
transacción.SourceAccountType,
transacción.TargetAccountType,
transacción.AccountNum,
cantidad de transacción
);
descanso;
defecto:
// procesar el tipo de transacción desconocido LogTransactionError("Tipo de
transacción desconocido", transacción); descanso;
}
}
452 Capítulo 19: Problemas generales de control
caso (TransactionType_Withdrawal):
transacción = nuevo Retiro (transactionData); descanso;
caso (TransactionType_Transfer):
transacción = nueva transferencia (transactionData); descanso;
defecto:
// procesar el tipo de transacción desconocido LogTransactionError("Tipo de transacción
desconocido", transactionData ); devolver;
}
transacción->Completa();
eliminar transacción;
}
Referencia cruzadaPara mejoras Ejemplo en C++ de buen código que usa polimorfismo y una fábrica de objetos
de código más beneficiosas datos de transacción datos de transacción;
como esta, consulte el Capítulo Transacción *transacción;
24, "Refactorización".
caso (TransactionType_Withdrawal):
devuelve un nuevo Retiro (transactionData); descanso;
caso (TransactionType_Transfer):
devuelve una nueva transferencia (transactionData);
descanso;
defecto:
// procesar tipo de transacción desconocido
LogTransactionError( "Tipo de transacción desconocido", transactionData ); devuelve NULL;
}
}
En términos más generales, el código complicado es una señal de que no comprende su programa lo
suficientemente bien como para simplificarlo. El anidamiento profundo es una señal de advertencia
que indica la necesidad de romper una rutina o rediseñar la parte del código que es complicada. No
significa que tenga que modificar la rutina, pero debe tener una buena razón para no hacerlo si no lo
hace.
- Vuelva a escribir el código para usar una variable de estado (en la Sección 17.3)
- Use cláusulas de protección para salir de una rutina y aclarar la ruta nominal a través del
código (en la Sección 17.1)
El núcleo de la programación estructurada es la idea simple de que un programa debe usar solo construcciones de
control de una entrada y una salida (también llamadas construcciones de control de entrada única y salida única).
Una construcción de control one-in, one-out es un bloque de código que tiene solo un lugar donde puede comenzar
y solo un lugar donde puede terminar. No tiene otras entradas ni salidas. La programación estructurada no es lo
mismo que el diseño estructurado de arriba hacia abajo. Se aplica sólo en el nivel de codificación detallada.
Los conceptos centrales de la programación estructurada siguen siendo útiles hoy en día y se aplican a las
consideraciones sobre el usodescanso,Seguir,lanzar,captura,devolver, y otros temas.
Secuencia
Referencia cruzadaPara obtener detalles Una secuencia es un conjunto de sentencias ejecutadas en orden. Las declaraciones secuenciales
sobre el uso de secuencias, consulte el
típicas incluyen asignaciones y llamadas a rutinas. Aquí hay dos ejemplos:
Capítulo 14, "Organización del código de
línea recta".
19.5 Una base de programación: programación estructurada 455
Selección
Referencia cruzadaPara obtener detalles Una selección es una estructura de control que hace que las sentencias se ejecuten de forma selectiva. los
sobre el uso de selecciones, consulte el
si-entonces-otrodeclaración es un ejemplo común. O elsi-entoncescláusula o lamásse ejecuta la cláusula,
Capítulo 15, "Uso de condicionales".
pero no ambas. Una de las cláusulas está “seleccionada” para su ejecución.
Iteración
Referencia cruzadaPara obtener detalles Una iteración es una estructura de control que hace que un grupo de instrucciones se ejecute varias
sobre el uso de iteraciones, consulte el
veces. Una iteración se conoce comúnmente como un "bucle". Los tipos de iteraciones incluyenPara-
Capítulo 16, "Control de bucles".
Siguienteen Visual Basic ytiempoyporen C++ y Java. Este fragmento de código muestra ejemplos de
iteración en Visual Basic:
La tesis central de la programación estructurada es que cualquier flujo de control puede crearse a
partir de estas tres construcciones de secuencia, selección e iteración (Böhm Jacopini 1966). Los
programadores a veces favorecen las estructuras de lenguaje que aumentan la comodidad, pero la
programación parece haber avanzado en gran medida al restringir lo que se nos permite hacer con
nuestros lenguajes de programación. Antes de la programación estructurada, el uso deirs
proporcionó lo último en conveniencia de flujo de control, pero el código escrito de esa manera
resultó ser incomprensible e imposible de mantener. Mi creencia es que el uso de cualquier
estructura de control distinta de las tres construcciones de programación estructurada estándar, es
decir, el uso dedescanso,Seguir,devolver,tirar-atrapar, etcétera, deben ser vistos con ojo crítico.
medida a la complejidad general del programa. El mal uso de las estructuras de control aumenta la complejidad; el
Haga las cosas lo más simples Una medida de la "complejidad de la programación" es la cantidad de objetos mentales que debe
posible, pero no más simples.
tener en cuenta simultáneamente para comprender un programa. Este acto de malabarismo mental
—Albert Einstein
es uno de los aspectos más difíciles de la programación y es la razón por la cual la programación
requiere más concentración que otras actividades. Es la razón por la que los programadores se
enojan con las "interrupciones rápidas"; tales interrupciones equivalen a pedirle a un malabarista
que mantenga tres pelotas en el aire y mantenga sus compras al mismo tiempo.
Referencia cruzadaPara obtener más Los investigadores en informática han sido conscientes de la importancia de la complejidad durante
información sobre la complejidad,
al menos dos décadas. Hace muchos años, Edsger Dijkstra advirtió contra los peligros de la
consulte “El imperativo técnico principal
3 La complejidad del flujo de control es importante porque se ha correlacionado con una baja confiabilidad y
2
1
errores frecuentes (McCabe 1976, Shen et al. 1985). William T. Ward informó una ganancia significativa en la
DATOS DUROS
confiabilidad del software como resultado del uso de la métrica de complejidad de McCabe en Hewlett-
Packard (1989b). La métrica de McCabe se utilizó en un programa de 77.000 líneas para identificar áreas
problemáticas. El programa tuvo una tasa de defectos posterior al lanzamiento de 0,31 defectos por cada mil
líneas de código. Un programa de 125.000 líneas tenía una tasa de defectos posteriores al lanzamiento de
0,02 defectos por cada mil líneas de código. Ward informó que, debido a su menor complejidad, ambos
programas tenían muchos menos defectos que otros programas de Hewlett-Packard. Mi propia empresa,
Construx Software, experimentó resultados similares utilizando medidas de complejidad para identificar
rutinas problemáticas en la década de 2000.
Cuadro 19-2 Técnicas para contar los puntos de decisión en una rutina
1. Comience con 1 para el camino directo a través de la rutina.
2. Agregue 1 para cada una de las siguientes palabras clave, o sus equivalentes:si while repetir para y o
En este fragmento, cuentas 1 para empezar, 2 para elsi, 3 para ely, 4 para elo, y 5 para ely. Así,
este fragmento contiene un total de cinco puntos de decisión.
Mover parte de una rutina a otra rutina no reduce la complejidad general del programa;
simplemente mueve los puntos de decisión. Pero reduce la cantidad de complejidad con la que tiene
que lidiar en un momento dado. Dado que el objetivo importante es minimizar la cantidad de
elementos que tiene que hacer malabares mentalmente, vale la pena reducir la complejidad de una
rutina determinada.
modelos de ingeniería de
control. Otras medidas incluyen la cantidad de datos utilizados, la cantidad de niveles de
software(Conte, Dunsmore y Shen anidamiento en construcciones de control, la cantidad de líneas de código, la cantidad de
1986).
líneas entre referencias sucesivas a variables ("span"), la cantidad de líneas que una variable
está en uso ("tiempo en vivo"), y la cantidad de entrada y salida. Algunos investigadores han
desarrollado métricas compuestas basadas en combinaciones de estas más simples.
- ¿Se comparan explícitamente los valores numéricos con sus valores de prueba?
- ¿Se usan aparatos ortopédicos en todas partes donde se necesitan para mayor claridad?
- ¿Se han simplificado las declaraciones anidadas volviendo a probar parte del
condicional, convirtiéndolo asi-entonces-otroocasodeclaraciones, moviendo el código
anidado a su propia rutina, convirtiéndolo a un diseño más orientado a objetos, o se
han mejorado de alguna otra manera?
- Si una rutina tiene un conteo de decisiones de más de 10, ¿hay alguna buena razón
para no rediseñarla?
460 Capítulo 19: Problemas generales de control
Puntos clave
- Hacer que las expresiones booleanas sean simples y legibles contribuye sustancialmente a la
calidad de su código.
- El anidamiento profundo hace que una rutina sea difícil de entender. Afortunadamente, puedes evitarlo con
relativa facilidad.
- La programación estructurada es una idea simple que aún es relevante: puede construir cualquier
programa a partir de una combinación de secuencias, selecciones e iteraciones.
En esta parte:
Temas relacionados
- Depuración: Capítulo 23
- ¿Se aplican requisitos previos a los proyectos de software modernos?: en la Sección 3.1
Este capítulo examina las técnicas de calidad del software desde el punto de vista de la
construcción. Todo el libro trata sobre la mejora de la calidad del software, por supuesto, pero
este capítulo se centra en la calidad y la garantía de calidad per se. Se enfoca más en
problemas generales que en técnicas prácticas. Si está buscando consejos prácticos sobre
desarrollo colaborativo, pruebas y depuración, pase a los siguientes tres capítulos.
- usabilidadLa facilidad con la que los usuarios pueden aprender y utilizar un sistema.
463
464 Capítulo 20: El panorama de la calidad del software
- Eficiencia Uso mínimo de los recursos del sistema, incluida la memoria y la ejecución
tiempo.
- Precisión El grado en que un sistema, tal como está construido, está libre de errores, especialmente
Algunas de estas características se superponen, pero todas tienen diferentes matices de significado que son
aplicables más en algunos casos, menos en otros.
Los programadores se preocupan tanto por las características internas del software como por las externas.
Este libro está centrado en el código, por lo que se enfoca en las características de calidad interna, que
incluyen
- PortabilidadLa facilidad con la que puede modificar un sistema para que funcione
en un entorno diferente de aquel para el que fue diseñado específicamente.
Al igual que en la lista de características de calidad externa, algunas de estas características internas
se superponen, pero también tienen diferentes matices de significado que son valiosos.
Los aspectos internos de la calidad del sistema son el tema principal de este libro y no se
tratan más en este capítulo.
La diferencia entre características internas y externas no está del todo clara porque en algún
nivel las características internas afectan a las externas. El software que no es comprensible o
mantenible internamente afecta su capacidad para corregir defectos, lo que a su vez afecta las
características externas de corrección y confiabilidad. El software que no es flexible no se
puede mejorar en respuesta a las solicitudes de los usuarios, lo que a su vez afecta la
característica externa de usabilidad. El punto es que se enfatizan algunas características de
calidad para hacer la vida más fácil al usuario y algunas se enfatizan para hacer la vida más fácil
al programador. Trate de saber cuál es cuál y cuándo y cómo interactúan estas características.
El intento de maximizar ciertas características inevitablemente entra en conflicto con el intento de maximizar
otras. Encontrar una solución óptima a partir de un conjunto de objetivos competitivos es una actividad que
hace que el desarrollo de software sea una verdadera disciplina de ingeniería. La figura 20-1 muestra la
forma en que centrarse en algunas características de calidad externa afecta a otras. Los mismos tipos de
relaciones se pueden encontrar entre las características internas de la calidad del software.
El aspecto más interesante de este gráfico es que centrarse en una característica específica no
siempre significa una compensación con otra característica. A veces uno lastima a otro, a veces
uno ayuda a otro, ya veces uno ni lastima ni ayuda a otro. Por ejemplo, la corrección es la
característica de funcionar exactamente según las especificaciones. La robustez es la capacidad
de continuar funcionando incluso en condiciones imprevistas. Centrarse en la corrección daña
la solidez y viceversa. Por el contrario, centrarse en la adaptabilidad ayuda a la robustez y
viceversa.
El gráfico muestra solo las relaciones típicas entre las características de calidad. En
cualquier proyecto dado, dos características pueden tener una relación diferente de su
relación típica. Es útil pensar en sus objetivos de calidad específicos y si cada par de
objetivos es mutuamente beneficioso o antagónico.
466 Capítulo 20: El panorama de la calidad del software
Cómo enfocar
en el factor
Adaptabilidad
Exactitud
Robustez
Fiabilidad
abajo afecta
Eficiencia
Precisión
usabilidad
Integridad
el factor a
la derecha
Exactitud
usabilidad
Eficiencia
Fiabilidad
Integridad
Adaptabilidad
Precisión lo ayuda
Robustez duele
Figura 20-1 Centrarse en una característica externa de la calidad del software puede afectar a otras
características de forma positiva, negativa o ninguna.
Objetivos de calidad del softwareUna técnica poderosa para mejorar la calidad del software es
establecer objetivos de calidad explícitos entre las características externas e internas descritas en la
sección anterior. Sin objetivos explícitos, los programadores pueden trabajar para maximizar
características diferentes de las que espera que maximicen. El poder de establecer objetivos
explícitos se analiza con más detalle más adelante en esta sección.
Referencia cruzadaPara obtener detalles estrategia de pruebaLas pruebas de ejecución pueden proporcionar una evaluación detallada de la
sobre las pruebas, consulte el Capítulo 22,
confiabilidad de un producto. Parte del aseguramiento de la calidad es desarrollar una estrategia de prueba
"Pruebas del desarrollador".
junto con los requisitos, la arquitectura y el diseño del producto. Los desarrolladores de muchos proyectos
confían en las pruebas como método principal tanto para la evaluación como para la mejora de la calidad. El
resto de este capítulo demuestra con más detalle que esta es una carga demasiado pesada para que la
prueba la lleve por sí sola.
Referencia cruzadaPara una Pautas de ingeniería de softwareLas pautas deben controlar el carácter técnico del software a
discusión de una clase de pautas
medida que se desarrolla. Dichas pautas se aplican a todas las actividades de desarrollo de software,
de ingeniería de software
apropiadas para la construcción,
incluida la definición de problemas, el desarrollo de requisitos, la arquitectura, la construcción y las
consulte la Sección 4.2, pruebas del sistema. Las pautas de este libro son, en cierto sentido, un conjunto de pautas de
“Convenciones de programación”.
ingeniería de software para la construcción.
Referencia cruzadaLas Revisiones técnicas formalesUna parte de la gestión de un proceso de ingeniería de software es
revisiones e inspecciones se
detectar problemas en la etapa de "valor más bajo", es decir, en el momento en que se ha realizado la
analizan en el Capítulo 21,
“Construcción colaborativa”. menor inversión y en el que cuesta menos corregir los problemas. Para lograr tal objetivo, los
desarrolladores utilizan "puertas de calidad", pruebas o revisiones periódicas que determinan si la
calidad del producto en una etapa es suficiente para respaldar el paso a la siguiente. Las puertas de
calidad generalmente se utilizan para la transición entre el desarrollo de requisitos y la arquitectura,
la arquitectura y la construcción, y la construcción y las pruebas del sistema. La "puerta" puede ser
una inspección, una revisión por pares, una revisión del cliente o una auditoría.
Referencia cruzadaPara obtener Una "puerta" no significa que la arquitectura o los requisitos deban estar 100 por ciento
más detalles sobre cómo varían
completos o congelados; significa que usará la puerta para determinar si los requisitos o la
los enfoques de desarrollo según
Auditorías externasUna auditoría externa es un tipo específico de revisión técnica utilizada para
determinar el estado de un proyecto o la calidad de un producto que se está desarrollando. Se trae
un equipo de auditoría desde fuera de la organización e informa sus hallazgos a quien encargó la
auditoría, generalmente la gerencia.
Proceso de desarrollo
Otras lecturasPara una discusión Cada uno de los elementos mencionados hasta ahora tiene algo que ver explícitamente con asegurar la
sobre el desarrollo de software
calidad del software e implícitamente con el proceso de desarrollo del software. Los esfuerzos de
como un proceso, ver
Desarrollo de software desarrollo que incluyen actividades de garantía de calidad producen un mejor software que aquellos que
profesional(McConnell 1994). no lo hacen. Otros procesos que no son explícitamente actividades de control de calidad también afectan
la calidad del software.
468 Capítulo 20: El panorama de la calidad del software
Referencia cruzadaPara obtener detalles Procedimientos de control de cambiosUn gran obstáculo para lograr la calidad del software son los
sobre el control de cambios, consulte la
cambios descontrolados. Los cambios de requisitos no controlados pueden provocar interrupciones en el
Sección 28.2, “Gestión de la
configuración”.
diseño y la codificación. Los cambios no controlados en el diseño pueden dar como resultado un código que
no está de acuerdo con sus requisitos, inconsistencias en el código o más tiempo dedicado a modificar el
código para cumplir con el diseño cambiante que el tiempo dedicado a hacer avanzar el proyecto. Los
cambios no controlados en el propio código pueden dar lugar a incoherencias internas e incertidumbres
sobre qué código se ha revisado y probado por completo y cuál no. El efecto natural del cambio es
desestabilizar y degradar la calidad, por lo que el manejo efectivo de los cambios es clave para lograr altos
niveles de calidad.
3 PrototiposLa creación de prototipos es el desarrollo de modelos realistas de las funciones clave de un sistema. Un
2
1
desarrollador puede crear prototipos de partes de una interfaz de usuario para determinar la usabilidad, cálculos
críticos para determinar el tiempo de ejecución o conjuntos de datos típicos para determinar los requisitos de
DATOS DUROS
memoria. Una encuesta de 16 estudios de casos publicados y 8 no publicados comparó la creación de prototipos con
los métodos tradicionales de desarrollo de especificaciones. La comparación reveló que la creación de prototipos
puede conducir a mejores diseños, mejores coincidencias con las necesidades del usuario y mejor capacidad de
mantenimiento (Gordon y Bieman 1991).
Estableciendo objetivos
Establecer explícitamente objetivos de calidad es un paso simple y obvio para lograr un software de
calidad, pero es fácil pasarlo por alto. Quizás se pregunte si, si establece objetivos de calidad
explícitos, los programadores realmente trabajarán para lograrlos. La respuesta es sí, lo harán, si
saben cuáles son los objetivos y si los objetivos son razonables. Los programadores no pueden
responder a un conjunto de objetivos que cambian a diario o que son imposibles de cumplir.
Gerald Weinberg y Edward Schulman realizaron un experimento fascinante para investigar el efecto
sobre el desempeño del programador al establecer objetivos de calidad (1974). Tenían cinco equipos
de programadores trabajando en cinco versiones del mismo programa. Se dieron los mismos cinco
objetivos de calidad a cada uno de los cinco equipos, y se le dijo a cada equipo que optimizara un
objetivo diferente. A un equipo se le dijo que minimizara la memoria requerida, a otro se le dijo que
produjera la salida más clara posible, a otro se le dijo que construyera
20.3 Eficacia relativa de las técnicas de calidad 469
el código más legible, a otro se le dijo que usara el número mínimo de declaraciones y al último
grupo se le dijo que completara el programa en la menor cantidad de tiempo posible. La tabla
20-1 muestra cómo se clasificó a cada equipo según cada objetivo.
Memoria mínima 1 4 4 2 5
Legibilidad de salida 5 1 1 5 3
Legibilidad del programa 3 2 2 3 4
código mínimo 2 5 3 1 3
Programación mínima 4 3 5 4 1
tiempo
3 Los resultados de este estudio fueron notables. Cuatro de los cinco equipos terminaron primeros en el
2
1
objetivo que se les dijo que optimizaran. El otro equipo terminó segundo en su objetivo. Ninguno de los
DATOS DUROS
equipos lo hizo consistentemente bien en todos los objetivos.
La implicación sorprendente es que las personas realmente hacen lo que les pides que hagan. Los
programadores tienen una alta motivación de logro: trabajarán con los objetivos especificados, pero
se les debe decir cuáles son los objetivos. La segunda implicación es que, como era de esperar, los
objetivos entran en conflicto y, por lo general, no es posible hacerlo bien en todos ellos.
punto en el proyecto. La tabla 20-2 muestra los porcentajes de defectos detectados por varias
técnicas comunes de detección de defectos.
Paso de eliminación La tasa más baja Tarifa Modal Tasa más alta
Revisiones informales de diseño 25% 35% 40%
Inspecciones formales de diseño 45% 55% sesenta y cinco%
3 Los hechos más interesantes que revelan estos datos es que las tasas modales no superan el 75 por ciento
2
1
para ninguna técnica individual y que las técnicas promedian alrededor del 40 por ciento. Además, para los
DATOS DUROS
tipos más comunes de detección de defectos (pruebas unitarias y pruebas de integración), las tasas modales
son solo del 30 al 35 por ciento. La organización típica utiliza un enfoque de eliminación de defectos con
muchas pruebas y logra solo alrededor del 85 por ciento de eficiencia en la eliminación de defectos. Las
organizaciones líderes utilizan una variedad más amplia de técnicas y logran eficiencias de eliminación de
defectos del 95 por ciento o más (Jones 2000).
La fuerte implicación es que si los desarrolladores de proyectos se esfuerzan por lograr una mayor
tasa de detección de defectos, deben utilizar una combinación de técnicas. Un estudio clásico de
Glenford Myers confirmó esta implicación (1978b). Myers estudió a un grupo de programadores con
un mínimo de 7 y un promedio de 11 años de experiencia profesional. Usando un programa con 15
errores conocidos, hizo que cada programador buscara errores usando una de estas técnicas:
3 Myers encontró una gran variación en el número de defectos detectados en el programa, que van desde 1,0
2
1
a 9,0 defectos encontrados. El número promedio encontrado fue de 5,1, o alrededor de un tercio de los
DATOS DUROS
conocidos.
Cuando se usó individualmente, ningún método tuvo una ventaja estadísticamente significativa sobre
cualquiera de los otros. Sin embargo, la variedad de errores que la gente encontró fue tan grande
que cualquier combinación de dos métodos, incluso tener dos grupos independientes usando el
mismo método, aumentó el número total de defectos encontrados por un factor de casi 2. Estudios
en el Laboratorio de Ingeniería de Software de la NASA, Boeing y otras compañías han informado que
diferentes personas tienden a encontrar diferentes defectos. Solo alrededor del 20 por ciento de los
errores encontrados por las inspecciones fueron encontrados por más de un inspector
(Kouchakdjian, Green y Basili 1989; Tripp, Struck y Pflug 1991; Schneider, Martin y Tsai 1992).
Glenford Myers señala que los procesos humanos (inspecciones y recorridos, por ejemplo) tienden a ser
mejores que las pruebas basadas en computadora para encontrar ciertos tipos de errores y que lo contrario
es cierto para otros tipos de errores (1979). Este resultado fue confirmado en un estudio posterior, que
encontró que la lectura de código detectó más defectos de interfaz y las pruebas funcionales detectaron más
defectos de control (Basili, Selby y Hutchens 1986). El gurú de las pruebas, Boris Beizer, informa que los
enfoques informales de las pruebas normalmente alcanzan solo un 50-60 por ciento de cobertura de la
prueba, a menos que esté utilizando un analizador de cobertura (Johnson 1994).
El resultado es que los métodos de detección de defectos funcionan mejor en combinación que por
separado. Jones señaló lo mismo cuando observó que la eficiencia acumulativa de detección de defectos es
significativamente mayor que la de cualquier técnica individual. La perspectiva de la efectividad de las
PUNTO CLAVE
pruebas utilizadas por sí solas es sombría. Jones señala que una combinación de pruebas unitarias, pruebas
funcionales y pruebas del sistema a menudo da como resultado una detección acumulada de defectos de
menos del 60 por ciento, lo que generalmente es inadecuado para el software de producción.
Estos datos también se pueden usar para comprender por qué los programadores que comienzan a
trabajar con una técnica disciplinada de eliminación de defectos, como la programación extrema,
experimentan niveles más altos de eliminación de defectos que los que habían experimentado
anteriormente. Como ilustra la tabla 20-3, se esperaría que el conjunto de prácticas de eliminación de
defectos utilizadas en la Programación extrema logre una eficiencia de eliminación de defectos de
alrededor del 90 por ciento en el caso promedio y del 97 por ciento en el mejor de los casos, que es
mucho mejor que el promedio de la industria. de 85 por ciento de eliminación de defectos. Aunque
algunas personas han relacionado esta efectividad con la sinergia entre las prácticas de Extreme
Programming, en realidad es solo un resultado predecible del uso de estas prácticas específicas de
eliminación de defectos. Otras combinaciones de prácticas pueden funcionar igual o mejor,
472 Capítulo 20: El panorama de la calidad del software
Paso de eliminación La tasa más baja Tarifa Modal Tasa más alta
Revisiones informales de diseño 25% 35% 40%
(programación en pareja)
Algunas prácticas de detección de defectos cuestan más que otras. Las prácticas más económicas dan como
resultado el menor costo por defecto encontrado, en igualdad de condiciones. La calificación de que todas
las demás cosas deben ser iguales es importante porque el costo por defecto está influenciado por el
número total de defectos encontrados, la etapa en la que se encuentra cada defecto y otros factores además
de la economía de una técnica específica de detección de defectos.
3 La mayoría de los estudios han encontrado que las inspecciones son más baratas que las pruebas. Un
2
1
estudio en el Laboratorio de Ingeniería de Software encontró que la lectura de códigos detectó un 80 por
DATOS DUROS
ciento más de fallas por hora que las pruebas (Basili y Selby 1987). Otra organización descubrió que costaba
seis veces más detectar defectos de diseño mediante pruebas que mediante inspecciones (Ackerman,
Buchwald y Lewski 1989). Un estudio posterior en IBM encontró que solo se necesitaban 3,5 horas de
personal para encontrar cada error cuando se usaban inspecciones de código, mientras que se necesitaban
entre 15 y 25 horas para encontrar cada error a través de pruebas (Kaplan 1995).
El costo de encontrar defectos es solo una parte de la ecuación de costos. El otro es el costo de
arreglar los defectos. A primera vista, podría parecer que la forma en que se encuentra el defecto no
importaría: siempre costaría lo mismo arreglarlo.
Referencia cruzadaPara obtener Eso no es cierto porque cuanto más tiempo permanece un defecto en el sistema, más costoso se vuelve
detalles sobre el hecho de que los
eliminarlo. Una técnica de detección que encuentra el error antes, por lo tanto, resulta en un menor costo
defectos se vuelven más costosos
3 La división de aplicaciones de Microsoft descubrió que se necesitan tres horas para encontrar y corregir un
2
1
defecto mediante la inspección de código, una técnica de un solo paso, y 12 horas para encontrar y corregir
un defecto mediante la prueba, una técnica de dos pasos (Moore 1992). Collofello y Woodfield informaron
DATOS DUROS
sobre un programa de 700.000 líneas creado por más de 400 desarrolladores (1989). Descubrieron que las
revisiones de código eran varias veces más rentables que las pruebas: un retorno de la inversión de 1,38
frente a 0,17.
- Inspecciones formales de todos los requisitos, toda la arquitectura y los diseños de las partes críticas
de un sistema
- Pruebas de ejecución
la arquitectura, por ejemplo, costoso se vuelve eliminarlo. Una falla en los requisitos puede producir una o más fallas
está fuera del alcance de este correspondientes en el diseño, lo que puede producir muchas fallas correspondientes en el código.
libro. El adicional
Un error de requisitos puede resultar en una arquitectura extra o en malas decisiones
“Recursos” al final del capítulo
describe libros a los que puede
arquitectónicas. La arquitectura adicional da como resultado código adicional, casos de prueba y
acudir para obtener más documentación. O un error de requisitos puede dar lugar a que se desechen la arquitectura, el código
información sobre y los casos de prueba. Así como es una buena idea resolver los defectos en los planos de una casa
a ellos.
antes de verter los cimientos en concreto, es una buena idea detectar los requisitos y los errores de
arquitectura antes de que afecten las actividades posteriores.
Además, los errores en los requisitos o la arquitectura tienden a ser más amplios que los errores de
construcción. Un solo error de arquitectura puede afectar a varias clases y docenas de rutinas,
mientras que es poco probable que un solo error de construcción afecte a más de una rutina o clase.
Por esta razón, también es rentable detectar los errores lo antes posible.
Los defectos se infiltran en el software en todas las etapas. En consecuencia, debe enfatizar el trabajo
de control de calidad en las primeras etapas y durante el resto del proyecto. Debe planificarse en el
proyecto a medida que comienza el trabajo; debe ser parte de la fibra técnica del proyecto a medida
PUNTO CLAVE
que continúa el trabajo; y debe puntuar el final del proyecto, verificando la calidad del producto a
medida que finaliza el trabajo.
Traducido del inglés al español - www.onlinedoctranslator.com
La comprensión de este principio depende de la comprensión de una observación clave: la mejor manera de
mejorar la productividad y la calidad es reducir el tiempo dedicado a la reelaboración del código, ya sea que
la reelaboración surja de cambios en los requisitos, cambios en el diseño o depuración. La productividad
promedio de la industria para un producto de software es de aproximadamente 10 a 50 líneas de código
entregado por persona por día (incluida toda la sobrecarga no relacionada con la codificación). Solo toma
unos minutos escribir de 10 a 50 líneas de código, entonces, ¿cómo pasa el resto del día?
Referencia cruzadaPara obtener Parte de la razón de estas cifras de productividad aparentemente bajas es que los números promedio de la
detalles sobre la diferencia entre
industria como estos tienen en cuenta el tiempo de los no programadores en la cifra de líneas de código por
escribir un programa individual y
La actividad más importante en la mayoría de los proyectos es depurar y corregir el código que no funciona
correctamente. La depuración y la refactorización asociada y otras reelaboraciones consumen alrededor del
50 por ciento del tiempo en un ciclo tradicional e ingenuo de desarrollo de software. (Consulte la Sección
3.1, “Importancia de los requisitos previos”, para obtener más detalles). La reducción de la depuración
mediante la prevención de errores mejora la productividad. Por lo tanto, el método más obvio para acortar
un cronograma de desarrollo es mejorar la calidad del producto y disminuir la cantidad de tiempo dedicado
a depurar y volver a trabajar el software.
3 Este análisis es confirmado por los datos de campo. En una revisión de 50 proyectos de desarrollo que
2
1
involucraron más de 400 años de trabajo de esfuerzo y casi 3 millones de líneas de código, un estudio en el
DATOS DUROS
Laboratorio de ingeniería de software de la NASA encontró que una mayor garantía de calidad se asoció con
una tasa de error menor, pero no aumentó el costo general de desarrollo (Card 1987).
Los proyectos de software con los niveles más bajos de defectos tenían los cronogramas de
desarrollo más cortos y la productividad de desarrollo más alta.... La eliminación de defectos de
software es en realidad la forma de trabajo más costosa y que consume más tiempo para el software
(Jones 2000).
20.5 El principio general de la calidad del software 475
DATOS DUROS
resultantes promediaron 220 líneas de código y un poco menos de cinco horas para escribir. El
resultado fascinante fue que los programadores que se tomaron el tiempo promedio para completar
sus programas produjeron programas con la mayor cantidad de errores. Los programadores que
tomaron más o menos tiempo que la mediana produjeron programas con significativamente menos
errores (DeMarco y Lister 1985). La Figura 20-2 grafica los resultados.
1.4
1.2
1.0
0.8
Promedio
Defectos
0.6
0.4
0.2
Figura 20-2 Ni el enfoque de desarrollo más rápido ni el más lento produce el soft-
artículos con la mayoría de los defectos.
Los dos grupos más lentos tardaron unas cinco veces más en lograr aproximadamente la misma tasa
de defectos que el grupo más rápido. No es necesariamente el caso que escribir software sin defectos
tome más tiempo que escribir software con defectos. Como muestra el gráfico, puede tardar menos.
Es cierto que, en ciertos tipos de proyectos, la garantía de calidad cuesta dinero. Si está escribiendo
código para el transbordador espacial o para un sistema médico de soporte vital, el grado de
confiabilidad requerido hace que el proyecto sea más costoso.
- ¿Su proyecto incluye un plan para tomar medidas para asegurar la calidad del
software durante cada etapa del desarrollo del software?
Recursos adicionales
cc2e.com/2050 No es difícil enumerar libros en esta sección porque prácticamente cualquier libro sobre metodologías de
software efectivas describe técnicas que dan como resultado una mejor calidad y productividad. La
dificultad es encontrar libros que traten sobre la calidad del software per se. Aquí hay dos:
Ginac, Frank P.Aseguramiento de la calidad del software orientado al cliente. Englewood Cliffs, NJ: Prentice
Hall, 1998. Este es un libro muy breve que describe los atributos de calidad, las métricas de calidad, los
programas de control de calidad y el papel de las pruebas en la calidad, así como los programas de mejora
de calidad más conocidos, incluido el del Software Engineering Institute. MMC e ISO 9000.
Estándares relevantes
cc2e.com/2057 IEEE Std 730-2002, estándar IEEE para planes de garantía de calidad de software.
IEEE Std 1061-1998, estándar IEEE para una metodología de métricas de calidad de software.
Puntos clave
- La calidad es gratuita, al final, pero requiere una reasignación de recursos para que los defectos se
- Ninguna técnica única de detección de defectos es completamente efectiva por sí misma. La prueba
por sí sola no es óptimamente efectiva para eliminar errores. Los programas exitosos de garantía de
calidad utilizan varias técnicas diferentes para detectar diferentes tipos de errores.
Construcción colaborativa
cc2e.com/2185 Contenido
Temas relacionados
- Depuración: Capítulo 23
Es posible que haya tenido una experiencia común a muchos programadores. Entras en el cubículo
de otro programador y dices: “¿Te importaría mirar este código? Estoy teniendo algunos problemas
con eso. Empiezas a explicar el problema: “No puede ser el resultado de esto, porque yo hice aquello.
Y no puede ser el resultado de esta otra cosa, porque yo hice esto. Y no puede ser el resultado de...
espera un minuto. Esopudoser el resultado de eso. ¡Gracias!" Has resuelto tu problema antes de que
tu “ayudante” haya tenido la oportunidad de decir una palabra.
De una forma u otra, todas las técnicas de construcción colaborativa son intentos de
formalizar el proceso de mostrar su trabajo a otra persona con el fin de eliminar los
errores.
479
480 Capítulo 21: Construcción colaborativa
Todas las técnicas de construcción colaborativa, a pesar de sus diferencias, se basan en la idea de
que los desarrolladores ignoran algunos de los puntos problemáticos de su trabajo, que otras
personas no tienen los mismos puntos ciegos y que es beneficioso para los desarrolladores tener a
3 alguien más. mira su trabajo. Los estudios del Instituto de Ingeniería de Software han encontrado
2
1
que los desarrolladores insertan un promedio de 1 a 3 defectos por hora en sus diseños y de 5 a 8
DATOS DUROS
defectos por hora en el código (Humphrey 1997), por lo que atacar estos puntos ciegos es clave para
una construcción efectiva.
DATOS DUROS
cuando se usan solas: la tasa promedio de detección de defectos es solo del 30 por ciento para las pruebas
unitarias, del 35 por ciento para las pruebas de integración y del 35 por ciento para las pruebas de bajo nivel.
pruebas beta de volumen. En contraste, las efectividades promedio de las inspecciones de código y diseño
son 55 y 60 por ciento (Jones 1996). El beneficio secundario de la construcción colaborativa es que reduce el
tiempo de desarrollo, lo que a su vez reduce los costos de desarrollo.
Los primeros informes sobre la programación en pares sugieren que puede alcanzar un nivel de calidad de
código similar al de las inspecciones formales (Shull et al 2002). El costo de la programación en pareja
completa es probablemente más alto que el costo del desarrollo en solitario, del orden de 10 a 25 por ciento
más alto, pero la reducción en el tiempo de desarrollo parece ser del orden del 45 por ciento, que en
PUNTO CLAVE
algunos casos puede ser una ventaja decisiva sobre el desarrollo en solitario (Boehm y Turner 2004), aunque
no sobre las inspecciones que han producido resultados similares.
Las revisiones técnicas se han estudiado durante mucho más tiempo que la programación en parejas y sus
resultados, como se describe en los estudios de casos y en otros lugares, han sido impresionantes:
3 - IBM descubrió que cada hora de inspección evitaba unas 100 horas de trabajo
2
1
relacionado (pruebas y corrección de defectos) (Holland 1999).
DATOS DUROS - Raytheon redujo su costo de corrección de defectos (retrabajo) de alrededor del 40 por ciento del
costo total del proyecto a alrededor del 20 por ciento a través de una iniciativa que se centró en las
inspecciones (Haley 1996).
21.1 Descripción general de las prácticas de desarrollo colaborativo 481
- Imperial Chemical Industries descubrió que el costo de mantener una cartera de alrededor de
400 programas era solo un 10 por ciento más alto que el costo de mantener un conjunto
similar de programas que no habían sido inspeccionados (Gilb y Graham 1993).
- Un estudio de programas grandes encontró que cada hora dedicada a las inspecciones evitaba un
promedio de 33 horas de trabajo de mantenimiento y que las inspecciones eran hasta 20 veces más
eficientes que las pruebas (Russell 1991).
una línea eran erróneos antes de que se introdujeran las revisiones de código. Después de que se
introdujeron las revisiones, sólo el 2 por ciento de los cambios fueron erróneos (Freedman y Weinberg
1990). Cuando se consideraron todos los cambios, el 95 por ciento fueron correctos la primera vez después
de que se introdujeron las revisiones. Antes de que se introdujeran las revisiones, menos del 20 por ciento
- Capers Jones informa que de todos los proyectos de software que ha estudiado que han logrado
tasas de eliminación de defectos del 99 por ciento o más, todos han utilizado inspecciones formales.
Además, ninguno de los proyectos que lograron menos del 75 por ciento de eficiencia en la
eliminación de defectos utilizó inspecciones formales (Jones 2000).
Varios de estos casos ilustran el Principio General de la Calidad del Software, que sostiene que
reducir la cantidad de defectos en el software también mejora el tiempo de desarrollo.
Varios estudios han demostrado que, además de ser más efectivos para detectar errores que para realizar
pruebas, las prácticas colaborativas encuentran diferentes tipos de errores que las pruebas (Myers 1978;
Basili, Selby y Hutchens 1986). Como señala Karl Wiegers, “un revisor humano puede detectar mensajes de
PUNTO CLAVE
error poco claros, comentarios inadecuados, valores de variables codificados de forma rígida y patrones de
código repetidos que deben consolidarse. Las pruebas no lo harán” (Wiegers 2002). Un efecto secundario es
que cuando las personas saben que su trabajo será revisado, lo examinan con más cuidado. Por lo tanto,
incluso cuando las pruebas se realizan de manera efectiva, se necesitan revisiones u otros tipos de
colaboración como parte de un programa integral de calidad.
482 Capítulo 21: Construcción colaborativa
Un equipo que usó inspecciones formales informó que las inspecciones llevaron rápidamente a
todos los desarrolladores al nivel de los mejores desarrolladores (Tackett y Van Doren 1999).
del código que escriben y existen - La mejor calidad del código surge de múltiples pares de ojos que ven el código y múltiples
restricciones oficiales o no oficiales para programadores que trabajan en el código.
modificar el código de otra persona. La
propiedad colectiva aumenta la necesidad - El impacto de que alguien abandone el proyecto se reduce porque varias personas están
de coordinación del trabajo, especialmente
familiarizadas con cada sección del código.
la gestión de la configuración. Para
obtener más información, consulte la - Los ciclos de corrección de defectos son más cortos en general porque cualquiera de varios programadores
Sección 28.2, “Administración de la
puede ser potencialmente asignado para corregir errores según disponibilidad.
configuración”.
Soporte de programación de pares con estándares de codificaciónLa programación en pareja no será efectiva si
las dos personas de la pareja pasan su tiempo discutiendo sobre el estilo de codificación. Trate de estandarizar lo que
el Capítulo 5, "Diseño en construcción", se refiere como los "atributos accidentales" de la programación para que los
programadores puedan concentrarse en la tarea "esencial" en cuestión.
No dejes que la programación en pareja se convierta en mirarLa persona sin teclado debe
ser un participante activo en la programación. Esa persona está analizando el código,
pensando en lo que se codificará a continuación, evaluando el diseño y planificando cómo
probar el código.
No fuerce la programación en pareja de las cosas fácilesUn grupo que usó la programación en
pareja para el código más complicado encontró más conveniente hacer un diseño detallado en la
pizarra durante 15 minutos y luego programar solo (Manzo 2002). La mayoría de las organizaciones
que han probado la programación en pares eventualmente se deciden a usar pares para parte de su
trabajo, pero no para todo (Boehm y Turner 2004).
Rotar parejas y asignaciones de trabajo con regularidad.En la programación en pareja, al igual que con
otras prácticas de desarrollo colaborativo, el beneficio surge de que diferentes programadores aprendan
diferentes partes del sistema. Rote las asignaciones de parejas regularmente para fomentar la polinización
cruzada; algunos expertos recomiendan cambiar las parejas con una frecuencia diaria (Reifer 2002).
Anime a las parejas a igualar el ritmo de cada unoUn socio que va demasiado rápido limita
el beneficio de tener al otro socio. El socio más rápido debe reducir la velocidad, o el par debe
dividirse y reconfigurarse con diferentes socios.
484 Capítulo 21: Construcción colaborativa
Asegúrese de que ambos socios puedan ver el monitorIncluso los problemas aparentemente mundanos,
como poder ver el monitor y usar fuentes que son demasiado pequeñas, pueden causar problemas.
No obligues a las personas que no se gustan a emparejarseA veces, los conflictos de personalidad impiden que
las personas se emparejen de manera efectiva. No tiene sentido obligar a las personas que no se llevan bien a
emparejarse, así que sea sensible a las coincidencias de personalidad (Beck 2000, Reifer 2002).
Evite emparejar a todos los novatosLa programación en pareja funciona mejor cuando al menos uno
de los socios se ha emparejado antes (Larman 2004).
Asignar un líder de equipoSi todo su equipo quiere hacer el 100 por ciento de su programación en
parejas, aún necesitará asignar a una persona para coordinar las asignaciones de trabajo, ser
responsable de los resultados y actuar como punto de contacto para las personas ajenas al
proyecto.
- Se sostiene mejor bajo estrés que el desarrollo en solitario. Los pares se alientan mutuamente a
mantener alta la calidad del código incluso cuando hay presión para escribir código rápido y sucio.
- Acorta los horarios. Los pares tienden a escribir código más rápido y con menos errores. El
equipo del proyecto dedica menos tiempo al final del proyecto a corregir defectos.
- ¿Está evitando programar en pareja todo y, en cambio, está seleccionando las tareas
que realmente se beneficiarán de la programación en pareja?
- ¿Hay un líder de equipo que actúe como punto focal para la gerencia y otras
personas fuera del proyecto?
21.3 Inspecciones formales 485
- Las listas de verificación enfocan la atención de los revisores en áreas que han sido problemáticas en el
pasado.
- Los revisores se preparan de antemano para la reunión de inspección y llegan con una lista
de los problemas que han descubierto.
- La reunión de inspección se lleva a cabo solo si todos los participantes se han preparado adecuadamente.
DATOS DUROS
volumen. Estos resultados se han confirmado numerosas veces en varias organizaciones, incluidas
Harris BCSD, National Software Quality Experiment, Software Engineering Institute, Hewlett Packard,
etc. (Shull et al 2002).
La combinación de inspecciones de código y diseño por lo general elimina del 70 al 85 por ciento o más de
los defectos en un producto (Jones 1996). Las inspecciones identifican las clases propensas a errores de
manera temprana y Capers Jones informa que dan como resultado entre un 20 y un 30 por ciento menos de
defectos por cada 1000 líneas de código que las prácticas de revisión menos formales. Los diseñadores y
codificadores aprenden a mejorar su trabajo participando en inspecciones, y las inspecciones aumentan la
productividad en un 20 por ciento (Fagan 1976, Humphrey 1989, Gilb y Graham 1993, Wiegers 2002). En un
proyecto que utiliza inspecciones para el diseño y el código, las inspecciones ocuparán entre el 10 y el 15 por
ciento del presupuesto del proyecto y, por lo general, reducirán el costo total del proyecto.
486 Capítulo 21: Construcción colaborativa
Las inspecciones también se pueden utilizar para evaluar el progreso, pero lo que se evalúa es el
progreso técnico. Eso generalmente significa responder dos preguntas: ¿Se está haciendo el trabajo
técnico? ¿Y se está haciendo el trabajo técnico?bien? Las respuestas a ambas preguntas son
subproductos de las inspecciones formales.
Una característica clave de una inspección es que cada persona involucrada tiene un papel distinto que
desempeñar. Aquí están los roles:
AutorLa persona que escribió el diseño o el código juega un papel relativamente menor en la
inspección. Parte del objetivo de una inspección es asegurarse de que el diseño o código hable
por sí mismo. Si el diseño o código bajo inspección resulta poco claro, se le asignará al autor el
trabajo de aclararlo. De lo contrario, los deberes del autor son explicar partes del diseño o
código que no están claras y, ocasionalmente, explicar por qué las cosas que parecen errores
son realmente aceptables. Si el proyecto no es familiar para los revisores, el autor también
puede presentar una descripción general del proyecto en preparación para la reunión de
inspección.
CríticoUn revisor es cualquier persona que tenga un interés directo en el diseño o el código pero
que no sea el autor. Un revisor de un diseño podría ser el programador que implementará el
diseño. También podría estar involucrado un probador o un arquitecto de nivel superior. El papel de
los revisores es encontrar defectos. Por lo general, encuentran defectos durante la preparación y, a
medida que se analiza el diseño o el código en la reunión de inspección, el grupo debe encontrar
muchos más defectos.
EscribaEl escribano registra los errores que se detectan y las asignaciones de elementos
de acción durante la reunión de inspección. Ni el autor ni el moderador deben ser
escribas.
administraciónIncluir la gestión en las inspecciones no suele ser una buena idea. El punto de una
inspección de software es que es una revisión puramente técnica. La presencia de la gerencia cambia
las interacciones: las personas sienten que ellos, en lugar de los materiales de revisión, están bajo
evaluación, lo que cambia el enfoque de técnico a político. Sin embargo, la gerencia tiene derecho a
conocer los resultados de una inspección y se prepara un informe de inspección para mantener
informada a la gerencia.
21.3 Inspecciones formales 487
Del mismo modo, bajo ninguna circunstancia se deben utilizar los resultados de la inspección para la
evaluación del desempeño. No mates a la gallina de los huevos de oro. El código examinado en una
inspección aún está en desarrollo. La evaluación del desempeño debe basarse en los productos
finales, no en el trabajo que no está terminado.
Visión generalCuando los revisores no están familiarizados con el proyecto que están
revisando, el autor puede dedicar hasta una hora más o menos a describir el entorno técnico
en el que se creó el diseño o el código. Tener una visión general tiende a ser una práctica
peligrosa porque puede llevar a pasar por alto puntos poco claros en el diseño o código bajo
inspección. El diseño o código debe hablar por sí mismo; la descripción general no debería
hablar por ello.
Referencia cruzadaPara obtener una lista de las PreparaciónCada revisor trabaja solo para examinar el diseño o el código en busca de errores. Los
listas de verificación que puede usar para
revisores usan la lista de verificación para estimular y dirigir su examen de los materiales de revisión.
mejorar la calidad del código, consulte la página
xxix.
Para una revisión del código de la aplicación escrito en un lenguaje de alto nivel, los revisores pueden
preparar alrededor de 500 líneas de código por hora. Para una revisión del código del sistema escrito
en un lenguaje de alto nivel, los revisores pueden preparar solo alrededor de 125 líneas de código
por hora (Humphrey 1989). La tasa de revisión más efectiva varía mucho, por lo tanto, mantenga
registros de las tasas de preparación en su organización para determinar la tasa más efectiva en su
entorno.
Algunas organizaciones han descubierto que las inspecciones son más efectivas cuando a cada
revisor se le asigna una perspectiva específica. Se le puede pedir a un revisor que se prepare para la
inspección desde el punto de vista del programador de mantenimiento, el cliente,
488 Capítulo 21: Construcción colaborativa
Una variación adicional en la preparación de la inspección es asignar a cada revisor uno o más
escenarios para verificar. Los escenarios pueden involucrar preguntas específicas que se le asigna a
un revisor para que las responda, como "¿Hay algún requisito que este diseño no satisfaga?" Un
escenario también puede implicar una tarea específica que se le asigna a un revisor, como enumerar
los requisitos específicos que satisface un elemento de diseño en particular. También puede asignar
algunos revisores para leer el material de adelante hacia atrás, de atrás hacia adelante o del revés.
Reunión de inspecciónEl moderador elige a alguien que no sea el autor para parafrasear el
diseño o leer el código (Wiegers 2003). Se explica toda la lógica, incluyendo cada rama de cada
estructura lógica. Durante esta presentación, el escribiente registra los errores a medida que
se detectan, pero la discusión de un error se detiene tan pronto como se reconoce como tal. El
escriba anota el tipo y la gravedad del error y la inspección continúa. Si tiene problemas para
mantener las discusiones enfocadas, el moderador puede hacer sonar una campana para
llamar la atención del grupo y volver a encarrilar la discusión.
La velocidad a la que se considera el diseño o el código no debe ser ni demasiado lenta ni demasiado
rápida. Si es demasiado lento, la atención puede demorarse y la reunión no será productiva. Si es
demasiado rápido, el grupo puede pasar por alto los errores que de otro modo detectaría. Las tasas
de inspección óptimas varían de un entorno a otro, al igual que las tasas de preparación. Mantenga
registros para que, con el tiempo, pueda determinar la tasa óptima para su entorno. Otras
organizaciones han encontrado que para el código del sistema, una tasa de inspección de 90 líneas
de código por hora es óptima. Para el código de aplicaciones, la tasa de inspección puede ser tan
rápida como 500 líneas de código por hora (Humphrey 1989). Un promedio de alrededor de 150 a 200
declaraciones fuente sin comentarios por hora es un buen punto de partida (Wiegers 2002).
No discuta soluciones durante la reunión. El grupo debe concentrarse en identificar los defectos.
Algunos grupos de inspección ni siquiera permiten discutir si un defecto es realmente un defecto.
Asumen que si alguien está lo suficientemente confundido como para pensar que es un defecto, es
necesario aclarar el diseño, el código o la documentación.
La reunión generalmente no debe durar más de dos horas. Esto no significa que tenga que
fingir una alarma contra incendios para que todos salgan a las dos horas, pero la experiencia
en IBM y otras compañías ha sido que los revisores no pueden concentrarse por mucho más
de dos horas a la vez. Por la misma razón, no es aconsejable programar más de una inspección
el mismo día.
se utiliza para desarrollar una lista de verificación que enfatiza los problemas específicos de la
organización. Si recopila datos sobre el tiempo empleado y la cantidad de errores encontrados a lo
largo del tiempo, puede responder a los desafíos sobre la eficacia de la inspección con datos
concretos. De lo contrario, se limitará a decir que las inspecciones parecen mejores. Eso no será tan
convincente para alguien que piensa que las pruebas parecen mejores. También podrá saber si las
inspecciones no funcionan en su entorno y modificarlas o abandonarlas, según corresponda. La
recopilación de datos también es importante porque cualquier nueva metodología debe justificar su
existencia.
RehacerEl moderador asigna los defectos a alguien, generalmente el autor, para que los repare. El
cesionario resuelve cada defecto de la lista.
Hacer un seguimientoEl moderador es responsable de ver que se lleve a cabo todo el retrabajo asignado
durante la inspección. Dependiendo de la cantidad de errores encontrados y la gravedad de esos errores,
puede hacer un seguimiento haciendo que los revisores vuelvan a inspeccionar todo el producto del trabajo,
haciendo que los revisores vuelvan a inspeccionar solo las correcciones o permitiendo que el autor complete
las correcciones sin ningún seguimiento.
Una vez que adquiera la habilidad de realizar inspecciones “al pie de la letra”, por lo general podrá
encontrar varias formas de mejorarlas. Sin embargo, no introduzca cambios de cualquier manera.
“Instrumente” el proceso de inspección para que sepa si sus cambios son beneficiosos.
Las empresas a menudo han descubierto que eliminar o combinar cualquiera de las
etapas cuesta más de lo que se ahorra (Fagan 1986). Si está tentado a cambiar el proceso
de inspección sin medir el efecto del cambio, no lo haga. Si ha medido el proceso y sabe
que su proceso modificado funciona mejor que el que se describe aquí, continúe.
A medida que realice inspecciones, notará que ciertos tipos de errores ocurren con más frecuencia que
otros. Cree una lista de verificación que llame la atención sobre ese tipo de errores para que los revisores se
centren en ellos. Con el tiempo, encontrará tipos de errores que no están en la lista de verificación; agregar
esos a la misma. Es posible que descubra que algunos errores en la lista de verificación inicial dejan de
ocurrir; eliminar esos. Después de algunas inspecciones, su organización tendrá una lista de verificación
para inspecciones adaptada a sus necesidades y también podría tener algunas pistas sobre áreas
problemáticas en las que sus programadores necesitan más capacitación o apoyo. Limite su lista de
verificación a una página o menos. Los más largos son difíciles de usar al nivel de detalle necesario en una
inspección.
490 Capítulo 21: Construcción colaborativa
Egos en Inspecciones
Otras lecturasPara una discusión El objetivo de la inspección en sí es descubrir defectos en el diseño o código. No se trata
sobre la programación sin ego,
de explorar alternativas ni de debatir quién tiene razón y quién no. El punto es sin duda
consulteLa psicología de la
Debido a que el diseño o código está siendo criticado y el autor probablemente se siente
un poco apegado a él, el autor naturalmente sentirá parte del calor dirigido al código. El
autor debe anticipar escuchar críticas de varios defectos que en realidad no son defectos
y varios más que parecen discutibles. A pesar de eso, el autor debe reconocer cada
supuesto defecto y seguir adelante. Reconocer una crítica no implica que el autor esté de
acuerdo con el contenido de la crítica. El autor no debe tratar de defender el trabajo bajo
revisión. Después de la revisión, el autor puede pensar en cada punto en privado y decidir
si es válido.
Los revisores deben recordar que el autor tiene la responsabilidad final de decidir qué hacer
con un defecto. Está bien disfrutar encontrando defectos (y fuera de la revisión, disfrutar
proponiendo soluciones), pero cada revisor debe respetar el derecho último del autor a decidir
cómo resolver un error.
Se podría pensar que no quedarían muchos errores en el libro que pasó por toda esa actividad de
revisión. Pero ese no fue el caso. Para crear la segunda edición, utilicé inspecciones formales de la
primera edición para identificar problemas que debían abordarse en la segunda edición. Equipos de
tres a cuatro revisores preparados de acuerdo con las pautas descritas en este capítulo. Algo para mi
sorpresa, nuestras inspecciones formales encontraron varios cientos de errores en el texto de la
primera edición que no habían sido detectados previamente a través de ninguna de las numerosas
actividades de revisión.
21.3 Inspecciones formales 491
Si tenía alguna duda sobre el valor de las inspecciones formales, mi experiencia en la creación
de la segunda edición deCódigo completolos eliminó.
Resumen de inspección
Las listas de verificación de inspección fomentan la concentración enfocada. El proceso de inspección es
sistemático debido a sus listas de verificación estándar y roles estándar. También se optimiza
automáticamente porque utiliza un ciclo de retroalimentación formal para mejorar las listas de verificación y
monitorear las tasas de preparación e inspección. Con este control sobre el proceso y la optimización
continua, la inspección se convierte rápidamente en una técnica poderosa casi sin importar cómo comience.
Otras lecturasPara obtener más El Instituto de Ingeniería de Software (SEI) ha definido un Modelo de Madurez de Capacidad
detalles sobre el concepto de
(CMM) que mide la efectividad del proceso de desarrollo de software de una organización (SEI
madurez del desarrollo de SEI,
- ¿Se les da suficiente tiempo a los revisores para prepararse antes de la reunión de inspección
y está cada uno preparado?
- ¿Se recopilan datos sobre los tipos de errores en cada inspección para que pueda adaptar
futuras listas de verificación a su organización?
492 Capítulo 21: Construcción colaborativa
- ¿Se recopilan datos sobre las tasas de preparación e inspección para que pueda
optimizar la preparación y las inspecciones futuras?
- ¿Existe un plan de seguimiento para garantizar que las correcciones se realicen correctamente?
Recorridos
Un recorrido es un tipo popular de revisión. El término está vagamente definido, y al menos parte de su
popularidad se puede atribuir al hecho de que las personas pueden llamar a prácticamente cualquier tipo
de revisión un "recorrido".
Debido a que el término se define de manera tan vaga, es difícil decir exactamente qué es un
recorrido. Ciertamente, un recorrido involucra a dos o más personas discutiendo un diseño o
código. Puede ser tan informal como una sesión de toros improvisada alrededor de una
pizarra; podría ser tan formal como una reunión programada con una presentación general
preparada por el departamento de arte y un resumen formal enviado a la gerencia. En cierto
sentido, “donde dos o tres están reunidos”, hay un recorrido. A los defensores de los
recorridos les gusta la holgura de tal definición, así que solo señalaré algunas cosas que todos
los recorridos tienen en común y dejaré el resto de los detalles a usted:
- El recorrido por lo general es organizado y moderado por el autor del diseño o código
que se está revisando.
- El recorrido es una oportunidad para que los programadores senior transmitan su experiencia y
cultura corporativa a los programadores junior. También es una oportunidad para que los
programadores junior presenten nuevas metodologías y desafíen suposiciones gastadas,
posiblemente obsoletas.
- La gerencia no atiende.
- El concepto de recorrido es flexible y se puede adaptar a las necesidades específicas de la
organización que lo utiliza.
3 Usados sin inteligencia, los recorridos son más problemáticos de lo que valen. El extremo inferior de su
2
1
efectividad, el 20 por ciento, no vale mucho, y al menos una organización (Boeing Computer Services)
DATOS DUROS
encontró que las revisiones de código por pares eran "extremadamente costosas". Boeing descubrió que era
difícil motivar al personal del proyecto para que aplicara las técnicas de recorrido de forma constante, y
cuando aumentaban las presiones del proyecto, los recorridos de recorrido se volvían casi imposibles (Glass
1982).
Me he vuelto más crítico con los recorridos durante los últimos 10 años como resultado de lo que he
visto en el negocio de consultoría de mi empresa. Descubrí que cuando las personas tienen malas
experiencias con las revisiones técnicas, casi siempre se trata de prácticas informales, como
recorridos, en lugar de inspecciones formales. Una revisión es básicamente una reunión, y las
reuniones son costosas. Si va a incurrir en los gastos generales de celebrar una reunión, vale la pena
estructurar la reunión como una inspección formal. Si el producto de trabajo que está revisando no
justifica los gastos generales de una inspección formal, no justifica en absoluto los gastos generales
de una reunión. En tal caso, es mejor utilizar la lectura de documentos u otro enfoque menos
interactivo.
Las inspecciones parecen ser más efectivas que los recorridos para eliminar errores. Entonces,
¿por qué alguien elegiría usar recorridos?
Si tiene un grupo de revisión grande, un recorrido es una buena opción de revisión porque aporta
muchos puntos de vista diversos sobre el elemento que se está revisando. Si todos los involucrados
en el recorrido pueden estar convencidos de que la solución está bien, probablemente no tenga
fallas importantes.
Si están involucrados revisores de otras organizaciones, también podría ser preferible un recorrido.
Los roles en una inspección están más formalizados y requieren algo de práctica antes de que las
personas los desempeñen de manera efectiva. Los revisores que no han participado antes en
inspecciones están en desventaja. Si desea solicitar sus contribuciones, un recorrido podría ser la
mejor opción.
Las inspecciones están más enfocadas que los recorridos y generalmente dan mejores resultados. En
consecuencia, si está eligiendo un estándar de revisión para su organización, elija primero las inspecciones a
menos que tenga una buena razón para no hacerlo.
PUNTO CLAVE
494 Capítulo 21: Construcción colaborativa
Lectura de código
3 Un estudio del Laboratorio de Ingeniería de Software de la NASA encontró que la lectura de códigos detectó
2
1
alrededor de 3,3 defectos por hora de esfuerzo. Las pruebas detectaron alrededor de 1,8 errores por hora (Card
1987). La lectura de código también encontró entre un 20 y un 60 por ciento más de errores durante la vida del
DATOS DUROS
Al igual que la idea de un recorrido, el concepto de lectura de código está vagamente definido. Una lectura
de código generalmente involucra a dos o más personas que leen el código de forma independiente y luego
se reúnen con el autor del código para discutirlo. Así es como va la lectura de código:
- En preparación para la reunión, el autor del código entrega listas de fuentes a los
lectores del código. Los listados van desde 1000 hasta 10,000 líneas de código; 4000
líneas es típico.
- Dos o más personas leen el código. Utilice al menos dos personas para fomentar la
competencia entre los revisores. Si usa más de dos, mida la contribución de todos
para saber cuánto contribuyen las personas adicionales.
- Los revisores leen el código de forma independiente. Estime una tasa de alrededor de 1000 líneas por día.
- Cuando los revisores han terminado de leer el código, el autor del código organiza
la reunión de lectura del código. La reunión dura una o dos horas y se centra en los
problemas descubiertos por los lectores de código. Nadie intenta recorrer el código
línea por línea. La reunión ni siquiera es estrictamente necesaria.
- El autor del código corrige los problemas identificados por los revisores.
La diferencia entre la lectura de código por un lado y las inspecciones y recorridos por el otro es que
la lectura de código se enfoca más en la revisión individual del código que en la reunión. El resultado
es que el tiempo de cada revisor se centra en encontrar problemas en el código. Se gasta menos
PUNTO CLAVE
tiempo en reuniones en las que cada persona contribuye sólo una parte del tiempo y en las que una
parte sustancial del esfuerzo se dedica a moderar las dinámicas de grupo. Se dedica menos tiempo a
retrasar las reuniones hasta que cada persona del grupo pueda reunirse durante dos horas. Las
lecturas de códigos son especialmente valiosas en situaciones en las que los revisores están
dispersos geográficamente.
DATOS DUROS
revisión, y solo alrededor del 10 por ciento se encontraron durante la revisión misma (Votta 1991, Glass
1999).
21.C peraK
4oOmth lalb
soi ndosfoCfoClo tim
rulocp
laobroartaivteivCeoDnesvte onenTtecPhranciqtiuces 495
Los espectáculos de perros y ponis son revisiones en las que se demuestra un producto de software a
un cliente. Las revisiones de los clientes son comunes en el software desarrollado para contratos
gubernamentales, que a menudo estipulan que se realizarán revisiones de requisitos, diseño y
código. El propósito de una exhibición de perros y ponis es demostrarle al cliente que el proyecto está
bien, por lo que es una revisión de la gerencia en lugar de una revisión técnica.
No confíe en los espectáculos de perros y ponis para mejorar la calidad técnica de sus productos.
Prepararse para ellas puede tener un efecto indirecto en la calidad técnica, pero por lo general se
dedica más tiempo a crear diapositivas de presentación atractivas que a mejorar la calidad del
software. Confíe en las inspecciones, los recorridos o la lectura de códigos para mejorar la calidad
técnica.
corrección
Esfuerzo de revisión enfocado: Informal, en todo caso Sí No
busca los tipos de errores más
frecuentes
Seguimiento para reducir las malas soluciones Sí Sí No
Menos errores futuros debido a la Incidental Sí Incidental
retroalimentación detallada de errores para
los programadores individuales
La programación en pares no tiene décadas de datos que respalden su efectividad como lo hace la
inspección formal, pero los datos iniciales sugieren que está más o menos en pie de igualdad con las
inspecciones, y los informes anecdóticos también han sido positivos.
Recursos adicionales
cc2e.com/2106 Aquí hay más recursos relacionados con la construcción colaborativa:
Programación en pareja
Williams, Laurie y Robert Kessler.Par Programación Iluminado. Boston, MA: Addison Wesley,
2002. Este libro explica los entresijos detallados de la programación en pareja, incluido cómo
manejar varias coincidencias de personalidad (por ejemplo, experto e inexperto, introvertido y
extrovertido) y otros problemas de implementación.
Inspecciones
Wiegers, Karl.Peer Reviews en software: una guía práctica. Boston, MA: Addison Wesley, 2002.
Este libro bien escrito describe los entresijos de varios tipos de revisiones, incluidas las
inspecciones formales y otras prácticas menos formales. Está bien investigado, tiene un
enfoque práctico y es fácil de leer.
Puntos clave 497
Estándares relevantes
Puntos clave
- Las prácticas de desarrollo colaborativo tienden a encontrar un mayor porcentaje de defectos que
las pruebas y a encontrarlos de manera más eficiente.
- Las prácticas de desarrollo colaborativo tienden a encontrar diferentes tipos de errores que
las pruebas, lo que implica que debe utilizar tanto las revisiones como las pruebas para
garantizar la calidad de su software.
- Las inspecciones formales utilizan listas de verificación, preparación, funciones bien definidas y
mejora continua del proceso para maximizar la eficiencia en la detección de errores. Tienden a
encontrar más defectos que recorridos.
- La programación en pares suele costar lo mismo que las inspecciones y produce un código de
calidad similar. La programación en pareja es especialmente valiosa cuando se desea reducir
el horario. Algunos desarrolladores prefieren trabajar en parejas a trabajar solos.
- Los recorridos y la lectura de códigos son alternativas a las inspecciones. La lectura de códigos
ofrece más flexibilidad en el uso efectivo del tiempo de cada persona.
capitulo 22
Pruebas de desarrollador
cc2e.com/2261 Contenido
- 22.1 Papel de las pruebas de desarrollador en la calidad del software: página 500
Temas relacionados
- Depuración: Capítulo 23
- Integración: Capítulo 29
La prueba es la actividad de mejora de la calidad más popular, una práctica respaldada por una gran
cantidad de investigaciones académicas e industriales y por la experiencia comercial. El software se
prueba de muchas maneras, algunas de las cuales normalmente las realizan los desarrolladores y
otras las realiza más comúnmente el personal de prueba especializado:
499
500 Capítulo 22: Pruebas de desarrollador
- Pruebas del sistemaes la ejecución del software en su configuración final, incluyendo la integración
con otros sistemas de software y hardware. Prueba la seguridad, el rendimiento, la pérdida de
recursos, los problemas de sincronización y otros problemas que no se pueden probar en los niveles
inferiores de integración.
En este capítulo, "pruebas" se refiere a las pruebas realizadas por el desarrollador, que generalmente
consisten en pruebas unitarias, pruebas de componentes y pruebas de integración, pero a veces pueden
incluir pruebas de regresión y pruebas del sistema. Numerosos tipos adicionales de pruebas son realizados
por personal de prueba especializado y rara vez son realizados por desarrolladores, incluidas pruebas beta,
pruebas de aceptación del cliente, pruebas de rendimiento, pruebas de configuración, pruebas de
plataforma, pruebas de estrés, pruebas de usabilidad, etc. Este tipo de pruebas no se analizan más en este
capítulo.
Las pruebas generalmente se dividen en dos grandes categorías: pruebas de caja negra y pruebas de caja blanca (o
caja de vidrio). La "prueba de caja negra" se refiere a las pruebas en las que el evaluador no puede ver el
funcionamiento interno del elemento que se está probando. ¡Obviamente, esto no se aplica cuando prueba el
PUNTO CLAVE
código que ha escrito! Las "pruebas de caja blanca" se refieren a las pruebas en las que el probador es consciente
del funcionamiento interno del elemento que se está probando. Este es el tipo de prueba que usted, como
desarrollador, utiliza para probar su propio código. Tanto las pruebas de caja negra como las de caja blanca tienen
fortalezas y debilidades; este capítulo se centra en las pruebas de caja blanca porque ese es el tipo de pruebas que
realizan los desarrolladores.
Algunos programadores usan los términos "prueba" y "depuración" indistintamente, pero los
programadores cuidadosos distinguen entre las dos actividades. La prueba es un medio para
detectar errores. La depuración es un medio para diagnosticar y corregir las causas raíz de los
errores que ya se han detectado. Este capítulo trata exclusivamente de la detección de errores. La
corrección de errores se analiza en detalle en el Capítulo 23, “Depuración”.
Todo el tema de las pruebas es mucho más amplio que el tema de las pruebas durante la
construcción. Las pruebas del sistema, las pruebas de estrés, las pruebas de caja negra y otros temas
para los especialistas en pruebas se analizan en la sección "Recursos adicionales" al final del capítulo.
V413HAV
22.1 Papel de las pruebas de desarrollador en la calidad del software 501
Los programas no adquieren errores Si tuviera que enumerar un conjunto de actividades de desarrollo de software en "Plaza Sésamo" y
como las personas adquieren
preguntar: "¿Cuál de estas cosas no es como las demás?" la respuesta sería "Prueba". La prueba es una
gérmenes, merodeando por otros
— Molinos de Harlan - El objetivo de las pruebas va en contra de los objetivos de otras actividades de desarrollo. El
objetivo es encontrar errores. Una prueba exitosa es aquella que rompe el software. El
objetivo de todas las demás actividades de desarrollo es evitar errores y evitar que el software
se rompa.
- Las pruebas nunca pueden probar completamente la ausencia de errores. Si ha realizado pruebas
exhaustivas y ha encontrado miles de errores, ¿significa que ha encontrado todos los errores o que
tiene miles más por encontrar? La ausencia de errores podría significar casos de prueba ineficaces o
incompletos tan fácilmente como podría significar un software perfecto.
- Las pruebas por sí solas no mejoran la calidad del software. Los resultados de las
pruebas son un indicador de calidad, pero en sí mismos no la mejoran. Tratar de mejorar
la calidad del software aumentando la cantidad de pruebas es como tratar de perder
peso pesándose con más frecuencia. Lo que come antes de subirse a la báscula
determina cuánto pesará, y las técnicas de desarrollo de software que utiliza determinan
cuántos errores encontrará la prueba. Si quieres perder peso, no compres una báscula
nueva; cambia tu dieta. Si desea mejorar su software, no se limite a probar más;
desarrollarse mejor.
3 - Las pruebas requieren que asumas que encontrarás errores en tu código. Si asume que no lo
2
1
hará, probablemente no lo hará, pero solo porque habrá establecido una profecía
DATOS DUROS
autocumplida. Si ejecuta el programa esperando que no tenga ningún error, será demasiado
fácil pasar por alto los errores que encuentre. En un estudio que se ha convertido en un
clásico, Glenford Myers hizo que un grupo de programadores experimentados probaran un
programa con 15 defectos conocidos. El programador promedio encontró solo 5 de los 15
errores. Los mejores encontraron solo 9. La principal fuente de errores no detectados fue que
la salida errónea no se examinó con suficiente cuidado. Los errores eran visibles, pero los
programadores no los notaron (Myers 1978).
Debe esperar encontrar errores en su código. Tal esperanza puede parecer un acto
antinatural, pero debes esperar que seas tú quien encuentre los errores y no otra
persona.
Una pregunta clave es: ¿cuánto tiempo se debe dedicar a las pruebas del desarrollador en un proyecto
típico? Una cifra comúnmente citada para todas las pruebas es el 50 por ciento del tiempo dedicado al
proyecto, pero eso es engañoso. Primero, esa figura en particular combina pruebas y depuración; la prueba
sola toma menos tiempo. En segundo lugar, esa cifra representa la cantidad de tiempo que normalmente se
dedica en lugar del tiempo que se debería dedicar. En tercer lugar, la cifra incluye pruebas independientes y
pruebas de desarrolladores.
502 Capítulo 22: Pruebas de desarrollador
Como muestra la Figura 22-1, según el tamaño y la complejidad del proyecto, las pruebas del desarrollador
probablemente deberían ocupar entre el 8 y el 25 por ciento del tiempo total del proyecto. Esto es
consistente con gran parte de los datos que se han informado.
100%
Pruebas del sistema
Integración
Pruebas de desarrollador
Porcentaje de
Tiempo de desarrollo
Codificación y depuración Construcción
Diseño detallado
Arquitectura
0%
2K 8K 32K 128K 512K
Tamaño del proyecto en líneas de código
Figura 22-1A medida que aumenta el tamaño del proyecto, las pruebas del desarrollador consumen un porcentaje
menor del tiempo total de desarrollo. Los efectos del tamaño del programa se describen con más detalle en el
Capítulo 27, “Cómo afecta el tamaño del programa a la construcción”.
Una segunda pregunta es: ¿Qué hace con los resultados de las pruebas de desarrollo? Más
inmediatamente, puede utilizar los resultados para evaluar la fiabilidad del producto en desarrollo.
Incluso si nunca corrige los defectos que encuentra la prueba, la prueba describe qué tan confiable
es el software. Otro uso para los resultados es que pueden y, por lo general, guían las correcciones al
software. Finalmente, con el tiempo, el registro de los defectos encontrados a través de las pruebas
ayuda a revelar los tipos de errores que son más comunes. Puede utilizar esta información para
seleccionar las clases de formación adecuadas, dirigir futuras actividades de revisión técnica y
diseñar futuros casos de prueba.
que cualquier nuevo error es el resultado de la nueva rutina o de interacciones con la nueva rutina.
El trabajo de depuración es más fácil.
Las prácticas de construcción colaborativa tienen muchas fortalezas para ofrecer que las pruebas no pueden igualar.
Pero parte del problema con las pruebas es que las pruebas a menudo no se realizan tan bien como podrían. Un
desarrollador puede realizar cientos de pruebas y aun así lograr solo una cobertura parcial del código. Asentimiento
de una buena cobertura de la prueba no significa que la cobertura real de la prueba sea adecuada. Una
comprensión de los conceptos básicos de las pruebas puede respaldar mejores pruebas y aumentar la eficacia de las
pruebas.
- Pruebe cada requisito relevante para asegurarse de que los requisitos se hayan implementado.
Planifique los casos de prueba para este paso en la etapa de requisitos o lo antes posible,
preferiblemente antes de comenzar a escribir la unidad que se probará. Considere probar las
omisiones comunes en los requisitos. El nivel de seguridad, el almacenamiento, el procedimiento de
instalación y la confiabilidad del sistema son un juego justo para las pruebas y, a menudo, se pasan
por alto en el momento de los requisitos.
- Pruebe cada problema de diseño relevante para asegurarse de que el diseño se haya implementado.
Planifique los casos de prueba para este paso en la etapa de diseño o tan pronto como sea posible
antes de comenzar la codificación detallada de la rutina o clase que se probará.
- Use "pruebas básicas" para agregar casos de prueba detallados a aquellos que prueban los requisitos y el
diseño. Agregue pruebas de flujo de datos y luego agregue los casos de prueba restantes necesarios para
ejercitar completamente el código. Como mínimo, debe probar cada línea de código. Las pruebas de base y
de flujo de datos se describen más adelante en este capítulo.
- Use una lista de verificación de los tipos de errores que ha cometido en el proyecto hasta la fecha o que ha
Diseñar los casos de prueba junto con el producto. Esto puede ayudar a evitar errores en los requisitos y el
diseño, que tienden a ser más costosos que los errores de codificación. Planee probar y encontrar defectos
lo antes posible porque es más barato corregir los defectos temprano.
Los desarrolladores a veces se preguntan si es mejor escribir casos de prueba después de que se haya
escrito el código o antes (Beck 2003). El gráfico de aumento del costo de defectos (consulte la Figura 3-1 en
la página 30) sugiere que escribir primero los casos de prueba minimizará la cantidad de tiempo entre el
momento en que se inserta un defecto en el código y el momento en que se detecta y elimina. Esta resulta
ser una de las muchas razones para escribir casos de prueba primero:
- Escribir casos de prueba antes de escribir el código no requiere más esfuerzo que escribir casos de prueba
después del código; simplemente vuelve a secuenciar la actividad de escritura de casos de prueba.
Traducido del inglés al español - www.onlinedoctranslator.com
- Cuando escribe primero los casos de prueba, detecta los defectos antes y puede corregirlos
más fácilmente.
- Escribir casos de prueba primero lo obliga a pensar al menos un poco sobre los requisitos y
el diseño antes de escribir el código, lo que tiende a producir un mejor código.
- Escribir casos de prueba primero expone los problemas de requisitos antes, antes de que se escriba
el código, porque es difícil escribir un caso de prueba para un requisito deficiente.
- Si guarda sus casos de prueba, lo que debe hacer, aún puede probar en último lugar, además
de probar primero.
Considerándolo todo, creo que la programación de prueba primero es una de las prácticas de software más
beneficiosas que surgieron durante la última década y es un buen enfoque general. Pero no es una panacea para las
pruebas, porque está sujeta a las limitaciones generales de las pruebas para desarrolladores, que se describen a
continuación.
Las pruebas de desarrollador tienden a ser "pruebas limpias"Los desarrolladores tienden a probar si el código
funciona (pruebas limpias) en lugar de probar todas las formas en que se rompe el código (pruebas sucias). Las
organizaciones de pruebas inmaduras tienden a tener alrededor de cinco pruebas limpias por cada prueba sucia.
Las organizaciones de pruebas maduras tienden a tener cinco pruebas sucias por cada prueba limpia. Esta relación
no se invierte al reducir las pruebas limpias; se hace creando 25 veces más pruebas sucias (Boris Beizer en Johnson
1994).
3 Las pruebas de los desarrolladores tienden a tener una visión optimista de la cobertura de las pruebasLos
2
1
programadores promedio creen que están logrando una cobertura de prueba del 95 por ciento, pero generalmente
logran una cobertura de prueba de más del 80 por ciento en el mejor de los casos, un 30 por ciento en el peor de los
DATOS DUROS
casos y más del 50-60 por ciento en el caso promedio (Boris Beizer en Johnson 1994).
Las pruebas de desarrollador tienden a omitir tipos más sofisticados de cobertura de pruebaLa
mayoría de los desarrolladores consideran adecuado el tipo de cobertura de prueba conocido como
"cobertura de declaraciones del 100 %". Este es un buen comienzo, pero no es suficiente. Un mejor estándar
de cobertura es cumplir con lo que se denomina "cobertura de sucursal del 100%", con cada término
predicado que se prueba para al menos un valor verdadero y uno falso. La Sección 22.3, “Bolsa de trucos de
prueba”, brinda más detalles sobre cómo lograr esto.
Ninguno de estos puntos reduce el valor de las pruebas de los desarrolladores, pero ayudan a poner las
pruebas de los desarrolladores en la perspectiva adecuada. A pesar de lo valiosas que son las pruebas de
los desarrolladores, no son suficientes para proporcionar una garantía de calidad adecuada por sí mismas y
deben complementarse con otras prácticas, incluidas las pruebas independientes y las técnicas de
construcción colaborativa.
22.3 Bolsa de trucos de prueba 505
Incluso con esta cantidad de entrada relativamente pequeña, tiene casos de prueba posibles de uno con 66
ceros. Para poner esto en perspectiva, si Noah se hubiera bajado del arca y hubiera comenzado a probar
este programa a una velocidad de un billón de casos de prueba por segundo, estaría mucho menos del 1
por ciento del camino hecho hoy. Obviamente, si agregara una cantidad de datos más realista, la tarea de
probar exhaustivamente todas las posibilidades se volvería aún más imposible.
Pruebas incompletas
Referencia cruzadaUna forma de saber si Dado que las pruebas exhaustivas son imposibles, en términos prácticos, el arte de las pruebas consiste en
ha cubierto todo el código es usar un
elegir los casos de prueba con mayor probabilidad de encontrar errores. de los 1066posibles casos de
monitor de cobertura. Para obtener
Cuando esté planeando pruebas, elimine aquellas que no le digan nada nuevo, es decir,
pruebas en datos nuevos que probablemente no produzcan un error si otros datos similares
no produjeron un error. Varias personas han propuesto varios métodos para cubrir las bases
de manera eficiente, y varios de estos métodos se analizan en las siguientes secciones.
A pesar del nombre peludo, la prueba de base estructurada es un concepto bastante simple. La idea
es que necesita probar cada declaración en un programa al menos una vez. Si el enunciado es un
enunciado lógico, unsio untiempo, por ejemplo, necesita variar las pruebas según
506 Capítulo 22: Pruebas de desarrollador
Es posible que haya oído hablar de las pruebas de "cobertura de código" o las pruebas de "cobertura lógica".
Son enfoques en los que pruebas todos los caminos a través de un programa. Dado que cubren todas las
rutas, son similares a las pruebas de base estructurada, pero no incluyen la idea de cubrir todas las rutas con
unmínimoconjunto de casos de prueba. Si usa cobertura de código o pruebas de cobertura lógica, puede
crear muchos más casos de prueba de los que necesitaría para cubrir la misma lógica con pruebas de base
estructurada.
Referencia cruzadaEste Puede calcular la cantidad mínima de casos necesarios para la prueba básica de esta
procedimiento es similar al
manera sencilla:
utilizado para medir la
2.Agregue 1 para cada una de las siguientes palabras clave, o sus equivalentes:si,tiempo,
repetir, por,y, yo.
En este caso, comienza con uno y cuenta lossiuna vez para hacer un total de dos. Eso significa
que necesita tener al menos dos casos de prueba para cubrir todas las rutas a través del
programa. En este ejemplo, necesitaría tener los siguientes casos de prueba:
El código de muestra debe ser un poco más realista para darle una idea precisa de cómo funciona
este tipo de prueba. El realismo en este caso incluye código que contiene defectos.
22.3 Bolsa de trucos de prueba 507
34
35 // agregue el cheque de pago de este empleado al total para la contabilidad totalWithholdings =
36 totalWithholdings + retenciones; JubilaciónTotalGobierno = JubilaciónTotalGobierno + JubilaciónGobierno;
37 JubilaciónTotal = JubilaciónTotal + JubilaciónEmpresa;
38
39 }
40
41 SavePayRecords(totalRetenciones, totalGobiernoRetiro, totalRetiro);
En este ejemplo, necesitará un caso de prueba inicial más uno para cada una de las cinco palabras
clave, para un total de seis. Eso no significa que seis casos de prueba cualesquiera cubrirán todas las
bases. Significa que, como mínimo, se requieren seis casos. A menos que los casos se construyan con
cuidado, es casi seguro que no cubrirán todas las bases. El truco consiste en prestar atención a las
mismas palabras clave que utilizó al contar el número de casos necesarios. Cada palabra clave en el
código representa algo que puede ser verdadero o falso; asegúrese de tener al menos un caso de
prueba para cada verdadero y al menos uno para cada falso.
Aquí hay un conjunto de casos de prueba que cubre todas las bases en este ejemplo:
condición es falsa
3 El primerosiEs falso m_employee[ id ].governmentRetirementWithheld
>=MAX_GOVT_RETIREMENT
4 El segundosies falsa no m_employee[ id ].WantsRetirement
porque la primera
parte de layEs falso
5 El segundosies falsa no elegible para la jubilación (m_employee [id])
porque la segunda
parte deyEs falso
6 El tercerosiEs falso no elegible para jubilación personal (m_employee
[id])
Nota: Esta tabla se ampliará con casos de prueba adicionales a lo largo del capítulo.
Si la rutina fuera mucho más complicada que esto, la cantidad de casos de prueba que tendría que
usar solo para cubrir todas las rutas aumentaría bastante rápido. Las rutinas más cortas tienden a
tener menos rutas para probar. Expresiones booleanas sin muchoyarenaos tienen menos variaciones
para probar. La facilidad de prueba es otra buena razón para mantener sus rutinas cortas y sus
expresiones booleanas simples.
Ahora que creó seis casos de prueba para la rutina y satisfizo las demandas de las pruebas de base
estructurada, ¿puede considerar que la rutina esté completamente probada? Probablemente no.
22.3 Bolsa de trucos de prueba 509
Este tipo de prueba solo le asegura que se ejecutará todo el código. No tiene en cuenta
las variaciones en los datos.
Si considera la última sección y esta en conjunto, le brinda otro ejemplo que ilustra que el flujo de
control y el flujo de datos son igualmente importantes en la programación de computadoras.
Las pruebas de flujo de datos se basan en la idea de que el uso de datos es al menos tan propenso a errores como el
flujo de control. Boris Beizer afirma que al menos la mitad de todo el código consiste en declaraciones e
- UsóLos datos se han utilizado para computación, como argumento para una rutina
o para otra cosa.
- DelicadoLos datos se definieron una vez, pero no se han definido de alguna manera. Por
ejemplo, si los datos son un puntero, quizás el puntero se haya liberado. Si es un índice
de bucle for, quizás el programa esté fuera del bucle y el lenguaje de programación no
define el valor de un índice de bucle for una vez que está fuera del bucle. Si es un
puntero a un registro en un archivo, tal vez el archivo se haya cerrado y el puntero de
registro ya no sea válido.
Además de tener los términos "definido", "usado" y "matado", es conveniente tener términos
que describan entrar o salir de una rutina inmediatamente antes o después de hacer algo en
una variable:
- IngresóEl flujo de control entra en la rutina inmediatamente antes de que se actúe sobre la
variable. Una variable de trabajo se inicializa en la parte superior de una rutina, por ejemplo.
- SalidoEl flujo de control abandona la rutina inmediatamente después de que se actúa sobre la
variable. Se asigna un valor de retorno a una variable de estado al final de una rutina, por
ejemplo.
La combinación normal de estados de datos es que una variable se define, se usa una o más
veces y quizás se elimine. Ver los siguientes patrones sospechosamente:
- Definido-DefinidoSi tiene que definir una variable dos veces antes de que se mantenga el valor, no
necesita un programa mejor, ¡necesita una computadora mejor! Es un desperdicio y propenso a
errores, incluso si no es realmente incorrecto.
510 Capítulo 22: Pruebas de desarrollador
- Asesinado-asesinadoUna variable no debería necesitar ser eliminada dos veces. Las variables no vuelven a
la vida. Una variable resucitada indica una programación descuidada. Las muertes dobles también son
fatales para los punteros: una de las mejores formas de colgar su máquina es matar (liberar) un puntero dos
veces.
Verifique estas secuencias anómalas de estados de datos antes de que comience la prueba. Una vez que
haya verificado las secuencias anómalas, la clave para escribir casos de prueba de flujo de datos es ejercitar
todas las rutas posibles definidas y utilizadas. Puede hacer esto con varios grados de minuciosidad,
incluyendo
- Todas las definiciones. Pruebe cada definición de cada variable, es decir, cada lugar en el que
cualquier variable recibe un valor. Esta es una estrategia débil porque si intenta ejercitar cada
línea de código, lo hará de manera predeterminada.
- Todas las combinaciones definidas-utilizadas. Pruebe cada combinación de definir una variable
en un lugar y usarla en otro. Esta es una estrategia más sólida que probar todas las
definiciones porque simplemente ejecutar cada línea de código no garantiza que se probarán
todas las combinaciones definidas y utilizadas.
22.3 Bolsa de trucos de prueba 511
Para cubrir todas las rutas del programa, necesita un caso de prueba en el queCondición 1es verdadera y
otra en la que es falsa. También necesita un caso de prueba en el queCondición 2es verdadera y otra en la
que es falsa. Esto puede ser manejado por dos casos de prueba: Caso 1 (Condición 1=Verdadero, Condición
2=Verdadero) y Caso 2 (Condición 1=Falso,Condición 2=Falso). Esos dos casos son todo lo que necesita para
las pruebas de base estructurada. También son todo lo que necesita para ejercitar cada línea de código que
define una variable; le brindan la forma débil de prueba de flujo de datos automáticamente.
Sin embargo, para cubrir cada combinación utilizada definida, debe agregar algunos
casos más. Ahora mismo tienes los casos creados al tenerCondición 1yCondición 2cierto
al mismo tiempo yCondición 1yCondición 2falso al mismo tiempo:
x= a
...
y=x+1
x= b
...
y=x-1
Pero necesita dos casos más para probar cada combinación utilizada definida: (1)x = uny entoncesy =
x - 1y 2)x = segundoy entoncesy = x + 1. En este ejemplo, puede obtener estas combinaciones
agregando dos casos más: Caso 3 (Condición 1=Verdadero,Condición 2=Falso) y Caso 4 (Condición
1=Falso,Condición 2=Verdadero).
Una buena manera de desarrollar casos de prueba es comenzar con una prueba de base estructurada, que le brinda algunos,
si no todos, los flujos de datos definidos y usados. Luego, agregue los casos que aún necesita para tener un conjunto
Como se discutió en la sección anterior, la prueba de base estructurada proporcionó seis casos de prueba para la
rutina que comienza en la página 507. La prueba de flujo de datos de cada par usado definido requiere varios casos
de prueba más, algunos de los cuales están cubiertos por casos de prueba existentes y otros que no lo son. Aquí
están todas las combinaciones de flujo de datos que agregan casos de prueba más allá de los generados por las
Una vez que realice el proceso de listar casos de prueba de flujo de datos varias veces, obtendrá una idea de
qué casos son fructíferos y cuáles ya están cubiertos. Cuando te quedes atascado, haz una lista de todas las
combinaciones usadas definidas. Eso puede parecer mucho trabajo, pero está garantizado que le mostrará
cualquier caso que no haya probado de forma gratuita en el enfoque de prueba de base.
Partición de equivalencia
Referencia cruzadaLa partición Un buen caso de prueba cubre una gran parte de los posibles datos de entrada. Si dos casos de prueba
de equivalencia se analiza con
arrojan exactamente los mismos errores, solo necesita uno de ellos. El concepto de "partición de
mucha más profundidad en los
libros enumerados en la sección
equivalencia" es una formalización de esta idea y ayuda a reducir el número de casos de prueba requeridos.
"Recursos adicionales" al final de
este capítulo.
En la lista que comienza en la página 507, la línea 7 es un buen lugar para usar la partición de
equivalencia. La condición a probar esm_employee[ ID ].governmentRetirementWithheld <
MAX_GOVT_RETIREMENT. Este caso tiene dos clases de equivalencia: la clase en la que
m_employee[ ID ].governmentRetirementWithheldes menos queMAX_GOVT_RETIREMENT y la
clase en la que es mayor o igual queMAX_GOVT_RETIREMENT. Otras partes del programa
pueden tener otras clases de equivalencia relacionadas que implican que necesita probar más
de dos valores posibles dem_employee[ ID ].government-RetirementWithheld, pero en lo que
respecta a esta parte del programa, solo se necesitan dos.
Adivinando errores
Referencia cruzadaPara obtener Además de las técnicas de prueba formales, los buenos programadores usan una variedad de
detalles sobre la heurística, consulte
técnicas heurísticas menos formales para exponer errores en su código. Una heurística es la
la Sección 2.2, “Cómo usar metáforas
de software”.
técnica de adivinar errores. El término "adivinación de errores" es un nombre vulgar para un
concepto sensato. Significa crear casos de prueba basados en conjeturas sobre dónde el
programa podría tener errores, aunque implica cierta sofisticación en las conjeturas.
Puede basar sus conjeturas en la intuición o en experiencias pasadas. El Capítulo 21, “Construcción
colaborativa”, señala que una virtud de las inspecciones es que producen y mantienen una lista de
errores comunes. La lista se utiliza para comprobar el código nuevo. Cuando mantiene registros de
los tipos de errores que ha cometido antes, aumenta la probabilidad de que su "suposición de error"
descubra un error.
Las próximas secciones describen tipos específicos de errores que se prestan a adivinar
errores.
Análisis de límites
Una de las áreas más fructíferas para las pruebas son las condiciones de contorno: errores de uno en uno.
Dichonúmero - 1cuando quieres decirnúmeroy diciendo>=cuando quieres decir>son errores comunes.
La idea del análisis de límites es escribir casos de prueba que ejerzan las condiciones de
límites. Gráficamente, si está probando un rango de valores que son menores quemáximo,
tienes tres condiciones posibles:
Perímetro Perímetro
abajomáx. máx. arribamáx.
Como se muestra, hay tres casos límite: justo menos demáximo,máximosí mismo, y apenas mayor que
máximo. Se necesitan tres casos para asegurarse de que no se ha cometido ninguno de los errores
comunes.
Límites compuestos
El análisis de límites también se aplica a los valores mínimos y máximos permitidos. En este
ejemplo, podría ser mínimo o máximo.Sueldo bruto,EmpresaRetiro, oPersonal-
JubilaciónContribución, pero debido a que los cálculos de esos valores están fuera del alcance
de la rutina, los casos de prueba para ellos no se analizan más aquí.
Un tipo más sutil de condición de frontera ocurre cuando la frontera implica una combinación de
variables. Por ejemplo, si dos variables se multiplican juntas, ¿qué sucede cuando ambas son
números positivos grandes? ¿Números negativos grandes?0? ¿Qué pasa si todas las cadenas pasadas
a una rutina son extraordinariamente largas?
En el ejemplo en ejecución, es posible que desee ver qué sucede con las variablestotal-
Retenciones, totalGobiernoRetiro, yretiro totalcuando cada miembro de un gran grupo de
empleados tiene un gran salario, digamos, un grupo de programadores a $ 250,000 cada uno.
(¡Siempre podemos esperar!) Esto requiere otro caso de prueba:
11 Un grupo grande de empleados, cada uno de los cuales tiene un salario grande (lo que
constituye "grande" depende del sistema específico que se esté desarrollando); por
ejemplo, diremos 1000 empleados, cada uno con un salario de $250 000, ninguno de
ellos a quienes se les ha retenido alguna contribución al seguro social y todos quieren
retención de jubilación.
Un caso de prueba en la misma línea pero en el lado opuesto del espejo sería un pequeño
grupo de empleados, cada uno de los cuales tiene un salario de $0.00:
Además de adivinar que los errores aparecen alrededor de las condiciones de contorno, puede adivinar y
probar otras clases de datos incorrectos. Los casos típicos de prueba de datos erróneos incluyen
- demasiados datos
22.3 Bolsa de trucos de prueba 515
- Datos no inicializados
Algunos de los casos de prueba en los que pensaría si siguiera estas sugerencias ya han sido
cubiertos. Por ejemplo, "muy pocos datos" se tratan en los casos 2 y 12, y es difícil encontrar
algo para "tamaño de datos incorrecto". Sin embargo, las clases de datos incorrectos dan lugar
a algunos casos más:
La configuración normal mínima es útil para probar no solo un elemento, sino un grupo de
elementos. Es similar en espíritu a la condición límite de muchos valores mínimos, pero es
diferente en que crea el conjunto de valores mínimos a partir del conjunto de lo que
normalmente se espera. Un ejemplo sería guardar una hoja de cálculo vacía al probar una hoja
de cálculo. Para probar un procesador de textos, sería guardar un documento vacío. En el caso
del ejemplo en ejecución, probar la configuración normal mínima agregaría el siguiente caso
de prueba:
de valores esperados. Un ejemplo de esto sería guardar una hoja de cálculo que sea tan
grande como el "tamaño máximo de hoja de cálculo" anunciado en el empaque del producto. O
imprimiendo la hoja de cálculo de tamaño máximo. Para un procesador de textos, sería
guardar un documento del mayor tamaño recomendado. En el caso del ejemplo en ejecución,
probar la configuración normal máxima depende del número normal máximo de empleados.
Suponiendo que es 500, agregaría el siguiente caso de prueba:
El último tipo de prueba de datos normal, la prueba de compatibilidad con datos antiguos,
entra en juego cuando el programa o la rutina reemplazan a un programa o rutina anterior. La
nueva rutina debería producir los mismos resultados con datos antiguos que la rutina anterior,
excepto en los casos en los que la rutina anterior era defectuosa. Este tipo de continuidad entre
versiones es la base de las pruebas de regresión, cuyo propósito es asegurar que las
correcciones y mejoras mantengan los niveles anteriores de calidad sin retroceder. En el caso
del ejemplo en ejecución, el criterio de compatibilidad no agregaría ningún caso de prueba.
Supongamos que está escribiendo un caso de prueba para un salario nominal; necesita un salario nominal,
y la forma de obtener uno es escribir cualquier número que encuentre en sus manos. Lo intentaré:
1239078382346
ESTÁ BIEN. Es un salario bastante alto, un poco más de un billón de dólares, de hecho, pero si lo reduzco
para que sea algo realista, obtengops90.783,82.
Ahora, suponga además que el caso de prueba tiene éxito, es decir, encuentra un error. ¿Cómo sabes
que ha encontrado un error? Bueno, presumiblemente, sabes cuál es la respuesta y cuál debería ser
porque calculaste la respuesta correcta a mano. Cuando intentas hacer cálculos manuales con un
número feo comops90.783,82, sin embargo, es tan probable que cometa un error en el cálculo
manual como que lo descubra en su programa. Por otro lado, un buen número par comops20,000
hace que el procesamiento de números sea muy fácil. los0s son fáciles de introducir en la
calculadora, y multiplicando por2es algo que la mayoría de los programadores pueden hacer sin usar
los dedos de las manos y los pies.
Podrías pensar que un número feo comops90.783,82sería más probable que revelara
errores, pero no lo es más que cualquier otro número en su clase de equivalencia.
22.4 Errores típicos 517
Es natural suponer que los defectos se distribuyen uniformemente en todo el código fuente. Si
tiene un promedio de 10 defectos por cada 1000 líneas de código, podría suponer que tendrá
un defecto en una clase que contiene 100 líneas de código. Esta es una suposición natural,
PUNTO CLAVE
pero es incorrecta.
Capers Jones informó que un programa enfocado de mejora de la calidad en IBM identificó 31 de 425
clases en el sistema IMS como propensas a errores. Las 31 clases se repararon o se rediseñaron por
completo y, en menos de un año, los defectos informados por los clientes contra IMS se redujeron
diez a uno. Los costos totales de mantenimiento se redujeron en aproximadamente un 45 por ciento.
La satisfacción del cliente mejoró de “inaceptable” a “buena” (Jones 2000).
La mayoría de los errores tienden a concentrarse en unas pocas rutinas altamente defectuosas. Aquí
está la relación general entre errores y código:
3 - El ochenta por ciento de los errores se encuentran en el 20 por ciento de las clases o rutinas de
2
1
un proyecto (Endres 1975, Gremillion 1984, Boehm 1987b, Shull et al 2002).
DATOS DUROS - El cincuenta por ciento de los errores se encuentran en el 5 por ciento de las clases de un proyecto (Jones
2000).
Estas relaciones pueden no parecer tan importantes hasta que reconozca algunos corolarios.
Primero, el 20% de las rutinas de un proyecto contribuyen con el 80% del costo de desarrollo
(Boehm 1987b). Eso no significa necesariamente que el 20% que más cuesta sea el mismo que
el 20% con más defectos, pero es bastante sugerente.
3 En segundo lugar, independientemente de la proporción exacta del costo aportado por las rutinas altamente
2
1
defectuosas, las rutinas altamente defectuosas son extremadamente costosas. En un estudio clásico de la
DATOS DUROS
década de 1960, IBM realizó un análisis de su sistema operativo OS/360 y descubrió que los errores no se
distribuían uniformemente en todas las rutinas, sino que se concentraban en unas pocas. Se descubrió que
esas rutinas propensas a errores eran "las entidades más costosas en programación" (Jones 1986a).
Contenían hasta 50 defectos por cada 1000 líneas de código, y repararlos a menudo costaba 10 veces más de
lo que se necesitaba para desarrollar todo el sistema. (Los costos incluyeron atención al cliente y
mantenimiento en el campo).
518 Capítulo 22: Pruebas de desarrollador
Referencia cruzadaOtra clase de Tercero, la implicación de costosas rutinas para el desarrollo es clara. Como dice la vieja
rutinas que tienden a contener
expresión, “el tiempo es dinero”. El corolario es que “el dinero es tiempo”, y si puede reducir
muchos errores es la clase de
rutinas demasiado complejas.
cerca del 80 por ciento del costo al evitar rutinas problemáticas, también puede reducir una
Para obtener detalles sobre cómo parte sustancial del horario. Esta es una clara ilustración del Principio General de la Calidad del
identificar y simplificar las rutinas,
Software: mejorar la calidad mejora el cronograma de desarrollo y reduce los costos de
consulte las “Pautas generales
para reducir la complejidad” en la
desarrollo.
Sección 19.6.
Cuarto, la implicación de evitar rutinas problemáticas para el mantenimiento es igualmente clara.
Las actividades de mantenimiento deben centrarse en identificar, rediseñar y reescribir desde cero
aquellas rutinas que han sido identificadas como propensas a errores. En el proyecto IMS
mencionado anteriormente, la productividad de las versiones IMS mejoró alrededor del 15 por ciento
después del reemplazo de las clases propensas a errores (Jones 2000).
Boris Beizer combinó datos de varios estudios y llegó a una taxonomía de errores excepcionalmente
detallada (Beizer 1990). El siguiente es un resumen de sus resultados:
25,18% Estructural
22,44% Datos
Beizer informó sus resultados con precisión de dos decimales, pero la investigación sobre los tipos
de error en general no ha sido concluyente. Diferentes estudios informan tipos de errores muy
diferentes, y los estudios que informan sobre tipos de errores similares llegan a resultados muy
diferentes, resultados que difieren en un 50% en lugar de en centésimas de punto porcentual.
Dadas las amplias variaciones en los informes, la combinación de resultados de múltiples estudios como lo
ha hecho Beizer probablemente no produzca datos significativos. Pero incluso si los datos no son
concluyentes, algunos de ellos son sugerentes. Las siguientes son algunas de las sugerencias que se
pueden derivar de los datos:
22.4 Errores típicos 519
3 El alcance de la mayoría de los errores es bastante limitado.Un estudio encontró que el 85 por
2
1
ciento de los errores podían corregirse sin modificar más de una rutina (Endres 1975).
DATOS DUROS
Muchos errores están fuera del dominio de la construcción.Los investigadores que realizaron una
serie de 97 entrevistas encontraron que las tres fuentes más comunes de errores eran el escaso
conocimiento del dominio de la aplicación, los requisitos fluctuantes y contradictorios y la falla en la
comunicación y la coordinación (Curtis, Krasner e Iscoe 1988).
Si ve huellas de pezuñas, piense en La mayoría de los errores de construcción son culpa de los programadores.Un par de estudios
caballos, no en cebras. El sistema
realizados hace muchos años encontraron que, del total de errores informados, aproximadamente el 95 %
operativo probablemente no esté
3 Los errores administrativos (tipografías) son una fuente sorprendentemente común de problemasUn
2
1
estudio encontró que el 36% de todos los errores de construcción fueron errores administrativos (Weiss
DATOS DUROS
1975). Un estudio de 1987 de casi 3 millones de líneas de software de dinámica de vuelo encontró que el
18% de todos los errores eran administrativos (Card 1987). Otro estudio encontró que el 4% de todos los
errores eran errores ortográficos en los mensajes (Endres 1975). En uno de mis programas, un colega
encontró varios errores ortográficos simplemente al pasar todas las cadenas del archivo ejecutable a través
de un corrector ortográfico. La atención al detalle cuenta. Si lo duda, considere que tres de los errores de
software más costosos de todos los tiempos, con un costo de $ 1.6 mil millones, $ 900 millones y $ 245
millones, involucraron el cambio de unpersonaje únicoen un programa previamente correcto (Weinberg
1983).
La mayoría de los errores son fáciles de corregirAlrededor del 85% de los errores se pueden corregir en menos de
unas pocas horas. Alrededor del 15% se puede arreglar en unas pocas horas a unos pocos días. Y alrededor del 1%
tarda más (Weiss 1975, Ostrand y Weyuker 1984, Grady 1992). Este resultado está respaldado por la observación de
Barry Boehm de que alrededor del 20% de los errores requieren alrededor del 80% de los recursos para corregirse
(Boehm 1987b). Evite la mayor cantidad posible de errores graves realizando revisiones de diseño y requisitos en
sentido ascendente. Maneje los numerosos errores pequeños tan eficientemente como pueda.
520 Capítulo 22: Pruebas de desarrollador
Es una buena idea medir las experiencias de su propia organización con los errores.La diversidad
de resultados citados en esta sección indica que las personas en diferentes organizaciones tienen
experiencias tremendamente diferentes. Eso hace que sea difícil aplicar las experiencias de otras
organizaciones a la suya. Algunos resultados van en contra de la intuición común; es posible que
necesite complementar su intuición con otras herramientas. Un buen primer paso es comenzar a
medir su proceso de desarrollo para que sepa dónde están los problemas.
Si los datos que clasifican los errores no son concluyentes, tampoco lo son muchos de los datos que
atribuyen los errores a las diversas actividades de desarrollo. Una certeza es que la construcción siempre
resulta en un número significativo de errores. A veces, la gente argumenta que los errores causados por la
construcción son más baratos de corregir que los errores causados por los requisitos o el diseño. La
corrección de errores de construcción individuales puede ser más económica, pero la evidencia no respalda
tal afirmación sobre el costo total.
3 - En proyectos pequeños, los defectos de construcción constituyen la mayor parte de todos los
2
1
errores. En un estudio de errores de codificación en un proyecto pequeño (1000 líneas de código), el
DATOS DUROS
75% de los defectos se debieron a la codificación, en comparación con el 10% de los requisitos y el
15% del diseño (Jones 1986a). Este desglose de errores parece ser representativo de muchos
proyectos pequeños.
- Los errores de construcción, aunque son más baratos de corregir que los requisitos y los errores de
diseño, siguen siendo costosos. Un estudio de dos proyectos muy grandes en Hewlett-Packard
encontró que el defecto de construcción promedio cuesta entre un 25% y un 50% más que el error
de diseño promedio (Grady 1987). Cuando se calculó la mayor cantidad de defectos de construcción
en la ecuación general, el costo total para reparar los defectos de construcción fue una o dos veces
mayor que el costo atribuido a los defectos de diseño.
La figura 22-2 proporciona una idea aproximada de la relación entre el tamaño del proyecto y la
fuente de errores.
22.4 Errores típicos 521
100%
Construcción
En algunos proyectos,
Errores de
este porcentaje de
Cada Actividad
los errores también pueden
ser de construcción.
Diseño
Requisitos
0%
2K 8K 32K 128K 512K
Tamaño del proyecto en líneas de código
Figura 22-2A medida que aumenta el tamaño del proyecto, disminuye la proporción de errores cometidos durante la
construcción. No obstante, los errores de construcción representan entre el 45% y el 75% de todos los errores, incluso en los
proyectos más grandes.
DATOS DUROS
utilizando una mezcolanza de técnicas (Boehm 1981, Gremillion 1984, Yourdon 1989a, Jones
1998, Jones 2000, Weber 2003). Los casos que tienen una décima parte de estos errores son
raros; los casos que tienen 10 veces más tienden a no ser reportados. (¡Probablemente nunca
se completen!)
- Harlan Mills fue pionero en el "desarrollo de sala limpia", una técnica que ha sido capaz de
lograr tasas tan bajas como 3 defectos por 1000 líneas de código durante las pruebas internas
y 0,1 defectos por 1000 líneas de código en el producto lanzado (Cobb y Mills 1990). Algunos
proyectos, por ejemplo, el software del transbordador espacial, han alcanzado un nivel de 0
defectos en 500 000 líneas de código mediante el uso de un sistema de métodos formales de
desarrollo, revisiones por pares y pruebas estadísticas (Fishman 1996).
3 - Watts Humphrey informa que los equipos que utilizan Team Software Process (TSP) han alcanzado niveles de
2
1
defectos de alrededor de 0,06 defectos por cada 1000 líneas de código. TSP se enfoca en capacitar a los
Los resultados de los proyectos TSP y cleanroom confirman otra versión del Principio General de la Calidad
del Software: es más barato construir software de alta calidad que construir y reparar software de baja
calidad. La productividad para un proyecto de sala limpia de 80 000 líneas completamente desprotegido fue
de 740 líneas de código por mes de trabajo. La tasa promedio de la industria para el código totalmente
verificado está más cerca de 250 a 300 líneas por mes de trabajo, incluidos todos los gastos generales que
no son de codificación (Cusumano et al 2003). El ahorro de costos y la productividad provienen del hecho de
que prácticamente no se dedica tiempo a la depuración en TSP o proyectos de sala limpia. ¿No dedica
tiempo a la depuración? ¡Esa es verdaderamente una meta digna!
Es posible que haya tenido una experiencia como esta: Se descubre que el software tiene un error. Tiene
algunas corazonadas inmediatas sobre qué parte del código podría estar equivocada, pero todo ese código
parece ser correcto. Ejecuta varios casos de prueba más para intentar refinar el error, pero todos los nuevos
PUNTO CLAVE
casos de prueba producen resultados correctos. Pasas varias horas leyendo y releyendo el código y
calculando a mano los resultados. Todos ellos echan un vistazo. Después de algunas horas más, algo hace
que vuelva a examinar los datos de la prueba. ¡Eureka! ¡El error está en los datos de prueba! ¡Qué idiota se
siente perder horas rastreando un error en los datos de prueba en lugar de en el código!
3 Esta es una experiencia común. Los casos de prueba a menudo tienen la misma o mayor probabilidad de
2
1
contener errores que el código que se está probando (Weiland 1983, Jones 1986a, Johnson 1994). Las
DATOS DUROS
razones son fáciles de encontrar, especialmente cuando el desarrollador escribe los casos de prueba. Los
casos de prueba tienden a crearse sobre la marcha en lugar de a través de un cuidadoso proceso de diseño y
construcción. A menudo se consideran pruebas de una sola vez y se desarrollan con el cuidado
correspondiente a algo que se debe desechar.
Puede hacer varias cosas para reducir la cantidad de errores en sus casos de prueba:
Revisa tu trabajoDesarrolle casos de prueba tan cuidadosamente como desarrolla el código. Dicho cuidado
ciertamente incluye la verificación doble de sus propias pruebas. Recorra el código de prueba en un depurador, línea
por línea, tal como lo haría con el código de producción. Los recorridos e inspecciones de los datos de prueba son
apropiados.
Planifique casos de prueba a medida que desarrolla su softwareLa planificación efectiva de las pruebas
debe comenzar en la etapa de requisitos o tan pronto como reciba la asignación para el programa. Esto
ayuda a evitar casos de prueba basados en suposiciones erróneas.
Mantenga sus casos de pruebaPase un poco de tiempo de calidad con sus casos de prueba.
Guárdelos para las pruebas de regresión y para trabajar en la versión 2. Es fácil justificar el
problema si sabe que los conservará en lugar de tirarlos.
Conecte las pruebas unitarias a un marco de pruebaPrimero escriba el código para las pruebas unitarias, pero
intégrelas en un marco de prueba de todo el sistema (como JUnit) a medida que completa cada prueba. Tener un
marco de prueba integrado evita la tendencia, que acabamos de mencionar, de desechar los casos de prueba.
22.5 Herramientas de soporte de prueba 523
Esta sección examina los tipos de herramientas de prueba que puede comprar comercialmente o construir
usted mismo. No nombrará productos específicos porque fácilmente podrían estar desactualizados cuando
lea esto. Consulte la revista de su programador favorito para conocer los detalles más recientes.
El término "andamio" proviene de la construcción de edificios. Los andamios se construyen para que los
trabajadores puedan llegar a partes de un edificio que de otro modo no podrían alcanzar. El andamiaje de
software se crea con el único propósito de facilitar el ejercicio del código.
Otras lecturasPara ver varios Un tipo de andamiaje es una clase que se manipula para que pueda ser utilizada por otra clase
buenos ejemplos de andamios,
que se está probando. Tal clase se denomina "objeto simulado" u "objeto de código
consulte el ensayo de Jon Bentley
"A Small Matter of Programming"
auxiliar" (Mackinnon, Freemand y Craig 2000; Thomas y Hunt 2002). Se puede usar un enfoque
enProgramación similar con las rutinas de bajo nivel, que se denominan "rutinas auxiliares". Puede hacer que
perlas, 2ª ed. (2000).
un objeto simulado o rutinas auxiliares sean más o menos realistas, según la veracidad que
necesite. En estos casos, el andamio puede
- Imprima un mensaje de diagnóstico, tal vez un eco de los parámetros de entrada, o registre un mensaje
en un archivo.
- Funciona como una versión lenta, gorda, simple o menos precisa del objeto real o de la rutina.
Otro tipo de andamiaje es una rutina falsa que llama a la rutina real que se está probando. Esto se
llama un "controlador" o, a veces, un "arnés de prueba". Este andamio puede
- Tome argumentos de la línea de comandos (en los sistemas operativos que lo admitan)
y llame al objeto.
Referencia cruzadaLa línea entre las Un último tipo de andamiaje es el archivo ficticio, una versión pequeña del archivo real que tiene los mismos
herramientas de prueba y las herramientas de
tipos de componentes que tiene un archivo de tamaño completo. Un pequeño archivo ficticio ofrece un par
depuración es borrosa. Para obtener detalles
cc2e.com/2268 Obviamente, construir andamios requiere algo de trabajo, pero si alguna vez se detecta un error en
una clase, puede reutilizar los andamios. Y existen numerosas herramientas para agilizar la creación
de objetos simulados y otros andamios. Si usa scaffolding, la clase también se puede probar sin el
riesgo de que se vea afectada por interacciones con otras clases. El andamiaje es particularmente útil
cuando se trata de algoritmos sutiles. Es fácil quedar atrapado en una rutina en la que lleva varios
minutos ejecutar cada caso de prueba porque el código que se está ejercitando está incrustado en
otro código. Scaffolding le permite ejercitar el código directamente. Los pocos minutos que pasa
construyendo andamios para ejercitar el código profundamente enterrado pueden ahorrar horas de
tiempo de depuración.
Puede usar cualquiera de los numerosos marcos de prueba disponibles para proporcionar
andamiaje para sus programas (JUnit, CppUnit, NUnit, etc.). Si su entorno no es compatible con
uno de los marcos de prueba existentes, puede escribir algunas rutinas en una clase e incluir
unprincipal()rutina de scaffolding en el archivo para probar la clase, aunque las rutinas que se
prueban no están pensadas para ser independientes. losprincipal()La rutina puede leer
argumentos de la línea de comando y pasarlos a la rutina que se está probando para que
pueda ejercitar la rutina por sí sola antes de integrarla con el resto del programa. Cuando
integre el código, deje las rutinas y el código de scaffolding que las ejecuta en el archivo y use
comandos de preprocesador o comentarios para desactivar el código de scaffolding. Como
está preprocesado, no afecta el código ejecutable y, como se encuentra en la parte inferior del
archivo, no estorba visualmente. No se hace daño al dejarlo adentro. Está ahí si lo necesita
nuevamente, y no gasta el tiempo que llevaría eliminarlo y archivarlo.
Herramientas de diferencias
Referencia cruzadaPara obtener más Las pruebas de regresión, o nuevas pruebas, son mucho más fáciles si tiene herramientas automatizadas
información sobre las pruebas de
para comparar el resultado real con el resultado esperado. Una forma fácil de verificar la salida impresa es
regresión, consulte "Nuevas pruebas
cc2e.com/2275 También puede escribir código para ejercitar piezas seleccionadas de un programa de forma sistemática. Hace
algunos años, desarrollé un algoritmo de encriptación patentado y escribí un programa de encriptación de archivos
para usarlo. La intención del programa era codificar un archivo para que pudiera ser
22.5 Herramientas de soporte de prueba 525
Configuré un generador de datos de prueba que ejercitó completamente las partes de cifrado
y descifrado del programa. Generaba archivos de caracteres aleatorios en tamaños aleatorios,
desde 0K hasta 500K. Generaba contraseñas de caracteres aleatorios en longitudes aleatorias
de 1 a 255. Para cada caso aleatorio, generaba dos copias del archivo aleatorio, cifraba una
copia, se reinicializaba, descifraba la copia y luego comparaba cada byte de la copia descifrada
con el copia inalterada. Si algún byte era diferente, el generador imprimía toda la información
que necesitaba para reproducir el error.
Pesé los casos de prueba hacia la longitud promedio de mis archivos, 30K, que era
considerablemente más corta que la longitud máxima de 500K. Si no hubiera ponderado los casos de
prueba hacia una longitud más corta, la longitud de los archivos se habría distribuido uniformemente
entre 0K y 500K. La longitud promedio del archivo probado habría sido de 250K. La longitud
promedio más corta significaba que podía probar más archivos, contraseñas, condiciones de fin de
archivo, longitudes de archivos impares y otras circunstancias que podrían producir errores que con
longitudes aleatorias uniformes.
Los resultados fueron gratificantes. Después de ejecutar solo unos 100 casos de prueba, encontré dos
errores en el programa. Ambos surgieron de casos especiales que tal vez nunca se hubieran presentado en
la práctica, pero no obstante eran errores y me alegré de encontrarlos. Después de corregirlos, ejecuté el
programa durante semanas, cifrando y descifrando más de 100 000 archivos sin ningún error. Dada la
variedad de contenidos de archivos, longitudes y contraseñas que probé, podía afirmar con confianza que el
programa era correcto.
- Los generadores de datos aleatorios correctamente diseñados pueden generar combinaciones inusuales de
- Los generadores de datos aleatorios pueden ejercitar su programa más a fondo que usted.
- Puede refinar los casos de prueba generados aleatoriamente a lo largo del tiempo para que
enfaticen un rango realista de entrada. Esto concentra las pruebas en las áreas más probables de ser
ejercidas por los usuarios, maximizando la confiabilidad en esas áreas.
- El diseño modular vale la pena durante las pruebas. Pude extraer el código de cifrado y
descifrado y usarlo independientemente del código de la interfaz de usuario, lo que facilitó el
trabajo de escribir un controlador de prueba.
- Puede reutilizar un controlador de prueba si alguna vez tiene que cambiar el código que prueba. Una vez que hube
corregido los dos primeros errores, pude comenzar a volver a realizar la prueba de inmediato.
526 Capítulo 22: Pruebas de desarrollador
Monitores de cobertura
cc2e.com/2282 Karl Wiegers informa que las pruebas realizadas sin medir la cobertura del código generalmente ejercitan
solo alrededor del 50% al 60% del código (Wiegers 2002). Un monitor de cobertura es una herramienta que
3
2 realiza un seguimiento del código que se ejerce y el código que no. Un monitor de cobertura es
1
especialmente útil para las pruebas sistemáticas porque le dice si un conjunto de casos de prueba ejercita
DATOS DUROS completamente el código. Si ejecuta su conjunto completo de casos de prueba y el monitor de cobertura
indica que aún no se ha ejecutado algún código, sabe que necesita más pruebas.
Registrador de datos/registro
Algunas herramientas pueden monitorear su programa y recopilar información sobre el estado del
programa en caso de falla, similar a la "caja negra" que usan los aviones para diagnosticar los resultados de
los accidentes. El registro sólido ayuda al diagnóstico de errores y admite un servicio efectivo después de
que se haya lanzado el software.
Puede crear su propio registrador de datos registrando eventos significativos en un archivo. Registre el
estado del sistema antes de un error y los detalles de las condiciones de error exactas. Esta funcionalidad
puede compilarse en la versión de desarrollo del código y compilarse a partir de la versión publicada. De
manera alternativa, si implementa el registro con almacenamiento de eliminación automática y una
ubicación y contenido cuidadosos de los mensajes de error, puede incluir funciones de registro en las
versiones de lanzamiento.
Depuradores simbólicos
Referencia cruzadaLa Un depurador simbólico es un complemento tecnológico para los recorridos e inspecciones del código. Un
disponibilidad de los depuradores
depurador tiene la capacidad de recorrer el código línea por línea, realizar un seguimiento de los valores de
varía según la madurez del
entorno tecnológico. Para obtener
las variables y siempre interpretar el código de la misma manera que lo hace la computadora. El proceso de
más información sobre este recorrer paso a paso un fragmento de código en un depurador y ver cómo funciona es enormemente
fenómeno, consulte la Sección 4.3,
valioso.
“Su ubicación en la ola
tecnológica”.
Recorrer el código en un depurador es, en muchos aspectos, el mismo proceso que hacer que otros
programadores revisen su código en una revisión. Ni sus compañeros ni el depurador tienen los
mismos puntos ciegos que usted. El beneficio adicional con un depurador es que requiere menos
mano de obra que una revisión en equipo. Ver su código ejecutarse bajo una variedad de conjuntos
de datos de entrada es una buena garantía de que ha implementado el código que pretendía.
Un buen depurador es incluso una buena herramienta para aprender sobre su idioma porque
puede ver exactamente cómo se ejecuta el código. Puede alternar entre una vista de su código
de lenguaje de alto nivel y una vista del código ensamblador para ver cómo se traduce el
código de alto nivel a ensamblador. Puede observar los registros y la pila para ver cómo se
pasan los argumentos. Puedes mirar el código que tiene tu compilador
22.5 Herramientas de soporte de prueba 527
optimizado para ver los tipos de optimizaciones que se realizan. Ninguno de estos beneficios
tiene mucho que ver con el uso previsto del depurador (diagnosticar errores que ya se han
detectado), pero el uso imaginativo de un depurador produce beneficios mucho más allá de su
estatuto inicial.
cc2e.com/2289 Otra clase de herramientas de soporte de pruebas están diseñadas para perturbar un sistema.
Mucha gente tiene historias de programas que funcionan 99 de cada 100 veces pero fallan en la
centésima ejecución con los mismos datos. El problema casi siempre es una falla al inicializar una
variable en algún lugar, y generalmente es difícil de reproducir porque 99 de cada 100 veces la
variable no inicializada resulta ser0.
Las herramientas de soporte de pruebas en esta clase tienen una variedad de capacidades:
- Temblor de memoriaEn los sistemas multitarea, algunas herramientas pueden reorganizar la memoria a
medida que opera su programa para que pueda estar seguro de que no ha escrito ningún código que
dependa de que los datos estén en ubicaciones absolutas en lugar de relativas.
cc2e.com/2296 Una poderosa herramienta de prueba es una base de datos de errores que se han informado. Dicha base de datos es
tanto una herramienta técnica como de gestión. Le permite comprobar si hay errores recurrentes, realizar un
seguimiento de la velocidad a la que se detectan y corrigen nuevos errores, y realizar un seguimiento del estado de
los errores abiertos y cerrados y su gravedad. Para obtener detalles sobre la información que debe conservar en una
Referencia cruzadaParte de la Una clave para las pruebas efectivas es la planificación desde el comienzo del proyecto hasta la
planificación para la prueba es
prueba. Poner las pruebas en el mismo nivel de importancia que el diseño o la codificación significa
formalizar sus planes por escrito. Para
Es casi imposible producir un producto de software de alta calidad a menos que pueda volver a probarlo
sistemáticamente después de que se hayan realizado los cambios. Si ejecuta diferentes pruebas después de cada
cambio, no tiene forma de saber con seguridad que no se han introducido nuevos defectos. En consecuencia, las
pruebas de regresión deben ejecutar las mismas pruebas cada vez. A veces se agregan nuevas pruebas a medida
que el producto madura, pero las pruebas anteriores también se mantienen.
Pruebas automatizadas
La única forma práctica de administrar las pruebas de regresión es automatizarlas. Las personas se
adormecen al realizar las mismas pruebas muchas veces y ver los mismos resultados muchas veces. Se
vuelve demasiado fácil pasar por alto los errores, lo que anula el propósito de las pruebas de regresión. El
PUNTO CLAVE
gurú de las pruebas, Boriz Beizer, informa que la tasa de errores en las pruebas manuales es comparable a
la tasa de errores en el código que se está probando. Estima que en las pruebas manuales, solo alrededor
de la mitad de todas las pruebas se ejecutan correctamente (Johnson 1994).
- Una prueba automatizada tiene menos posibilidades de fallar que una prueba manual.
- Una vez que automatiza una prueba, está disponible para el resto del proyecto con poco
esfuerzo incremental de su parte.
22.7 Mantenimiento de registros de pruebas 529
- Si las pruebas están automatizadas, se pueden ejecutar con frecuencia para ver si alguna verificación
de código ha roto el código. La automatización de pruebas es parte de la base de las prácticas
intensivas en pruebas, como la prueba diaria de compilación y humo y la Programación extrema.
- Las pruebas automatizadas brindan una red de seguridad para los cambios de código a gran escala porque
aumentan la posibilidad de detectar rápidamente los defectos insertados durante las modificaciones.
Referencia cruzadaPara obtener - Las pruebas automatizadas son especialmente útiles en entornos tecnológicos nuevos y
más información sobre la relación
volátiles porque eliminan los cambios en los entornos más temprano que tarde.
entre la madurez de la tecnología y
tecnológica”. de prueba, generan entradas, capturan salidas y comparan la salida real con la salida esperada. La variedad
de herramientas discutidas en la sección anterior realizará algunas o todas estas funciones.
Además de hacer que el proceso de prueba sea repetible, debe medir el proyecto para poder
saber con seguridad si los cambios lo mejoran o lo degradan. Aquí hay algunos tipos de datos
que puede recopilar para medir su proyecto:
PUNTO CLAVE
- Subclasificación de un defecto de codificación: fallado por uno, asignación incorrecta, índice de matriz
Una vez que recopile los datos, puede analizar algunos números para determinar si su proyecto se está
volviendo más enfermo o más saludable:
530 Capítulo 22: Pruebas de desarrollador
- Número de defectos en cada clase, ordenados de peor a mejor clase, posiblemente normalizados
por tamaño de clase
Además de los registros de prueba a nivel de proyecto, puede que le resulte útil realizar un seguimiento de
sus registros de prueba personales. Estos registros pueden incluir tanto una lista de verificación de los
errores que comete con mayor frecuencia como un registro de la cantidad de tiempo que dedica a escribir
código, probar código y corregir errores.
Recursos adicionales
cc2e.com/2203 Los estatutos federales de veracidad en los consejos me obligan a revelar que varios otros libros
cubren las pruebas con más profundidad que este capítulo. Los libros dedicados a las pruebas
analizan las pruebas de sistema y de caja negra, que no se han tratado en este capítulo. También
profundizan en temas de desarrolladores. Discuten enfoques formales como gráficos de causa y
efecto y los pormenores de establecer una organización de prueba independiente.
Pruebas
Kaner, Cem, Jack Falk y Hung Q. Nguyen.Pruebas de software informático, 2ª ed. Nueva York,
NY: John Wiley & Sons, 1999. Este es probablemente el mejor libro actual sobre pruebas de
software. Es más aplicable a las aplicaciones de prueba que se distribuirán a una amplia base
de clientes, como sitios web de gran volumen y aplicaciones de ajuste reducido, pero también
es útil en general.
Kaner, Cem, James Bach y Bret Pettichord.Lecciones aprendidas en pruebas de software. New
York, NY: John Wiley & Sons, 2002. Este libro es un buen complemento paraPruebas de
software informático, 2ª ed. Está organizado en 11 capítulos que enumeran 250 lecciones
aprendidas por los autores.
Tamre, Luisa.Introducción a las pruebas de software. Boston, MA: Addison-Wesley, 2002. Este es un libro de
pruebas accesible dirigido a desarrolladores que necesitan comprender las pruebas. Contradiciendo el
título, el libro profundiza en los detalles de las pruebas que son útiles incluso para los probadores
experimentados.
Recursos adicionales 531
WhittakerJames A.Cómo romper el software: una guía práctica para las pruebas. Boston, MA: Addison-
Wesley, 2002. Este libro enumera 23 ataques que los probadores pueden usar para hacer que el software
falle y presenta ejemplos para cada ataque usando paquetes de software populares. Puede usar este libro
como una fuente primaria de información sobre las pruebas o, debido a que su enfoque es distintivo, puede
usarlo para complementar otros libros de pruebas.
Whittaker, James A. “¿Qué son las pruebas de software? ¿Y por qué es tan difícil?”Software IEEE, enero
de 2000, págs. 70–79. Este artículo es una buena introducción a los problemas de pruebas de
software y explica algunos de los desafíos asociados con las pruebas de software efectivas.
MyersGlenford J.El arte de las pruebas de software. Nueva York, NY: John Wiley, 1979. Este es el libro
clásico sobre pruebas de software y aún se encuentra en circulación (aunque es bastante costoso). El
contenido del libro es sencillo: una prueba de autoevaluación; La Psicología y Economía de las
Pruebas de Programas; Inspecciones, recorridos y revisiones del programa; diseño de casos de
prueba; Pruebas de clase; Pruebas de orden superior; Depuración; Herramientas de prueba y otras
técnicas. Es corto (177 páginas) y legible. El cuestionario al principio lo ayuda a comenzar a pensar
como un probador y demuestra de cuántas maneras se puede descifrar una pieza de código.
Andamio de prueba
Bentley, Jon. “Un pequeño asunto de programación” enPerlas de programación, 2ª ed. Boston, MA:
Addison-Wesley, 2000. Este ensayo incluye varios buenos ejemplos de andamiaje de prueba.
Mackinnon, Tim, Steve Freeman y Philip Craig. “Endo-Pruebas: Pruebas unitarias con objetos
simulados,”Programación eXtreme e Ingeniería de Software de Procesos Flexibles - Conferencia
XP2000”, 2000. Este es el documento original para analizar el uso de objetos simulados para
respaldar las pruebas de los desarrolladores.
Thomas, Dave y Andy Hunt. "Objetos simulados",Software IEEE, mayo/junio de 2002. Esta es una introducción fácil
de leer sobre el uso de objetos simulados para respaldar las pruebas de los desarrolladores.
cc2e.com/2217 www.junit.org. Este sitio proporciona soporte para desarrolladores que utilizan JUnit. Se
proporcionan recursos similares encppunit.sourceforge.netynunit.sourceforge.net.
Beck, Kent.Desarrollo basado en pruebas: por ejemplo. Boston, MA: Addison-Wesley, 2003.
Beck describe los entresijos del "desarrollo basado en pruebas", un enfoque de desarrollo que
se caracteriza por escribir primero casos de prueba y luego escribir el código para satisfacer
los casos de prueba. A pesar del tono a veces evangélico de Beck, el consejo es sólido y el libro
es breve y directo. El libro tiene un extenso ejemplo de ejecución con código real.
532 Capítulo 22: Pruebas de desarrollador
Estándares relevantes
- ¿Cada elemento del diseño que se aplica a la clase o rutina tiene su propio
caso de prueba?
- ¿Se ha probado cada línea de código con al menos un caso de prueba? ¿Se ha verificado
esto calculando el número mínimo de pruebas necesarias para ejecutar cada línea de
código?
- ¿Se han probado todas las rutas de flujo de datos utilizadas definidas con al menos un caso de
prueba?
- ¿Se ha utilizado una lista de errores comunes para escribir casos de prueba para detectar
errores que ocurrieron con frecuencia en el pasado?
- ¿Se han probado todos los límites simples: límites máximos, mínimos y
fuera de uno?
- ¿Se han probado los límites compuestos, es decir, combinaciones de datos de entrada que podrían
dar como resultado una variable calculada que es demasiado pequeña o demasiado grande?
- ¿Los casos de prueba verifican el tipo de datos incorrecto, por ejemplo, un número
negativo de empleados en un programa de nómina?
- ¿Se prueba la compatibilidad con datos antiguos? ¿Y se prueba el hardware antiguo, las
versiones antiguas del sistema operativo y las interfaces con versiones antiguas de otro
software?
Puntos clave
- La prueba por parte del desarrollador es una parte clave de una estrategia de prueba completa. Las pruebas
independientes también son importantes, pero están fuera del alcance de este libro.
- Escribir casos de prueba antes del código requiere la misma cantidad de tiempo y esfuerzo que escribir los
casos de prueba después del código, pero acorta los ciclos de detección de defectos, depuración y
corrección.
- Incluso considerando los numerosos tipos de pruebas disponibles, las pruebas son solo una parte de
un buen programa de calidad de software. Los métodos de desarrollo de alta calidad, incluida la
minimización de defectos en los requisitos y el diseño, son al menos igual de importantes. Las
prácticas de desarrollo colaborativo también son al menos tan efectivas para detectar errores como
las pruebas, y estas prácticas detectan diferentes tipos de errores.
- Puede generar muchos casos de prueba de forma determinista utilizando pruebas de base, análisis
de flujo de datos, análisis de límites, clases de datos incorrectos y clases de datos correctos. Puede
generar casos de prueba adicionales con adivinación de errores.
- Los errores tienden a agruparse en unas pocas clases y rutinas propensas a errores. Encuentre ese código
propenso a errores, rediseñelo y reescríbalo.
- Los datos de prueba tienden a tener una mayor densidad de errores que el código que se está probando.
Debido a que la búsqueda de tales errores es una pérdida de tiempo sin mejorar el código, los errores de
datos de prueba son más agravantes que los errores de programación. Evítelos desarrollando sus pruebas
tan cuidadosamente como su código.
- Las pruebas automatizadas son útiles en general y son esenciales para las pruebas de regresión.
depuración
cc2e.com/2361 Contenido
Temas relacionados
- Refactorización: Capítulo 24
La depuración es el doble de difícil La depuración es el proceso de identificar la causa raíz de un error y corregirlo. Se contrasta
que escribir el código en primer
con la prueba, que es el proceso de detección inicial del error. En algunos proyectos, la
lugar. Por lo tanto, si escribe el
para depurarlo. La depuración no tiene por qué ser la parte más difícil. Si sigue los consejos de este libro, tendrá menos
—Brian W Kernighan
errores para depurar. La mayoría de los defectos que tendrá serán descuidos menores y errores
tipográficos, que se encuentran fácilmente mirando una lista de código fuente o revisando el código en un
depurador. Para los errores más difíciles restantes, este capítulo describe cómo hacer que la depuración sea
mucho más fácil de lo que suele ser.
La difunta contralmirante Grace Hopper, co-inventora de COBOL, siempre decía que la palabra "error" en el
software se remontaba a la primera computadora digital a gran escala, la Mark I (IEEE 1992). Los
programadores rastrearon el mal funcionamiento de un circuito hasta la presencia de una gran polilla que
había encontrado su camino hacia la computadora y, a partir de ese momento, los problemas de la
computadora se atribuyeron a "errores". Fuera del software, la palabra "error" se remonta al menos a
Thomas Edison, de quien se dice que la usó ya en 1878 (Tenner 1997).
535
536 Capítulo 23: Depuración
Sin embargo, la realidad de los defectos de software es que los errores no son organismos que se
cuelan en su código cuando olvida rociarlo con pesticida. son errores Un error en el software
significa que un programador cometió un error. El resultado del error no es como la linda imagen
que se muestra arriba. Es más probable que sea una nota como esta:
De:
A:
Re:
En el contexto de este libro, la precisión técnica requiere que los errores en el código se
llamen "errores", "defectos" o "fallas".
Al igual que las pruebas, la depuración no es una forma de mejorar la calidad de su software per se; es una
forma de diagnosticar defectos. La calidad del software debe incorporarse desde el principio. La mejor
manera de crear un producto de calidad es desarrollar requisitos cuidadosamente, diseñar bien y usar
prácticas de codificación de alta calidad. La depuración es el último recurso.
No, no todos saben cómo depurar. Los estudios de programadores experimentados han encontrado
aproximadamente una diferencia de 20 a 1 en el tiempo que tardan los programadores experimentados en
encontrar el mismo conjunto de defectos que encuentran los programadores sin experiencia. Además, algunos
PUNTO CLAVE
programadores encuentran más defectos y hacen correcciones con mayor precisión. Aquí están los
23.1 Descripción general de los problemas de depuración 537
resultados de un estudio clásico que examinó la eficacia con la que programadores profesionales
con al menos cuatro años de experiencia depuraron un programa con 12 defectos:
3 Los tres programadores que eran mejores en la depuración pudieron encontrar los defectos
2
1
en aproximadamente un tercio del tiempo e insertaron solo dos quintas partes de los defectos
DATOS DUROS
nuevos que los tres peores. El mejor programador encontró todos los defectos y no insertó
ningún defecto nuevo al corregirlos. El peor pasó por alto 4 de los 12 defectos e insertó 11
nuevos defectos al corregir los 8 defectos que encontró.
Pero este estudio en realidad no cuenta toda la historia. Después de la primera ronda de depuración, los tres
programadores más rápidos todavía tienen 3,7 defectos en su código y los más lentos todavía tienen 9,4
defectos. Ninguno de los grupos ha terminado de depurar todavía. Me preguntaba qué pasaría si aplicara las
mismas proporciones de encontrar y reparar mal a ciclos de depuración adicionales. Mis resultados no son
estadísticamente válidos, pero siguen siendo interesantes. Cuando apliqué las mismas proporciones de
búsqueda y reparación incorrecta a ciclos de depuración sucesivos hasta que a cada grupo le quedó menos
de la mitad de un defecto, el grupo más rápido requirió un total de tres ciclos de depuración, mientras que el
grupo más lento requirió 14 ciclos de depuración. Teniendo en cuenta que cada ciclo del grupo más lento
tarda casi tres veces más que cada ciclo del grupo más rápido, el grupo más lento tardaría unas 13 veces
más en depurar completamente sus programas que el grupo más rápido, según mi extrapolación no
científica de este estudio. Esta amplia variación ha sido confirmada por otros estudios (Gilb 1977, Curtis
1981).
Referencia cruzadaPara obtener Además de brindar información sobre la depuración, la evidencia respalda el Principio general de la
detalles sobre la relación entre
calidad del software: mejorar la calidad reduce los costos de desarrollo. Los mejores programadores
calidad y costo, consulte la
¿Qué significa tener un defecto? Suponiendo que no desea que el programa tenga un defecto,
significa que no comprende completamente lo que hace el programa. La idea de no entender
lo que hace el programa es inquietante. Después de todo, si usted creó el programa, debería
hacer su oferta. Si no sabe exactamente lo que le está diciendo a la computadora que haga,
solo está a un pequeño paso de simplemente intentar cosas diferentes hasta que
538 Capítulo 23: Depuración
algo parece funcionar, es decir, programación por ensayo y error. Y si está programando
por ensayo y error, los defectos están garantizados. No necesita aprender a corregir
defectos; necesitas aprender cómo evitarlos en primer lugar.
Sin embargo, la mayoría de las personas son algo falibles, y usted podría ser un excelente
programador que simplemente ha cometido un pequeño descuido. Si este es el caso, un error en su
programa le brinda una gran oportunidad para aprender muchas cosas. Puedes:
Otras lecturasPara obtener Aprende sobre los tipos de errores que cometesSi escribiste el programa, insertaste el
detalles sobre las prácticas que lo
defecto. No todos los días un foco expone una debilidad con una claridad deslumbrante,
ayudarán a conocer los tipos de
Conoce la calidad de tu código desde el punto de vista de alguien que tiene que leerloTendrás
que leer tu código para encontrar el defecto. Esta es una oportunidad para mirar críticamente la
calidad de su código. Es fácil de leer? ¿Cómo podría ser mejor? Utilice sus descubrimientos para
refactorizar su código actual o para mejorar el código que escriba a continuación.
A fin de cuentas, la depuración es un suelo extraordinariamente rico en el que plantar las semillas de su
propia mejora. Es donde se cruzan todos los caminos de la construcción: legibilidad, diseño, calidad del
código, lo que sea. Aquí es donde la creación de un buen código vale la pena, especialmente si lo hace lo
suficientemente bien como para no tener que depurar muy a menudo.
23.1 Descripción general de los problemas de depuración 539
Un enfoque ineficaz
Desafortunadamente, las clases de programación en colegios y universidades casi nunca ofrecen
instrucción en depuración. Si estudió programación en la universidad, es posible que haya tenido
una clase dedicada a la depuración. Aunque mi educación en ciencias de la computación fue
excelente, el alcance del consejo de depuración que recibí fue "poner instrucciones impresas en el
programa para encontrar el defecto". Esto no es adecuado. Si las experiencias educativas de otros
programadores son como la mía, muchos programadores se ven obligados a reinventar los
conceptos de depuración por su cuenta. ¡Que desperdicio!
Los programadores no siempre En la visión del infierno de Dante, el círculo más bajo está reservado para el mismo Satanás. En los tiempos
utilizan los datos disponibles para
modernos, Old Scratch ha acordado compartir el círculo más bajo con los programadores que no aprenden
restringir su razonamiento.
No pierdas el tiempo tratando de entender el problema.Es probable que el problema sea trivial y que
no necesite comprenderlo por completo para solucionarlo. Simplemente encontrarlo es suficiente.
Solucione el error con la solución más obviaPor lo general, es bueno solucionar el problema
específico que ve, en lugar de perder mucho tiempo haciendo una corrección grande y
ambiciosa que afectará a todo el programa. Este es un ejemplo perfecto:
x = Calcular ( y ) si ( y =
17 )
X = $25,15 - - Compute() no funciona para y = 17, así que arréglalo
¿Quién necesita cavar todo el camino enCalcular()por un oscuro problema con el valor de 17cuando
puedes simplemente escribir un caso especial para ello en el lugar obvio?
Satanás ha arrendado parte del infierno a los programadores que depuran por superstición. Cada grupo
tiene un programador que tiene un sinfín de problemas con máquinas demoníacas, defectos misteriosos
del compilador, defectos ocultos del lenguaje que aparecen cuando hay luna llena, malos
540 Capítulo 23: Depuración
datos, pérdida de cambios importantes, un editor poseído que guarda programas incorrectamente,
lo que sea. Esto es “programación por superstición”.
Incluso si un error al principio parece no ser su culpa, le conviene mucho asumir que sí lo es.
Esa suposición te ayuda a depurar. Ya es bastante difícil encontrar un defecto en su código
cuando lo está buscando; es aún más difícil cuando asume que su código está libre de errores.
PUNTO CLAVE
Asumir que el error es culpa suya también mejora su credibilidad. Si afirma que un error
surgió del código de otra persona, los demás programadores creerán que ha investigado el
problema cuidadosamente. Si asume que el error es suyo, evitará la vergüenza de tener que
retractarse públicamente más tarde cuando descubra que, después de todo, fue su defecto.
Afortunadamente, no tienes que hacer un pacto con Satanás para encontrar un método
de depuración que sea mejor que adivinar al azar. Depurar pensando en el problema es
mucho más eficaz e interesante que depurar con ojo de tritón y polvo de oreja de rana.
Suponga que le piden que resuelva un misterio de asesinato. ¿Qué sería más interesante: ir de
puerta en puerta por todo el condado, verificar la coartada de cada persona para la noche del 17 de
octubre o encontrar algunas pistas y deducir la identidad del asesino? La mayoría de la gente
preferiría deducir la identidad de la persona, y la mayoría de los programadores encuentran más
satisfactorio el enfoque intelectual para la depuración. Aún mejor, los programadores eficaces que
depuran en una vigésima parte del tiempo empleado por los programadores ineficaces no están
adivinando al azar cómo arreglar el programa. Están utilizando el método científico, es decir, el
proceso de descubrimiento y demostración necesario para la investigación científica.
El método científico tiene muchos paralelos en la depuración. Aquí hay un enfoque efectivo para
encontrar un defecto:
b.Analice los datos que se han recopilado y formule una hipótesis sobre el
defecto.
3.Reparar el defecto.
4.Pruebe la corrección.
El primer paso es similar al primer paso del método científico en que se basa en la
repetibilidad. El defecto es más fácil de diagnosticar si puede estabilizarlo, es decir, hacer que
ocurra de manera confiable. El segundo paso utiliza los pasos del método científico. Reúne los
datos de prueba que divulgaron el defecto, analiza los datos que se han producido y formula
una hipótesis sobre el origen del error. Luego, diseña un caso de prueba o una inspección para
evaluar la hipótesis, y declara el éxito (con respecto a probar su hipótesis) o renueva sus
esfuerzos, según corresponda. Cuando haya probado su hipótesis, corrija el defecto, pruebe la
corrección y busque en su código errores similares.
Veamos cada uno de los pasos junto con un ejemplo. Suponga que tiene un programa de base de
datos de empleados que tiene un error intermitente. Se supone que el programa debe imprimir una
lista de empleados y sus retenciones de impuestos sobre la renta en orden alfabético. Aquí hay parte
de la salida:
Estabilizar el error
Si un defecto no ocurre de forma fiable, es casi imposible de diagnosticar. Hacer que un defecto
intermitente ocurra de manera predecible es una de las tareas más desafiantes en la depuración.
Referencia cruzadaPara obtener detalles Un error que no ocurre de manera predecible suele ser un error de inicialización, un problema de
sobre el uso seguro de punteros, consulte
sincronización o un problema de puntero colgante. Si el cálculo de una suma a veces es correcto y a
la Sección 13.2, “Puntero”.
veces incorrecto, es probable que una variable involucrada en el cálculo no se esté inicializando
correctamente; la mayoría de las veces simplemente comienza en0. Si el problema es un fenómeno
extraño e impredecible y está usando punteros, es casi seguro que tiene un puntero no inicializado o
está usando un puntero después de que se haya desasignado la memoria a la que apunta.
Estabilizar un error generalmente requiere más que encontrar un caso de prueba que produzca el
error. Incluye reducir el caso de prueba al más simple que todavía produce el error. El objetivo de
simplificar el caso de prueba es hacerlo tan simple que cambiar cualquier aspecto cambie el
comportamiento del error. Luego, cambiando cuidadosamente el caso de prueba y observando el
comportamiento del programa bajo condiciones controladas, puede diagnosticar el problema. Si
trabaja en una organización que tiene un equipo de prueba independiente, a veces el trabajo del
equipo es simplificar los casos de prueba. La mayoría de las veces, es tu trabajo.
Para simplificar el caso de prueba, vuelve a poner en juego el método científico. Suponga que
tiene 10 factores que, usados en combinación, producen el error. Formular una hipótesis
sobre qué factores fueron irrelevantes para producir el error. Cambie los factores
supuestamente irrelevantes y vuelva a ejecutar el caso de prueba. Si aún obtiene el error,
puede eliminar esos factores y ha simplificado la prueba. Entonces puede intentar simplificar
aún más la prueba. Si no obtiene el error, ha refutado esa hipótesis específica y sabe más de lo
que sabía antes. Puede ser que algún cambio sutilmente diferente aún produzca el error, pero
conoce al menos un cambio específico que no lo hace.
no es hastaBucle De Frutas, Fritase ingresa y aparece en una posición incorrecta que recuerda que
Módulo, Mildredse había ingresado justo antes de aparecer en el lugar equivocado también. Lo
extraño de ambos casos es que se ingresaron individualmente. Por lo general, los empleados se
ingresan en grupos.
23.2 Búsqueda de un defecto 543
Usted hipotetiza: el problema tiene algo que ver con ingresar un solo nuevo empleado. Si esto es
cierto, ejecutar el programa de nuevo debería ponerBucle De Frutas, Fritaen la posición correcta.
Aquí está el resultado de una segunda ejecución:
Esta ejecución exitosa apoya la hipótesis. Para confirmarlo, desea intentar agregar
algunos empleados nuevos, uno a la vez, para ver si aparecen en el orden incorrecto y si
el orden cambia en la segunda ejecución.
En el ejemplo actual, el origen del problema podría ser un defecto de uno que ocurre
cuando agrega un nuevo empleado, pero no cuando agrega dos o más. Examinando el
código, no encuentra un defecto obvio. Al recurrir al Plan B, ejecuta un caso de prueba
con un solo empleado nuevo para ver si ese es el problema. Añades Hardcase, Henry
como un solo empleado y plantea la hipótesis de que su registro estará desordenado.
Esto es lo que encuentras:
la línea paraHardcase, Henryestá exactamente donde debería estar, lo que significa que su
primera hipótesis es falsa. El problema no es causado simplemente por agregar un empleado
a la vez. Es un problema más complicado o algo completamente diferente.
Al examinar de nuevo la salida de la prueba, se da cuenta de queBucle De Frutas, FritayMuchos bucles, Mavis
son los únicos nombres que contienen guiones.Aro de frutaestaba fuera de servicio cuando entró por
primera vez, peroMuchos buclesno lo era, ¿verdad? Aunque no tiene una copia impresa de la entrada
original, en el error originalMódulo, Mildredparecía estar fuera de servicio, pero ella estaba al ladoMuchos
bucles. QuizásMuchos buclesestaba fuera de servicio ymóduloestaba bien
544 Capítulo 23: Depuración
Vuelve a formular una hipótesis: el problema surge de los nombres con guiones, no de los nombres que se ingresan
individualmente.
Pero, ¿cómo explica eso el hecho de que el problema aparece solo la primera vez que se ingresa a un
empleado? Observa el código y descubre que se utilizan dos rutinas de clasificación diferentes. Uno
se usa cuando se ingresa un empleado y otro se usa cuando se guardan los datos. Una mirada más
cercana a la rutina utilizada cuando se ingresa por primera vez a un empleado muestra que no se
supone que ordene los datos por completo. Solo pone los datos en un orden aproximado para
acelerar la clasificación de la rutina de guardado. Por lo tanto, el problema es que los datos se
imprimen antes de ordenarlos. El problema con los nombres con guiones surge porque la rutina de
clasificación aproximada no maneja sutilezas como los caracteres de puntuación. Ahora, puede
refinar la hipótesis aún más.
Plantea su hipótesis por última vez: los nombres con caracteres de puntuación no se ordenan
correctamente hasta que se guardan.
Una vez que haya estabilizado un error y refinado el caso de prueba que lo produce, encontrar
su fuente puede ser trivial o desafiante, dependiendo de qué tan bien haya escrito su código.
Si tiene dificultades para encontrar un defecto, podría deberse a que el código no está bien
escrito. Puede que no quieras escuchar eso, pero es verdad. Si tienes problemas, considera
estos consejos:
Usa todos los datos disponibles para hacer tu hipótesisAl crear una hipótesis sobre el origen de
un defecto, tenga en cuenta la mayor cantidad de datos posible en su hipótesis. En el ejemplo, te
habrás dado cuenta de queBucle De Frutas, Fritaestaba fuera de servicio y creó una hipótesis de que
los nombres que comienzan con una "F" están ordenados incorrectamente. Esa es una hipótesis
pobre porque no tiene en cuenta el hecho de queMódulo, Mildredestaba fuera de servicio o que los
nombres se ordenaron correctamente la segunda vez. Si los datos no se ajustan a la hipótesis, no los
descarte; pregunte por qué no se ajustan y cree una nueva hipótesis.
La segunda hipótesis en el ejemplo, que el problema surge de los nombres con guiones, no de
los nombres que se ingresan individualmente, tampoco parecía explicar inicialmente el hecho
de que los nombres se clasificaron correctamente la segunda vez. En este caso, sin embargo, la
segunda hipótesis condujo a una hipótesis más refinada que resultó ser correcta. Está bien que
la hipótesis no tenga en cuenta todos los datos al principio, siempre y cuando sigas refinando
la hipótesis para que finalmente lo haga.
Refinar los casos de prueba que producen el error.Si no puede encontrar el origen de un
error, intente refinar los casos de prueba más de lo que ya lo ha hecho. Es posible que pueda
variar un parámetro más de lo que había supuesto, y centrarse en uno de los parámetros
podría proporcionar el avance crucial.
23.2 Búsqueda de un defecto 545
Referencia cruzadaPara obtener más información Ejercite el código en su conjunto de pruebas unitariasLos defectos tienden a ser más fáciles de encontrar en pequeños
sobre los marcos de pruebas unitarias, consulte
fragmentos de código que en grandes programas integrados. Use sus pruebas unitarias para probar el código de forma
"Conecte las pruebas unitarias en un marco de prueba"
en la Sección 22.4.
aislada.
Utilice las herramientas disponiblesNumerosas herramientas están disponibles para admitir sesiones de
depuración: depuradores interactivos, compiladores exigentes, verificadores de memoria, editores dirigidos
por sintaxis, etc. La herramienta adecuada puede hacer que un trabajo difícil sea fácil. Con un error difícil de
encontrar, por ejemplo, una parte del programa estaba sobrescribiendo la memoria de otra parte. Este
error fue difícil de diagnosticar utilizando prácticas de depuración convencionales porque el programador
no pudo determinar el punto específico en el que el programa sobrescribía incorrectamente la memoria. El
programador usó un punto de interrupción de memoria para configurar un reloj en una dirección de
memoria específica. Cuando el programa escribió en esa ubicación de memoria, el depurador detuvo el
código y se expuso el código culpable.
Este es un ejemplo de un problema que es difícil de diagnosticar analíticamente pero que se vuelve
bastante simple cuando se aplica la herramienta adecuada.
Reproducir el error de varias maneras diferentesA veces, probar casos que son similares al caso
que produce el error, pero que no son exactamente iguales, es instructivo. Piense en este enfoque
como una triangulación del defecto. Si puede obtener una solución desde un punto y una solución
desde otro, puede determinar mejor dónde está exactamente.
Como se ilustra en la Figura 23-1, reproducir un error de varias maneras ayuda a diagnosticar
la causa del error. Una vez que crea que ha identificado el defecto, ejecute un caso que esté
cerca de los casos que producen errores pero que no debería producir un error en sí mismo.
Si produce un error, aún no comprende completamente el problema. Los errores a menudo
surgen de combinaciones de factores, y tratar de diagnosticar el problema con un solo caso
de prueba a menudo no diagnostica el problema raíz.
Programa Programa
Primer examen
Defecto Defecto
Segunda prueba
Programa Programa
Defecto Defecto
Pruebas posteriores
Tercera prueba
Figura 23-1 Intente reproducir un error de varias maneras diferentes para determinar su causa exacta.
546 Capítulo 23: Depuración
Generar más datos para generar más hipótesisElija casos de prueba que sean diferentes de
los casos de prueba que ya sabe que son erróneos o correctos. Ejecútelos para generar más
datos y use los nuevos datos para agregarlos a su lista de posibles hipótesis.
Usar los resultados de las pruebas negativasSuponga que crea una hipótesis y ejecuta un
caso de prueba para probarla. Suponga además que el caso de prueba refuta la hipótesis, por
lo que aún no conoce la fuente del error. Sabes algo que no sabías antes, a saber, que el
defecto no está en el área que pensabas que estaba. Eso reduce su campo de búsqueda y el
conjunto de hipótesis posibles restantes.
Lluvia de ideas para posibles hipótesisEn lugar de limitarte a la primera hipótesis que se te
ocurra, trata de proponer varias. No los analices al principio, solo piensa en todos los que
puedas en unos minutos. Luego observe cada hipótesis y piense en casos de prueba que la
probarían o refutarían. Este ejercicio mental es útil para romper el atasco de depuración que
resulta de concentrarse demasiado en una sola línea de razonamiento.
Mantenga un bloc de notas junto a su escritorio y haga una lista de cosas para intentarUna de las razones por
las que los programadores se atascan durante las sesiones de depuración es que van demasiado lejos por caminos
sin salida. Haga una lista de cosas para probar, y si un enfoque no funciona, pase al siguiente enfoque.
Estrechar la región sospechosa del códigoSi ha estado probando todo el programa o toda una
clase o rutina, pruebe una parte más pequeña. Utilice instrucciones de impresión, registro o
seguimiento para identificar qué sección del código está generando el error.
Si necesita una técnica más poderosa para reducir la región sospechosa del código,
elimine sistemáticamente partes del programa y vea si el error aún ocurre. Si no es así,
sabes que está en la parte que quitaste. Si es así, sabrá que está en la parte que ha
conservado.
En lugar de eliminar regiones al azar, divide y vencerás. Use un algoritmo de búsqueda binaria para enfocar
su búsqueda. Intente eliminar aproximadamente la mitad del código la primera vez. Determine la mitad en
la que se encuentra el defecto y luego divida esa sección. De nuevo, determina qué mitad contiene el defecto
y, de nuevo, corta esa sección por la mitad. Continúe hasta que encuentre el defecto.
Si usa muchas rutinas pequeñas, podrá cortar secciones de código simplemente comentando
las llamadas a las rutinas. De lo contrario, puede usar comentarios o comandos de
preprocesador para eliminar código.
Referencia cruzadaPara obtener más Desconfíe de las clases y rutinas que han tenido defectos antesEs probable que las clases que han tenido
detalles sobre el código propenso a
defectos antes continúen teniendo defectos. Es más probable que una clase que ha sido problemática en el
errores, consulte "Módulos propensos a
Comprobar el código que ha cambiado recientementeSi tiene un nuevo error que es difícil de
diagnosticar, generalmente está relacionado con el código que se modificó recientemente. Podría estar en
un código completamente nuevo o en cambios al código antiguo. Si no puede encontrar un defecto, ejecute
una versión anterior del programa para ver si se produce el error. Si no es así, sabrá que el error está en la
nueva versión o se debe a una interacción con la nueva versión. Examine las diferencias entre la versión
antigua y la nueva. Verifique el registro de control de versiones para ver qué código ha cambiado
recientemente. Si eso no es posible, use una herramienta diff para comparar los cambios en el código
fuente antiguo y en funcionamiento con el código fuente nuevo y roto.
Expandir la región sospechosa del códigoEs fácil concentrarse en una pequeña sección de código,
seguro de que "el defectodeberestar en esta sección.” Si no lo encuentra en la sección, considere la
posibilidad de que el defecto no esté en la sección. Expanda el área de código que sospecha y luego
concéntrese en partes de él utilizando la técnica de búsqueda binaria descrita anteriormente.
Referencia cruzadaPara una Integrar de forma incrementalLa depuración es fácil si agrega piezas a un sistema de una en una.
discusión completa de la integración,
Si agrega una pieza a un sistema y encuentra un nuevo error, elimine la pieza y pruébela por
vea el Capítulo 29, “Integración”.
separado.
Referencia cruzadaPara obtener Habla con otra persona sobre el problema.Algunas personas llaman a esto "depuración
detalles sobre cómo la participación
confesional". A menudo descubres tu propio defecto en el acto de explicárselo a otra persona.
de otros desarrolladores puede poner
desarrollo colaborativo”. Oye, Jennifer, ¿tienes un minuto? tengo un problema Tengo esta lista de salarios de
empleados que se supone que debe estar ordenada, pero algunos nombres están
desordenados. Están ordenados bien la segunda vez que los imprimo, pero no la primera.
Revisé para ver si eran nombres nuevos, pero probé algunos que funcionaron. Sé que deben
ordenarse la primera vez que los imprimo porque el programa ordena todos los nombres a
medida que se ingresan y nuevamente cuando se guardan, espere un minuto, no, no los
ordena cuando se ingresan. Así es. Sólo los ordena de forma aproximada. Gracias, Jennifer.
Has sido de gran ayuda.
548 Capítulo 23: Depuración
Jennifer no dijo una palabra y resolviste tu problema. Este resultado es típico, y este enfoque es una
herramienta poderosa para resolver defectos difíciles.
Toma un descanso del problemaA veces te concentras tanto que no puedes pensar. ¿Cuántas
veces te has detenido a tomar una taza de café y te has dado cuenta del problema de camino a
la máquina de café? ¿O en medio del almuerzo? ¿O de camino a casa? ¿O en la ducha a la
mañana siguiente? Si está depurando y no progresa, una vez que haya probado todas las
opciones, déjelo descansar. Ir a caminar. Trabaja en otra cosa. Ve a casa por el día. Deje que su
mente subconsciente busque una solución al problema.
La fuerza bruta es un enfoque que a menudo se pasa por alto para depurar problemas de
software. Por "fuerza bruta", me refiero a una técnica que puede ser tediosa, ardua y lenta,
pero que esgarantizadopara resolver el problema. Las técnicas específicas que están
garantizadas para resolver un problema dependen del contexto, pero aquí hay algunos
candidatos generales:
- Realice una revisión completa del diseño y/o del código en el código roto.
- Compile el código en el nivel de advertencia más exigente y corrija todas las advertencias del compilador exigente.
- Pase por un bucle grande en el depurador manualmente hasta que llegue a la condición
de error.
- Integre código nuevo en partes pequeñas, probando completamente cada parte a medida que se integra.
23.2 Búsqueda de un defecto 549
Establezca un tiempo máximo para una depuración rápida y suciaPara cada técnica de
fuerza bruta, tu reacción bien podría ser: “¡No puedo hacer eso, es demasiado trabajo!”. El
punto es que es demasiado trabajo si lleva más tiempo que lo que yo llamo "depuración rápida
y sucia". Siempre es tentador tratar de adivinar rápidamente en lugar de instrumentar
sistemáticamente el código y no darle al defecto un lugar donde esconderse. El jugador que
hay en cada uno de nosotros preferiría usar un enfoque arriesgado que podría encontrar el
defecto en cinco minutos que el enfoque infalible que encontraría el defecto en media hora. El
riesgo es que si el enfoque de cinco minutos no funciona, te vuelves terco. Encontrar el defecto
de la manera “fácil” se convierte en una cuestión de principios, y las horas pasan
improductivas, al igual que los días, las semanas, los meses... ¿Cuántas veces has pasado dos
horas depurando código que solo te llevó 30 minutos escribir? Eso es una mala distribución del
trabajo,
Cuando decidas ir por la victoria rápida, establece un límite de tiempo máximo para intentarlo de la
manera más rápida. Si supera el límite de tiempo, resígnese a la idea de que el defecto será más
difícil de diagnosticar de lo que pensó originalmente y elimínelo de la manera más difícil. Este
enfoque le permite obtener los defectos fáciles de inmediato y los defectos difíciles después de un
poco más de tiempo.
Haz una lista de técnicas de fuerza brutaAntes de comenzar a depurar un error difícil, pregúntese:
“Si me quedo atascado en la depuración de este problema, ¿hay alguna manera de que pueda
solucionarlo?garantizadopara poder solucionar el problema? Si puede identificar al menos una
técnica de fuerza bruta que solucionará el problema, incluida la reescritura del código en cuestión, es
menos probable que pierda horas o días cuando haya una alternativa más rápida.
Errores de sintaxis
Los problemas de errores de sintaxis siguen el mismo camino que el mamut lanudo y el tigre dientes de
sable. Los compiladores están mejorando en los mensajes de diagnóstico, y los días en los que tenía que
pasar dos horas para encontrar un punto y coma fuera de lugar en una lista de Pascal casi han
desaparecido. Aquí hay una lista de pautas que puede usar para acelerar la extinción de esta especie en
peligro de extinción:
No confíes en los números de línea en los mensajes del compiladorCuando su compilador informe un
misterioso error de sintaxis, mire inmediatamente antes e inmediatamente después del error; el compilador
podría haber entendido mal el problema o simplemente podría tener un diagnóstico deficiente. Una vez que
encuentre el defecto real, intente determinar la razón por la cual el compilador colocó el mensaje en la
declaración incorrecta. Comprender mejor su compilador puede ayudarlo a encontrar futuros defectos.
No confíes en los mensajes del compiladorLos compiladores intentan decirte exactamente qué es lo que
está mal, pero los compiladores están disimulando pequeños bribones y, a menudo, tienes que leer entre
líneas para saber qué significa realmente. Por ejemplo, en UNIX C, puede obtener un mensaje que dice
"excepción flotante" para un número entero dividido por 0. Con la plantilla estándar de C++
550 Capítulo 23: Depuración
Biblioteca, puede obtener un par de mensajes de error: el primer mensaje es el error real en el uso de
la STL; el segundo mensaje es un mensaje del compilador que dice: “Mensaje de error demasiado
largo para que la impresora lo imprima; Mensaje truncado." Probablemente puedas encontrar
muchos ejemplos propios.
No confíes en el segundo mensaje del compiladorAlgunos compiladores son mejores que otros
para detectar múltiples errores. Algunos compiladores se emocionan tanto después de detectar el
primer error que se vuelven vertiginosos y demasiado confiados; parlotean con docenas de mensajes
de error que no significan nada. Otros compiladores son más sensatos y, aunque deben sentir una
sensación de logro cuando detectan un error, se abstienen de arrojar mensajes inexactos. Cuando su
compilador genera una serie de mensajes de error en cascada, no se preocupe si no puede encontrar
rápidamente el origen del segundo o tercer mensaje de error. Arregle el primero y vuelva a compilar.
Divide y conquistarasLa idea de dividir el programa en secciones para ayudar a detectar defectos
funciona especialmente bien para los errores de sintaxis. Si tiene un error de sintaxis problemático,
elimine parte del código y vuelva a compilar. No obtendrá ningún error (porque el error está en la
parte que eliminó), obtendrá el mismo error (lo que significa que debe eliminar una parte diferente)
u obtendrá un error diferente (porque habrá engañado al compilador para que produzca un mensaje
que tiene más sentido).
Referencia cruzadaLa disponibilidad Encuentra comentarios fuera de lugar y comillasMuchos editores de texto de programación dan
de editores dirigidos por sintaxis es
formato automáticamente a comentarios, cadenas literales y otros elementos sintácticos. En
una característica de los entornos de
tecnológica”.
/*"/**/
Esta frase de código terminará un comentario o una cadena, lo que es útil para reducir el
espacio en el que se oculta el comentario o la cadena sin terminar.
La parte difícil de la depuración es encontrar el defecto. Arreglar el defecto es la parte fácil. Pero como ocurre con
muchas tareas sencillas, el hecho de que sea sencilla hace que sea especialmente propensa a errores. Al menos un
estudio encontró que las correcciones de defectos tienen más del 50 por ciento de posibilidades de ser incorrectas la
primera vez (Yourdon 1986b). Aquí hay algunas pautas para reducir la posibilidad de error:
Entender el problema antes de solucionarlo“The Devil's Guide to Debugging” tiene razón: la mejor
manera de complicarte la vida y corroer la calidad de tu programa es arreglar los problemas sin
entenderlos realmente. Antes de solucionar un problema, asegúrese de comprenderlo hasta el
PUNTO CLAVE
fondo. Triangular el defecto tanto con los casos que deberían reproducir el error como con los casos
que no deberían reproducir el error. Continúe hasta que comprenda el problema lo suficientemente
bien como para predecir su ocurrencia correctamente cada vez.
23.3 Reparación de un defecto 551
Nunca depure de pie. RelaxUn programador estaba listo para un viaje de esquí. Su producto estaba listo para
—Gerald Weinberg
enviarse, ya estaba retrasado y solo tenía un defecto más que corregir. Cambió el archivo
fuente y lo verificó en el control de versiones. No volvió a compilar el programa y no verificó
que el cambio fuera correcto.
De hecho, el cambio no fue correcto y su gerente se indignó. ¿Cómo podía cambiar el código
de un producto que estaba listo para enviarse sin verificarlo? ¿Qué podría ser peor? ¿No es
este el pináculo de la imprudencia profesional?
En notable contraste, durante los últimos días del desarrollo de Microsoft Windows 2000, un
desarrollador necesitaba corregir un defecto que era el último que quedaba antes de que se pudiera
crear una Release Candidate. El desarrollador cambió el código, verificó su solución y probó su
solución en su compilación local. Pero no verificó la corrección en el control de versiones en ese
momento. En cambio, fue a jugar al baloncesto. Él dijo: “Me siento demasiado estresado en este
momento para estar seguro de que he considerado todo lo que debería considerar. Voy a despejar
mi mente durante una hora, y luego volveré y revisaré el código, una vez que me haya convencido de
que la solución es realmente correcta”.
Relájese lo suficiente para asegurarse de que su solución sea la correcta. No caiga en la tentación de tomar
atajos. Puede tomar más tiempo, pero probablemente tomará menos. Por lo menos, solucionará el
problema correctamente y su gerente no le devolverá la llamada de su viaje de esquí.
552 Capítulo 23: Depuración
Referencia cruzadaLos problemas Guarde el código fuente originalAntes de comenzar a corregir el defecto, asegúrese de
generales relacionados con el cambio de
archivar una versión del código a la que pueda volver más tarde. Es fácil olvidar qué cambio en
código se analizan en profundidad en el
Supongamos además que cuandoclientees igual45,sumaresulta estar equivocado por $3.45. Esta es la
forma incorrecta de solucionar el problema:
si (cliente == 45) {
Aquí está la "solución". suma[ 45 ] = suma[ 45 ] + 3,45;
}
Ahora supongamos que cuandoclientees igual37y el número de reclamaciones para el cliente es0, no estás
recibiendo0. Esta es la forma incorrecta de solucionar el problema:
si (cliente == 45) {
suma[ 45 ] = suma[ 45 ] + 3,45;
}
Aquí está la segunda "solución". else if ( ( cliente == 37 ) && ( numClaims[ cliente ] == 0 ) ) {
suma[ 37 ] = 0.0;
}
Si esto no le produce un escalofrío en la espalda, tampoco le afectará nada más en este libro.
Es imposible enumerar todos los problemas con este enfoque en un libro que tiene solo
alrededor de 1000 páginas, pero aquí están los tres principales:
23.3 Reparación de un defecto 553
- Las correcciones no funcionarán la mayor parte del tiempo. Los problemas parecen ser el resultado
de defectos de inicialización. Los defectos de inicialización son, por definición, impredecibles, por lo
que el hecho de que la suma del cliente 45 tenga un descuento de $3,45 hoy no dice nada sobre el
futuro. Podría estar equivocado por $10,000.02, o podría ser correcto. Esa es la naturaleza de los
defectos de inicialización.
- Es inmantenible. Cuando el código tiene mayúsculas y minúsculas especiales para evitar errores, las
mayúsculas y minúsculas especiales se convierten en la característica más destacada del código. Los
$3,45 no siempre serán $3,45 y aparecerá otro error más adelante. El código se modificará
nuevamente para manejar el nuevo caso especial y el caso especial de $3.45 no se eliminará.
El código se volverá cada vez más percebe con casos especiales. Eventualmente, los percebes
serán demasiado pesados para que el código los soporte, y el código se hundirá en el fondo
del océano, un lugar adecuado para él.
- Utiliza la computadora para algo que es mejor hacerlo a mano. Las computadoras son buenas para
realizar cálculos predecibles y sistemáticos, pero los humanos son mejores para falsificar datos de
forma creativa. Sería más inteligente tratar la salida con borrador y una máquina de escribir que
jugar con el código.
Cambie el código solo por una buena razónRelacionada con la corrección de síntomas está la técnica de
cambiar el código al azar hasta que parezca funcionar. La línea típica de razonamiento es la siguiente: “Este
bucle parece contener un defecto. Es probable que sea un error de uno por uno, así que pondré un-1aquí y
pruébalo. ESTÁ BIEN. Eso no funcionó, así que pondré un+1en su lugar. ESTÁ BIEN. Eso parece funcionar.
Diré que está arreglado.
Tan popular como es esta práctica, no es efectiva. Hacer cambios en el código al azar es como
rotar los neumáticos de un Pontiac Aztek para solucionar un problema del motor. No estás
aprendiendo nada; solo estás bromeando. Al cambiar el programa al azar, dices en efecto: "No
sé qué está pasando aquí, pero probaré este cambio y espero que funcione". No cambie el
código al azar. Eso es programación vudú. Cuanto más diferente lo hagas sin entenderlo,
menos confianza tendrás en que funciona correctamente.
Antes de realizar un cambio, confíe en que funcionará. Estar equivocado acerca de un cambio debería
dejarte asombrado. Debería causar dudas sobre uno mismo, reevaluación personal y un profundo examen
de conciencia. Debería suceder rara vez.
Haz un cambio a la vezLos cambios son bastante complicados cuando se hacen uno
a la vez. Cuando se hacen dos a la vez, pueden introducir errores sutiles que se
parecen a los errores originales. Entonces se encuentra en la incómoda posición de
no saber si no corrigió el error, si corrigió el error pero introdujo uno nuevo que se
ve similar, o si no corrigió el error e introdujo un nuevo error similar. . Manténgalo
simple: haga solo un cambio a la vez.
Referencia cruzadaPara obtener detalles Comprueba tu arregloVerifique el programa usted mismo, haga que otra persona lo revise por
sobre las pruebas de regresión
usted o revíselo con otra persona. Ejecute los mismos casos de prueba de triangulación que utilizó
automatizadas, consulte “Reevaluación
(Pruebas de regresión)” en la
para diagnosticar el problema para asegurarse de que se hayan resuelto todos los aspectos del
Sección 22.6. problema. Si ha resuelto solo una parte del problema, descubrirá que todavía tiene trabajo por hacer.
554 Capítulo 23: Depuración
Vuelva a ejecutar todo el programa para verificar los efectos secundarios de sus cambios. La forma más
sencilla y eficaz de verificar los efectos secundarios es ejecutar el programa a través de un conjunto
automatizado de pruebas de regresión en JUnit, CppUnit o equivalente.
Agregue una prueba unitaria que exponga el defectoCuando encuentre un error que no fue expuesto por su
conjunto de pruebas, agregue un caso de prueba para exponer el error para que no se vuelva a presentar más tarde.
Busque defectos similaresCuando encuentre un defecto, busque otros que sean similares.
Los defectos tienden a ocurrir en grupos, y uno de los valores de prestar atención a los tipos de
defectos que haces es que puedes corregir todos los defectos de ese tipo. Buscar defectos
similares requiere que tenga una comprensión profunda del problema. Esté atento a la señal
de advertencia: si no puede averiguar cómo buscar defectos similares, es una señal de que aún
no comprende completamente el problema.
París en el
la primavera
En este rompecabezas clásico, las personas a menudo ven solo un "el". La gente ve lo que espera ver.
Considera lo siguiente:
falso, en lugar de sólo en la parte superior o inferior (Curtis et al. 1986). Ellos esperan un tiempo
loop para actuar como "while" lo hace en lenguaje natural.
si ( x < y )
= X;
intercambio
X = y;
y = intercambio;
X = y;
y = intercambio;
}
La gente espera que un nuevo fenómeno se asemeje a fenómenos similares que han visto antes.
Esperan que una nueva construcción de control funcione igual que las construcciones anteriores;
lenguaje de programacióntiempoinstrucciones para que funcionen igual que las declaraciones
"while" de la vida real; y los nombres de las variables sean los mismos que antes. Ve lo que espera
ver y, por lo tanto, pasa por alto las diferencias, como la falta de ortografía de la palabra "lenguaje"
en la oración anterior.
¿Qué tiene que ver el conjunto psicológico con la depuración? Primero, habla de la importancia de las
buenas prácticas de programación. Un buen formateo, comentarios, nombres de variables, nombres
de rutinas y otros elementos del estilo de programación ayudan a estructurar el fondo de
programación para que los defectos probables aparezcan como variaciones y se destaquen.
El segundo impacto del conjunto psicológico está en la selección de partes del programa para
examinar cuando se encuentra un error. La investigación ha demostrado que los programadores que
depuran con mayor eficacia eliminan mentalmente partes del programa que no son relevantes
durante la depuración (Basili, Selby y Hutchens 1986). En general, la práctica permite a los
programadores excelentes reducir sus campos de búsqueda y encontrar defectos más rápidamente.
A veces, sin embargo, la parte del programa que contiene el defecto se corta por error. Pasa tiempo
examinando una sección de código en busca de un defecto e ignora la sección que contiene el
defecto. Dio un giro equivocado en la bifurcación de la carretera y necesita retroceder antes de poder
avanzar de nuevo. Algunas de las sugerencias en la discusión de la Sección 23.2 sobre consejos para
encontrar defectos están diseñadas para superar esta "ceguera de depuración".
556 Capítulo 23: Depuración
11.7, “Tipos de
podría confundir fácilmente "Queck" con "Quack" porque las dos palabras se parecen. La
Nombres a evitar”. distancia psicológica entre las palabras es pequeña. Es mucho menos probable que confunda
"Tuack" con "Quack", aunque la diferencia sea solo una letra nuevamente. "Tuack" se parece
menos a "Quack" que a "Queck" porque la primera letra de una palabra es más prominente
que la del medio.
Mientras realiza la depuración, prepárese para los problemas causados por una distancia psicológica
insuficiente entre nombres de variables similares y entre nombres de rutinas similares. A medida que
construye el código, elija nombres con grandes diferencias para evitar el problema.
Establezca el nivel de advertencia de su compilador en el nivel más alto y exigente posible y corrija
PUNTO CLAVE los errores que informaEs descuidado ignorar los errores del compilador. Es aún más descuidado
desactivar las advertencias para que ni siquiera puedas verlas. Los niños a veces piensan que si cierran los
ojos y no pueden verte, te han hecho desaparecer. Establecer un interruptor en el compilador para
desactivar las advertencias solo significa que no puede ver los errores. No hace que desaparezcan más de
lo que cerrar los ojos hace que desaparezca un adulto.
Suponga que las personas que escribieron el compilador saben mucho más sobre su
idioma que usted. Si te advierten sobre algo, generalmente significa que tienes la
oportunidad de aprender algo nuevo sobre tu idioma. Haga el esfuerzo de comprender
lo que realmente significa la advertencia.
Tratar las advertencias como erroresAlgunos compiladores le permiten tratar las advertencias como
errores. Una razón para usar la función es que eleva la importancia aparente de una advertencia. Así como
configurar su reloj cinco minutos más rápido lo engaña para que piense que es cinco minutos más tarde de
lo que es, configurar su compilador para tratar las advertencias como errores lo engaña para que las tome
más en serio. Otra razón para tratar las advertencias como errores es que a menudo afectan la compilación
de su programa. Cuando compila y vincula un programa, las advertencias normalmente no impedirán que el
programa se vincule, pero los errores sí lo harán. Si desea verificar las advertencias antes de vincular,
configure el modificador del compilador que trata las advertencias como errores.
Iniciar estándares en todo el proyecto para la configuración en tiempo de compilaciónEstablezca un estándar que
requiera que todos los miembros de su equipo compilen el código utilizando la misma configuración del compilador. De lo
contrario, cuando intente integrar código compilado por diferentes personas con diferentes configuraciones, obtendrá una
avalancha de mensajes de error y una pesadilla de integración. Esto es fácil de aplicar si utiliza un archivo de creación
Perfiladores de ejecución
Es posible que no piense en un generador de perfiles de ejecución como una herramienta de depuración, pero unos minutos
dedicados a estudiar el perfil de un programa pueden descubrir algunos defectos sorprendentes (y ocultos).
Por ejemplo, sospeché que una rutina de administración de memoria en uno de mis programas era un
cuello de botella en el rendimiento. La gestión de la memoria había sido originalmente un componente
pequeño que usaba una matriz ordenada linealmente de punteros a la memoria. reemplacé el
558 Capítulo 23: Depuración
matriz ordenada linealmente con una tabla hash con la expectativa de que el tiempo de ejecución se
reduzca al menos a la mitad. Pero después de perfilar el código, no encontré ningún cambio en el
rendimiento. Examiné el código más de cerca y encontré un defecto que estaba desperdiciando una gran
cantidad de tiempo en el algoritmo de asignación. El cuello de botella no había sido la técnica de búsqueda
lineal; fue el defecto Después de todo, no había necesitado optimizar la búsqueda. Examine la salida de un
generador de perfiles de ejecución para asegurarse de que su programa pasa una cantidad de tiempo
razonable en cada área.
Marcos de prueba/andamiaje
Referencia cruzadaPara obtener Como se mencionó en la Sección 23.2 sobre la búsqueda de defectos, extraer un código
detalles sobre el andamiaje, consulte
problemático, escribir código para probarlo y ejecutarlo por sí mismo suele ser la forma más efectiva
"Construir andamiaje para probar
Depuradores
Los depuradores disponibles comercialmente han avanzado constantemente a lo largo de los años, y las
capacidades disponibles en la actualidad pueden cambiar la forma en que programa. Los buenos
depuradores le permiten establecer puntos de interrupción para romper cuando la ejecución llega a una
línea específica, o lanortevez que llega a una línea específica, o cuando cambia una variable global, o cuando
se le asigna un valor específico a una variable. Le permiten recorrer paso a paso el código línea por línea,
paso a paso o sobre las rutinas. Permiten que el programa se ejecute hacia atrás, retrocediendo hasta el
punto donde se originó un defecto. Le permiten registrar la ejecución de declaraciones específicas, similar a
la dispersión "¡Estoy aquí!" imprimir sentencias a lo largo de un programa.
Los buenos depuradores permiten un examen completo de los datos, incluidos los datos estructurados y
asignados dinámicamente. Facilitan la visualización del contenido de una lista vinculada de punteros o una
matriz asignada dinámicamente. Son inteligentes acerca de los tipos de datos definidos por el usuario. Le
permiten realizar consultas ad hoc sobre datos, asignar nuevos valores y continuar la ejecución del
programa.
Puede mirar el lenguaje de alto nivel o el lenguaje ensamblador generado por su compilador.
Si usa varios idiomas, el depurador muestra automáticamente el idioma correcto para cada
sección de código. Puede mirar una cadena de llamadas a rutinas y ver rápidamente el código
fuente de cualquier rutina. Puede cambiar los parámetros de un programa dentro del entorno
del depurador.
Los mejores depuradores de hoy en día también recuerdan los parámetros de depuración (puntos
de interrupción, variables que se observan, etc.) para cada programa individual para que no tenga
que volver a crearlos para cada programa que depure.
esencial cuando está depurando programas que son sensibles al tiempo o la cantidad de
memoria disponible.
Un depurador interactivo es un Dada la enorme potencia que ofrecen los depuradores modernos, es posible que se sorprenda de
excelente ejemplo de lo que no se
que alguien los critique. Pero algunas de las personas más respetadas en informática recomiendan
necesita: fomenta la piratería de
- Mantenga un bloc de notas junto a su escritorio y haga una lista de cosas para probar.
- Divide y conquistaras.
- Utilice un editor dirigido por la sintaxis para encontrar comentarios y comillas fuera de
lugar.
- Relax.
Recursos adicionales
cc2e.com/2375 Los siguientes recursos también abordan la depuración:
Agans, David J.Depuración: las nueve reglas indispensables para encontrar incluso los problemas de
software y hardware más esquivos. Amacom, 2003. Este libro proporciona principios generales de
depuración que se pueden aplicar en cualquier idioma o entorno.
MyersGlenford J.El arte de las pruebas de software. New York, NY: John Wiley & Sons, 1979. El
capítulo 7 de este libro clásico está dedicado a la depuración.
Allen, Eric.Patrones de errores en Java. Berkeley, CA: Apress, 2002. Este libro presenta
un enfoque para depurar programas Java que es conceptualmente muy similar a lo
que se describe en este capítulo, incluido "El método científico de depuración", que
distingue entre depuración y prueba, e identifica errores comunes. patrones.
Los siguientes dos libros son similares en el sentido de que sus títulos sugieren que solo se aplican a
los programas de Microsoft Windows y .NET, pero ambos contienen discusiones sobre la depuración
en general, el uso de aserciones y prácticas de codificación que ayudan a evitar errores en primer
lugar:
Puntos clave
- La depuración es un aspecto decisivo del desarrollo de software. El mejor enfoque es
usar otras técnicas descritas en este libro para evitar defectos en primer lugar. Sin
embargo, aún vale la pena dedicar tiempo a mejorar sus habilidades de depuración,
porque la diferencia entre un buen y un mal rendimiento de depuración es de al menos
10 a 1.
- Entienda la raíz del problema antes de arreglar el programa. Las conjeturas aleatorias
sobre las fuentes de errores y las correcciones aleatorias dejarán el programa en
peores condiciones que cuando comenzó.
refactorización
cc2e.com/2436 Contenido
Temas relacionados
- Áreas con probabilidad de cambio: “Identificar áreas con probabilidad de cambio” en la Sección 5.3
Todo el software exitoso se Mito: un proyecto de software bien administrado lleva a cabo un desarrollo metódico de requisitos y
cambia.
define una lista estable de responsabilidades del programa. El diseño sigue los requisitos y se realiza
—fred brooks
con cuidado para que la codificación pueda proceder linealmente, de principio a fin, lo que implica
que la mayor parte del código se puede escribir una vez, probar y olvidar. Según el mito, la única vez
que el código se modifica significativamente es durante la fase de mantenimiento del software, algo
que sucede solo después de que se haya entregado la versión inicial de un sistema.
3 Realidad: el código evoluciona sustancialmente durante su desarrollo inicial. Muchos de los cambios
2
1
observados durante la codificación inicial son al menos tan drásticos como los observados durante el
DATOS DUROS
mantenimiento. La codificación, la depuración y las pruebas unitarias consumen entre el 30 y el 65 por ciento
del esfuerzo en un proyecto típico, según el tamaño del proyecto. (Consulte el Capítulo 27, "Cómo el tamaño
del programa afecta la construcción", para obtener más información). Si la codificación y las pruebas
unitarias fueran procesos sencillos, no consumirían más del 20-30 por ciento del esfuerzo total de un
proyecto. Sin embargo, incluso en proyectos bien administrados, los requisitos cambian entre uno y cuatro
por ciento por mes (Jones 2000). Los cambios en los requisitos provocan invariablemente cambios en el
código correspondientes, a veces cambios sustanciales en el código.
563
Traducido del inglés al español - www.onlinedoctranslator.com
Otra realidad: las prácticas modernas de desarrollo aumentan el potencial de cambios de código durante la
construcción. En los ciclos de vida más antiguos, el enfoque, exitoso o no, estaba en evitar cambios en el
código. Los enfoques más modernos se alejan de la previsibilidad de la codificación. Los enfoques actuales
PUNTO CLAVE
están más centrados en el código y, durante la vida de un proyecto, puede esperar que el código evolucione
más que nunca.
La distinción clave entre los tipos de evolución del software es si la calidad del programa mejora o se
degrada con la modificación. Si corrige errores con cinta adhesiva lógica y superstición, la calidad se
degrada. Si trata las modificaciones como oportunidades para ajustar el diseño original del
PUNTO CLAVE
programa, la calidad mejora. Si ve que la calidad del programa se está degradando, es como ese
canario silencioso en el pozo de una mina que he mencionado antes. Es una advertencia de que el
programa está evolucionando en la dirección equivocada.
Una segunda distinción en los tipos de evolución del software es la que existe entre los cambios realizados
durante la construcción y los realizados durante el mantenimiento. Estos dos tipos de evolución difieren en
varios aspectos. Los cambios de construcción generalmente los realizan los desarrolladores originales,
generalmente antes de que el programa se haya olvidado por completo. El sistema aún no está en línea, por
lo que la presión para finalizar los cambios es solo la presión del cronograma: no son 500 usuarios enojados
que se preguntan por qué su sistema no funciona. Por la misma razón, los cambios durante la construcción
pueden ser más libres: el sistema se encuentra en un estado más dinámico y la penalización por cometer
errores es baja. Estas circunstancias implican un estilo de evolución del software diferente al que encontraría
durante el mantenimiento del software.
empeorar.
desarrollo es un fenómeno inevitable e importante y lo planifica, puede utilizarlo en su
—Gerald Weinberg beneficio.
La evolución es a la vez peligrosa y una oportunidad para acercarse a la perfección. Cuando tenga
que hacer un cambio, esfuércese por mejorar el código para que los cambios futuros sean más
fáciles. Nunca sabes tanto cuando comienzas a escribir un programa como lo sabes después.
Cuando tenga la oportunidad de revisar un programa, use lo que ha aprendido para mejorarlo.
Realice tanto su código inicial como sus cambios con más cambios en mente.
24.2 Introducción a la refactorización 565
La regla cardinal de la evolución del software es que la evolución debe mejorar la calidad
interna del programa. Las siguientes secciones describen cómo lograr esto.
PUNTO CLAVE
A veces, el código se degenera con el mantenimiento y, a veces, el código simplemente no era muy
bueno en primer lugar. En cualquier caso, aquí hay algunas señales de advertencia, a veces llamadas
"olores" (Fowler 1999), que indican dónde se necesitan refactorizaciones:
El código está duplicadoEl código duplicado casi siempre representa una falla en factorizar
completamente el diseño en primer lugar. El código duplicado lo configura para realizar
modificaciones paralelas: cada vez que realiza cambios en un lugar, debe realizar cambios
paralelos en otro lugar. También viola lo que Andrew Hunt y Dave Thomas denominan el
“principio DRY”: Don't Repeat Yourself (2000). Creo que David Parnas lo dice mejor: “Copiar y
pegar es un error de diseño” (McConnell 1998b).
Una rutina es demasiado largaEn la programación orientada a objetos, las rutinas más largas que una pantalla rara
vez son necesarias y, por lo general, representan el intento de forzar el ajuste de un pie de programación
estructurado en un zapato orientado a objetos.
A uno de mis clientes se le asignó la tarea de romper la rutina más larga de un sistema
heredado, que tenía más de 12 000 líneas. Con esfuerzo, pudo reducir el tamaño de la
rutina más grande a solo unas 4000 líneas.
Un bucle es demasiado largo o demasiado anidadoLas entrañas de los bucles tienden a ser
buenas candidatas para convertirse en rutinas, lo que ayuda a factorizar mejor el código y reducir la
complejidad del bucle.
566 Capítulo 24: Refactorización
Una clase tiene poca cohesión.Si encuentra una clase que asume la responsabilidad de una mezcolanza de
responsabilidades no relacionadas, esa clase debe dividirse en varias clases, cada una de las cuales tiene la
responsabilidad de un conjunto cohesivo de responsabilidades.
Una interfaz de clase no proporciona un nivel consistente de abstracciónIncluso las clases que
comienzan su vida con una interfaz cohesiva pueden perder su consistencia original. Las interfaces de clase
tienden a transformarse con el tiempo como resultado de las modificaciones que se realizan en el calor del
momento y que favorecen la conveniencia de la integridad de la interfaz. Finalmente, la interfaz de clase se
convierte en un monstruo de mantenimiento frankensteiniano que hace poco por mejorar la manejabilidad
intelectual del programa.
Una lista de parámetros tiene demasiados parámetrosLos programas bien factorizados tienden a tener
muchas rutinas pequeñas y bien definidas que no necesitan grandes listas de parámetros. Una lista de
parámetros larga es una advertencia de que la abstracción de la interfaz de rutina no ha sido bien pensada.
Los cambios dentro de una clase tienden a compartimentarseA veces una clase tiene dos o
más responsabilidades distintas. Cuando eso sucede, te encuentras cambiando una parte de la
clase u otra parte de la clase, pero pocos cambios afectan a ambas partes de la clase. Esa es
una señal de que la clase debe dividirse en múltiples clases a lo largo de las líneas de las
responsabilidades separadas.
Los cambios requieren modificaciones paralelas a múltiples clasesVi un proyecto que tenía una
lista de verificación de aproximadamente 15 clases que debían modificarse cada vez que se agregaba
un nuevo tipo de salida. Cuando te encuentras realizando cambios rutinarios en el mismo conjunto
de clases, eso sugiere que el código en esas clases podría reorganizarse para que los cambios afecten
solo a una clase. En mi experiencia, este es un ideal difícil de lograr, pero no obstante es una buena
meta.
Los elementos de datos relacionados que se usan juntos no están organizados en clasesSi se
encuentra manipulando repetidamente el mismo conjunto de elementos de datos, debe preguntarse
si esas manipulaciones deben combinarse en una clase propia.
Una rutina usa más características de otra clase que de su propia claseEsto sugiere que la
rutina debe moverse a la otra clase y luego ser invocada por su clase anterior.
24.2 Introducción a la refactorización 567
Un tipo de datos primitivo está sobrecargadoLos tipos de datos primitivos se pueden utilizar para
representar un número infinito de entidades del mundo real. Si su programa usa un tipo de datos primitivo
como un número entero para representar una entidad común como el dinero, considere crear un simple
Dineroclass para que el compilador pueda realizar la verificación de tipos enDinerovariables, para que
pueda agregar comprobaciones de seguridad sobre los valores asignados al dinero, y así sucesivamente. Si
ambos DineroyLa temperaturason números enteros, el compilador no le advertirá sobre asignaciones
erróneas comosaldoBanco = registroBajaTemperatura.
Una clase no hace muchoA veces, el resultado de la refactorización del código es que una
clase antigua no tiene mucho que hacer. Si una clase no parece tener su peso, pregunte si
debe asignar todas las responsabilidades de esa clase a otras clases y eliminar la clase por
completo.
Una cadena de rutinas pasa datos vagabundosEncontrarse pasando datos a una rutina solo para
que la rutina pueda pasarlos a otra rutina se llama "datos vagabundos" (Page-Jones 1988). Esto
podría estar bien, pero pregúntese si pasar los datos específicos en cuestión es consistente con la
abstracción presentada por cada una de las interfaces de rutina. Si la abstracción para cada rutina
está bien, pasar los datos está bien. Si no es así, encuentre alguna manera de hacer que la interfaz
de cada rutina sea más consistente.
Un objeto intermediario no está haciendo nadaSi encuentra que la mayor parte del código
en una clase simplemente pasa llamadas a rutinas en otras clases, considere si debe eliminar al
intermediario y llamar directamente a esas otras clases.
Una clase es demasiado íntima con otra.La encapsulación (ocultación de información) es probablemente
la herramienta más poderosa que tiene para hacer que su programa sea intelectualmente manejable y para
minimizar los efectos dominó de los cambios de código. Cada vez que vea una clase que sabe más sobre
otra clase de lo que debería, incluidas las clases derivadas que saben demasiado sobre sus padres, erre del
lado de una encapsulación más fuerte en lugar de una más débil.
Una rutina tiene un nombre pobreSi una rutina tiene un nombre deficiente, cambie el nombre de la rutina
donde está definida, cambie el nombre en todos los lugares donde se llama y luego vuelva a compilar. Tan
difícil como puede ser hacer esto ahora, será aún más difícil más adelante, así que hágalo tan pronto como
note que es un problema.
Los miembros de datos son públicosEn mi opinión, los miembros de datos públicos siempre son una mala
idea. Desdibujan la línea entre la interfaz y la implementación, e inherentemente violan la encapsulación y
limitan la flexibilidad futura. Considere seriamente ocultar miembros de datos públicos detrás de rutinas de
acceso.
Una subclase usa solo un pequeño porcentaje de las rutinas de sus padresPor lo
general, esto indica que esa subclase se creó porque una clase principal contenía las
rutinas que necesitaba, no porque la subclase sea lógicamente un descendiente de la
superclase. Considere lograr una mejor encapsulación cambiando la relación de la
subclase con su superclase de una relación es-un a una relación tiene-un; convertir el
568 Capítulo 24: Refactorización
superclase a los datos de miembros de la subclase anterior y exponga solo las rutinas de la subclase
anterior que realmente se necesitan.
Los comentarios se utilizan para explicar el código difícil.Los comentarios tienen un papel importante
que desempeñar, pero no deben usarse como muletas para explicar el código incorrecto. La antigua
sabiduría es acertada: “No documente el código incorrecto, reescríbalo” (Kernighan y Plauger 1978).
Referencia cruzadaPara obtener Se utilizan variables globalesCuando vuelva a visitar una sección de código que usa variables globales,
pautas sobre el uso de variables
tómese el tiempo para volver a examinarlas. Es posible que haya pensado en una forma de evitar el uso de
globales, consulte la Sección 13.3,
dolor causado por no hacerlo. Muerde la bala y haz las modificaciones beneficiosas. La codificación inicial
será lo suficientemente antigua como para que pueda ser objetivo con respecto a su trabajo, pero lo
suficientemente cercana como para recordar la mayor parte de lo que necesita para hacer las revisiones
correctamente. El tiempo durante las primeras revisiones es el momento perfecto para mejorar el código.
Una rutina usa un código de configuración antes de una llamada de rutina o un código de eliminación después de una llamada de rutina
Ejemplo incorrecto en C++ de configuración y eliminación de código para una llamada de rutina
ProcesarRetiro(retiro);
Una señal de advertencia similar es cuando te encuentras creando un constructor especial para el
RetiroTransacciónclase que toma un subconjunto de sus datos de inicialización normales para que
pueda escribir código como este:
Mal ejemplo de C++ de configuración y eliminación de código para una llamada de método
Cada vez que vea un código que se configura para una llamada a una rutina o se desconecta
después de una llamada a una rutina, pregunte si la interfaz de la rutina presenta la abstracción
correcta. En este caso, tal vez la lista de parámetros deProcesarRetirodebe modificarse para admitir
código como este:
Buen ejemplo en C++ de una rutina que no requiere configuración ni código de eliminación
ProcessWithdrawal (ID de cliente, saldo, cantidad de retiro, fecha de retiro);
Tenga en cuenta que el reverso de este ejemplo presenta un problema similar. Si se da cuenta
de que normalmente tiene unRetiroTransacciónobjeto en la mano pero necesita pasar varios
de sus valores a una rutina como la que se muestra aquí, también debe considerar refactorizar
elProcesarRetirointerfaz para que requiera laRetiroTransacciónobjeto en lugar de sus campos
individuales:
Cualquiera de estos enfoques puede ser correcto y cualquiera puede ser incorrecto;
depende de si elProcesarRetiro()La abstracción de la interfaz es que espera tener cuatro
datos distintos o espera tener unaRetiroTransacciónobjeto.
Un programa contiene código que parece que podría ser necesario algún díaLos programadores
son notoriamente malos para adivinar qué funcionalidad podría ser necesaria algún día. El “diseño
anticipado” está sujeto a numerosos problemas predecibles:
- Los requisitos para el código de "diseño anticipado" no se han desarrollado por completo, lo
que significa que el programador probablemente se equivocará sobre esos requisitos futuros.
El trabajo de "código por adelantado" finalmente se desechará.
- Los futuros programadores que usan el código de "diseño anticipado" no saben que era un
código de "diseño anticipado", o asumen que el código funciona mejor de lo que lo hace.
Suponen que el código ha sido codificado, probado y revisado al mismo nivel que el otro
código. Pierden mucho tiempo creando un código que utiliza el código de "diseño anticipado",
solo para descubrir finalmente que el código de "diseño anticipado" en realidad no funciona.
Los expertos coinciden en que la mejor manera de prepararse para los requisitos futuros es no
escribir código especulativo; es para hacer elrequerido actualmentecódigo lo más claro y
directo posible para que los futuros programadores sepan lo que hace y lo que no hace y
hagan sus cambios en consecuencia (Fowler 1999, Beck 2000).
- Los elementos de datos relacionados que se usan juntos no están organizados en clases.
- Una rutina utiliza más características de otra clase que de su propia clase.
- Una subclase usa solo un pequeño porcentaje de las rutinas de sus padres.
- Una rutina usa un código de configuración antes de una llamada de rutina o un código de eliminación después de una llamada
de rutina.
- Un programa contiene código que parece que podría ser necesario algún día.
24.3 Refactorizaciones específicas 571
En el lenguaje común, "refactorización" se usa vagamente para referirse a corregir defectos, agregar
funcionalidad, modificar el diseño, esencialmente como sinónimo de realizar cualquier cambio en el
código. Esta dilución común del significado del término es desafortunada. El cambio en sí mismo no
es una virtud, pero el cambio intencionado, aplicado con una cucharadita de disciplina, puede ser la
estrategia clave que respalde la mejora constante en la calidad de un programa bajo mantenimiento
y evite la muy familiar espiral mortal de la entropía del software.
Aquí hay refactorizaciones que mejoran el uso de variables y otros tipos de datos.
Reemplazar un número mágico con una constante con nombreSi está utilizando un literal numérico o de
cadena como3.14, reemplace ese literal con una constante nombrada comoPi.
Cambiar el nombre de una variable con un nombre más claro o más informativoSi el nombre de una variable no
está claro, cámbielo por un nombre mejor. El mismo consejo se aplica al cambio de nombre de constantes, clases y
rutinas, por supuesto.
Reemplazar una expresión con una rutinaReemplace una expresión con una rutina (generalmente
para que la expresión no se duplique en el código).
Convierta una variable multiuso en múltiples variables de un solo usoSi una variable se usa para más
de un propósito, los culpables comunes soni,j,temperatura, yX—crear variables separadas para cada uso,
cada una de las cuales tiene un nombre más específico.
Use una variable local para fines locales en lugar de un parámetroSi se utiliza un
parámetro de rutina de solo entrada como variable local, cree una variable local y utilícela en
su lugar.
572 Capítulo 24: Refactorización
Convertir una primitiva de datos en una claseSi una primitiva de datos necesita un comportamiento
adicional (incluida una verificación de tipo más estricta) o datos adicionales, convierta los datos en un objeto
y agregue el comportamiento que necesita. Esto puede aplicarse a tipos numéricos simples comoDineroyLa
temperatura. También se puede aplicar a tipos enumerados comoColor,Forma,País, oTipo de salida.
Convierta un conjunto de códigos de tipo en una clase o una enumeraciónEn programas más antiguos,
es común ver asociaciones como
En lugar de definir constantes independientes, cree una clase para que pueda recibir los beneficios
de una verificación de tipos más estricta y prepárese para proporcionar una semántica más rica para
Tipo de salidasi alguna vez lo necesitas. A veces, crear una enumeración es una buena alternativa a
crear una clase.
Convierta un conjunto de códigos de tipo en una clase con subclasesSi los diferentes elementos
asociados con diferentes tipos pueden tener un comportamiento diferente, considere crear una clase
base para el tipo con subclases para cada código de tipo. Para elTipo de salidaclase base, puede
crear subclases comoPantalla,Impresora, yExpediente.
Cambiar una matriz a un objetoSi está utilizando una matriz en la que los diferentes elementos son de
diferentes tipos, cree un objeto que tenga un campo para cada elemento anterior de la matriz.
Encapsular una colecciónSi una clase devuelve una colección, tener varias instancias de la
colección flotando puede crear dificultades de sincronización. Considere hacer que la clase
devuelva una colección de solo lectura y proporcione rutinas para agregar y eliminar
elementos de la colección.
Reemplazar un registro tradicional con una clase de datosCree una clase que contenga los
miembros del registro. La creación de una clase le permite centralizar la verificación de errores, la
persistencia y otras operaciones relacionadas con el registro.
Mover una expresión booleana compleja a una función booleana bien nombradaSi la
expresión es lo suficientemente complicada, esta refactorización puede mejorar la
legibilidad. Si la expresión se usa más de una vez, elimina la necesidad de modificaciones
paralelas y reduce la posibilidad de error al usar la expresión.
24.3 Refactorizaciones específicas 573
Usardescansoodevolveren lugar de una variable de control de bucleSi tienes una variable dentro de un
ciclo comohechoque se usa para controlar el bucle, usedescansoodevolverpara salir del bucle en su lugar.
Regrese tan pronto como sepa la respuesta en lugar de asignar un valor de retorno dentro de
anidadossi-entonces-otrodeclaracionesEl código suele ser más fácil de leer y menos propenso a errores si
sale de una rutina tan pronto como sepa el valor de retorno. La alternativa de establecer un valor de retorno
y luego desenredar su camino a través de mucha lógica puede ser más difícil de seguir.
Cree y use objetos nulos en lugar de probar valores nulosA veces, un objeto nulo
tendrá un comportamiento genérico o datos asociados, como referirse a un residente
cuyo nombre no se conoce como "ocupante". En este caso, considere mover la
responsabilidad de manejar valores nulos del código del cliente a la clase, es decir, tener
la Clienteclase define al residente desconocido como "ocupante" en lugar de tenerCliente
El código de cliente de prueba repetidamente si se conoce el nombre del cliente y
sustituye "ocupante" si no.
Mover el código de una rutina en líneaTome el código de una rutina cuyo cuerpo es simple y se
explica por sí mismo, y mueva el código de esa rutina en línea donde se usa.
Convertir una rutina larga en una claseSi una rutina es demasiado larga, a veces
convertirla en una clase y luego factorizar la rutina anterior en varias rutinas mejorará la
legibilidad.
Agregar un parámetroSi una rutina necesita más información de su llamador, agregue un parámetro para
que se pueda proporcionar esa información.
Rutinas separadas cuyo comportamiento depende de los parámetros pasados enSi una
rutina ejecuta un código diferente según el valor de un parámetro de entrada, considere dividir
la rutina en rutinas separadas que se puedan llamar por separado, sin pasar ese parámetro de
entrada en particular.
Cambiar objetos de referencia a objetos de valorSi se encuentra realizando una gran cantidad de
limpieza de referencia para objetos pequeños o simples, cambie el uso de esos objetos para que
todos los objetos sean objetos de valor.
Reemplace las rutinas virtuales con la inicialización de datosSi tiene un conjunto de subclases que
varían solo de acuerdo con los valores constantes que devuelven, en lugar de anular las rutinas de los
miembros en las clases derivadas, haga que las clases derivadas inicialicen la clase con los valores
constantes apropiados y luego tenga un código genérico en la clase base que funcione. con esos
valores.
24.3 Refactorizaciones específicas 575
Cambiar la rutina del miembro o la ubicación de los datosConsidere realizar varios cambios
generales en una jerarquía de herencia. Estos cambios normalmente se realizan para eliminar la
duplicación en las clases derivadas:
Extraer código especializado en una subclaseSi una clase tiene un código que solo usa un
subconjunto de sus instancias, mueva ese código especializado a su propia subclase.
Combinar código similar en una superclaseSi dos subclases tienen un código similar,
combine ese código y muévalo a la superclase.
Mover una rutina a otra claseCree una nueva rutina en la clase de destino y mueva el
cuerpo de la rutina de la clase de origen a la clase de destino. A continuación, puede llamar a
la nueva rutina desde la rutina anterior.
Convertir una clase en dosSi una clase tiene dos o más áreas distintas de responsabilidad, divida la
clase en varias clases, cada una de las cuales tiene una responsabilidad claramente definida.
eliminar una claseSi una clase no está haciendo mucho, mueva su código a otras clases que
sean más cohesivas y elimine la clase.
Ocultar un delegadoA veces, la Clase A llama a la Clase B y a la Clase C, cuando en realidad la Clase
A debería llamar solo a la Clase B y la Clase B debería llamar a la Clase C. Pregúntese cuál es la
abstracción correcta para la interacción de A con B. Si B debe ser responsable de llamar a C, tenga B
llama a C.
Reemplazar herencia con delegaciónSi una clase necesita usar otra clase pero quiere más control
sobre su interfaz, convierta la superclase en un campo de la subclase anterior y luego exponga un
conjunto de rutinas que proporcionarán una abstracción cohesiva.
Reemplazar delegación con herenciaSi una clase expone todas las rutinas públicas de una
clase delegada (clase miembro), herede de la clase delegada en lugar de usar solo la clase.
Introducir una rutina extrañaSi una clase necesita una rutina adicional y no puede modificar la
clase para proporcionarla, puede crear una nueva rutina dentro de la clase de cliente que
proporcione esa funcionalidad.
Introducir una clase de extensiónSi una clase necesita varias rutinas adicionales y no
puede modificar la clase, puede crear una nueva clase que combine la funcionalidad de
la clase no modificable con la funcionalidad adicional. Puede hacerlo subclasificando la
clase original y agregando nuevas rutinas o ajustando la clase y exponiendo las rutinas
que necesita.
Encapsular una variable miembro expuestaSi los datos de los miembros son públicos, cambie los datos de los
miembros a privados y, en su lugar, exponga el valor de los datos de los miembros a través de una rutina.
RemoverEstablecer()rutinas para campos que no se pueden cambiarSi se supone que un campo debe
configurarse en el momento de la creación del objeto y no cambiarse después, inicialice ese campo en el constructor
del objeto en lugar de proporcionar un campo engañoso.Establecer()rutina.
Ocultar rutinas que no están destinadas a ser utilizadas fuera de la claseSi la interfaz de
clase sería más coherente sin una rutina, oculte la rutina.
Contraer una superclase y una subclase si sus implementaciones son muy similaresSi la
subclase no proporciona mucha especialización, combínela con su superclase.
Cree una fuente de referencia definitiva para los datos que no puede controlarA veces, tiene
datos mantenidos por el sistema a los que no puede acceder de manera conveniente o consistente
desde otros objetos que necesitan saber sobre esos datos. Un ejemplo común son los datos
mantenidos en un control GUI. En tal caso, puede crear una clase que refleje los datos en el control
de GUI y luego hacer que tanto el control de GUI como el otro código traten esa clase como la fuente
definitiva de esos datos.
24.3 Refactorizaciones específicas 577
- Cambie el nombre de una variable con un nombre más claro o más informativo.
- Mueve una expresión booleana compleja a una función booleana bien nombrada.
578 Capítulo 24: Refactorización
- Regrese tan pronto como sepa la respuesta en lugar de asignar un valor de retorno dentro de
anidadossi-entonces-otrodeclaraciones.
- Agregar un parámetro.
- Eliminar un parámetro.
- Encapsular downcasting.
- Ocultar un delegado.
- Eliminar un intermediario.
- Oculte las rutinas que no están destinadas a ser utilizadas fuera de la clase.
- Cree una fuente de referencia definitiva para los datos que no puede controlar.
lavadora. ¿Sería más fácil el Guarda el código con el que empiezasAntes de comenzar a refactorizar, asegúrese de poder volver al
mantenimiento si se llamara código con el que comenzó. Guarde una versión en su sistema de control de revisiones o copie los archivos
“Cirugía Cerebral por Software”?
correctos en un directorio de respaldo.
—Gerald Weinberg
580 Capítulo 24: Refactorización
Mantenga pequeñas las refactorizacionesAlgunas refactorizaciones son más grandes que otras, y lo que
constituye exactamente "una refactorización" puede ser un poco confuso. Mantenga pequeñas las refactorizaciones
para que comprenda completamente todos los impactos de los cambios que realice. Las refactorizaciones detalladas
descritas enrefactorización(Fowler 1999) proporcionan muchos buenos ejemplos de cómo hacer esto.
Hacer refactorizaciones una a la vezAlgunas refactorizaciones son más complicadas que otras. Para todas las
refactorizaciones, excepto las más simples, realice las refactorizaciones de una en una, vuelva a compilar y volver a
probar después de una refactorización antes de realizar la siguiente.
Haz una lista de los pasos que piensas darUna extensión natural del proceso de programación de
pseudocódigo es hacer una lista de las refactorizaciones que lo llevarán del punto A al punto B.
Hacer una lista lo ayuda a mantener cada cambio en contexto.
Haz controles frecuentesEs fácil encontrar que el código se desvía repentinamente mientras está
refactorizando. Además de guardar el código con el que comenzó, guarde los puntos de control en varios
pasos en una sesión de refactorización para que pueda volver a un programa que funcione si se codifica a sí
mismo en un callejón sin salida.
Usa las advertencias de tu compiladorEs fácil cometer pequeños errores que pasan desapercibidos para el
compilador. Establecer su compilador en el nivel de advertencia más exigente posible ayudará a detectar muchos
volver a probarLas revisiones del código modificado deben complementarse con nuevas pruebas. Por supuesto,
esto depende de tener un buen conjunto de casos de prueba en primer lugar. Las pruebas de regresión y otros
temas de prueba se describen con más detalle en el Capítulo 22, "Pruebas de desarrollador".
Agregar casos de pruebaAdemás de volver a probar con sus pruebas anteriores, agregue nuevas pruebas unitarias para
ejercitar el nuevo código. Elimine cualquier caso de prueba que haya quedado obsoleto debido a las refactorizaciones.
Referencia cruzadaPara obtener detalles Revisa los cambiosSi las revisiones son importantes la primera vez, lo son aún más durante las
sobre las revisiones, consulte el Capítulo
modificaciones posteriores. Ed Yourdon informa que los programadores suelen tener más del
21, “Construcción colaborativa”.
50 por ciento de posibilidades de cometer un error en su primer intento de realizar un cambio
(Yourdon 1986b). Curiosamente, si los programadores trabajan con una parte sustancial del
código, en lugar de solo unas pocas líneas, la posibilidad de hacer un
24.4 Refactorización segura 581
la modificación correcta mejora, como se muestra en la Figura 24-1. Específicamente, a medida que el
número de líneas cambiadas aumenta de una a cinco líneas, aumenta la posibilidad de hacer un cambio
incorrecto. Después de eso, la posibilidad de hacer un mal cambio disminuye.
100%
Oportunidad
de error
0%
0 5 10 15 20
Líneas cambiadas
Figura 24-1Los cambios pequeños tienden a ser más propensos a errores que los cambios más grandes
(Weinberg 1983).
Los programadores tratan los pequeños cambios de forma casual. No los revisan en el escritorio, no hacen
que otros los revisen y, a veces, ni siquiera ejecutan el código para verificar que la solución funciona
correctamente.
3 La moraleja es simple: trate los cambios simples como si fueran complicados. Una organización que
2
1
introdujo revisiones para cambios de una línea descubrió que su tasa de error pasó del 55 por ciento
DATOS DUROS
antes de las revisiones al 2 por ciento después (Freedman y Weinberg 1982). Una organización de
telecomunicaciones pasó del 86 por ciento correcto antes de revisar los cambios de código al 99,6
por ciento después (Perrott 2004).
Para refactorizaciones más arriesgadas, erre por el lado de la precaución. Realice las refactorizaciones de una en
una. Pídale a otra persona que revise la refactorización o use la programación en pares para esa refactorización,
La refactorización es una técnica poderosa, pero no es una panacea y está sujeta a algunos tipos
específicos de abuso.
No escriba parcialmente una No utilice la refactorización como una tapadera para el código y la correcciónEl peor problema con la
función con la intención de
refactorización es cómo se usa mal. Los programadores a veces dirán que están refactorizando, cuando en realidad
refactorizarla para completarla
más tarde.
todo lo que están haciendo es ajustar el código, con la esperanza de encontrar una forma de hacerlo funcionar. La
—Juan Manzo refactorización se refiere acambios en el código de trabajoque no afectan el comportamiento del programa. Los
Una gran refactorización es una receta Evite refactorizar en lugar de reescribirA veces, el código no necesita pequeños
cambios, debe desecharse para que pueda comenzar de nuevo. Si se encuentra en una
para el desastre.
—Kent Beck
sesión de refactorización importante, pregúntese si, en cambio, debería rediseñar y
volver a implementar esa sección de código desde cero.
Refactoriza cuando agregas una rutinaCuando agregue una rutina, verifique si las rutinas
relacionadas están bien organizadas. Si no, refactorizarlos.
Refactoriza cuando agregas una claseAgregar una clase a menudo trae problemas con el código existente. Utilice
este tiempo como una oportunidad para refactorizar otras clases que estén estrechamente relacionadas con la clase
que está agregando.
Refactoriza cuando arreglas un defectoUse la comprensión que obtiene al corregir un error para
mejorar otro código que podría ser propenso a defectos similares.
Referencia cruzadaPara obtener más Módulos propensos a errores de destinoAlgunos módulos son más propensos a errores y frágiles que
información sobre el código propenso a errores,
otros. ¿Hay alguna sección de código que usted y todos los demás miembros de su equipo teman?
consulte "¿Qué clases contienen la mayoría de los
Apunta a módulos de alta complejidadOtro enfoque es centrarse en los módulos que tienen las
calificaciones de complejidad más altas. (Consulte "Cómo medir la complejidad" en la Sección 19.6
para obtener detalles sobre estas métricas). Un estudio clásico encontró que la calidad del programa
mejoró drásticamente cuando los programadores de mantenimiento centraron sus esfuerzos de
mejora en los módulos que tenían la mayor complejidad (Henry y Kafura 1984).
En un entorno de mantenimiento, mejore las partes que tocaEl código que nunca se
modifica no necesita ser refactorizado. Pero cuando toque una sección de código, asegúrese
de dejarla mejor de lo que la encontró.
Defina una interfaz entre código limpio y código feo, y luego mueva el código a través de la interfazEl
“mundo real” a menudo es más desordenado de lo que te gustaría. El desorden puede provenir de reglas
comerciales complicadas, interfaces de hardware o interfaces de software. Un problema común con los
sistemas geriátricos es un código de producción mal escrito que debe permanecer abierto.
Una efectiva
código como bei
y algunos c
bonito ordenado yo
tratar mundo
Figura 24-2Su código no tiene que ser desordenado solo porque el mundo real es desordenado. Conciba su
sistema como una combinación de código ideal, interfaces del código ideal al desordenado mundo real y al
desordenado mundo real.
584 Capítulo 24: Refactorización
A medida que trabaja con el sistema, puede comenzar a mover el código a través de la "interfaz del
mundo real" hacia un mundo ideal más organizado. Cuando comienza a trabajar con un sistema
heredado, el código heredado mal escrito puede representar casi todo el sistema. Una política que
funciona bien es que cada vez que toca una sección de código desordenado, debe abrirla
Arkansas
mento en ca
Estado objetivo
Código heredado en su mayoría mal escrito Código refactorizado mayormente bien escrito
Figura 24-3Una estrategia para mejorar el código de producción es refactorizar el código heredado mal
escrito a medida que lo toca, para moverlo al otro lado de la "interfaz con el desordenado mundo real".
- ¿Ha hecho una lista de los pasos que piensa tomar durante su refactorización?
- ¿Tiene un estacionamiento para que pueda recordar las ideas que se le ocurren a mitad de
la refactorización?
- ¿Ha evitado usar la refactorización como una tapadera para el código y la corrección o como una excusa
Recursos adicionales
cc2e.com/2464 El proceso de refactorización tiene mucho en común con el proceso de corrección de defectos. Para obtener más información
sobre la reparación de defectos, consulte la Sección 23.3, “Reparación de un defecto”. Los riesgos asociados con la
refactorización son similares a los riesgos asociados con el ajuste de código. Para obtener más información sobre la gestión
de los riesgos del ajuste de código, consulte la Sección 25.6, "Resumen del enfoque para el ajuste de código".
Fowler, Martín.Refactorización: mejora del diseño del código existente. Reading, MA: Addison
Wesley, 1999. Esta es la guía definitiva para la refactorización. Contiene discusiones detalladas de
muchas de las refactorizaciones específicas resumidas en este capítulo, así como un puñado de otras
refactorizaciones no resumidas aquí. Fowler proporciona numerosos ejemplos de código para
ilustrar cómo se realiza cada refactorización paso a paso.
Puntos clave
- Los cambios de programa son una realidad tanto durante el desarrollo inicial como después del lanzamiento
inicial.
- Una clave para el éxito en la refactorización es aprender a prestar atención a las numerosas señales
de advertencia u olores que indican la necesidad de refactorizar.
- Una clave final para el éxito es tener una estrategia para refactorizar de manera segura. Algunos enfoques
cc2e.com/2578 Contenido
Temas relacionados
Este capítulo analiza la cuestión del ajuste del rendimiento, históricamente un tema controvertido. Los
recursos informáticos estaban severamente limitados en la década de 1960 y la eficiencia era una
preocupación primordial. A medida que las computadoras se volvieron más poderosas en la década de
1970, los programadores se dieron cuenta de cuánto su enfoque en el rendimiento había afectado la
legibilidad y la capacidad de mantenimiento, y el ajuste del código recibió menos atención. El regreso de las
limitaciones de rendimiento con la revolución de las microcomputadoras de la década de 1980 volvió a
poner de relieve la eficiencia, que luego disminuyó a lo largo de la década de 1990. En la década de 2000, las
limitaciones de memoria en el software integrado para dispositivos como teléfonos y PDA y el tiempo de
ejecución del código interpretado han vuelto a convertir la eficiencia en un tema clave.
Puede abordar las preocupaciones de rendimiento en dos niveles: estratégico y táctico. Este capítulo
aborda cuestiones estratégicas de desempeño: qué es el desempeño, qué tan importante es y el
enfoque general para lograrlo. Si ya domina bien las estrategias de rendimiento y está buscando
técnicas específicas a nivel de código que mejoren el rendimiento, continúe con el Capítulo 26,
"Técnicas de ajuste de código". Sin embargo, antes de comenzar cualquier trabajo de rendimiento
importante, al menos hojee la información de este capítulo para que no pierda el tiempo optimizando
cuando debería estar haciendo otros tipos de trabajo.
587
588 Capítulo 25: Estrategias de ajuste de código
El ajuste de código es una forma de mejorar el rendimiento de un programa. A menudo, puede encontrar
otras formas de mejorar más el rendimiento, en menos tiempo y con menos daño para el código, que
mediante el ajuste del código. Esta sección describe las opciones.
Aquí hay una ilustración. Tomo al menos 50 fotos a la semana con mi cámara digital. Para cargar las
imágenes en mi computadora, el software que viene con la cámara requiere que seleccione cada
imagen una por una, viéndolas en una ventana que muestra solo seis imágenes a la vez. Cargar 50
imágenes es un proceso tedioso que requiere docenas de clics del mouse y mucha navegación a
través de la ventana de seis imágenes. Después de aguantar esto durante unos meses, compré un
lector de tarjetas de memoria que se conecta directamente a mi computadora y que mi computadora
cree que es una unidad de disco. Ahora puedo usar Windows Explorer para copiar las imágenes a mi
computadora. Lo que antes requería docenas de clics del mouse y mucha espera ahora requiere
aproximadamente dos clics del mouse, Ctrl+A y arrastrar y soltar. Realmente no me importa si el
lector de tarjetas de memoria transfiere cada archivo en la mitad o el doble de tiempo que el otro
software, porque mi rendimiento es más rápido. Independientemente de si el código del lector de
tarjetas de memoria es más rápido o más lento, su rendimiento es mejor.
El rendimiento solo está vagamente relacionado con la velocidad del código. En la medida en que trabaje en la
velocidad de su código, no estará trabajando en otras características de calidad. Tenga cuidado de no sacrificar otras
características para que su código sea más rápido. Su trabajo sobre la velocidad podría perjudicar el rendimiento
PUNTO CLAVE
general en lugar de mejorarlo.
- Diseño de programa
- Compilación de código
- Hardware
- ajuste de código
El rendimiento se establece como un requisito con mucha más frecuencia de lo que realmente es un
requisito. Barry Boehm cuenta la historia de un sistema en TRW que inicialmente requería un tiempo de
respuesta inferior al segundo. Este requerimiento llevó a un diseño altamente complejo y un costo estimado
de $100 millones. Un análisis posterior determinó que los usuarios estarían satisfechos con respuestas de
cuatro segundos el 90 por ciento de las veces. La modificación del requisito de tiempo de respuesta redujo
el costo general del sistema en alrededor de $70 millones. (Boehm 2000b).
Antes de invertir tiempo en resolver un problema de rendimiento, asegúrese de que está resolviendo
un problema que debe resolverse.
Diseño de programa
Referencia cruzadaPara obtener El diseño de programas incluye los rasgos principales del diseño de un solo programa,
detalles sobre el diseño del
principalmente la forma en que un programa se divide en clases. Algunos diseños de programas
rendimiento en un programa, consulte
En este caso, sin abordar el riesgo en el diseño de alto nivel, los programadores se habrían
encontrado tratando de optimizar las matemáticas para evaluar un polinomio de orden 13 en el
software, es decir, un polinomio con 14 términos, incluidas las variables elevadas a la 13 potencia. En
cambio, abordaron el problema con hardware diferente y un diseño de alto nivel que usaba docenas
de polinomios de tercer orden. Este cambio no podría haberse realizado mediante el ajuste del
código, y es poco probable que cualquier cantidad de ajuste del código hubiera resuelto el problema.
Este es un ejemplo de un problema que tuvo que ser abordado en el nivel de diseño del programa.
V413HAV
590 Capítulo 25: Estrategias de ajuste de código
Referencia cruzadaPara obtener detalles sobre Si sabe que el tamaño y la velocidad de un programa son importantes, diseñe la arquitectura del programa
la forma en que los programadores trabajan
para que pueda cumplir razonablemente con sus objetivos de tamaño y velocidad. Diseñe una arquitectura
para alcanzar los objetivos, consulte
- Establecer objetivos de recursos individuales hace que el rendimiento final del sistema sea predecible. Si
cada característica cumple con sus objetivos de recursos, todo el sistema cumplirá sus objetivos. Puede
identificar los subsistemas que tienen problemas para cumplir sus objetivos de manera temprana y
orientarlos para rediseñarlos o ajustar el código.
- El mero acto de hacer explícitos los objetivos mejora la probabilidad de que se logren.
Los programadores trabajan por objetivos cuando saben cuáles son; cuanto más
explícitos sean los objetivos, más fácil será trabajar con ellos.
- Puede establecer metas que no logren la eficiencia directamente pero que promuevan la eficiencia a
largo plazo. La eficiencia a menudo se trata mejor en el contexto de otros temas. Por ejemplo, lograr
un alto grado de modificabilidad puede proporcionar una mejor base para cumplir los objetivos de
PUNTO CLAVE
eficiencia que establecer explícitamente un objetivo de eficiencia. Con un diseño altamente modular
y modificable, puede cambiar fácilmente los componentes menos eficientes por otros más eficientes.
Referencia cruzadaPara estrategias Si su programa funciona con archivos externos, memoria dinámica o dispositivos de salida, probablemente
a nivel de código que abordan rutinas
esté interactuando con el sistema operativo. Si el rendimiento no es bueno, puede deberse a que las rutinas
de sistemas operativos lentos o
Compilación de código
Los buenos compiladores convierten un código de lenguaje claro y de alto nivel en un código de máquina
optimizado. Si elige el compilador correcto, es posible que no necesite pensar más en optimizar la
velocidad.
Hardware
Ajuste de código
Puede realizar mejoras drásticas en cada nivel, desde el diseño del sistema hasta el ajuste del código.
Jon Bentley cita un argumento de que en algunos sistemas las mejoras en cada nivel se pueden
multiplicar (1982). Debido a que puede lograr una mejora de 10 veces en cada uno de los seis niveles,
eso implica una mejora potencial del rendimiento de un millón de veces. Aunque tal multiplicación de
mejoras requiere un programa en el que las ganancias en un nivel sean independientes de las
ganancias en otros niveles, lo cual es raro, el potencial es inspirador.
El ajuste de código es atractivo por varias razones. Uno de los atractivos es que parece desafiar las leyes de
la naturaleza. Es increíblemente satisfactorio tomar una rutina que se ejecuta en 20 microsegundos,
modificar algunas líneas y reducir la velocidad de ejecución a 2 microsegundos.
También es atractivo porque dominar el arte de escribir código eficiente es un rito de iniciación para
convertirse en un programador serio. En el tenis, no obtienes ningún punto de juego por la forma en
que recoges una pelota de tenis, pero aún necesitas aprender la forma correcta de hacerlo. No
puedes simplemente inclinarte y recogerlo con la mano. Si eres bueno, lo golpeas con la cabeza de tu
raqueta hasta que rebota a la altura de la cintura y luego lo atrapas. golpeándolo
592 Capítulo 25: Estrategias de ajuste de código
más de tres veces, incluso no rebotarlo la primera vez, es una falta grave. A pesar de su aparente
poca importancia, la forma en que recoges la pelota tiene cierto prestigio dentro de la cultura del
tenis. Del mismo modo, a nadie más que a ti y a otros programadores les suele importar lo ajustado
que sea tu código. No obstante, dentro de la cultura de la programación, escribir código
microeficiente demuestra que eres genial.
El problema con el ajuste del código es que el código eficiente no es necesariamente un código
"mejor". Ese es el tema de las próximas secciones.
El principio de Pareto
El Principio de Pareto, también conocido como la regla del 80/20, establece que puedes obtener el 80 por
ciento del resultado con el 20 por ciento del esfuerzo. El principio se aplica a muchas áreas además de la
programación, pero definitivamente se aplica a la optimización de programas.
Barry Boehm informa que el 20 por ciento de las rutinas de un programa consumen el 80 por ciento de su
tiempo de ejecución (1987b). En su artículo clásico "An Empirical Study of Fortran Programs", Donald Knuth
descubrió que menos del cuatro por ciento de un programa generalmente representa más del 50 por ciento
PUNTO CLAVE
de su tiempo de ejecución (1971).
Knuth usó un perfilador de conteo de líneas para descubrir esta sorprendente relación, y las
implicaciones para la optimización son claras. Debe medir el código para encontrar los puntos
críticos y luego poner sus recursos en optimizar el pequeño porcentaje que se usa más. Knuth perfiló
su programa de conteo de líneas y descubrió que estaba gastando la mitad de su tiempo de
ejecución en dos bucles. Cambió algunas líneas de código y duplicó la velocidad del generador de
perfiles en menos de una hora.
Jon Bentley describe un caso en el que un programa de 1000 líneas pasó el 80 por ciento de su tiempo en
una rutina de raíz cuadrada de cinco líneas. Al triplicar la velocidad de la rutina de la raíz cuadrada, duplicó
la velocidad del programa (1988). El Principio de Pareto también es la fuente del consejo de escribir la mayor
parte del código en un lenguaje interpretado como Python y luego reescribir los puntos calientes en un
lenguaje compilado más rápido como C.
Bentley también informa sobre el caso de un equipo que descubrió que la mitad del tiempo de un
sistema operativo se gastaba en un pequeño bucle. Reescribieron el bucle en microcódigo y lo
hicieron 10 veces más rápido, pero no cambió el rendimiento del sistema: ¡habían reescrito el bucle
inactivo del sistema!
El equipo que diseñó el lenguaje ALGOL, el abuelo de la mayoría de los lenguajes modernos y
uno de los más influyentes de todos los tiempos, recibió el siguiente consejo: "Lo mejor es
enemigo de lo bueno". Trabajar hacia la perfección podría impedir la finalización. Complétalo
primero y luego perfeccionalo. La parte que necesita ser perfecta suele ser pequeña.
25.2 Introducción al ajuste de código 593
Mucho de lo que ha escuchado sobre el ajuste de código es falso, incluidos los siguientes
malentendidos comunes:
Reducir las líneas de código en un lenguaje de alto nivel mejora la velocidad o el tamaño del
código de máquina resultante: ¡falso!Muchos programadores se aferran tenazmente a la
creencia de que si pueden escribir código en una o dos líneas, será lo más eficiente posible.
Considere el siguiente código que inicializa una matriz de 10 elementos:
para i = 1 a 10
un[ yo ] = yo
fin para
¿Adivinarías que estas líneas son más rápidas o más lentas que las siguientes 10 líneas que hacen el
mismo trabajo?
un[ 1 ] = 1
un[ 2 ] = 2
un[ 3 ] = 3
un[ 4 ] = 4
un[ 5 ] = 5
un[ 6 ] = 6
un[ 7 ] = 7
un[ 8 ] = 8
un[ 9 ] = 9
un[ 10 ] = 10
Si sigue el antiguo dogma de que "menos líneas son más rápidas", adivinará que el primer código es más
rápido. Pero las pruebas en Microsoft Visual Basic y Java han demostrado que el segundo fragmento es al
menos un 60 por ciento más rápido que el primero. Aquí están los números:
Nota(1) Los tiempos en esta y las siguientes tablas de este capítulo se dan en segundos y son
significativos solo para comparaciones entre filas en cada tabla. Los tiempos reales variarán según el
compilador, las opciones del compilador utilizadas y el entorno en el que se ejecuta cada prueba. (2)
Los resultados de referencia generalmente se componen de varios miles a muchos millones de
ejecuciones de fragmentos de código para suavizar las fluctuaciones de muestra a muestra en los
resultados. (3) No se indican las marcas y versiones específicas de los compiladores. Las
características de rendimiento varían significativamente de una marca a otra y de una versión a otra.
(4) Las comparaciones entre los resultados de diferentes lenguajes no siempre son significativas porque los
compiladores para diferentes lenguajes no siempre ofrecen opciones de generación de código comparables. (5) Los
resultados que se muestran para los lenguajes interpretados (PHP y Python) generalmente se basan en menos del 1 %
de las ejecuciones de prueba utilizadas para los otros lenguajes. (6) Es posible que algunos de los porcentajes de
"ahorro de tiempo" no se reproduzcan exactamente a partir de los datos de estas tablas debido al redondeo de las
entradas de "tiempo normal" y "tiempo ajustado por código".
Traducido del inglés al español - www.onlinedoctranslator.com
Esto ciertamente no implica que aumentar el número de líneas de código de lenguaje de alto nivel
siempre mejore la velocidad o reduzca el tamaño. Implica que, independientemente del atractivo
estético de escribir algo con la menor cantidad de líneas de código, no existe una relación predecible
entre la cantidad de líneas de código en un lenguaje de alto nivel y el tamaño y la velocidad finales de
un programa.
Ciertas operaciones son probablemente más rápidas o más pequeñas que otras, ¡falso!No hay
lugar para "probablemente" cuando se habla de rendimiento. Siempre debe medir el desempeño
para saber si sus cambios ayudaron o dañaron su programa. Las reglas del juego cambian cada vez
que cambias de idioma, compiladores, versiones de compiladores, bibliotecas, versiones de
bibliotecas, procesador, cantidad de memoria en la máquina, color de la camiseta que llevas puesta
(bueno, esta no), etc. en. Lo que era cierto en una máquina con un conjunto de herramientas puede
fácilmente ser falso en otra máquina con un conjunto diferente de herramientas.
Este fenómeno sugiere varias razones para no mejorar el rendimiento mediante el ajuste del código.
Si desea que su programa sea portátil, las técnicas que mejoran el rendimiento en un entorno
pueden degradarlo en otros. Si cambia los compiladores o actualiza, el nuevo compilador podría
optimizar automáticamente el código de la forma en que lo estaba ajustando a mano y su trabajo se
habrá desperdiciado. Peor aún, el ajuste de su código podría derrotar optimizaciones de compilador
más potentes que han sido diseñadas para funcionar con código sencillo.
Cuando ajusta el código, se registra implícitamente para volver a perfilar cada optimización
cada vez que cambia la marca del compilador, la versión del compilador, la versión de la
biblioteca, etc. Si no vuelve a generar el perfil, una optimización que mejore el rendimiento en
una versión de un compilador o biblioteca podría degradar el rendimiento cuando cambie el
entorno de compilación.
Deberíamos olvidarnos de las pequeñas Debe optimizar sobre la marcha, ¡falso!Una teoría es que si se esfuerza por escribir el código más rápido y
eficiencias, digamos alrededor del 97% del
pequeño posible mientras escribe cada rutina, su programa será rápido y pequeño. Este enfoque crea una
tiempo: la optimización prematura es la
- Es casi imposible identificar cuellos de botella en el rendimiento antes de que un programa funcione
por completo. Los programadores son muy malos para adivinar qué cuatro por ciento del código
representa el 50 por ciento del tiempo de ejecución, por lo que los programadores que optimizan
sobre la marcha dedicarán, en promedio, el 96 por ciento de su tiempo a optimizar el código que no
necesita ser optimizado. . Eso deja poco tiempo para optimizar el cuatro por ciento que realmente
cuenta.
- En el raro caso en que los desarrolladores identifiquen los cuellos de botella correctamente, exageran
los cuellos de botella que identificaron y permiten que otros se vuelvan críticos. Nuevamente, el
efecto final es una reducción en el rendimiento. Las optimizaciones realizadas después de que se
completa un sistema pueden identificar cada área problemática y su importancia relativa para que el
tiempo de optimización se asigne de manera efectiva.
25.2 Introducción al ajuste de código 595
- Centrarse en la optimización durante el desarrollo inicial resta valor al logro de otros objetivos
del programa. Los desarrolladores se sumergen en análisis de algoritmos y debates arcanos
que al final no aportan mucho valor al usuario. Preocupaciones como la corrección, la
ocultación de información y la legibilidad se convierten en objetivos secundarios, aunque es
más fácil mejorar el rendimiento más tarde que estas otras preocupaciones. El trabajo de
rendimiento post hoc suele afectar a menos del cinco por ciento del código de un programa.
¿Preferiría volver atrás y hacer el trabajo de rendimiento en el cinco por ciento del código o el
trabajo de legibilidad en el 100 por ciento?
Ocasionalmente, la optimización post hoc no será suficiente para cumplir con los objetivos de rendimiento y
deberá realizar cambios importantes en el código completo. En esos casos, las optimizaciones pequeñas y
localizadas no habrían proporcionado las ganancias necesarias de todos modos. El problema en tales casos
no es una calidad de código inadecuada, sino una arquitectura de software inadecuada.
Si necesita optimizar antes de que se complete un programa, minimice los riesgos incorporando perspectiva a su
proceso. Una forma es especificar objetivos de tamaño y velocidad para las características y luego optimizar para
alcanzar los objetivos a medida que avanza. Establecer tales objetivos en una especificación es una forma de
mantener un ojo en el bosque mientras descubre qué tan grande es su árbol en particular.
Otras lecturasPara muchos Un programa rápido es tan importante como uno correcto—¡falso!Casi nunca es cierto que los
otros entretenimientos y
programas necesitan ser rápidos o pequeños antes de que necesiten ser correctos. Gerald Weinberg
anécdotas esclarecedoras, ver
Gerald Weinberg'sPsicología cuenta la historia de un programador que fue trasladado en avión a Detroit para ayudar a depurar un
de la programación programa problemático. El programador trabajó con el equipo que había desarrollado el programa y
informática(1998).
concluyó después de varios días que la situación era desesperada.
En el vuelo a casa, reflexionó sobre la situación y se dio cuenta de cuál era el problema. Al final
del vuelo, tenía un esquema para el nuevo código. Probó el código durante varios días y
estaba a punto de regresar a Detroit cuando recibió un telegrama que decía que el proyecto
había sido cancelado porque el programa era imposible de escribir. Regresó a Detroit de todos
modos y convenció a los ejecutivos de que el proyecto podía completarse.
Luego tuvo que convencer a los programadores originales del proyecto. Escucharon su
presentación y, cuando terminó, el creador del antiguo sistema preguntó: "¿Y cuánto
tiempo dura su programa?"
“¡Ajá! Pero mi programa toma solo un segundo por entrada”. El veterano se echó hacia atrás, satisfecho de
haber dejado perplejo al advenedizo. Los otros programadores parecían estar de acuerdo, pero el nuevo
programador no se dejó intimidar.
“Sí, pero su programano funciona. Si el mío no tiene que funcionar, puedo hacerlo funcionar
instantáneamente”.
Para cierta clase de proyectos, la velocidad o el tamaño es una preocupación importante. Esta clase es la minoría, es
mucho más pequeña de lo que la mayoría de la gente piensa, y cada vez es más pequeña. Para estos proyectos, los
riesgos de rendimiento deben abordarse mediante un diseño inicial. Para otros proyectos, la optimización temprana
representa una amenaza significativa para la calidad general del software, incluyendo el rendimiento.
Cuándo sintonizar
Reglas de optimización de Jackson: Usa un diseño de alta calidad. Haz bien el programa. Hágalo modular y fácilmente modificable para
Regla 1. No lo hagas. Regla 2 (solo
que sea fácil trabajar en él más tarde. Cuando esté completo y correcto, compruebe el rendimiento. Si
para expertos). No lo haga todavía, es
optimizar.
—ma jackson Hace unos años trabajé en un proyecto de C++ que producía resultados gráficos para analizar datos
de inversión. Después de que mi equipo consiguió que funcionara el primer gráfico, las pruebas
informaron que el programa tardó unos 45 minutos en dibujar el gráfico, lo que claramente no era
aceptable. Celebramos una reunión de equipo para decidir qué hacer al respecto. Uno de los
desarrolladores se enfureció y gritó: “Si queremos tener alguna posibilidad de lanzar un producto
aceptable, tenemos que empezar a reescribir todo el código base en ensamblador.en este momento
.” Respondí que no lo creía, que el cuatro por ciento del código probablemente representaba el 50
por ciento o más del cuello de botella de rendimiento. Sería mejor abordar ese cuatro por ciento
hacia el final del proyecto. Después de un poco más de gritos, nuestro gerente me asignó hacer un
trabajo de interpretación inicial (que en realidad fue un caso de "¡Oh, no! ¡Por favor, no me arrojen a
ese campo de zarzales!").
Como suele ser el caso, el trabajo de un día identificó un par de cuellos de botella evidentes en el
código. Una pequeña cantidad de cambios en el ajuste del código redujeron el tiempo de dibujo de
45 minutos a menos de 30 segundos. Mucho menos del uno por ciento del código representó el 90
por ciento del tiempo de ejecución. Cuando lanzamos el software meses después, varios cambios de
ajuste de código adicionales redujeron ese tiempo de dibujo a poco más de 1 segundo.
Las optimizaciones del compilador moderno pueden ser más poderosas de lo que espera. En el caso
que describí anteriormente, mi compilador hizo un trabajo tan bueno al optimizar un bucle anidado
como el que pude hacer al reescribir el código en un estilo supuestamente más eficiente. Cuando
compre un compilador, compare el rendimiento de cada compilador en su programa. Cada
25.3 Tipos de grasa y melaza 597
Los compiladores de optimización son mejores para optimizar el código sencillo que para optimizar el
código complicado. Si hace cosas "inteligentes", como perder el tiempo con los índices de bucle, su
compilador tendrá más dificultades para hacer su trabajo y su programa sufrirá. Consulte "Uso de solo una
declaración por línea" en la Sección 31.5 para ver un ejemplo en el que un enfoque sencillo dio como
resultado un código optimizado para el compilador que fue un 11 por ciento más rápido que el código
"complicado" comparable.
Con un buen compilador de optimización, la velocidad de su código puede mejorar en un 40 por ciento o más en
todos los ámbitos. Muchas de las técnicas descritas en el próximo capítulo producen ganancias de solo 15 a 30 por
ciento. ¿Por qué no simplemente escribir un código claro y dejar que el compilador haga el trabajo? Estos son los
resultados de algunas pruebas para verificar cuánto aceleró un optimizador una rutina de clasificación por inserción:
La única diferencia entre las versiones de la rutina era que las optimizaciones del compilador se
desactivaron para la primera compilación y se activaron para la segunda. Claramente, algunos
compiladores optimizan mejor que otros y algunos son mejores sin optimizaciones en primer
lugar. Algunas máquinas virtuales Java (JVM) también son claramente mejores que otras.
Deberá verificar su propio compilador, JVM o ambos para medir el efecto.
Aquí hay una comparación de rendimiento entre el código que accede a elementos aleatorios en una matriz
en memoria de 100 elementos y el código que accede a elementos aleatorios del mismo tamaño en un
archivo de disco de 100 registros:
De acuerdo con estos datos, el acceso en memoria es del orden de 1000 veces más rápido que el acceso
a datos en un archivo externo. De hecho, con el compilador de C++ que utilicé, el tiempo necesario para
acceder a la memoria no se podía medir.
La comparación de rendimiento para una prueba similar de tiempos de acceso secuencial es similar:
Si la prueba hubiera utilizado un medio más lento para el acceso externo, por ejemplo, un disco
duro a través de una conexión de red, la diferencia habría sido aún mayor. El rendimiento se ve
así cuando se realiza una prueba de acceso aleatorio similar en una ubicación de red en lugar
de en la máquina local:
Idioma Hora del archivo local Tiempo de archivo de red Ahorro de tiempo
Por supuesto, estos resultados pueden variar drásticamente según la velocidad de su red, la
carga de la red, la distancia entre la máquina local y la unidad de disco en red, la velocidad de
la unidad de disco en red en comparación con la velocidad de la unidad local, la fase actual de
la luna y otros factores.
25.3 Tipos de grasa y melaza 599
En general, el efecto del acceso en memoria es lo suficientemente significativo como para hacerle pensar dos veces
PaginaciónUna operación que hace que el sistema operativo intercambie páginas de memoria es
mucho más lenta que una operación que funciona en una sola página de memoria. A veces, un
simple cambio hace una gran diferencia. En el siguiente ejemplo, un programador escribió un ciclo de
inicialización que produjo muchas fallas de página en un sistema que usaba páginas de 4K.
Este es un bucle bien formateado con buenos nombres de variables, entonces, ¿cuál es el problema? El
problema es que cada elemento demesatiene unos 4000 bytes de largo. Simesatiene demasiadas filas, cada
vez que el programa accede a una fila diferente, el sistema operativo tendrá que cambiar de página de
memoria. Por la forma en que está estructurado el bucle, cada acceso a la matriz individual cambia de fila, lo
que significa que cada acceso a la matriz provoca la paginación en el disco.
Este código sigue provocando un error de página cada vez que cambia de fila, pero solo cambia de
fila. MAX_ROWSveces en lugar deMAX_ROWS * MAX_COLUMNASveces.
Llamadas al sistemaLas llamadas a las rutinas del sistema suelen ser costosas. A menudo implican
un cambio de contexto: guardar el estado del programa, recuperar el estado del kernel y viceversa.
Las rutinas del sistema incluyen operaciones de entrada/salida a disco, teclado, pantalla, impresora u
otro dispositivo; rutinas de gestión de memoria; y ciertas rutinas de utilidad. Si realiza-
600 Capítulo 25: Estrategias de ajuste de código
Si el control es un problema, averigüe qué tan costosas son sus llamadas al sistema. Si son caros,
considera estas opciones:
- Escriba sus propios servicios. A veces, solo necesita una pequeña parte de la funcionalidad que
ofrece una rutina del sistema y puede crear la suya propia a partir de rutinas del sistema de
nivel inferior. Escribir su propio reemplazo le brinda algo que es más rápido, más pequeño y se
adapta mejor a sus necesidades.
- Evite ir al sistema.
- Trabaje con el proveedor del sistema para que la llamada sea más rápida. La mayoría de los
proveedores quieren mejorar sus productos y están contentos de conocer partes de sus sistemas
con un rendimiento bajo. (Pueden parecer un poco gruñones al principio, pero realmente están
interesados).
Como puede ver, C++, Visual Basic y C# son comparables. Java está cerca pero tiende a ser más
lento que los otros lenguajes. PHP y Python son lenguajes interpretados, y el código en esos
lenguajes tendía a ejecutarse en un factor de 100 o más lento que el código en C++, Visual
Basic, C# y Java. Los números generales presentados en esta tabla deben ser vistos con
cautela. Para cualquier pieza de código en particular, C++, Visual Basic, C# o Java
25.3 Tipos de grasa y melaza 601
podría ser el doble o la mitad de rápido que los otros idiomas. (Puedes ver esto por ti mismo en
los ejemplos detallados en el Capítulo 26.)
erroresUna última fuente de problemas de rendimiento son los errores en el código. Los errores pueden incluir
dejar activado el código de depuración (como registrar información de seguimiento en un archivo), olvidar
desasignar la memoria, diseñar incorrectamente las tablas de la base de datos, sondear dispositivos inexistentes
hasta que se agote el tiempo de espera, etc.
Una aplicación de la versión 1.0 en la que trabajé tenía una operación particular que era mucho más
lenta que otras operaciones similares. Surgió una gran cantidad de mitología del proyecto para
explicar la lentitud de esta operación. Lanzamos la versión 1.0 sin entender del todo por qué esta
operación en particular era tan lenta. Sin embargo, mientras trabajaba en la versión 1.1, descubrí
que la tabla de la base de datos utilizada por la operación no estaba indexada. Simplemente indexar
la tabla mejoró el rendimiento en un factor de 30 para algunas operaciones. Definir un índice en una
tabla de uso común no es optimización; es solo una buena práctica de programación.
La mayoría de las operaciones comunes tienen aproximadamente el mismo precio: las llamadas de rutina, las
asignaciones, la aritmética de números enteros y la aritmética de coma flotante son todas aproximadamente
iguales. Las funciones matemáticas trascendentales son extremadamente caras. Las llamadas de rutina polimórficas
son un poco más caras que otros tipos de llamadas de rutina.
La tabla 25-2, o una similar que usted haga, es la llave que desbloquea todas las mejoras de
velocidad descritas en el Capítulo 26. En todos los casos, mejorar la velocidad proviene de
reemplazar una operación costosa por una más barata. El Capítulo 26 proporciona ejemplos de
cómo hacerlo.
25.4 Medición
Debido a que las partes pequeñas de un programa generalmente consumen una parte desproporcionada
del tiempo de ejecución, mida su código para encontrar los puntos críticos. Una vez que haya encontrado los
puntos críticos y los haya optimizado, vuelva a medir el código para evaluar cuánto lo ha mejorado. Muchos
aspectos del rendimiento son contradictorios. El caso anterior de este capítulo, en el que 10 líneas de código
eran significativamente más rápidas y más pequeñas que una línea, es un ejemplo de las formas en que el
código puede sorprenderlo.
La experiencia tampoco ayuda mucho con la optimización. La experiencia de una persona puede provenir
de una máquina, un lenguaje o un compilador antiguos; cuando cualquiera de esas cosas cambia, todas las
apuestas están canceladas. Nunca se puede estar seguro del efecto de una optimización hasta que se mide
PUNTO CLAVE
el efecto.
Hace muchos años escribí un programa que sumaba los elementos en una matriz. El
código original se veía así:
Ejemplo en C++ de código sencillo para sumar los elementos de una matriz
suma = 0;
for (fila = 0; fila < número de filas; fila++) {
for (columna = 0; columna <columnaCuenta; columna++) {
suma = suma + matriz[fila][columna];
}
}
Este código era sencillo, pero el rendimiento de la rutina de suma de matrices era fundamental
y sabía que todos los accesos a matrices y las pruebas de bucle tenían que ser costosos. Sabía
por las clases de informática que cada vez que el código accedía a una matriz bidimensional,
realizaba costosas multiplicaciones y sumas. Para una matriz de 100 por 100, eso sumó 10 000
multiplicaciones y sumas, más la sobrecarga del bucle. Por
604 Capítulo 25: Estrategias de ajuste de código
Otras lecturasJon Bentley Ejemplo en C++ de un intento de ajustar el código para sumar los elementos en una matriz
informó de una experiencia suma = 0;
similar en la que la conversión a punteroelemento = matriz;
punteros perjudicó el rendimiento lastElementPointer = matriz[ número de filas - 1 ][ número de columnas - 1 ] + 1; while
en un 10 por ciento. La misma (elementPointer < lastElementPointer) {
conversión, en otro entorno, suma = suma + *elementPointer++;
mejoró el rendimiento en más del }
50 por ciento. Consulte “Software
Exploratorium: Escritura de
programas eficientes en Aunque el código no era tan legible como el primer código, especialmente para los programadores que no
C” (Bentley 1991).
son expertos en C++, estaba magníficamente satisfecho conmigo mismo. Para una matriz de 100 por 100,
calculé que había ahorrado 10 000 multiplicaciones y una gran cantidad de sobrecarga de bucle. Estaba tan
complacido que decidí medir la mejora de la velocidad, algo que no siempre hacía en ese entonces, para
poder darme palmaditas en la espalda de forma más cuantitativa.
Ningún programador tiene ¿Sabes lo que encontré? Sin mejora alguna. No con una matriz de 100 por 100. No con una matriz de
alguna vez sido capaz de
10 por 10. No con cualquier tamaño de matriz. Estaba tan decepcionado que busqué en el código
predecir o analizar dónde están
los cuellos de botella de ensamblador generado por el compilador para ver por qué mi optimización no había funcionado.
rendimientosin datos. No Para mi sorpresa, resultó que no era el primer programador que necesitaba iterar a través de los
importa a dónde creas que va,
elementos de una matriz: el optimizador del compilador ya estaba convirtiendo los accesos a la
te sorprenderá descubrir que
va a otro lugar.
matriz en punteros. Aprendí que el único resultado de la optimización del que generalmente puede
—Joseph M. Recién llegado estar seguro sin medir el rendimiento es que ha hecho que su código sea más difícil de leer. Si no vale
la pena medir para saber que es más eficiente, no vale la pena sacrificar la claridad por una apuesta
de rendimiento.
son útiles, o puede usar el reloj y las rutinas de su sistema que registran los tiempos transcurridos para las
operaciones informáticas.
Ya sea que use la herramienta de otra persona o escriba su propio código para realizar las
mediciones, asegúrese de medir solo el tiempo de ejecución del código que está ajustando. Utilice el
número de ciclos de reloj de la CPU asignados a su programa en lugar de la hora del día. De lo
contrario, cuando el sistema cambie de su programa a otro programa, una de sus rutinas será
penalizada por el tiempo empleado en ejecutar otro programa. Del mismo modo, intente eliminar la
sobrecarga de medición y la sobrecarga de inicio del programa para que ni el código original ni el
intento de ajuste sean penalizados injustamente.
25.5 Iteración 605
25.5 Iteración
Una vez que haya identificado un cuello de botella en el rendimiento, se sorprenderá de cuánto puede
mejorar el rendimiento ajustando el código. Rara vez obtendrá una mejora de 10 veces con una técnica,
pero puede combinar técnicas de manera efectiva; así que sigue intentándolo, incluso después de encontrar
uno que funcione.
Una vez escribí una implementación de software del Estándar de cifrado de datos (DES). En
realidad, no lo escribí una sola vez, lo escribí unas 30 veces. El cifrado según DES codifica los
datos digitales para que no se puedan descifrar sin una contraseña. El algoritmo de cifrado es
tan intrincado que parece que se ha utilizado en sí mismo. El objetivo de rendimiento de mi
implementación de DES era cifrar un archivo de 18 K en 37 segundos en una PC IBM original.
Mi primera implementación se ejecutó en 21 minutos y 40 segundos, por lo que tenía un largo
camino por recorrer.
El ajuste de código que hice en este caso es el ajuste de código más agresivo que he hecho. Al
mismo tiempo, el código final es el código más ilegible e imposible de mantener que he
escrito. El algoritmo inicial es complicado. El código resultante de la transformación del
lenguaje de alto nivel apenas se podía leer. La traducción a ensamblador produjo una sola
rutina de 500 líneas que tengo miedo de mirar. En general, esta relación entre el ajuste del
código y la calidad del código es cierta. Aquí hay una tabla que muestra un historial de las
optimizaciones:
1.Desarrolle el software utilizando un código bien diseñado que sea fácil de entender y
modificar.
una.Guarde una versión de trabajo del código para que pueda volver al "último estado
bueno conocido".
Recursos adicionales
cc2e.com/2585 Esta sección contiene recursos relacionados con la mejora del rendimiento en general. Para obtener
recursos adicionales que analizan técnicas específicas de ajuste de código, consulte la sección
"Recursos adicionales" al final del Capítulo 26.
Actuación
Smith, Connie U. y Lloyd G. Williams.Soluciones de rendimiento: una guía práctica para crear
software adaptable y escalable. Boston, MA: Addison-Wesley, 2002. Este libro cubre la
ingeniería de rendimiento del software, un enfoque para construir el rendimiento en
Recursos adicionales 607
sistemas de software en todas las etapas de desarrollo. Hace un uso extensivo de ejemplos y
estudios de casos para varios tipos de programas. Incluye recomendaciones específicas para
aplicaciones Web y presta especial atención a la escalabilidad.
Estos son los primeros tres volúmenes de una serie que originalmente tenía la intención de
crecer a siete volúmenes. Pueden ser algo intimidantes. Además de la descripción en inglés de
los algoritmos, se describen en notación matemática o MIX, un lenguaje ensamblador para la
computadora MIX imaginaria. Los libros contienen detalles exhaustivos sobre una gran
cantidad de temas, y si tiene un gran interés en un algoritmo en particular, no encontrará una
mejor referencia.
Sedgewick, Robert.Algoritmos en Java, Partes 1-4, 3d ed. Boston, MA: Addison-Wesley, 2002. Las
cuatro partes de este libro contienen un estudio de los mejores métodos para resolver una amplia
variedad de problemas. Sus áreas temáticas incluyen fundamentos, clasificación, búsqueda,
implementación de tipos de datos abstractos y temas avanzados. de SedgewickAlgoritmos en Java,
Parte 5, 3d ed. (2003) cubre algoritmos gráficos. de SedgewickAlgoritmos en C++, Partes 1-4, 3d ed.
(1998),Algoritmos en C++, Parte 5, 3d ed. (2002),Algoritmos en C, Partes 1-4, 3d ed. (1997), y
Algoritmos en C, Parte 5, 3d ed. (2001) están organizados de manera similar. Sedgewick era un Ph.D.
estudiante de Knuth.
- ¿Ha medido los cuellos de botella de rendimiento antes de comenzar a ajustar el código?
Puntos clave
- El rendimiento es solo un aspecto de la calidad general del software y, por lo general, no es el más
importante. El código ajustado con precisión es solo un aspecto del rendimiento general y, por lo
general, no es el más significativo. La arquitectura del programa, el diseño detallado y la estructura
de datos y la selección de algoritmos generalmente tienen más influencia en la velocidad y el tamaño
de ejecución de un programa que la eficiencia de su código.
- La mayoría de los programas pasan la mayor parte de su tiempo en una pequeña fracción
de su código. No sabrá qué código es hasta que lo mida.
- Por lo general, se necesitan múltiples iteraciones para lograr las mejoras de rendimiento deseadas a
través del ajuste del código.
- 26.7 Cuanto más cambian las cosas, más permanecen igual: página 643
Temas relacionados
- Refactorización: Capítulo 24
El ajuste de códigos ha sido un tema popular durante la mayor parte de la historia de la programación de
computadoras. En consecuencia, una vez que haya decidido que necesita mejorar el rendimiento y que
quiere hacerlo a nivel de código (teniendo en cuenta las advertencias del Capítulo 25, "Estrategias de ajuste
de código"), tiene un amplio conjunto de técnicas en tu disposición.
Este capítulo se centra en mejorar la velocidad e incluye algunos consejos para reducir el tamaño del código. El
rendimiento generalmente se refiere tanto a la velocidad como al tamaño, pero las reducciones de tamaño tienden
a provenir más del rediseño de clases y datos que del ajuste del código. El ajuste de código se refiere a cambios a
pequeña escala en lugar de cambios en diseños a mayor escala.
Pocas de las técnicas de este capítulo son de aplicación tan general que podrá copiar el
código de ejemplo directamente en sus programas. El objetivo principal de la discusión
aquí es ilustrar un puñado de ajustes de código que puede adaptar a su situación.
Los cambios de ajuste de código descritos en este capítulo pueden parecer cosméticamente
similares a las refactorizaciones descritas en el Capítulo 24, pero las refactorizaciones son cambios
que mejoran la estructura interna de un programa (Fowler 1999). Los cambios en este capítulo
podrían llamarse mejor "anti-refactorizaciones". Lejos de “mejorar la estructura interna”, estos
cambios degradan la estructura interna a cambio de mejoras en el desempeño. Esto es
609
610 Capítulo 26: Técnicas de ajuste de código
Referencia cruzadaLos ajustes de código son Algunos libros presentan técnicas de sintonización de códigos como "reglas generales" o citan
heurísticos. Para obtener más información sobre
investigaciones que sugieren que una sintonización específica producirá el efecto deseado. Como
las heurísticas, consulte la Sección 5.3, “Bloques
26.1 Lógica
Referencia cruzadaPara obtener más Gran parte de la programación consiste en manipular la lógica. Esta sección describe
cómo manipular expresiones lógicas a su favor.
detalles sobre el uso de la lógica de
Una vez que haya determinado queXno es mayor que 5, no necesita realizar la segunda
mitad de la prueba.
Referencia cruzadaPara obtener más Algunos lenguajes proporcionan una forma de evaluación de expresiones conocida como "evaluación de
información sobre la evaluación de
cortocircuito", lo que significa que el compilador genera código que detiene automáticamente la prueba tan
cortocircuitos, consulte "Saber cómo se
si ( 5 < x ) entonces
si (x < 10) entonces...
El principio de no probar después de saber la respuesta también es bueno para muchos otros tipos
de casos. Un bucle de búsqueda es un caso común. Si está escaneando una matriz de números de
entrada en busca de un valor negativo y simplemente necesita saber si hay un valor negativo
presente, un enfoque es verificar cada valor, estableciendo unnegativoEncontradovariable cuando
encuentre una. Así es como se vería el bucle de búsqueda:
26.1 Lógica 611
}
}
Un mejor enfoque sería dejar de escanear tan pronto como encuentre un valor negativo.
Cualquiera de estos enfoques resolvería el problema:
Nota(1) Los tiempos en esta y las siguientes tablas de este capítulo se dan en segundos y son significativos
solo para comparaciones entre filas de cada tabla. Los tiempos reales variarán según el compilador, las
opciones del compilador utilizadas y el entorno en el que se ejecuta cada prueba. (2) Los resultados de
referencia generalmente se componen de varios miles a muchos millones de ejecuciones de fragmentos de
código para suavizar las fluctuaciones de muestra a muestra en los resultados. (3) No se indican las marcas y
versiones específicas de los compiladores. Las características de rendimiento varían significativamente de
una marca a otra y de una versión a otra. (4) Las comparaciones entre los resultados de diferentes lenguajes
no siempre son significativas porque los compiladores para diferentes lenguajes no siempre ofrecen
opciones de generación de código comparables. (5) Los resultados que se muestran para los lenguajes
interpretados (PHP y Python) generalmente se basan en menos del 1 % de las ejecuciones de prueba
utilizadas para los otros lenguajes. (6) Es posible que algunos de los porcentajes de "ahorro de tiempo" no se
reproduzcan exactamente a partir de los datos de estas tablas debido al redondeo de las entradas de
"tiempo normal" y "tiempo ajustado por código".
El impacto de este cambio varía mucho según la cantidad de valores que tenga y la frecuencia
con la que espere encontrar un valor negativo. Esta prueba asumió un promedio de 100
valores y supuso que se encontraría un valor negativo el 50 por ciento de las veces.
612 Capítulo 26: Técnicas de ajuste de código
Organice las pruebas para que la que sea más rápida y con más probabilidades de ser cierta se
realice primero. Debería ser fácil pasar por el caso normal, y si hay ineficiencias, deberían estar en el
procesamiento de los casos poco comunes. Este principio se aplica acasodeclaraciones y cadenas de
si-entonces-otros.
Aquí está unSeleccione el casodeclaración que responde a la entrada del teclado en un procesador de textos:
Los casos en estecasose ordenan en algo parecido al orden de clasificación ASCII. en uncasodeclaración, sin
embargo, el efecto es a menudo el mismo que si hubiera escrito un gran conjunto deifthen-elses, así que si
obtienes un"a"como carácter de entrada, el programa comprueba si se trata de un símbolo matemático, un
signo de puntuación, un dígito o un espacio antes de determinar que se trata de un carácter alfabético. Si
conoce la frecuencia probable de sus caracteres de entrada, puede poner primero los casos más comunes.
Aquí está el reordenadocasodeclaración:
Debido a que el caso más común suele encontrarse antes en el código optimizado, el efecto neto
será la realización de menos pruebas. Los siguientes son los resultados de esta optimización con una
combinación típica de caracteres:
Los resultados de Microsoft Visual Basic son los esperados, pero los resultados de Java y C# no son los
esperados. Aparentemente eso es por la formacaja de interruptoresLas declaraciones están estructuradas
en C# y Java, dado que cada valor debe enumerarse individualmente en lugar de en rangos, el código de C#
y Java no se beneficia de la optimización como lo hace el código de Visual Basic. Este resultado subraya la
importancia de no seguir ciegamente ningún consejo de optimización: las implementaciones específicas del
compilador afectarán significativamente los resultados.
Podría suponer que el código generado por el compilador de Visual Basic para un conjunto deifthen-
elses que realizan la misma prueba que elcasodeclaración sería similar. Echa un vistazo a esos
resultados:
Los resultados son bastante diferentes. Para el mismo número de pruebas, el compilador de Visual Basic
tarda aproximadamente cinco veces más en el caso no optimizado, cuatro veces en el caso optimizado. Esto
sugiere que el compilador está generando un código diferente para elcasoenfoque que para elsi-entonces-
otroAcercarse.
La mejora consi-entonces-otros es más consistente de lo que era con elcasodeclaraciones, pero eso
es una bendición mixta. En C# y Visual Basic, ambas versiones delcaso enfoque de declaración son
más rápidos que ambas versiones delsi-entonces-otroenfoque, mientras que en Java ambas versiones
son más lentas. Esta variación en los resultados sugiere una tercera optimización posible, que se
describe en la siguiente sección.
614 Capítulo 26: Técnicas de ajuste de código
Estos resultados desafían cualquier explicación lógica. En uno de los idiomas,casoes dramáticamente
superior asi-entonces-otro, y en otro,si-entonces-otroes dramáticamente superior acaso. En el tercer
idioma, la diferencia es relativamente pequeña. Podría pensar que debido a que C# y Java comparten
una sintaxis similar paracasodeclaraciones, sus resultados serían similares, pero de hecho sus
resultados son opuestos entre sí.
Este ejemplo ilustra claramente la dificultad de realizar cualquier tipo de "regla general" o "lógica"
para ajustar el código: simplemente no existe un sustituto confiable paramediciónresultados.
A 1 B
1 2
1
2 2
0 3
C
26.1 Lógica 615
Puede reemplazar esta prueba con una tabla de búsqueda más modificable y de mayor rendimiento:
Ejemplo de C++ del uso de una tabla de búsqueda para reemplazar la lógica complicada
// definir tabla de categorías
Esta definición de static int tabla de categorías[ 2 ][ 2 ][ 2 ] = {
tabla es algo difícil de // !b!c !bc b!c bc
comprender. Cualquier comentario que 0, 3, 2, 2, // !a
pueda hacer para que las definiciones de 1, 2, 1, 1 // a
las tablas sean legibles ayuda. };
...
categoría = tablacategoría[ a ][ b ][ c ];
Aunque la definición de la tabla es difícil de leer, si está bien documentada, no será más
difícil de leer que el código de la complicada cadena lógica. Si la definición cambia, la
tabla será mucho más fácil de mantener de lo que hubiera sido la lógica anterior. Estos
son los resultados de rendimiento:
Uno de mis antiguos compañeros de habitación era un gran procrastinador. Justificó su pereza
diciendo que muchas de las cosas por las que la gente se siente apurada simplemente no
necesitan hacerse. Si esperaba lo suficiente, afirmó, las cosas que no eran importantes serían
postergadas hasta el olvido y no perdería el tiempo haciéndolas.
La evaluación perezosa se basa en el principio que usó mi compañero de cuarto. Si un programa usa
evaluación perezosa, evita hacer cualquier trabajo hasta que se necesita el trabajo. La evaluación perezosa
es similar a las estrategias justo a tiempo que hacen el trabajo más cerca de cuando se necesita.
616 Capítulo 26: Técnicas de ajuste de código
Suponga, por ejemplo, que su programa contiene una tabla de 5000 valores, genera toda la tabla en
el momento del inicio y luego la usa a medida que se ejecuta el programa. Si el programa usa solo un
pequeño porcentaje de las entradas en la tabla, podría tener más sentido calcularlas a medida que se
necesitan en lugar de todas a la vez. Una vez que se calcula una entrada, aún se puede almacenar
para referencia futura (también conocido como "almacenado en caché").
26.2 Bucles
Referencia cruzadaPara obtener más Debido a que los bucles se ejecutan muchas veces, los puntos calientes de un programa suelen estar dentro de los
detalles sobre los bucles, consulte el
bucles. Las técnicas de esta sección hacen que el bucle sea más rápido.
Capítulo 16, “Control de bucles”.
desconexión
Cambiar se refiere a tomar una decisión dentro de un bucle cada vez que se ejecuta. Si la
decisión no cambia mientras se ejecuta el bucle, puede desactivar el bucle tomando la
decisión fuera del bucle. Por lo general, esto requiere dar la vuelta al bucle, poner los
bucles dentro del condicional en lugar de poner el condicional dentro del bucle. Aquí hay
un ejemplo de un bucle antes de desconectar:
Un peligro distinto de este caso es que los dos bucles deben mantenerse en paralelo. Si contar
cambios anúmero de clientes, debe recordar cambiarlo en ambos lugares, lo que es una molestia
para usted y un dolor de cabeza de mantenimiento para cualquier otra persona que tenga que
trabajar con el código.
Este ejemplo también ilustra un desafío clave en el ajuste de código: el efecto de cualquier ajuste de
código específico no es predecible. El ajuste del código produjo mejoras significativas en tres de los
cuatro idiomas, pero no en Visual Basic. Para realizar esta optimización específica en esta versión
específica de Visual Basic, se produciría un código menos mantenible sin ninguna ganancia
compensatoria en el rendimiento. La lección general es que debe medir el efecto de cada
optimización específica para estar seguro de su efecto, sin excepciones.
Interferencia
Jamming, o “fusión”, es el resultado de combinar dos bucles que operan en el mismo conjunto de
elementos. La ganancia radica en reducir la sobrecarga del bucle de dos bucles a uno. Aquí hay un
candidato para interferencia de bucle:
Cuando intercala bucles, encuentra código en dos bucles que puede combinar en uno. Por lo general, eso
significa que los contadores de bucle tienen que ser los mismos. En este ejemplo, ambos bucles se ejecutan
desde0aempleadoCuenta - 1, para que puedas atascarlos:
618 Capítulo 26: Técnicas de ajuste de código
La interferencia de bucle tiene dos peligros principales. En primer lugar, los índices de las dos partes
que se han atascado pueden cambiar y dejar de ser compatibles. En segundo lugar, es posible que
no pueda combinar los bucles fácilmente. Antes de combinar los bucles, asegúrese de que sigan en
el orden correcto con respecto al resto del código.
desenrollar
El objetivo del desenrollado de bucles es reducir la cantidad de limpieza del bucle. En el Capítulo 25, se
desenrolló completamente un bucle y se demostró que 10 líneas de código eran más rápidas que
3. En ese caso, el bucle que pasó de 3 a 10 líneas se desenrolló para que los 10 accesos a la
matriz se hicieran individualmente.
Aunque desenrollar completamente un bucle es una solución rápida y funciona bien cuando se
trata de una pequeña cantidad de elementos, no es práctico cuando tiene una gran cantidad
de elementos o cuando no sabe de antemano cuántos elementos va a utilizar. tener. He aquí
un ejemplo de un bucle general:
Para desenrollar el bucle parcialmente, maneja dos o más casos en cada paso a través del bucle en
lugar de uno. Este desenrollado perjudica la legibilidad pero no perjudica la generalidad del ciclo.
Aquí está el bucle desenrollado una vez:
26.2 Bucles 619
La técnica reemplazó a la original.un[ yo ] = yolínea con dos líneas, yise incrementa por2en
lugar de por1. El código adicional después de latiempose necesita bucle cuandocontares impar
y al ciclo le queda una iteración después de que termina el ciclo.
Cuando cinco líneas de código sencillo se expanden a nueve líneas de código complicado, el código se
vuelve más difícil de leer y mantener. Excepto por la ganancia en velocidad, su calidad es pobre. Sin
embargo, parte de cualquier disciplina de diseño es hacer las concesiones necesarias. Por lo tanto, aunque
una técnica en particular generalmente representa una mala práctica de codificación, las circunstancias
específicas pueden hacer que sea la mejor para usar.
Una ganancia del 16 al 43 por ciento es respetable, aunque nuevamente debe tener cuidado con el
rendimiento, como muestra el punto de referencia de Python. El peligro principal del desenrollado del
bucle es un error de uno en uno en el código después del bucle que recoge el último caso.
¿Qué pasa si desenrollas el bucle aún más, haciendo dos o más desenrollamientos? ¿Obtiene más
beneficios si desenrolla un bucle dos veces?
}
620 Capítulo 26: Técnicas de ajuste de código
si ( yo <= cuenta - 1 ) {
a[ cuenta - 1 ] = cuenta - 1;
}
si ( yo == contar - 2 ) {
a[ cuenta -2 ] = cuenta - 2;
}
Doble desenrollado
Idioma Tiempo exacto Tiempo Ahorro de tiempo
Los resultados indican que un mayor desenvolvimiento del bucle puede resultar en un mayor ahorro de
tiempo, pero no necesariamente, como muestra la medición de Java. La principal preocupación es cuán
bizantino se vuelve su código. Cuando observa el código anterior, es posible que no piense que parece
increíblemente complicado, pero cuando se da cuenta de que comenzó hace un par de páginas como un
bucle de cinco líneas, puede apreciar la compensación entre el rendimiento y la legibilidad.
Una clave para escribir bucles efectivos es minimizar el trabajo realizado dentro de un bucle. Si
puede evaluar una declaración o parte de una declaración fuera de un ciclo para que solo se use el
resultado dentro del ciclo, hágalo. Es una buena práctica de programación y, en algunos casos,
mejora la legibilidad.
Suponga que tiene una expresión de puntero complicada dentro de un bucle activo que se ve así:
En este caso, asignar la expresión de puntero complicado a una variable bien nombrada
mejora la legibilidad y, a menudo, mejora el rendimiento.
La variable adicional,descuento por cantidad, deja claro que latasa básicamatriz se multiplica
por un factor de descuento por cantidad para calcular la tasa neta. Eso no estaba del todo claro
a partir de la expresión original en el bucle. Poner la complicada expresión del puntero en una
variable fuera del bucle también evita que el puntero sea desreferenciado tres veces por cada
pasada por el bucle, lo que da como resultado los siguientes ahorros:
Excepto por el compilador de Java, los ahorros no son nada del otro mundo, lo que implica que
durante la codificación inicial puede usar cualquier técnica que sea más legible sin preocuparse por la
velocidad del código hasta más tarde.
Valores centinela
Cuando tiene un ciclo con una prueba compuesta, a menudo puede ahorrar tiempo simplificando la
prueba. Si el ciclo es un ciclo de búsqueda, una forma de simplificar la prueba es usar un valor
centinela, un valor que coloca justo después del final del rango de búsqueda y que garantiza que
terminará la búsqueda.
El ejemplo clásico de una prueba compuesta que se puede mejorar mediante el uso de un centinela
es el bucle de búsqueda que comprueba tanto si ha encontrado el valor que busca como si se ha
quedado sin valores. Aquí está el código:
}
más {
yo++;
}
}
si se encuentra ) {
...
En este código, cada iteración del ciclo prueba para!fundary parayo < cuenta. El propósito de!
fundarprueba es determinar cuándo se ha encontrado el elemento deseado. El propósito
622 Capítulo 26: Técnicas de ajuste de código
delyo < cuentaprueba es evitar correr más allá del final de la matriz. Dentro del ciclo, cada valor deartículo[]
se prueba individualmente, por lo que el ciclo realmente tiene tres pruebas para cada iteración.
En este tipo de bucle de búsqueda, puede combinar las tres pruebas para probar solo una vez
por iteración colocando un "centinela" al final del rango de búsqueda para detener el bucle. En
este caso, simplemente puede asignar el valor que está buscando al elemento justo más allá
del final del rango de búsqueda. (Recuerde dejar espacio para ese elemento cuando declare la
matriz). Luego verifica cada elemento, y si no encuentra el elemento hasta que encuentra el
que atascó al final, sabe que el valor que está buscando porque no está realmente allí. Aquí
está el código:
Los resultados de Visual Basic son particularmente espectaculares, pero todos los resultados son buenos. Sin
embargo, cuando cambia el tipo de matriz, los resultados también cambian. Cuandoartículoes una matriz de
números de punto flotante de precisión simple, los resultados son los siguientes:
La técnica centinela se puede aplicar a prácticamente cualquier situación en la que utilice una
búsqueda lineal, tanto en listas vinculadas como en matrices. Las únicas advertencias son que debe
elegir el valor centinela con cuidado y que debe tener cuidado con la forma en que coloca el valor
centinela en la estructura de datos.
La clave para mejorar el ciclo es que el ciclo externo se ejecuta con mucha más frecuencia que el ciclo
interno. Cada vez que se ejecuta el bucle, tiene que inicializar el índice del bucle, incrementarlo en
cada pasada por el bucle y comprobarlo después de cada pasada. El número total de ejecuciones de
bucle es 100 para el bucle exterior y 100 * 5 = 500 para el bucle interior, para un total de 600
iteraciones. Simplemente cambiando los bucles interior y exterior, puede cambiar el número total de
iteraciones a 5 para el bucle exterior y 5 * 100 = 500 para el bucle interior, para un total de 505
iteraciones. Analíticamente, esperaría ahorrar alrededor de (600 – 505) / 600 = 16 por ciento al
cambiar los bucles. Aquí está la diferencia medida en el rendimiento:
Los resultados varían significativamente, lo que muestra una vez más que debe medir el efecto
en su entorno particular antes de poder estar seguro de que su optimización ayudará.
Reducción de fuerza
Reducir la fuerza significa reemplazar una operación costosa como la multiplicación por una
operación más económica como la suma. A veces tendrás una expresión dentro de un ciclo que
depende de multiplicar el índice del ciclo por un factor. La suma suele ser más rápida que la
multiplicación, y si puede calcular el mismo número sumando la cantidad en cada iteración del
bucle en lugar de multiplicar, el código normalmente se ejecutará más rápido. Aquí hay un
ejemplo de código que usa la multiplicación:
Traducido del inglés al español - www.onlinedoctranslator.com
Este código es sencillo pero costoso. Puede reescribir el bucle para acumular
múltiplos en lugar de calcularlos cada vez. Esto reduce la fuerza de las
operaciones desde la multiplicación hasta la suma.
La multiplicación es costosa y este tipo de cambio es como un cupón del fabricante que le da
un descuento en el costo del bucle. El código original aumentóicada vez y lo multiplicó por
ingresos * comisión base * descuento—primero por 1, luego por 2, luego por 3, y así
sucesivamente. Los conjuntos de códigos optimizadoscomisión incrementaligual aingresos
* comisión base * descuento. Luego agregacomisión incrementalacomisión acumulativaen
cada paso por el bucle. En la primera pasada, se ha añadido una vez; en la segunda pasada, se
ha añadido dos veces; en el tercer pase, se ha agregado tres veces; y así. El efecto es el mismo
que multiplicarcomisión incrementalpor 1, luego por 2, luego por 3, y así sucesivamente, pero
es más barato.
La clave es que la multiplicación original tiene que depender del índice del ciclo. En este caso, el
índice de bucle era la única parte de la expresión que variaba, por lo que la expresión podía
recodificarse de forma más económica. Esto es lo que ayudó la reescritura en algunos casos de
prueba:
datos fundamentales".
Ejemplo de Visual Basic de un bucle que utiliza un índice de bucle de punto flotante que consume
mucho tiempo
Dim x como individual
CODIFICACIÓN
HORROR
Para x = 0 a 99
una( x ) = 0
próximo
Compare esto con un bucle similar de Visual Basic que usa explícitamente el tipo entero:
Ejemplo de Visual Basic de un bucle que utiliza un índice de bucle entero que ahorra tiempo
Dim i como entero
para i = 0 a 99
un( yo ) = 0
próximo
¿Cuánta diferencia hace? Estos son los resultados para este código de Visual Basic y para
código similar en C++ y PHP:
Referencia cruzadaPara obtener detalles La sabiduría convencional sostiene que las dimensiones múltiples en matrices son costosas. Si puede
sobre matrices, consulte la Sección 12.8,
estructurar sus datos para que estén en una matriz unidimensional en lugar de una matriz
“Matrices”.
bidimensional o tridimensional, es posible que pueda ahorrar algo de tiempo. Suponga que tiene un
código de inicialización como este:
Cuando este código se ejecuta con 50 filas y 20 columnas, toma el doble de tiempo con mi
compilador Java actual que cuando se reestructura la matriz para que sea unidimensional. Así
es como se vería el código revisado:
Y aquí hay un resumen de los resultados, con la adición de resultados comparables en varios
otros idiomas:
Los resultados de esta optimización son excelentes en Visual Basic y Java, buenos en PHP y Python,
pero mediocres en C++ y C#. Por supuesto, el tiempo no optimizado del compilador de C# fue
fácilmente el mejor del grupo, por lo que no puede ser demasiado duro con él.
Esta amplia gama de resultados muestra nuevamente el peligro de seguir ciegamente cualquier consejo de ajuste de
código. Nunca puede estar seguro hasta que pruebe el consejo en sus circunstancias específicas.
Un ejemplo del uso de un índice suplementario se puede encontrar en las diferentes estrategias de
almacenamiento de cadenas. En C, las cadenas terminan con un byte que se establece en 0. En el formato de
cadena de Visual Basic, un byte de longitud oculto al principio de cada cadena indica la longitud de la
cadena. Para determinar la longitud de una cadena en C, un programa debe comenzar al principio de la
cadena y contar cada byte hasta que encuentre el byte que está configurado en 0. Para determinar la
longitud de una cadena de Visual Basic, el programa simplemente mira el byte de longitud. El byte de
longitud de Visual Basic es un ejemplo de cómo aumentar un tipo de datos con un índice para hacer que
ciertas operaciones, como calcular la longitud de una cadena, sean más rápidas.
Puede aplicar la idea de indexar por longitud a cualquier tipo de datos de longitud variable. A
menudo es más eficiente hacer un seguimiento de la longitud de la estructura en lugar de calcular la
longitud cada vez que la necesita.
628 Capítulo 26: Técnicas de ajuste de código
A veces es más eficiente manipular un índice para un tipo de datos que manipular el tipo de datos en
sí. Si los elementos en el tipo de datos son grandes o difíciles de mover (quizás en el disco), ordenar y
buscar referencias de índice es más rápido que trabajar con los datos directamente. Si cada elemento
de datos es grande, puede crear una estructura auxiliar que consta de valores clave y punteros a la
información detallada. Si la diferencia de tamaño entre el elemento de la estructura de datos y el
elemento de la estructura auxiliar es lo suficientemente grande, a veces puede almacenar el
elemento clave en la memoria incluso cuando el elemento de datos tiene que almacenarse
externamente. Toda la búsqueda y clasificación se realiza en la memoria, y solo tiene que acceder al
disco una vez, cuando sabe la ubicación exacta del elemento que desea.
El almacenamiento en caché significa guardar algunos valores de tal manera que pueda recuperar los
valores más utilizados más fácilmente que los valores menos utilizados. Si un programa lee
aleatoriamente registros de un disco, por ejemplo, una rutina podría usar un caché para guardar los
registros leídos con mayor frecuencia. Cuando la rutina recibe una solicitud de un registro,
comprueba la memoria caché para ver si tiene el registro. Si lo hace, el registro se devuelve
directamente desde la memoria en lugar de desde el disco.
Además de almacenar registros en caché en el disco, puede aplicar el almacenamiento en caché en otras áreas. En un
programa de revisión de fuentes de Microsoft Windows, el cuello de botella de rendimiento estaba en recuperar el ancho de
cada carácter a medida que se mostraba. El almacenamiento en caché del ancho de carácter utilizado más recientemente
También puede almacenar en caché los resultados de los cálculos que consumen mucho
tiempo, especialmente si los parámetros del cálculo son simples. Suponga, por ejemplo, que
necesita calcular la longitud de la hipotenusa de un triángulo rectángulo, dadas las longitudes
de los otros dos lados. La implementación directa de la rutina se vería así:
Si sabe que los mismos valores tienden a solicitarse repetidamente, puede almacenar los valores en caché de esta
manera:
26.3 Transformaciones de datos 629
return cachedHypotenuse;
}
La segunda versión de la rutina es más complicada que la primera y ocupa más espacio, por lo que la velocidad tiene
que ser un bien escaso para justificarla. Muchos esquemas de almacenamiento en caché almacenan en caché más
de un elemento, por lo que tienen una sobrecarga aún mayor. Aquí está la diferencia de velocidad entre estas dos
versiones:
El éxito de la memoria caché depende de los costos relativos de acceder a un elemento almacenado en la memoria
caché, crear un elemento no almacenado en la memoria caché y guardar un nuevo elemento en la memoria caché.
El éxito también depende de la frecuencia con la que se solicite la información almacenada en caché. En algunos
casos, el éxito también puede depender del almacenamiento en caché realizado por el hardware. Generalmente,
cuanto más cuesta generar un nuevo elemento y más veces se solicita la misma información, más valioso es un
caché. Cuanto más barato sea acceder a un elemento en caché y guardar nuevos elementos en el caché, más valioso
es un caché. Al igual que con otras técnicas de optimización, el almacenamiento en caché agrega complejidad y
26.4 Expresiones
Referencia cruzadaPara obtener Gran parte del trabajo en un programa se realiza dentro de expresiones matemáticas o lógicas. Las
más información sobre las
expresiones complicadas tienden a ser costosas, por lo que esta sección analiza formas de
expresiones, consulte la Sección 19.1,
“Expresiones booleanas”.
abaratarlas.
Puede usar identidades algebraicas para reemplazar operaciones costosas por otras más baratas. Por
ejemplo, las siguientes expresiones son lógicamente equivalentes:
no a y no b
no (a o b)
Aunque los ahorros de evitar una solanooperación son probablemente intrascendentes, el principio
general es poderoso. Jon Bentley describe un programa que probó siraíz cuadrada (x) < raíz cuadrada
(y)(mil novecientos ochenta y dos). Ya quesqrt(x)es menos quesqrt(y)sólo cuandoXes menos quey,
puede reemplazar la primera prueba conx < y. Dado el costo de lasqrt()rutina, usted esperaría que los
ahorros fueran dramáticos, y lo son. Aquí están los resultados:
- Reemplazarlargo largoenteros conlargos oEn ts (pero observe los problemas de rendimiento asociados
- Reemplace los números de punto flotante con números de punto fijo o enteros.
- Reemplace los puntos flotantes de precisión doble con números de precisión simple.
- Reemplace la multiplicación por dos y la división por dos de enteros con operaciones de desplazamiento.
26.4 Expresiones 631
Suponga que tiene que evaluar un polinomio. Si estás oxidado con los polinomios, son
las cosas que se parecen a AX2+ BX+C. Las letrasA,B, yCson coeficientes, yXes una
variable Código general para evaluar unnortepolinomio de orden th se ve así:
Esto produce una ventaja notable si está trabajando con polinomios de segundo orden, es decir,
polinomios en los que el término de mayor potencia está elevado al cuadrado, o polinomios de orden
superior:
Ahorros
Primero Segundo sobre primero
Este es un buen ejemplo de que la teoría no se sostiene muy bien en la práctica. El código con
fuerza reducida parece que debería ser más rápido, pero no lo es. Una posibilidad es que
decrementando un ciclo por1en lugar de incrementarlo por1en Visual Basic perjudica el
rendimiento, pero tendría que medir esa hipótesis para estar seguro.
Si está utilizando una constante con nombre o un número mágico en una llamada de rutina y es el
único argumento, esa es una pista de que podría precalcular el número, ponerlo en una constante y
evitar la llamada de rutina. El mismo principio se aplica a las multiplicaciones, divisiones, sumas y
otras operaciones.
Una vez necesité calcular el logaritmo en base dos de un número entero, truncado al número
entero más cercano. El sistema no tenía una rutina log-base-two, así que escribí la mía. El
enfoque rápido y fácil fue usar este hecho:
Referencia cruzadaPara obtener detalles Ejemplo en C++ de una rutina Log-Base-Two basada en rutinas del sistema
sobre cómo vincular variables a sus
int sin firmar Log2 (int sin firmar x) {
valores, consulte la Sección 10.6, “Tiempo
return (int sin signo) ( log( x ) / log( 2 ) );
de vinculación”.
}
Esta rutina era muy lenta, y debido a que el valor deregistro (2)nunca cambié, reemplacé
registro (2)con su valor calculado,0.69314718, como esto:
Ejemplo en C++ de una rutina Log-Base-Two basada en una rutina del sistema y una constante
constante doble LOG2 = 0,69314718; . . .
Ya queIniciar sesión()tiende a ser una rutina costosa (mucho más costosa que las conversiones de
tipo o la división) usted esperaría que cortar las llamadas alIniciar sesión()función a la mitad reduciría
el tiempo requerido para la rutina a la mitad. Aquí están los resultados medidos:
En este caso, la conjetura informada sobre la importancia relativa de la división y las conversiones de
tipos y la estimación del 50 por ciento estaban bastante cerca. Teniendo en cuenta la previsibilidad de
los resultados descritos en este capítulo, la precisión de mi predicción en este caso solo demuestra
que incluso una ardilla ciega encuentra una nuez de vez en cuando.
Las rutinas del sistema son costosas y brindan una precisión que a menudo se desperdicia. Las
rutinas matemáticas típicas del sistema, por ejemplo, están diseñadas para poner a un astronauta en
la luna dentro de ±2 pies del objetivo. Si no necesita ese grado de precisión, tampoco necesita
dedicar tiempo a calcularlo.
En el ejemplo anterior, elRegistro2()la rutina devolvió un valor entero pero usó un punto flotanteIniciar
sesión()rutina para calcularlo. Eso fue excesivo para un resultado entero, así que después de mi primer
intento, escribí una serie de pruebas de enteros que eran perfectamente precisas para calcular un log2
entero. Aquí está el código:
}
634 Capítulo 26: Técnicas de ajuste de código
Esta rutina usa operaciones con números enteros, nunca convierte a coma flotante y elimina las
puertas de ambas versiones de coma flotante:
La mayoría de las llamadas funciones "trascendentales" están diseñadas para el peor de los
casos, es decir, se convierten internamente en punto flotante de doble precisión incluso si les
da un argumento entero. Si encuentra uno en una sección estrecha de código y no necesita
tanta precisión, preste atención inmediata.
Otra opción es aprovechar el hecho de que una operación de desplazamiento a la derecha es lo mismo que
dividir por dos. El número de veces que puede dividir un número por dos y aún así tener un valor distinto de
cero es el mismo que el logaritmo2de ese numero Así es como se ve el código basado en esa observación:
HORROR
int sin signo i = 0;
mientras ( ( x = ( x >> 1 ) ) != 0 ) {
yo++;
}
devolver yo;
}
Para los programadores que no son de C++, este código es particularmente difícil de leer. La expresión
complicada en eltiempocondition es un ejemplo de una práctica de codificación que debe evitar a menos
que tenga una buena razón para usarla.
Esta rutina tarda aproximadamente un 350 por ciento más que la versión más larga anterior, y se
ejecuta en 2,4 segundos en lugar de 0,66 segundos. Pero es más rápido que el primer enfoque y se
adapta fácilmente a entornos de 32 bits, 64 bits y otros.
Un compilador menos avanzado o un intérprete genera código para una conversión en tiempo de ejecución,
por lo que es posible que se quede atascado. Aquí hay algunas diferencias en el rendimiento entre las
inicializaciones de una variable de punto flotanteXy una variable enteraien dos casos. En el primer caso, las
inicializaciones se ven así:
X = 5
i = 3.14
X = 3.14
i = 5
y no requieren conversiones de tipo. Aquí están los resultados, y la variación entre los
compiladores es una vez más notable:
Una decisión común de diseño de bajo nivel es la opción de calcular los resultados sobre la
marcha o calcularlos una vez, guardarlos y buscarlos según sea necesario. Si los resultados se
usan muchas veces, a menudo es más económico calcularlos una vez y buscarlos el resto del
tiempo.
Esta elección se manifiesta de varias maneras. En el nivel más simple, puede calcular parte de una
expresión fuera de un bucle en lugar de dentro. Un ejemplo de esto apareció anteriormente en el
capítulo. En un nivel más complicado, puede calcular una tabla de búsqueda una vez que comienza la
ejecución del programa, usándola cada vez que lo haga, o puede almacenar los resultados en un
archivo de datos o incrustarlos en un programa.
636 Capítulo 26: Técnicas de ajuste de código
Referencia cruzadaPara obtener más En un videojuego de guerra espacial, por ejemplo, los programadores calcularon inicialmente los
información sobre el uso de datos en
coeficientes de gravedad para diferentes distancias del sol. El cálculo de los coeficientes de gravedad
tablas en lugar de lógica compleja,
Suponga que tiene una rutina que calcula los montos de los pagos de préstamos para
automóviles. El código para tal rutina se vería así:
En t meses,
doble tasa de interés
){
devolver monto del préstamo /
(
( 1.0 - Math.pow( ( 1.0 + ( tasa de interés / 12.0 ) ), -meses ) ) / ( tasa de interés / 12.0 )
);
}
La fórmula para calcular los pagos de un préstamo es complicada y bastante costosa. Poner la
información en una tabla en lugar de calcularla cada vez probablemente sería más barato.
¿Qué tan grande sería la mesa? La variable más amplia esmonto del préstamo. La variable tasa
de interéspodría oscilar entre el 5 y el 20 por ciento por cuarto de punto, pero eso es solo 61
tasas distintas.mesespuede variar de 12 a 72, pero eso es solo 61 períodos distintos.monto del
préstamoposiblemente podría oscilar entre $ 1000 y $ 100,000, que son más entradas de las
que generalmente desea manejar en una tabla de búsqueda.
La mayor parte del cálculo no depende demonto del préstamo, sin embargo, para que pueda poner
la parte realmente fea del cálculo (el denominador de la expresión más grande) en una tabla que
está indexada portasa de interésymeses. vuelves a calcular elmonto del préstamoparte cada vez:
En t meses,
doble tasa de interés
){
la nueva variableíndice de interés En t índice de interés =
se crea para proporcionar un Math.round( (tasa de interés - TASA_MAS BAJA) * GRANULARIDAD * 100.00); return
subíndice en el cantidadprestamo/divisorprestamo[indiceintereses][meses];
préstamoDivisorformación. }
26.4 Expresiones 637
En este código, el cálculo peludo ha sido reemplazado por el cálculo de un índice de matriz y
un único acceso a la matriz. Estos son los resultados de ese cambio:
No es necesario que cree una tabla para aprovechar las ganancias de rendimiento que puede
lograr precalculando una expresión. Un código similar al código de los ejemplos anteriores plantea
la posibilidad de un tipo diferente de precálculo. Supongamos que tiene un código que calcula los
pagos de muchos montos de préstamos, como se muestra aquí:
);
El siguiente código ...
haría algo conpago }
aquí; para el punto de este }
ejemplo, no importa qué.
}
}
638 Capítulo 26: Técnicas de ajuste de código
Esto es similar a las técnicas sugeridas anteriormente de colocar las referencias de matrices y las
desreferencias de punteros fuera de un bucle. Los resultados para Java en este caso son comparables a los
resultados del uso de la tabla precalculada en la primera optimización:
Python mejoró aquí, pero no en el primer intento de optimización. Muchas veces, cuando una
optimización no produce los resultados deseados, una optimización aparentemente similar
funcionará como se esperaba.
- Calcular los resultados antes de que se ejecute el programa y conectarlos a constantes que se
asignan en tiempo de compilación
- Calcular los resultados antes de que se ejecute el programa y codificarlos en variables utilizadas
en tiempo de ejecución
- Calcular los resultados antes de que se ejecute el programa y colocarlos en un archivo que se
carga en tiempo de ejecución
- Calcular los resultados una vez, al inicio del programa, y luego hacer referencia a ellos cada vez
que se necesitan
- Calcular tanto como sea posible antes de que comience un ciclo, minimizando el trabajo realizado
dentro del ciclo
- Calcular los resultados la primera vez que se necesitan y almacenarlos para que pueda
recuperarlos cuando se vuelvan a necesitar
);
26.5 Rutinas 639
En este ejemplo, puede asignartasa de interés/12.0a una variable a la que luego se hace
referencia dos veces en lugar de calcular la expresión dos veces. Si ha elegido bien el nombre
de la variable, esta optimización puede mejorar la legibilidad del código al mismo tiempo que
mejora el rendimiento. Este es el código revisado:
);
parece que elMatemáticas.pow()La rutina es tan costosa que eclipsa los ahorros de la
eliminación de subexpresiones. O posiblemente el compilador ya esté eliminando la
subexpresión. Si la subexpresión fuera una parte mayor del costo de la expresión completa o si
el optimizador del compilador fuera menos efectivo, la optimización podría tener un mayor
impacto.
26.5 Rutinas
Referencia cruzadaPara obtener detalles Una de las herramientas más poderosas en el ajuste de código es una buena descomposición de rutinas. Las
sobre cómo trabajar con rutinas, consulte
rutinas pequeñas y bien definidas ahorran espacio porque reemplazan la realización de trabajos por
el Capítulo 7, “Rutinas de alta calidad”.
separado en varios lugares. Hacen que un programa sea fácil de optimizar porque puede refactorizar el
código en una rutina y, por lo tanto, mejorar cada rutina que lo llama. Las rutinas pequeñas son
relativamente fáciles de reescribir en un lenguaje de bajo nivel. Las rutinas largas y tortuosas son bastante
difíciles de entender por sí solas; en un lenguaje de bajo nivel como ensamblador, son imposibles.
Las computadoras modernas cobran un peaje mucho menor por llamar a una rutina. Estos son los resultados de
En algunos casos, es posible que pueda ahorrar unos pocos nanosegundos colocando el código de una rutina en el
programa directamente donde se necesita a través de una función de lenguaje como C++.en línea palabra clave. Si
está trabajando en un idioma que no admiteen líneadirectamente, pero eso tiene un preprocesador de macros,
puede usar una macro para ingresar el código, activándolo y desconectándolo según sea necesario. Pero las
máquinas modernas, y "moderno" significa cualquier máquina en la que probablemente trabaje, prácticamente no
imponen ninguna penalización por llamar a una rutina. Como muestra el ejemplo, es tan probable que degrade el
rendimiento al mantener el código en línea como al optimizarlo.
Referencia cruzadaPara obtener detalles 3.Si se necesitan mejoras de rendimiento después de eso, perfile la aplicación para identificar los puntos críticos.
sobre el fenómeno de un pequeño
Dado que aproximadamente el 5 por ciento de un programa generalmente representa aproximadamente
porcentaje de un programa que
4.Recodifique algunas piezas pequeñas en un lenguaje de bajo nivel para mejorar el rendimiento general.
El hecho de que siga este camino trillado depende de qué tan cómodo se sienta con los lenguajes de
bajo nivel, qué tan bien se adapta el problema a los lenguajes de bajo nivel y de su nivel de
desesperación. Obtuve mi primer contacto con esta técnica en el programa Estándar de cifrado de
datos que mencioné en el capítulo anterior. Probé todas las optimizaciones de las que había oído
hablar y el programa seguía siendo el doble de lento que el objetivo de velocidad. Recodificar parte
del programa en ensamblador era la única opción que quedaba. Como ensamblador novato, todo lo
que podía hacer era hacer una traducción directa de un lenguaje de alto nivel a ensamblador, pero
obtuve una mejora del 50 por ciento incluso en ese nivel rudimentario.
Suponga que tiene una rutina que convierte datos binarios en caracteres ASCII en mayúsculas. El
siguiente ejemplo muestra el código Delphi para hacerlo:
26.6 Grabación en un lenguaje de bajo nivel 641
);
variable
índice: entero;
índice de destino: entero;
empezar
índiceobjetivo := 1;
para el índice: = 1 a byteCount, comience
target[ targetIndex ] := ( (source[ index ] and $F0) shr 4 ) + $41; target[ targetIndex+1 ] :=
(source[ index ] and $0f) + $41; índiceobjetivo := índiceobjetivo + 2;
final;
final;
Aunque es difícil ver dónde está la grasa en este código, contiene mucha manipulación de bits, que no es
exactamente el fuerte de Delphi. Sin embargo, la manipulación de bits es el fuerte del ensamblador, por lo que este
código es un buen candidato para la recodificación. Aquí está el código del ensamblador:
variable fuente;
variable objetivo;
número de bytes : Entero
);
etiqueta
EXPANDIR;
Asm
MOVIMIENTO ECX, número de bytes // carga el numero de bytes a expandir //
MOVIMIENTO ESI,fuente fuente compensar
EXPANDIR:
MOVIMIENTO EBX... EAX // desplazamiento de matriz
La reescritura en ensamblador en este caso fue rentable, lo que resultó en un ahorro de tiempo del 41 por
ciento. Es lógico suponer que el código en un lenguaje que es más adecuado para la manipulación de bits
(C++, por ejemplo) tendría menos que ganar que el código Delphi. Aquí están los resultados:
La imagen del “antes” en estas medidas refleja las fortalezas de los dos lenguajes en la
manipulación de bits. La imagen del "después" parece prácticamente idéntica y parece que el
código ensamblador ha minimizado las diferencias de rendimiento inicial entre Delphi y C++.
La rutina del ensamblador muestra que reescribir en ensamblador no tiene que producir una
rutina enorme y fea. Estas rutinas suelen ser bastante modestas, como lo es esta. A veces, el código
ensamblador es casi tan compacto como su equivalente en lenguaje de alto nivel.
Una estrategia relativamente fácil y eficaz para la grabación en ensamblador es comenzar con un
compilador que genere listas de ensamblador como subproducto de la compilación. Extraiga el
código ensamblador de la rutina que necesita ajustar y guárdelo en un archivo fuente separado.
Usando el código ensamblador del compilador como base, optimice manualmente el código,
verificando la corrección y midiendo las mejoras en cada paso. Algunos compiladores intercalan las
declaraciones de lenguaje de alto nivel como comentarios en el código ensamblador. Si el suyo lo
hace, puede guardarlos en el código ensamblador como documentación.
- Atasco de bucles.
- Desenrollar bucles.
En otros aspectos, los problemas de rendimiento apenas han cambiado. Es posible que las personas que escriben
aplicaciones de escritorio no necesiten esta información, pero las personas que escriben software para sistemas
integrados, sistemas en tiempo real y otros sistemas con restricciones estrictas de velocidad o espacio aún pueden
beneficiarse de ella.
La necesidad de medir el impacto de todos y cada uno de los intentos de ajuste de código ha sido una
constante desde que Donald Knuth publicó su estudio de los programas de Fortran en 1971. De acuerdo
con las mediciones de este capítulo, el efecto de cualquier optimización específica es en realidadmenos
predeciblesde lo que era hace 10 años. El efecto de cada ajuste de código se ve afectado por el lenguaje de
programación, el compilador, la versión del compilador, las bibliotecas de código, las versiones de la
biblioteca y la configuración del compilador, entre otras cosas.
He encontrado que insistir enmejora mediblees una buena manera de resistir la tentación de optimizar
prematuramente y una buena manera de imponer un sesgo hacia un código claro y directo. Si una optimización es lo
suficientemente importante como para sacar el generador de perfiles y medir el efecto de la optimización, entonces
probablemente sea lo suficientemente importante como para permitirlo, siempre que funcione. Pero si una
suficientemente importante como para degradar la legibilidad, la mantenibilidad y otras características del código. El
impacto del ajuste de código no medido en el rendimiento es, en el mejor de los casos, especulativo, mientras que el
Recursos adicionales
cc2e.com/2679 Mi referencia favorita sobre el ajuste de código esEscribir programas eficientes(Bentley, Englewood
Cliffs, Nueva Jersey: Prentice Hall, 1982). El libro está agotado, pero vale la pena leerlo si puedes
encontrarlo. Es un tratamiento experto de ajuste de código, en términos generales. Bentley describe
técnicas que intercambian tiempo por espacio y espacio por tiempo. Proporciona varios ejemplos de
rediseño de tipos de datos para reducir tanto el espacio como el tiempo. Su enfoque es un poco más
anecdótico que el adoptado aquí, y sus anécdotas son interesantes. Lleva algunas rutinas a través de
varios pasos de optimización para que pueda ver los efectos del primer, segundo y tercer intento en
un solo problema. Bentley recorre los contenidos principales del libro en 135 páginas. El libro tiene
una relación señal/ruido inusualmente alta; es una de las gemas raras que todo programador en
ejercicio debería tener.
cc2e.com/2686 También puede encontrar una gama completa de libros de optimización específicos de la tecnología. Varios se
Booth, Rick.Inner Loops: un libro de consulta para el desarrollo rápido de software de 32 bits. Boston,
MA: Addison-Wesley, 1997.
Killelea, Patrick.Ajuste del rendimiento web, 2ª ed. Sebastopol, CA: O'Reilly & Associates,
2002.
Larman, Craig y Rhett Guthrie.Guía de lenguaje y rendimiento de Java 2. Englewood Cliffs, Nueva
Jersey: Prentice Hall, 2000.
Shirazi, Jack.Ajuste del rendimiento de Java. Sebastopol, CA: O'Reilly & Associates, 2000.
Puntos clave
- Los resultados de las optimizaciones varían ampliamente con diferentes
lenguajes, compiladores y entornos. Sin medir cada optimización específica, no
tendrá idea de si ayudará o dañará su programa.
- La primera optimización a menudo no es la mejor. Incluso después de encontrar uno bueno, sigue
buscando uno que sea mejor.
En esta parte:
cc2e.com/2761 Contenido
- 27.3 Efecto del tamaño del proyecto en los errores: página 651
- 27.5 Efecto del tamaño del proyecto en las actividades de desarrollo: página 654
Temas relacionados
Ampliar el desarrollo de software no es una simple cuestión de tomar un proyecto pequeño y hacer
que cada parte sea más grande. Suponga que escribió el paquete de software Gigatron de 25 000
líneas en 20 meses-personal y encontró 500 errores en las pruebas de campo. Suponga que Gigatron
1.0 tiene éxito, al igual que Gigatron 2.0, y comienza a trabajar en Gigatron Deluxe, una versión muy
mejorada del programa que se espera que tenga 250.000 líneas de código.
Aunque es 10 veces más grande que el Gigatron original, el desarrollo del Gigatron
Deluxe no requerirá 10 veces más esfuerzo; tomará 30 veces el esfuerzo. Además,
30 veces el esfuerzo total no implica 30 veces más construcción. Probablemente
implica 25 veces más construcción y 40 veces más pruebas de arquitectura y
sistema. Tampoco tendrás 10 veces más errores; tendrás 15 veces más, o más.
649
650 Capítulo 27: Cómo el tamaño del programa afecta la construcción
3 6
45
10
10
Como puede ver, un proyecto de dos personas tiene solo una vía de comunicación. Un proyecto de
cinco personas tiene 10 caminos. Un proyecto de diez personas tiene 45 caminos, asumiendo que
cada persona habla con todas las demás. El 10 por ciento de los proyectos que tienen 50 o más
PUNTO CLAVE
programadores tienen al menos 1200 caminos potenciales. Cuantas más vías de comunicación tenga,
más tiempo dedicará a comunicarse y se crearán más oportunidades para cometer errores de
comunicación. Los proyectos de mayor envergadura exigen técnicas organizativas que agilicen la
comunicación o la limiten de forma sensata.
¿El tamaño del proyecto en el que está trabajando es típico? La amplia gama de tamaños de proyectos
significa que no puede considerar que un solo tamaño sea típico. Una forma de pensar en el tamaño del
proyecto es pensar en el tamaño de un equipo de proyecto. Aquí hay una estimación aproximada de los
porcentajes de todos los proyectos realizados por equipos de varios tamaños:
Un aspecto de los datos del tamaño del proyecto que puede no ser evidente de inmediato es la
diferencia entre los porcentajes de proyectos de varios tamaños y la cantidad de programadores que
trabajan en proyectos de cada tamaño. Debido a que los proyectos más grandes usan más
programadores en cada proyecto que los pequeños, emplean un gran porcentaje de todos los
programadores. Aquí hay una estimación aproximada del porcentaje de todos los programadores
que trabajan en proyectos de varios tamaños:
Referencia cruzadaPara obtener más Tanto la cantidad como el tipo de errores se ven afectados por el tamaño del proyecto. Es posible que no piense que el tipo de
detalles sobre los errores, consulte la
error se vería afectado, pero a medida que aumenta el tamaño del proyecto, un mayor porcentaje de errores generalmente se
Sección 22.4, “Errores típicos”.
puede atribuir a errores en los requisitos y el diseño, como se muestra en la Figura 27-2.
652 Capítulo 27: Cómo el tamaño del programa afecta la construcción
100%
Construcción
En algunos proyectos,
Errores de
este porcentaje de
Cada Actividad
los errores también pueden
ser de construcción.
Diseño
Requisitos
0%
2K 8K 32K 128K 512K
Tamaño del proyecto en líneas de código
Figura 27-2A medida que aumenta el tamaño del proyecto, los errores generalmente provienen más de los
requisitos y el diseño. A veces todavía provienen principalmente de la construcción (Boehm 1981, Grady 1987,
Jones 1998).
3 En proyectos pequeños, los errores de construcción representan alrededor del 75 por ciento de todos los errores
2
1
encontrados. La metodología tiene menos influencia en la calidad del código, y la mayor influencia en la calidad del
programa es a menudo la habilidad del individuo que escribe el programa (Jones 1998).
DATOS DUROS
En proyectos más grandes, los errores de construcción pueden disminuir hasta cerca del 50 por ciento del
total de errores; los requisitos y los errores de arquitectura compensan la diferencia. Presumiblemente, esto
está relacionado con el hecho de que se requiere más desarrollo de requisitos y diseño arquitectónico en
proyectos grandes, por lo que la oportunidad de que surjan errores de esas actividades es
proporcionalmente mayor. Sin embargo, en algunos proyectos muy grandes, la proporción de errores de
construcción sigue siendo alta; a veces, incluso con 500.000 líneas de código, hasta el 75 por ciento de los
errores pueden atribuirse a la construcción (Grady 1987).
Así como los tipos de defectos cambian con el tamaño, también cambia el número de defectos.
Naturalmente, esperaría que un proyecto que es dos veces más grande que otro tenga el doble de errores.
Pero la densidad de defectos, el número de defectos por cada 1000 líneas de código, aumenta. Es probable
PUNTO CLAVE
que el producto que es el doble de grande tenga más del doble de errores. La tabla 27-1 muestra el rango de
densidades de defectos que puede esperar en proyectos de varios tamaños.
Más pequeño que 2K 0–25 errores por mil líneas de código (KLOC) 0–
2K–16K 40 errores por KLOC
16K–64K 0,5–50 errores por KLOC
64K–512K 2–70 errores por KLOC 4–
512K o más 100 errores por KLOC
Fuentes: "Calidad del programa y productividad del programador" (Jones 1977),Estimación de costos de software(Jones
1998).
27.4 Efecto del tamaño del proyecto en la productividad 653
Referencia cruzadaLos datos de Los datos de esta tabla se derivaron de proyectos específicos y es posible que los números se parezcan poco
esta tabla representan el
a los de los proyectos en los que ha trabajado. Sin embargo, como una instantánea de la industria, los datos
rendimiento promedio. Un
puñado de organizaciones han
son esclarecedores. Indica que la cantidad de errores aumenta drásticamente a medida que aumenta el
informado mejores tasas de error tamaño del proyecto, con proyectos muy grandes que tienen hasta cuatro veces más errores por cada mil
que los mínimos que se muestran
líneas de código que los proyectos pequeños. Un proyecto grande necesitará trabajar más duro que un
aquí. Para ver ejemplos, consulte
proyecto pequeño para lograr la misma tasa de error.
"¿Cuántos errores debe esperar
encontrar?" en la Sección 22.4.
La productividad tiene mucho en común con la calidad del software cuando se trata del tamaño del
proyecto. En tamaños pequeños (2000 líneas de código o menos), la mayor influencia en la productividad es
la habilidad del programador individual (Jones 1998). A medida que aumenta el tamaño del proyecto, el
tamaño del equipo y la organización se vuelven más influyentes en la productividad.
3 ¿Qué tan grande debe ser un proyecto antes de que el tamaño del equipo comience a afectar la
2
1
productividad? En "Creación de prototipos frente a especificación: un experimento multiproyecto", Boehm,
DATOS DUROS
Gray y Seewaldt informaron que los equipos más pequeños completaron sus proyectos con un 39 % más de
productividad que los equipos más grandes. ¿El tamaño de los equipos? Dos personas para los proyectos
pequeños y tres para los grandes (1984). La tabla 27-2 brinda información detallada sobre la relación
general entre el tamaño del proyecto y la productividad.
Tamaño del proyecto (en Líneas de Código por Año-Personal (Cocomo II Nominal entre
líneas de código) Paréntesis)
La productividad está determinada sustancialmente por el tipo de software en el que está trabajando, la
calidad del personal, el lenguaje de programación, la metodología, la complejidad del producto, el entorno
de programación, el soporte de herramientas, cómo se cuentan las "líneas de código", cómo se tiene en
cuenta el esfuerzo de soporte que no es programador en el " líneas de código por año de personal”, y
muchos otros factores, por lo que las cifras específicas de la tabla 27-2 varían drásticamente.
Tenga en cuenta, sin embargo, que la tendencia general que muestran los números es significativa. La productividad
en proyectos pequeños puede ser 2 o 3 veces mayor que la productividad en proyectos grandes, y la productividad
puede variar en un factor de 5 a 10 desde los proyectos más pequeños hasta los más grandes.
Traducido del inglés al español - www.onlinedoctranslator.com
actividades que necesita un proyecto cambian drásticamente. La Figura 27-3 muestra las proporciones de
100%
Pruebas del sistema
Integración
Pruebas de desarrollador
Porcentaje de
Tiempo de desarrollo
Codificación y depuración Construcción
Diseño detallado
Arquitectura
0%
2K 8K 32K 128K 512K
Tamaño del proyecto en líneas de código
Figura 27-3Las actividades de construcción dominan los proyectos pequeños. Los proyectos más grandes
requieren más arquitectura, trabajo de integración y pruebas del sistema para tener éxito. El trabajo de
requisitos no se muestra en este diagrama porque el esfuerzo de requisitos no es una función tan directa
del tamaño del programa como lo son otras actividades (Albrecht 1979; Glass 1982; Boehm, Gray y Seewaldt
1984; Boddie 1987; Card 1987; McGarry, Waligora y McDermott 1989; Brooks 1995; Jones 1998; Jones 2000;
Boehm et al. 2000).
En un proyecto pequeño, la construcción es la actividad más destacada con diferencia, y ocupa hasta el 65
por ciento del tiempo total de desarrollo. En un proyecto de tamaño mediano, la construcción sigue siendo
la actividad dominante, pero su participación en el esfuerzo total se reduce a alrededor del 50 por ciento. En
PUNTO CLAVE
proyectos muy grandes, la arquitectura, la integración y las pruebas del sistema toman más tiempo y la
construcción se vuelve menos dominante. En resumen, a medida que aumenta el tamaño del proyecto, la
construcción se convierte en una parte menor del esfuerzo total. Parece que el gráfico podría extenderse
hacia la derecha y hacer que la construcción desapareciera por completo, por lo que, con el fin de proteger
mi trabajo, lo he cortado en 512K.
La construcción se vuelve menos predominante porque a medida que aumenta el tamaño del proyecto, las
proporcionalmente, pero muchas otras actividades aumentan más rápido. La figura 27-4 proporciona una ilustración.
27.5 Efecto del tamaño del proyecto en las actividades de desarrollo 655
Otro
actividades
Esfuerzo Construcción
Tamaño
Figura 27-4La cantidad de trabajo de construcción de software es una función casi lineal del tamaño del proyecto. Otros tipos
de trabajo aumentan de forma no lineal a medida que aumenta el tamaño del proyecto.
Los proyectos que tienen un tamaño similar realizarán actividades similares, pero a medida que los tamaños
difieren, los tipos de actividades también lo harán. Como se describe en la introducción de este capítulo,
cuando el Gigatron Deluxe salga al mercado con un tamaño 10 veces mayor que el Gigatron original,
necesitará 25 veces más esfuerzo de construcción, 25 a 50 veces el esfuerzo de planificación, 30 veces el
esfuerzo de integración y 40 veces más la arquitectura y las pruebas del sistema.
3 Las proporciones de las actividades varían porque diferentes actividades se vuelven críticas en diferentes
2
1
tamaños de proyectos. Barry Boehm y Richard Turner descubrieron que gastar alrededor del cinco por
DATOS DUROS
ciento de los costos totales del proyecto en arquitectura y requisitos produjo el costo más bajo para
proyectos en el rango de 10,000 líneas de código. Pero para proyectos en el rango de 100.000 líneas de
código, gastar entre el 15 y el 20 por ciento del esfuerzo del proyecto en arquitectura y requisitos produjo
los mejores resultados (Boehm y Turner 2004).
Aquí hay una lista de actividades que crecen a un ritmo más que lineal a medida que aumenta el tamaño del proyecto:
- Comunicación
- Planificación
- administración
- Desarrollo de requisitos
- Diseño funcional del sistema
- Arquitectura
- Integración
- Eliminación de defectos
- Producción de documentos
Independientemente del tamaño de un proyecto, algunas técnicas siempre son valiosas: prácticas de
codificación disciplinadas, inspecciones de código y diseño por parte de otros desarrolladores, buen soporte
de herramientas y uso de lenguajes de alto nivel. Estas técnicas son valiosas en proyectos pequeños e
invaluables en proyectos grandes.
656 Capítulo 27: Cómo el tamaño del programa afecta la construcción
El tipo de software más simple es un único "programa" que es utilizado por sí mismo por la persona que lo
desarrolló o, de manera informal, por algunos otros.
Se requiere otro nivel de sofisticación para desarrollar un grupo de programas que funcionen
juntos. Tal grupo se llama un "sistema" de software. El desarrollo de un sistema es más
complicado que el desarrollo de un programa simple debido a la complejidad de desarrollar
interfaces entre las piezas y el cuidado necesario para integrar las piezas. En general, un
sistema también cuesta alrededor de tres veces más que un programa simple.
3 Cuando se desarrolla un “producto de sistema”, tiene el brillo de un producto y las múltiples partes de un
2
1
sistema. Los productos del sistema cuestan alrededor de nueve veces más que los programas simples
DATOS DUROS
(Brooks 1995, Shull et al. 2002).
A medida que aumenta la escala, la construcción se convierte en una parte más pequeña del esfuerzo total
de un proyecto. Si basa sus estimaciones únicamente en la experiencia de construcción, el error de
estimación aumenta. Si usó su propia experiencia en la construcción de 2K para estimar el tiempo que
llevaría desarrollar un programa de 32K, su estimación sería solo el 50 por ciento del tiempo total requerido;
el desarrollo tomaría un 100 por ciento más de tiempo de lo que estimarías.
27.5 Efecto del tamaño del proyecto en las actividades de desarrollo 657
El error de estimación aquí sería completamente atribuible a que no comprende el efecto del
tamaño en el desarrollo de programas más grandes. Si además no tuvo en cuenta el grado
adicional de pulido requerido para un producto en lugar de un mero programa, el error podría
aumentar fácilmente por un factor de tres o más.
Metodología y Tamaño
Las metodologías se utilizan en proyectos de todos los tamaños. En proyectos pequeños, las metodologías
tienden a ser casuales e instintivas. En proyectos grandes, tienden a ser rigurosos y cuidadosamente
planificados.
Algunas metodologías pueden ser tan flexibles que los programadores ni siquiera se dan cuenta de
que las están usando. Algunos programadores argumentan que las metodologías son demasiado
rígidas y dicen que no las tocarán. Si bien puede ser cierto que un programador no haya
seleccionado una metodología conscientemente, cualquier enfoque de programación constituye una
metodología, sin importar cuán inconsciente o primitivo sea el enfoque. Simplemente levantarse de
la cama e ir a trabajar por la mañana es una metodología rudimentaria aunque no muy creativa. El
programador que insiste en evitar metodologías en realidad solo está evitando elegir una
explícitamente; nadie puede evitar usarlas por completo.
Los enfoques formales no siempre son divertidos y, si no se aplican correctamente, los gastos generales engullen
sus otros ahorros. Sin embargo, la mayor complejidad de los proyectos más grandes requiere una mayor atención
consciente a la metodología. La construcción de un rascacielos requiere un enfoque diferente al de la construcción
de una caseta para perros. Los diferentes tamaños de proyectos de software funcionan de la misma manera. En
proyectos grandes, las elecciones inconscientes son inadecuadas para la tarea. Los planificadores de proyectos
PUNTO CLAVE
exitosos eligen explícitamente sus estrategias para grandes proyectos.
En entornos sociales, cuanto más formal sea el evento, más incómoda tiene que ser tu ropa
(tacones, corbatas, etc.). En el desarrollo de software, cuanto más formal es el proyecto, más
papel tiene que generar para asegurarse de haber hecho su tarea. Capers Jones señala que un
proyecto de 1.000 líneas de código promediará alrededor del 7 por ciento de su esfuerzo en
papeleo, mientras que un proyecto de 100.000 líneas de código promediará alrededor del 26
por ciento de su esfuerzo en papeleo (Jones 1998).
Este papeleo no se crea por el puro placer de escribir documentos. Se crea como
resultado directo del fenómeno ilustrado en la figura 27-1: cuantos más cerebros de
personas tenga que coordinar, más documentación formal necesitará para coordinarlos.
Usted no crea nada de esta documentación por su propio bien. El objetivo de escribir un plan
de administración de configuración, por ejemplo, no es ejercitar sus músculos de escritura. El
objetivo de escribir el plan es forzarlo a pensar detenidamente sobre la administración de la
configuración y explicar su plan a todos los demás. La documentación es un efecto secundario
tangible del trabajo real que realiza al planificar y construir un sistema de software. Si siente
que está siguiendo los pasos y escribiendo documentos genéricos, algo anda mal.
658 Capítulo 27: Cómo el tamaño del programa afecta la construcción
Recursos adicionales
cc2e.com/2768 Utilice los siguientes recursos para investigar más a fondo el tema de este capítulo:
Boehm, Barry y Richard Turner.Equilibrar la agilidad y la disciplina: una guía para perplejos. Boston,
MA: Addison-Wesley, 2004. Boehm y Turner describen cómo el tamaño del proyecto afecta el uso de
métodos ágiles y basados en planes, junto con otros problemas ágiles y basados en planes.
Boehm, Barry W.Ingeniería de Software Economía. Englewood Cliffs, NJ: Prentice Hall, 1981. El libro de
Boehm es un tratamiento extenso de las ramificaciones de costo, productividad y calidad del tamaño
del proyecto y otras variables en el proceso de desarrollo de software. Incluye discusiones sobre el
efecto del tamaño en la construcción y otras actividades. El capítulo 11 es una excelente explicación
de las deseconomías de escala del software. Otra información sobre el tamaño del proyecto se
distribuye a lo largo del libro. Libro de Boehm 2000Estimación de costos de software con Cocomo II
contiene mucha más información actualizada sobre el modelo de estimación de Cocomo de Boehm,
pero el libro anterior proporciona discusiones de fondo más profundas que aún son relevantes.
Jones, Alcaparras.Estimación de costos de software. Nueva York, NY: McGraw-Hill, 1998. Este libro está
repleto de tablas y gráficos que diseccionan las fuentes de la productividad del desarrollo de software. Para
el impacto del tamaño del proyecto específicamente, el libro de Jones de 1986,Programación Productividad,
contiene una excelente discusión en la sección titulada “El impacto del tamaño del programa” en el Capítulo
3.
Brooks, Frederick P. Jr.The Mythical Man-Month: ensayos sobre ingeniería de software, edición de
aniversario(2ª ed.). Reading, MA: Addison-Wesley, 1995. Brooks fue el gerente de desarrollo de OS/
360 de IBM, un proyecto gigantesco que requirió 5000 años de personal. Analiza cuestiones de
gestión relacionadas con equipos pequeños y grandes y presenta un relato particularmente vívido de
los equipos de jefes de programación en esta atractiva colección de ensayos.
Puntos clave 659
Jones, T. Alcaparras. "Calidad del programa y productividad del programador".Informe técnico de IBM
TR 02.764(enero de 1977): 42–78. También disponible en JonesTutorial: Productividad en
Programación: Temas para los Ochenta, 2ª ed. Los Ángeles, CA: IEEE Computer Society Press, 1986.
Este documento contiene el primer análisis en profundidad de las razones por las que los proyectos
grandes tienen patrones de gasto diferentes a los pequeños. Es una discusión exhaustiva de las
diferencias entre proyectos grandes y pequeños, incluidos los requisitos y las medidas de garantía de
calidad. Está anticuado pero sigue siendo interesante.
Puntos clave
- A medida que aumenta el tamaño del proyecto, es necesario apoyar la comunicación. El objetivo de
la mayoría de las metodologías es reducir los problemas de comunicación, y una metodología debe
vivir o morir según sus méritos como facilitador de la comunicación.
- En igualdad de condiciones, un proyecto grande tendrá más errores por cada mil líneas
de código que uno pequeño.
- Las actividades que se dan por sentadas en proyectos pequeños deben planificarse cuidadosamente en proyectos
más grandes. La construcción se vuelve menos predominante a medida que aumenta el tamaño del proyecto.
- Ampliar una metodología ligera tiende a funcionar mejor que reducir una
metodología pesada. El enfoque más eficaz de todos es utilizar una metodología
de "peso adecuado".
capitulo 28
Gestión de la construcción
cc2e.com/2836 Contenido
Temas relacionados
La gestión del desarrollo de software ha sido un desafío formidable durante las últimas décadas.
Como sugiere la figura 28-1, el tema general de la administración de proyectos de software se
extiende más allá del alcance de este libro, pero este capítulo analiza algunos temas de
administración específicos que se aplican directamente a la construcción. Si es un desarrollador, este
capítulo lo ayudará a comprender los problemas que los administradores deben tener en cuenta. Si
usted es gerente, este capítulo lo ayudará a comprender cómo ven los desarrolladores la
administración y cómo administrar la construcción de manera efectiva. Debido a que el capítulo
cubre una amplia colección de temas, varias de sus secciones también describen dónde puede
obtener más información.
General
administración
Software
administración
Administración de
construcción
Figura 28-1Este capítulo cubre los temas de administración de software relacionados con la construcción.
661
662 Capítulo 28: Gestión de la construcción
Si alguien en un proyecto va a definir estándares, haga que un arquitecto respetado defina los estándares en
lugar del gerente. Los proyectos de software operan tanto en una "jerarquía de experiencia" como en una
"jerarquía de autoridad". Si se considera que el arquitecto es el líder intelectual del proyecto, el equipo del
proyecto generalmente seguirá los estándares establecidos por esa persona.
Si elige este enfoque, asegúrese de que el arquitecto realmente sea respetado. A veces, un arquitecto de proyecto es
solo una persona de alto nivel que ha estado presente durante demasiado tiempo y no está al tanto de los
problemas de codificación de producción. A los programadores les molestará ese tipo de "arquitecto" que define
estándares que están fuera de contacto con el trabajo que están haciendo.
Los estándares son más útiles en algunas organizaciones que en otras. Algunos desarrolladores dan la
bienvenida a los estándares porque reducen la variación arbitraria en el proyecto. Si su grupo se resiste a
adoptar estándares estrictos, considere algunas alternativas: lineamientos flexibles, una colección de
sugerencias en lugar de lineamientos, o un conjunto de ejemplos que incorporen las mejores prácticas.
Referencia cruzadaPara obtener más Asigne dos personas a cada parte del proyecto.Si dos personas tienen que trabajar en cada línea de código,
detalles sobre la programación en pares,
garantizará que al menos dos personas piensen que funciona y es legible. Los mecanismos para formar equipos de
consulte la Sección 21.2, “Programación
en pares”.
dos personas pueden variar desde la programación en parejas hasta las parejas de mentores en formación y las
revisiones del sistema de compañeros.
28.1 Fomento de la buena codificación 663
Referencia cruzadaPara obtener Revisa cada línea de códigoUna revisión de código generalmente involucra al programador y al
detalles sobre las revisiones, consulte
menos dos revisores. Eso significa que al menos tres personas leen cada línea de código. Otro
la Sección 21.3, “Inspecciones
Requerir aprobaciones de códigoEn otros campos, los dibujos técnicos son aprobados y firmados por el
ingeniero gerente. La firma significa que, según el leal saber y entender del ingeniero, los dibujos son
técnicamente competentes y no contienen errores. Algunas empresas tratan el código de la misma
manera. Antes de que se considere que el código está completo, el personal técnico superior debe firmar la
lista de códigos.
Enrutar buenos ejemplos de código para su revisiónUna gran parte de una buena gestión consiste en
comunicar claramente sus objetivos. Una forma de comunicar sus objetivos es hacer circular un buen
código entre sus programadores o publicarlo para mostrarlo al público. Al hacerlo, proporciona un claro
ejemplo de la calidad que busca. De manera similar, un manual de estándares de codificación puede
consistir principalmente en un conjunto de "listas de mejores códigos". Identificar ciertos listados como
"mejores" establece un ejemplo para que otros lo sigan. Dicho manual es más fácil de actualizar que un
manual de estándares en inglés y presenta sin esfuerzo sutilezas en el estilo de codificación que son difíciles
de capturar punto por punto en descripciones en prosa.
Referencia cruzadaUna gran parte de la Enfatice que los listados de códigos son activos públicosLos programadores a veces sienten que el código
programación consiste en comunicar tu
que han escrito es “su código”, como si fuera propiedad privada. Aunque es el resultado de su trabajo, el
trabajo a otras personas. Para obtener más
3
2
Uno de los proyectos más exitosos jamás reportados desarrolló 83,000 líneas de
1
código en 11 años de trabajo de esfuerzo. Solo se detectó un error que resultó en
DATOS DUROS
una falla del sistema en los primeros 13 meses de operación. Este logro es aún más
dramático cuando te das cuenta de que el proyecto se completó a fines de la década
de 1960, sin compilación en línea ni depuración interactiva. La productividad del
proyecto (7500 líneas de código por año de trabajo a fines de la década de 1960)
sigue siendo impresionante según los estándares actuales. El programador jefe del
proyecto informó que una de las claves del éxito del proyecto fue la identificación de
todas las ejecuciones informáticas (erróneas o no) como activos públicos en lugar de
privados (Baker y Mills 1973). Esta idea se ha extendido a contextos modernos,
V413HAV
664 Capítulo 28: Gestión de la construcción
Recompense el buen códigoUtilice el sistema de recompensas de su organización para reforzar las buenas
prácticas de codificación. Tenga en cuenta estas consideraciones al desarrollar su sistema de refuerzo:
- El código que recibe un premio debe ser excepcionalmente bueno. Si le das un premio a un
programador que todo el mundo sabe que hace un mal trabajo, te pareces a Homer Simpson
tratando de hacer funcionar un reactor nuclear. No importa que el programador tenga una
actitud cooperativa o que siempre llegue a tiempo al trabajo. Pierde credibilidad si su
recompensa no coincide con los méritos técnicos de la situación. Si no tiene la habilidad
técnica suficiente para hacer el juicio de buen código, ¡no lo haga! No haga el premio en
absoluto, o deje que su equipo elija al destinatario.
Si no controla los cambios en los requisitos, puede terminar escribiendo código para partes del
sistema que finalmente se eliminan. Puede escribir código que sea incompatible con partes nuevas
del sistema. Es posible que no detecte muchas de las incompatibilidades hasta que
28.2 Gestión de la configuración 665
tiempo de integración, que se convertirá en tiempo de señalar con el dedo porque nadie sabrá realmente
lo que está pasando.
Si no se controlan los cambios en el código, puede cambiar una rutina que otra persona está cambiando al
mismo tiempo; combinar con éxito sus cambios con los de ellos será problemático. Los cambios de código
no controlados pueden hacer que el código parezca más probado de lo que es. La versión que se ha probado
probablemente será la versión anterior sin cambios; es posible que la versión modificada no haya sido
probada. Sin un buen control de cambios, puede realizar cambios en una rutina, encontrar nuevos errores y
no poder realizar una copia de seguridad de la antigua rutina de trabajo.
DATOS DUROS
los programadores ni siquiera estaban familiarizados con la idea (Beck y Perkins 1983), y hay pocos indicios
de que eso haya cambiado. Un estudio más reciente realizado por el Instituto de Ingeniería de Software
encontró que, de las organizaciones que utilizan prácticas informales de desarrollo de software, menos del
20 por ciento tenía una gestión de configuración adecuada (SEI 2003).
La gestión de la configuración no fue inventada por los programadores, pero debido a que los proyectos de
programación son tan volátiles, es especialmente útil para los programadores. Aplicada a proyectos de
software, la gestión de la configuración suele denominarse “gestión de la configuración del software” (SCM).
SCM se centra en los requisitos, el código fuente, la documentación y los datos de prueba de un programa.
El problema sistémico con SCM es el control excesivo. La forma más segura de detener los accidentes
automovilísticos es evitar que todos conduzcan, y una forma segura de evitar problemas de desarrollo de
software es detener todo el desarrollo de software. Aunque esa es una forma de controlar los cambios, es
una forma terrible de desarrollar software. Tiene que planificar SCM cuidadosamente para que sea un activo
en lugar de un lastre alrededor de su cuello.
Referencia cruzadaPara obtener detalles En un proyecto pequeño de una sola persona, probablemente le vaya bien sin SCM más allá de la
sobre los efectos del tamaño del proyecto
planificación de copias de seguridad periódicas informales. No obstante, la gestión de la configuración sigue
en la construcción, consulte el Capítulo 27,
Una solución a este problema es anotar todas las ideas y sugerencias, sin importar cuán
fáciles sean de implementar, y guardarlas hasta que tenga tiempo para trabajar en ellas.
Luego, viéndolos como un grupo, elija los que serán más beneficiosos.
Estimar el costo de cada cambioCada vez que su cliente, su jefe o usted se sientan tentados a
cambiar el sistema, estime el tiempo que le llevaría realizar el cambio, incluida la revisión del código
para el cambio y volver a probar todo el sistema. Incluya en su estimación el tiempo para lidiar con el
efecto dominó del cambio a través de los requisitos para diseñar el código para probar los cambios
en la documentación del usuario. Informe a todas las partes interesadas que el software está
intrincadamente entrelazado y que la estimación del tiempo es necesaria incluso si el cambio parece
pequeño a primera vista.
Independientemente de cuán optimista se sienta cuando se sugiere el cambio por primera vez, absténgase de dar
una estimación improvisada. Tales estimaciones a menudo se confunden por un factor de 2 o más.
Referencia cruzadaPara Tenga cuidado con los altos volúmenes de cambioSi bien cierto grado de cambio es inevitable, un gran
otro punto de vista sobre el manejo
volumen de solicitudes de cambio es una señal de advertencia clave de que los requisitos, la arquitectura o
de cambios, consulte “Manejo
"Refactorización".
28.2 Gestión de la configuración 667
Establezca un tablero de control de cambios o su equivalente de una manera que tenga sentido
para su proyectoEl trabajo de una junta de control de cambios es separar el trigo de la paja en las
solicitudes de cambio. Cualquiera que quiera proponer un cambio presenta la solicitud de cambio a la
junta de control de cambios. El término "solicitud de cambio" se refiere a cualquier solicitud que
cambiaría el software: una idea para una nueva función, un cambio en una función existente, un
"informe de error" que podría o no informar un error real, etc. La junta se reúne periódicamente para
revisar los cambios propuestos. Aprueba, desaprueba o aplaza cada cambio. Los tableros de control
de cambios se consideran una mejor práctica para priorizar y controlar los cambios de requisitos; sin
embargo, todavía son poco comunes en entornos comerciales (Jones 1998, Jones 2000).
Esté atento a la burocracia, pero no permita que el miedo a la burocracia impida un control de
cambios efectivoLa falta de control de cambios disciplinado es uno de los mayores problemas de gestión
que enfrenta la industria del software en la actualidad. Un porcentaje significativo de los proyectos que se
perciben como retrasados estarían realmente a tiempo si tuvieran en cuenta el impacto de los cambios sin
seguimiento pero acordados. Un control de cambios deficiente permite que los cambios se acumulen fuera
de los libros, lo que socava la visibilidad del estado, la previsibilidad a largo plazo, la planificación de
proyectos, la gestión de riesgos específicamente y la gestión de proyectos en general.
El control de cambios tiende a desviarse hacia la burocracia, por lo que es importante buscar formas de simplificar el
proceso de control de cambios. Si prefiere no utilizar las solicitudes de cambio tradicionales, configure un simple
alias de correo electrónico "ChangeBoard" y haga que las personas envíen solicitudes de cambio por correo
electrónico a la dirección. O haga que las personas presenten propuestas de cambio de forma interactiva en una
reunión de la junta de cambio. Un enfoque especialmente poderoso es registrar las solicitudes de cambio como
defectos en su software de seguimiento de defectos. Los puristas clasificarán dichos cambios como "defectos de los
requisitos", o usted podría clasificarlos como cambios en lugar de defectos.
Puede implementar la propia Junta de control de cambios formalmente, o puede definir un Grupo de
planificación de productos o un Consejo de guerra que tenga las responsabilidades tradicionales de una
junta de control de cambios. O puede identificar a una sola persona para que sea el Zar del Cambio. Pero
como sea que lo llames, ¡hazlo!
Ocasionalmente veo proyectos que sufren implementaciones torpes de control de cambios. Pero 10
veces más a menudo veo proyectos que no tienen ningún control de cambios significativo. La esencia
del control de cambios es lo importante, así que no deje que el miedo a la burocracia le impida
PUNTO CLAVE
aprovechar sus muchos beneficios.
Otro problema de gestión de la configuración es el control del código fuente. Si cambia el código y
aparece un nuevo error que parece no estar relacionado con el cambio que realizó, probablemente
desee comparar la nueva versión del código con la anterior en su búsqueda del origen del error. Si
eso no le dice nada, es posible que desee ver una versión que sea aún más antigua. Este tipo de
excursión a través de la historia es fácil si tiene herramientas de control de versiones que realizan un
seguimiento de múltiples versiones del código fuente.
668 Capítulo 28: Gestión de la construcción
Software de control de versionesUn buen software de control de versiones funciona tan fácilmente que
apenas se da cuenta de que lo está utilizando. Es especialmente útil en proyectos de equipo. Un estilo de
control de versiones bloquea los archivos de origen para que solo una persona pueda modificar un archivo a
PUNTO CLAVE
la vez. Por lo general, cuando necesita trabajar en el código fuente de un archivo en particular, desprotege el
archivo del control de versiones. Si alguien más ya lo revisó, se le notifica que no puede hacerlo. Cuando
puede desproteger el archivo, trabaja en él tal como lo haría sin el control de versiones hasta que esté listo
para desprotegerlo. Otro estilo permite que varias personas trabajen en archivos simultáneamente y maneja
el problema de fusionar cambios cuando el código está registrado. En cualquier caso, cuando registra el
archivo, el control de versiones le pregunta por qué lo cambió y usted escribe una razón.
- Usted no pisa los dedos de los pies de nadie trabajando en un archivo mientras alguien más
está trabajando en él (o al menos lo sabrá si lo hace).
- Puede actualizar fácilmente sus copias de todos los archivos del proyecto a las versiones actuales,
generalmente emitiendo un solo comando.
- Puede retroceder a cualquier versión de cualquier archivo que alguna vez se registró en el control de
versiones.
- Puede obtener una lista de los cambios realizados en cualquier versión de cualquier archivo.
- No tiene que preocuparse por las copias de seguridad personales porque la copia de control de versiones es una
red de seguridad.
El control de versiones es indispensable en los proyectos de equipo. Se vuelve aún más poderoso cuando
se integran el control de versiones, el seguimiento de defectos y la gestión de cambios. La división de
aplicaciones de Microsoft descubrió que su herramienta patentada de control de versiones era una “gran
ventaja competitiva” (Moore 1992).
Versiones de herramientas
Para algunos tipos de proyectos, puede ser necesario poder reconstruir el entorno exacto utilizado
para crear cada versión específica del software, incluidos compiladores, enlazadores, bibliotecas de
códigos, etc. En ese caso, también debe poner todas esas herramientas en el control de versiones.
Configuraciones de la máquina
Muchas empresas (incluida mi empresa) han obtenido buenos resultados al crear configuraciones de
máquinas de desarrollo estandarizadas. Se crea una imagen de disco de una estación de trabajo de
desarrollador estándar, incluidas todas las herramientas de desarrollador comunes, aplicaciones de
oficina, etc. Esa imagen se carga en la máquina de cada desarrollador. Tener configuraciones
estandarizadas ayuda a evitar una serie de problemas asociados con
28.2 Gestión de la configuración 669
diferentes ajustes de configuración, diferentes versiones de herramientas utilizadas, etc. Una imagen de disco
estandarizada también agiliza en gran medida la configuración de nuevas máquinas en comparación con tener
que instalar cada pieza de software individualmente.
Plan de respaldo
Un plan de respaldo no es un nuevo concepto dramático; es la idea de hacer una copia de seguridad
de su trabajo periódicamente. Si estuvieras escribiendo un libro a mano, no dejarías las páginas
amontonadas en tu porche. Si lo hiciera, podrían llover sobre ellos o volarlos, o el perro de su vecino
podría tomarlos prestados para leer un poco antes de acostarse. Los pondrías en un lugar seguro. El
software es menos tangible, por lo que es más fácil olvidar que tiene algo de enorme valor en una
máquina.
Muchas cosas pueden pasar con los datos computarizados: un disco puede fallar; usted u otra persona
puede eliminar archivos clave accidentalmente; un empleado enojado puede sabotear su máquina; o podría
perder una máquina por robo, inundación o incendio. Tome medidas para salvaguardar su trabajo. Su plan
de copia de seguridad debe incluir la realización de copias de seguridad periódicas y la transferencia
periódica de copias de seguridad a un almacenamiento externo, y debe abarcar todos los materiales
importantes de su proyecto (documentos, gráficos y notas), además del código fuente.
Un aspecto que a menudo se pasa por alto al diseñar un plan de respaldo es una prueba de su procedimiento de
respaldo. Intente realizar una restauración en algún momento para asegurarse de que la copia de seguridad
contenga todo lo que necesita y que la recuperación funcione.
Cuando termine un proyecto, cree un archivo de proyecto. Guarde una copia de todo: código fuente,
compiladores, herramientas, requisitos, diseño, documentación, todo lo que necesita para volver a crear el
producto. Guárdelo todo en un lugar seguro.
- ¿Agrupa las solicitudes de cambio, ya sea a través de medios informales (como una
lista de cambios pendientes) o mediante un enfoque más sistemático (como una junta
de control de cambios)?
- ¿Considera que los cambios importantes son una advertencia de que el desarrollo de requisitos aún
no está completo?
670 Capítulo 28: Gestión de la construcción
Instrumentos
- ¿Utiliza software de control de versiones para reducir los problemas de coordinación del
trabajo en equipo?
Respaldo
- ¿Realiza copias de seguridad de todos los materiales del proyecto periódicamente?
- ¿Se transfieren periódicamente las copias de seguridad de los proyectos a un almacenamiento externo?
- ¿Se realizan copias de seguridad de todos los materiales, incluidos el código fuente, los documentos, los
Bahías, Michael.Metodología de lanzamiento de software. Englewood Cliffs, NJ: Prentice Hall, 1999.
Este libro analiza la gestión de la configuración del software con énfasis en la liberación del software
para la producción.
Bersoff, Edward H. y Alan M. Davis. "Impactos de los modelos de ciclo de vida en la gestión de
configuración de software".Comunicaciones del ACM 34, no. 8 (agosto de 1991): 104–118. Este
artículo describe cómo SCM se ve afectado por los enfoques más nuevos para el desarrollo de
software, especialmente los enfoques de creación de prototipos. El artículo es especialmente
aplicable en entornos que utilizan prácticas de desarrollo ágiles.
28.3 Estimación de un cronograma de construcción 671
3
Administrar un proyecto de software es uno de los formidables desafíos del siglo XXI, y estimar el tamaño de
2
1 un proyecto y el esfuerzo requerido para completarlo es uno de los aspectos más desafiantes de la
administración de proyectos de software. El proyecto de software grande promedio tiene un año de retraso
DATOS DUROS
y está 100 por ciento por encima del presupuesto (Standish Group 1994, Jones 1997, Johnson 1999). A nivel
individual, las encuestas de cronogramas estimados versus reales han encontrado que las estimaciones de
los desarrolladores tienden a tener un factor de optimismo de 20 a 30 por ciento (van Genuchten 1991). Esto
tiene tanto que ver con estimaciones de tamaño y esfuerzo deficientes como con estimaciones deficientes
esfuerzos de desarrollo Esta sección describe los problemas involucrados en la estimación de proyectos de
software e indica dónde buscar más información.
Enfoques de estimación
Otras lecturasPara leer Puede estimar el tamaño de un proyecto y el esfuerzo requerido para completarlo de varias
más en horario-
maneras:
técnicas de estimación, véase
el capítulo 8 deDesarrollo
- Usa un software de estimación.
rápido(McConnell 1996) y
Estimación de costos de
- Utilice un enfoque algorítmico, como Cocomo II, el modelo de estimación de Barry
software con Cocomo II(
Boehm et al. 2000).
Boehm (Boehm et al. 2000).
- Mantenga las estimaciones anteriores y vea qué tan precisas fueron. Úselos para ajustar nuevas
estimaciones.
Otras lecturasEste Establecer objetivos¿Por qué necesitas un presupuesto? ¿Qué estás estimando? ¿Está
enfoque está adaptado de
estimando solo las actividades de construcción o todo el desarrollo? ¿Está estimando solo el
Ingeniería de Software
Economía(Boehm 1981). esfuerzo para su proyecto, o su proyecto más vacaciones, vacaciones, capacitación y otras
actividades ajenas al proyecto? ¿Qué tan precisa debe ser la estimación para cumplir con sus
objetivos? ¿Qué grado de certeza debe asociarse con la estimación? ¿Una estimación optimista
o pesimista produciría resultados sustancialmente diferentes?
Deje tiempo para el presupuesto y planifíqueloLas estimaciones apresuradas son estimaciones inexactas.
Si está estimando un proyecto grande, trate la estimación como un miniproyecto y tómese el tiempo para
miniplanificar la estimación para que pueda hacerlo bien.
672 Capítulo 28: Gestión de la construcción
Referencia cruzadaPara obtener más Explicar los requisitos del softwareAsí como un arquitecto no puede estimar cuánto costará una
información sobre los requisitos de
casa “bastante grande”, usted no puede estimar de manera confiable un proyecto de software
software, consulte la Sección 3.4,
“Requisitos previos”.
“bastante grande”. No es razonable que alguien espere que pueda estimar la cantidad de trabajo
necesario para construir algo cuando "algo" aún no se ha definido. Defina los requisitos o planifique
una fase preliminar de exploración antes de realizar una estimación.
Estimar con un bajo nivel de detalleDependiendo de los objetivos que haya identificado, base la
estimación en un examen detallado de las actividades del proyecto. En general, cuanto más detallado
sea su examen, más precisa será su estimación. La Ley de los Grandes Números dice que un error
del 10 por ciento en una pieza grande será un 10 por ciento alto o un 10 por ciento bajo. En 50 piezas
pequeñas, algunos de los errores del 10 por ciento en las piezas serán altos y algunos serán bajos, y
los errores tenderán a cancelarse entre sí.
Referencia cruzadaEs difícil Utilice varias técnicas de estimación diferentes y compare los resultadosLa lista de
encontrar un área de desarrollo de
enfoques de estimación al comienzo de la sección identificó varias técnicas. No todos
software en la que la iteración no
Ningún enfoque es el mejor en todas las circunstancias, y las diferencias entre ellos pueden ser
esclarecedoras. Por ejemplo, para la primera edición de este libro, mi estimación original de la
longitud del libro era de 250 a 300 páginas. Cuando finalmente hice una estimación detallada, la
estimación resultó en 873 páginas. “Eso no puede ser correcto”, pensé. Así que lo estimé usando una
técnica completamente diferente. La segunda estimación resultó en 828 páginas. Teniendo en cuenta
que estas estimaciones estaban dentro de un cinco por ciento entre sí, llegué a la conclusión de que
el libro iba a estar mucho más cerca de las 850 páginas que de las 250 páginas, y pude ajustar mis
planes de escritura en consecuencia.
cc2e.com/2864
Variación en Proyecto
Estimaciones de alcance
(Esfuerzo, Costo o
Características)
4x
2x
1.5x
1,25x
1.0x
0.8x
0.67x
0.25x
Tiempo
Figura 28-2Las estimaciones creadas al principio de un proyecto son intrínsecamente inexactas. A medida que
avanza el proyecto, las estimaciones pueden volverse más precisas. Vuelva a estimar periódicamente a lo largo de
un proyecto y use lo que aprenda durante cada actividad para mejorar su estimación para la próxima actividad.
Influencias en el horario
Referencia cruzadaEl efecto del tamaño La mayor influencia en el cronograma de un proyecto de software es el tamaño del programa que se va a
de un programa sobre la productividad y la
producir. Pero muchos otros factores también influyen en el cronograma de desarrollo de software. Los
calidad no siempre es evidente de forma
explicación de cómo afecta el tamaño a la Tabla 28-1 Factores que influyen en el esfuerzo del proyecto de software
construcción.
Potencial Potencial
Útil Dañino
Factor Influencia Influencia
Coubicación frente a desarrollo multisitio Tamaño - 14% 22%
de la base de datos - 10% 28%
Coincidencia de la documentación con las necesidades del proyecto - 19% 23%
Flexibilidad permitida en la interpretación de los requisitos Qué tan - 9% 10%
activamente se abordan los riesgos - 12% 14%
Experiencia en lenguaje y herramientas - dieciséis% 20%
Continuidad del personal (rotación) - 19% 29%
Volatilidad de la plataforma - 13% 30%
Madurez del proceso - 13% 15%
Complejidad del producto - 27% 74%
capacidad del programador - 24% 34%
Se requiere confiabilidad - 18% 26%
Capacidad del analista de requisitos - 29% 42%
Requisitos de reutilización - 5% 24%
Aplicación de última generación - 11% 12%
Restricción de almacenamiento (cuánto del almacenamiento 0% 46%
disponible se consumirá)
Estos son algunos de los factores menos fáciles de cuantificar que pueden influir en un cronograma
de desarrollo de software. Estos factores se extraen de Barry Boehm.Estimación de costos de
software con Cocomo II(2000) y Capers JonesEstimación de costos de software(1998).
- Calidad de gestión
- Cantidad de código reutilizado
- Rotación de personal
- Cantidad de documentación
- Objetivos del proyecto (calendario frente a calidad frente a usabilidad frente a muchos otros
objetivos posibles)
Cada uno de estos factores puede ser significativo, así que considérelos junto con los factores que se
muestran en la Tabla 28-1 (que incluye algunos de estos factores).
Estimación vs Control
La pregunta importante La estimación es una parte importante de la planificación necesaria para completar un proyecto de
es, ¿quieres predicción o
software a tiempo. Una vez que se tiene una fecha de entrega y una especificación del producto, el
quieres control?
—tom gilb principal problema es cómo controlar el gasto de recursos humanos y técnicos para una entrega a
tiempo del producto. En ese sentido, la precisión de la estimación inicial es mucho menos importante
que su posterior éxito en el control de los recursos para cumplir con el cronograma.
3 Espero que te pongas al díaEl optimismo esperanzado es una respuesta común al retraso de un
2
1
proyecto. La racionalización generalmente es así: “Los requisitos tardaron un poco más de lo que
DATOS DUROS
esperábamos, pero ahora son sólidos, por lo que seguramente ahorraremos tiempo más adelante.
Compensaremos el déficit durante la codificación y las pruebas”. Este casi nunca es el caso. Una
encuesta de más de 300 proyectos de software concluyó que los retrasos y los sobrecostos
generalmente aumentan hacia el final de un proyecto (van Genuchten 1991). Los proyectos no
recuperan el tiempo perdido más tarde; se quedan más atrás.
676 Capítulo 28: Gestión de la construcción
Ampliar el equipoDe acuerdo con la ley de Fred Brooks, agregar personas a un proyecto de software
tardío lo retrasa (Brooks 1995). Es como echar gasolina a un fuego. La explicación de Brooks es
convincente: las personas nuevas necesitan tiempo para familiarizarse con un proyecto antes de que
puedan volverse productivas. Su formación ocupa el tiempo de las personas que ya han sido
formadas. Y simplemente aumentar el número de personas aumenta la complejidad y la cantidad de
comunicación del proyecto. Brooks señala que el hecho de que una mujer pueda tener un bebé en
nueve meses no implica que nueve mujeres puedan tener un bebé en un mes.
Sin duda, la advertencia de la ley de Brooks debe ser atendida más a menudo de lo que es. Es
tentador enviar personas a un proyecto y esperar que lo entreguen a tiempo. Los gerentes deben
comprender que desarrollar software no es como remachar una hoja de metal: más trabajadores
trabajando no significa necesariamente que se realizará más trabajo.
Sin embargo, la simple declaración de que agregar programadores a un proyecto tardío lo retrasa,
enmascara el hecho de que, en algunas circunstancias, es posible agregar personas a un proyecto
tardío y acelerarlo. Como señala Brooks en el análisis de su ley, agregar personas a proyectos de
software en los que las tareas no se pueden dividir y realizar de forma independiente no ayuda. Pero
si las tareas de un proyecto se pueden dividir, puede dividirlas aún más y asignarlas a diferentes
personas, incluso a personas que se agregan más adelante en el proyecto. Otros investigadores han
identificado formalmente las circunstancias bajo las cuales se pueden agregar personas a un
proyecto tardío sin hacerlo más tarde (Abdel-Hamid 1989, McConnell 1999).
Otras lecturasPara obtener un Reducir el alcance del proyecto.A menudo se pasa por alto la poderosa técnica de
argumento a favor de crear solo
reducir el alcance del proyecto. Si elimina una función, elimina el diseño, la codificación,
las funciones más necesarias,
Además de descartar una función por completo, puede proporcionar una versión más económica de la
misma funcionalidad. Puede proporcionar una versión que esté a tiempo pero que no se haya ajustado para
el rendimiento. Puede proporcionar una versión en la que la funcionalidad menos importante se implemente
de manera tosca. Puede decidir retroceder en un requisito de velocidad porque es mucho más fácil
proporcionar una versión lenta. Es posible que retroceda en un requisito de espacio porque es más fácil
proporcionar una versión con uso intensivo de memoria.
Vuelva a estimar el tiempo de desarrollo para las características menos importantes. ¿Qué
funcionalidad puede proporcionar en dos horas, dos días o dos semanas? ¿Qué gana al construir la
versión de dos semanas en lugar de la versión de dos días, o la versión de dos días en lugar de la
versión de dos horas?
28.4 Medición 677
Boehm, Barry, et al.Estimación de costos de software con Cocomo II. Boston, MA:
Addison-Wesley, 2000. Este libro describe los entresijos del modelo de estimación
Cocomo II, que sin duda es el modelo más popular en uso en la actualidad.
Boehm, Barry W.Ingeniería de Software Economía. Englewood Cliffs, NJ: Prentice Hall, 1981. Este libro
antiguo contiene un tratamiento exhaustivo de la estimación de proyectos de software considerada
de manera más general que en el libro más nuevo de Boehm.
Humphrey, Watts S.Una disciplina para la ingeniería de software. Reading, MA: Addison-
Wesley, 1995. El capítulo 5 de este libro describe el método de prueba de Humphrey, que es
una técnica para estimar el trabajo a nivel de desarrollador individual.
28.4 Medición
Los proyectos de software se pueden medir de muchas maneras. Aquí hay dos razones sólidas para
medir su proceso:
Para cualquier atributo del proyecto, es posible medir ese atributo de una manera
que es superior a no medirlo en absoluto.Es posible que la medición no sea
perfectamente precisa, que sea difícil de realizar y que deba perfeccionarse con el
tiempo, pero la medición le dará una idea del proceso de desarrollo de software que no
PUNTO CLAVE
Si los datos se van a utilizar en un experimento científico, deben cuantificarse. ¿Se imagina a un científico
recomendando la prohibición de un nuevo producto alimenticio porque un grupo de ratas blancas "parecía
enfermarse más" que otro grupo? Eso es absurdo. Exigiría una razón cuantificada, como "Las ratas que
comieron el nuevo producto alimenticio estuvieron enfermas 3,7 días más al mes que las ratas que no lo
hicieron". Para evaluar los métodos de desarrollo de software, debe medirlos. Declaraciones como “Este
nuevo método parece más productivo” no son lo suficientemente buenas.
678 Capítulo 28: Gestión de la construcción
Lo que se mide se Tenga en cuenta los efectos secundarios de la mediciónLa medición tiene un efecto motivacional.
hace.
Las personas prestan atención a lo que se mide, suponiendo que se utilice para evaluarlos. Elige lo
—tom peters
que mides con cuidado. La gente tiende a centrarse en el trabajo medido e ignorar el trabajo que no
lo es.
Puede medir prácticamente cualquier aspecto del proceso de desarrollo de software. La tabla 28-2
enumera algunas medidas que otros profesionales han encontrado útiles.
Puede recopilar la mayoría de estas medidas con las herramientas de software disponibles
actualmente. Las discusiones a lo largo del libro indican las razones por las que cada medida
es útil. En este momento, la mayoría de las medidas no son útiles para hacer distinciones
precisas entre programas, clases y rutinas (Shepperd e Ince 1989). Son útiles principalmente
para identificar rutinas que son "atípicos"; las mediciones anormales en una rutina son una
señal de advertencia de que debe volver a examinar esa rutina, verificando si la calidad es
inusualmente baja.
Asegúrate de recopilar datos por un motivo. Establezca metas, determine las preguntas que
necesita hacer para alcanzar las metas y luego mida para responder las preguntas (Basili y
Weiss 1984). Asegúrese de solicitar solo la información que sea factible de obtener y tenga en
cuenta que la recopilación de datos siempre quedará relegada a los plazos (Basili et al. 2002).
Omán, Paul y Shari Lawrence Pfleeger, eds.Aplicación de métricas de software. Los Alamitos,
CA: IEEE Computer Society Press, 1996. Este volumen recopila más de 25 artículos clave sobre
medición de software en una sola portada.
Basili, Víctor R., et al. 2002. “Lecciones aprendidas de 25 años de mejora de procesos: el auge y
la caída del Laboratorio de Ingeniería de Software de la NASA”,Actas de la 24ª Conferencia
Internacional sobre Ingeniería de Software. Orlando, FL, 2002. Este documento cataloga las
lecciones aprendidas por una de las organizaciones de desarrollo de software más sofisticadas
del mundo. Las lecciones se centran en temas de medición.
cc2e.com/2899 Gil, Tom.Ingeniería Competitiva. Boston, MA: Addison-Wesley, 2004. Este libro presenta
un enfoque centrado en la medición para definir requisitos, evaluar diseños, medir la
calidad y, en general, gestionar proyectos. Se puede descargar desde el sitio web de Gilb.
Operando
Fuente Correo/Misc. Técnico Procedimientos, Programa
Actividad Código Negocio Personal Reuniones Capacitación Documentos Manuales Varios Prueba Totales
3
El talento y el esfuerzo entre los programadores individuales varían enormemente, como lo hacen en
2
1 todos los campos. Un estudio encontró que en una variedad de profesiones (escritura, fútbol,
invención, trabajo policial y pilotaje de aviones) el 20 por ciento superior de las personas producía
DATOS DUROS
alrededor del 50 por ciento de la producción (Augustine 1979). Los resultados del estudio se basan en
un análisis de datos de productividad, como touchdowns, patentes, casos resueltos, etc. Dado que
algunas personas no hacen ninguna contribución tangible y no fueron consideradas en el estudio
(mariscales de campo que no anotan touchdowns, inventores que no poseen patentes, detectives que
no cierran casos, etc.), los datos probablemente subestiman la variación real en productividad.
682 Capítulo 28: Gestión de la construcción
variación individual
3 El estudio original que mostró grandes variaciones en la productividad de programación individual fue
2
1
realizado a fines de la década de 1960 por Sackman, Erikson y Grant (1968). Estudiaron a programadores
DATOS DUROS
profesionales con un promedio de 7 años de experiencia y descubrieron que la proporción del tiempo de
codificación inicial entre los mejores y los peores programadores era de aproximadamente 20 a 1, la
proporción de tiempos de depuración de más de 25 a 1, del tamaño del programa de 5 a 1, y de la velocidad
de ejecución del programa de aproximadamente 10 a 1. No encontraron relación entre la cantidad de
experiencia de un programador y la calidad o productividad del código.
DATOS DUROS
programadores" son significativas y han sido confirmadas por muchos otros estudios de
programadores profesionales (Curtis 1981, Mills 1983). , DeMarco y Lister 1985, Curtis y otros
1986, Card 1987, Boehm y Papaccio 1988, Valett y McGarry 1989, Boehm y otros 2000).
Los equipos de programación también exhiben diferencias considerables en la calidad y productividad del
software. Los buenos programadores tienden a agruparse, al igual que los malos programadores, una
observación que ha sido confirmada por un estudio de 166 programadores profesionales de 18
organizaciones (Demarco y Lister 1999).
3 En un estudio de siete proyectos idénticos, los esfuerzos realizados variaron en un factor de 3,4 a 1 y
2
1
los tamaños de los programas en un factor de 3 a 1 (Boehm, Gray y Seewaldt 1984). A pesar del
DATOS DUROS
rango de productividad, los programadores en este estudio no eran un grupo diverso. Todos eran
programadores profesionales con varios años de experiencia que estaban inscritos en un programa
de posgrado en ciencias de la computación. Es razonable suponer que un estudio de un grupo
menos homogéneo arrojaría diferencias aún mayores.
3 Después de revisar más de 20 años de datos en la construcción del modelo de estimación Cocomo II,
2
1
Barry Boehm y otros investigadores concluyeron que desarrollar un programa con un equipo en el
DATOS DUROS
percentil 15 de programadores clasificados por habilidad generalmente requiere alrededor de 3.5
veces más meses de trabajo que desarrollar un programa con un equipo en el percentil 90 (Boehm et
al. 2000). Boehm y otros investigadores han encontrado que el 80 por ciento de la contribución
proviene del 20 por ciento de los contribuyentes (Boehm 1987b).
28.5 Tratar a los programadores como personas 683
La implicación para el reclutamiento y la contratación es clara. Si tiene que pagar más para obtener un
programador del 10 por ciento superior en lugar de un programador del 10 por ciento inferior, aproveche la
oportunidad. Obtendrá una recompensa inmediata en la calidad y productividad del programador que
contrate, y obtendrá un efecto residual en la calidad y productividad de los otros programadores que su
organización puede retener porque los buenos programadores tienden a agruparse.
Cuestiones Religiosas
Los gerentes de proyectos de programación no siempre son conscientes de que ciertos problemas de
programación son cuestiones de religión. Si es un gerente e intenta exigir el cumplimiento de ciertas
prácticas de programación, está provocando la ira de sus programadores. Aquí hay una lista de cuestiones
religiosas:
- Lenguaje de programación
- Estilo de sangría
- Colocación de brackets
- Elección de IDE
- Estilo de comentario
- Utilidades de programación
- Convenciones de nombres
- Uso deirs
- Uso de variables globales
El denominador común entre estos temas es que la posición de un programador en cada uno es un
reflejo de su estilo personal. Si crees que necesitas controlar a un programador en alguna de estas
áreas religiosas, considera estos puntos:
Resuelva los problemas que pueda eludiendo los mandatos explícitosPara refinar el estilo de sangría o
la colocación de llaves, requiere que el código fuente se ejecute a través de un formateador de impresora
bonita antes de que se declare terminado. Deje que la linda impresora haga el formateo. Para refinar el
estilo de los comentarios, solicite que se revise todo el código y que el código poco claro se modifique hasta
que quede claro.
Traducido del inglés al español - www.onlinedoctranslator.com
Haga que sus programadores desarrollen sus propios estándaresComo se mencionó en otra parte, los
detalles de un estándar específico a menudo son menos importantes que el hecho de que exista algún
estándar. No establezca estándares para sus programadores, pero insista en que los estandaricen en las
áreas que son importantes para usted.
¿Cuáles de los temas religiosos son lo suficientemente importantes como para justificar ir a la lona? La
conformidad en asuntos menores de estilo en cualquier área probablemente no producirá suficientes
beneficios para compensar los efectos de una moral más baja. Si encuentra un uso indiscriminado deirs o
variables globales, estilos ilegibles u otras prácticas que afectan proyectos completos, prepárese para
soportar algunas fricciones para mejorar la calidad del código. Si sus programadores son concienzudos, esto
rara vez es un problema. Las batallas más grandes tienden a ser sobre los matices del estilo de codificación,
y puedes quedarte fuera de ellas sin perder el proyecto.
Entorno físico
He aquí un experimento: sal al campo, encuentra una granja, encuentra un agricultor y pregunta
cuánto dinero en equipo tiene el agricultor para cada trabajador. El agricultor mirará el granero y
verá algunos tractores, algunos vagones, una cosechadora para el trigo y un peaviner para los
guisantes y le dirá que son más de $ 100,000 por empleado.
El entorno físico hace una gran diferencia en la productividad. DeMarco y Lister preguntaron a 166
programadores de 35 organizaciones sobre la calidad de sus entornos físicos. La mayoría de los empleados
calificaron sus lugares de trabajo como no aceptables. En una competencia de programación posterior, los
programadores que se desempeñaron en el 25 por ciento superior tenían oficinas más grandes, más
silenciosas y más privadas y menos interrupciones de personas y llamadas telefónicas. Aquí hay un resumen
de las diferencias en el espacio de oficina entre los mejores y los peores:
3 Los datos muestran una fuerte correlación entre la productividad y la calidad del lugar de trabajo.
2
1 Los programadores del 25 por ciento superior fueron 2,6 veces más productivos que los
programadores del 25 por ciento inferior. DeMarco y Lister pensaron que los mejores
DATOS DUROS
programadores naturalmente podrían tener mejores oficinas porque habían sido ascendidos, pero
un examen más profundo reveló que no era así. Los programadores de las mismas organizaciones
tenían instalaciones similares, independientemente de las diferencias en su desempeño.
Las grandes organizaciones que hacen un uso intensivo del software han tenido experiencias similares.
Xerox, TRW, IBM y Bell Labs han indicado que obtienen una productividad significativamente mejorada con
una inversión de capital de $10 000 a $30 000 por persona, sumas que fueron más que recuperadas en una
productividad mejorada (Boehm 1987a). Con las “oficinas de productividad”, las estimaciones
autoinformadas oscilaron entre un 39 y un 47 por ciento de mejora en la productividad (Boehm et al. 1984).
En resumen, si su lugar de trabajo es un entorno del 25 por ciento inferior, puede lograr una mejora
del 100 por ciento en la productividad al convertirlo en un entorno del 25 por ciento superior. Si su
lugar de trabajo es promedio, aún puede obtener una mejora de la productividad del 40 por ciento o
más al convertirlo en un entorno del 25 por ciento superior.
Weinberg, Gerald M.La psicología de la programación informática, 2ª ed. New York, NY: Van
Nostrand Reinhold, 1998. Este es el primer libro que identifica explícitamente a los programadores
como seres humanos, y sigue siendo el mejor sobre la programación como actividad humana. Está
repleto de agudas observaciones sobre la naturaleza humana de los programadores y sus
implicaciones.
DeMarco, Tom y Timothy Lister.Peopleware: Proyectos y Equipos Productivos, 2ª ed. New York, NY:
Dorset House, 1999. Como sugiere el título, este libro también trata el factor humano en la ecuación
de programación. Está lleno de anécdotas sobre la gestión de personas, el entorno de la oficina, la
contratación y el desarrollo de las personas adecuadas, el crecimiento de los equipos y el disfrute del
trabajo. Los autores se apoyan en las anécdotas para apoyar algunos puntos de vista poco comunes
y su lógica es débil en algunos lugares, pero el espíritu del libro centrado en las personas es lo
importante y los autores transmiten ese mensaje sin vacilar.
cc2e.com/2820 McCue, Gerald M. "Laboratorio de Santa Teresa de IBM: diseño arquitectónico para el desarrollo de
programas"Diario de sistemas de IBM17, núm. 1 (1978): 4–25. McCue describe el proceso que utilizó
IBM para crear su complejo de oficinas de Santa Teresa. IBM estudió las necesidades de los
programadores, creó pautas arquitectónicas y diseñó la instalación pensando en los programadores.
Los programadores participaron en todo momento. El resultado es que en las encuestas de opinión
anuales de cada año, las instalaciones físicas de la planta de Santa Teresa son las más altas de la
empresa.
686 Capítulo 28: Gestión de la construcción
Carnegie, Dale.Como ganar amigos y influenciar personas, Edición revisada. New York, NY:
Pocket Books, 1981. Cuando Dale Carnegie escribió el título de la primera edición de este libro
en 1936, no podía darse cuenta de la connotación que tendría hoy. Suena como un libro que
Maquiavelo habría exhibido en su estante. Sin embargo, el espíritu del libro es diametralmente
opuesto a la manipulación maquiavélica, y uno de los puntos clave de Carnegie es la
importancia de desarrollar un interés genuino en otras personas. Carnegie tiene una visión
aguda de las relaciones cotidianas y explica cómo trabajar con otras personas entendiéndolas
mejor. El libro está lleno de anécdotas memorables, a veces dos o tres por página. Cualquiera
que trabaje con personas debería leerlo en algún momento, y cualquiera que gestione
personas debería leerlo.ahora.
En una jerarquía, cada Si su gerente es más típico, se enfrenta a la tarea poco envidiable de administrar a
empleado tiende a subir a su
su gerente. “Administrar a su gerente” significa que debe decirle a su gerente qué
nivel de incompetencia.
—El principio de Peter hacer y no al revés. El truco es hacerlo de una manera que le permita a su gerente
seguir creyendo que usted es el que está siendo dirigido. Aquí hay algunos
enfoques para tratar con su gerente:
- Plante ideas sobre lo que quiere hacer y luego espere a que su gerente tenga una
lluvia de ideas (su idea) sobre cómo hacer lo que quiere hacer.
- Eduque a su gerente sobre la manera correcta de hacer las cosas. Este es un trabajo continuo
porque los gerentes a menudo son ascendidos, transferidos o despedidos.
- Concéntrese en los intereses de su gerente, haga lo que él o ella realmente quiere que
haga, y no lo distraiga con detalles de implementación innecesarios. (Piense en ello
como una "encapsulación" de su trabajo).
La mejor solución a largo plazo es tratar de educar a su gerente. No siempre es una tarea fácil,
pero una forma de prepararse es leyendo el libro de Dale Carnegie.Como ganar amigos y
influenciar personas.
Recursos adicionales sobre la gestión de la construcción 687
McConnell, Steve.Desarrollo rápido. Redmond, WA: Microsoft Press, 1996. Este libro cubre temas de
liderazgo y administración de proyectos desde la perspectiva de proyectos que están
experimentando una presión de cronograma significativa, que en mi experiencia es la mayoría de
los proyectos.
Brooks, Frederick P. Jr.The Mythical Man-Month: ensayos sobre ingeniería de software, edición
de aniversario(2ª edición). Reading, MA: Addison-Wesley, 1995. Este libro es una mezcolanza de
metáforas y folklore relacionado con la gestión de proyectos de programación. Es entretenido y
le dará muchas ideas esclarecedoras sobre sus propios proyectos. Se basa en los desafíos de
Brooks en el desarrollo del sistema operativo OS/360, lo que me da algunas reservas. Está lleno
de consejos como "Hicimos esto y falló" y "Deberíamos haberlo hecho porque hubiera
funcionado". Las observaciones de Brooks sobre las técnicas que fallaron están bien
fundamentadas, pero sus afirmaciones de que otras técnicas habrían funcionado son
demasiado especulativas. Lea el libro críticamente para separar las observaciones de las
especulaciones. Esta advertencia no disminuye el valor básico del libro. Todavía se cita en la
literatura informática con más frecuencia que cualquier otro libro, y aunque fue publicado
originalmente en 1975, parece nuevo hoy. Es difícil leerlo sin decir "¡Correcto!" cada par de
páginas.
Estándares relevantes
IEEE Std 12207-1997, Tecnología de la información—Procesos del ciclo de vida del software.
IEEE Std 1540-2001, Estándar para procesos del ciclo de vida del software—Gestión de riesgos.
IEEE Std 1490-1998, Guía—Adopción del estándar PMI—Una guía para el conocimiento de la
dirección de proyectos.
688 Capítulo 28: Gestión de la construcción
Puntos clave
- Las buenas prácticas de codificación se pueden lograr a través de estándares
obligatorios o mediante enfoques más ligeros.
- Una buena estimación de software es un desafío importante. Las claves del éxito son el
uso de múltiples enfoques, el ajuste de sus estimaciones a medida que avanza en el
proyecto y el uso de datos para crear las estimaciones.
- La medición es la clave para una gestión de la construcción exitosa. Puede encontrar formas
de medir cualquier aspecto de un proyecto que son mejores que no medirlo en absoluto. La
medición precisa es clave para una programación precisa, para el control de calidad y para
mejorar su proceso de desarrollo.
- Los programadores y administradores son personas y trabajan mejor cuando se les trata como
tales.
capitulo 29
Integración
cc2e.com/2985 Contenido
Temas relacionados
- Depuración: Capítulo 23
689
690 Capítulo 29: Integración
Figura 29-1El complemento del estadio de fútbol de la Universidad de Washington se derrumbó porque no
era lo suficientemente fuerte para sostenerse durante la construcción. Probablemente habría sido lo
suficientemente fuerte cuando se completó, pero se construyó en el orden incorrecto, un error de
integración.
Debido a que se realiza después de que un desarrollador haya terminado la prueba del desarrollador y junto con la
prueba del sistema, a veces se considera que la integración es una actividad de prueba. Sin embargo, es lo
suficientemente complejo como para que deba verse como una actividad independiente.
PUNTO CLAVE
- Menos defectos
- Menos andamios
- moral mejorada
- Mayor probabilidad de finalización del proyecto
- Menos documentación
29.2 Frecuencia de integración: ¿por fases o incremental? 691
Estos pueden parecer reclamos elevados para el primo olvidado de las pruebas de sistemas, pero el hecho
de que se pase por alto a pesar de su importancia es precisamente la razón por la cual la integración tiene
su propio capítulo en este libro.
1.Diseñe, codifique, pruebe y depure cada clase. Este paso se llama “desarrollo de la unidad”.
Un problema con la integración por fases es que cuando las clases en un sistema se juntan por
primera vez, inevitablemente surgen nuevos problemas y las causas de los problemas pueden
estar en cualquier parte. Dado que tiene una gran cantidad de clases que nunca han trabajado
juntas antes, el culpable podría ser una clase mal probada, un error en la interfaz entre dos
clases o un error causado por una interacción entre dos clases. Todas las clases son
sospechosas.
Global
Variables
Error diferente-
manejo
suposiciones Big Bang
Integración
Mal
documentado
interfaces
Débil
encapsulación
Figura 29-2¡La integración por fases también se llama integración "big bang" por una buena razón!
692 Capítulo 29: Integración
La integración por fases no puede comenzar hasta el final del proyecto, después de que todas las clases
hayan sido probadas por el desarrollador. Cuando las clases finalmente se combinan y los errores surgen
por puntaje, los programadores entran inmediatamente en modo de depuración de pánico en lugar de
detección y corrección metódica de errores.
Para programas pequeños, no, para programas diminutos, la integración por fases podría ser el
mejor enfoque. Si el programa tiene solo dos o tres clases, la integración por fases puede ahorrarle
tiempo, si tiene suerte. Pero en la mayoría de los casos, otro enfoque es mejor.
Integración Incremental
Referencia cruzadaLas metáforas En la integración incremental, escribe y prueba un programa en pequeñas partes y luego
apropiadas para la integración
combina las partes una a la vez. En este enfoque de integración de una pieza a la vez,
incremental se analizan en
Construction: Building Software", 1.Desarrollar una parte pequeña y funcional del sistema. Puede ser la parte funcional más
ambos en la Sección 2.3. pequeña, la parte más difícil, una parte clave o alguna combinación. Pruébelo a fondo y
depúrelo. Servirá como un esqueleto sobre el que colgar los músculos, los nervios y la
piel que componen las partes restantes del sistema.
Ocasionalmente, es posible que desee integrar unidades más grandes que una sola clase. Si un
componente se probó exhaustivamente, por ejemplo, y cada una de sus clases se sometió a una
miniintegración, puede integrar todo el componente y seguir realizando una integración incremental.
A medida que le agrega piezas, el sistema crece y gana impulso de la misma manera que una bola de
nieve crece y gana impulso cuando rueda cuesta abajo, como se muestra en la figura 29-3.
Integración Incremental
bola de nieve
Integración
Figura 29-3La integración incremental ayuda a que un proyecto genere impulso, como una bola de nieve que
cae por una colina.
29.2 Frecuencia de integración: ¿por fases o incremental? 693
3
2
Los errores son fáciles de localizarCuando surgen nuevos problemas durante la
1
integración incremental, la nueva clase obviamente está involucrada. O su interfaz
DATOS DUROS
con el resto del programa contiene un error o su interacción con una clase
previamente integrada produce un error. De cualquier manera, como sugiere la
figura 29-4, sabe exactamente dónde buscar. Además, simplemente porque tiene
menos problemas a la vez, reduce el riesgo de que múltiples problemas interactúen
o que un problema enmascare otro. Cuantos más errores de interfaz tenga, más
ayudará este beneficio de la integración incremental a sus proyectos. Una
contabilidad de errores para un proyecto reveló que el 39 por ciento eran errores de
interfaz entre módulos (Basili y Perricone 1984). Dado que los desarrolladores de
muchos proyectos dedican hasta el 50 % de su tiempo a la depuración,
dyub
nsucn
Ubr
susnd
d yubwebd Jst esquí
nsucn dolsn.
ydbs ;ld9oiicv
dios b solo
dolsn.
;ld9oiicv
b ydbs
Si dios iwj oispoj.
Idiopi
kiuebna
iwj oispoj.
Idiopi ydbs
sksi kiuebna ksi. B
Ubrid
susnd
Dyubwebd
ksi. B
Ubr id
susnd iwj
Dyubwebd Idiopi
kibna
Zust si
ldoicv.
kibna
Zust si .U
jyeteb
ydbs ldoicv. ptosm
jy eteb ksi. Si
oispoj. m ikocos .U
kis Ubrid
Ylkr ptosm
iwj oispoj. ocos
Idiopi Ylkr kis m ik
susnd
iwj poj. kis
ebd id ksi.
Idiopi dyubw Ubr esquí
kiuebna Ylkr Jst
Tst sksi ylkr nsucn bd
susnd
iwj poj. cv dolsn.
Idiopi
kiuebna d yubwe ;ld9oii solo
Tst sksi b ydbs
ptosmo.
nsucn cv dolsn. .
m ikocos dios ;ld9oii oispoj
ptosmo. b ydbs iwj
.
kis m
ik ocos Si dios a Idiopi oispoj B ydbs
iwj ksi.
kiuebn Ubrid
a Idiopi
susnd B
kiuebn ebd id ksi.
sksi Ubr iwj
Dyubw Idiopi
susnd
ebd si kibna
Dyubw . Zust
r kyosi k r ldoicv
. Zust
si kibna
Y kyoY ldoicv
.j .j ydbs . kis
opa opa oispoj . Ylkr
oispoj poj.
j gs j gs . iwj iwj
wi i wi i ms Idiopi a Idiopi ylkr
ot . m kiuebn poj.
jw pao paoi et ro sksi iwj
Tst a Idiopi
sb i i gs
dyo gsdyopas o s oett pa kiuebn o.
B. dy anoi paoi ano anogs Co ros
Tst
sksi ptosm
gs
o o.
m ikocos
i s B. bi gs bm
k i s rtki dyoa .j opa bmrttu rtii ki Cok ptosm
di k d es no si iek tu ek m i
m ik
ocos
r b i r t s tu bi rt k gsoj i s i sk sietk m kis
t s i .j
j sk opa tu b tu Z. ies t wi i .j ks t s ts ro etro
s t si .j dno dno vCi s tu pa opa s T T
i S . nos sj . gs oj opa s tu rts sturts odyo Z. v i gs
o si ogs
dyo k
.i s
di .i sk yort ode no syo wi i si ogsj edbm edbm s b Ci o
bm r b di v ortd pao wi d
tmi y bmt m tud r b tuCii o e vCi i gs dyo i w i b wibtudy yo
ij i 9d i o anopaoi gs tu y
dyo y D D
tu. yji tuno s tu dno
rt s rt yo
; 9 bm
ms . ms s edb tu s e s bd dyo ; s rttui
e
ano
b
d
ot oett m bm y bdy i s m
et k rttu
ro
pas pa ro wi b wib bs b ks iiek
oC s gs
gs o tu tu oi s
o Coki y d yd di S oi d
ki noC rt no
Ctu
ms msiet tu rt
s
seno no e
ietk kro
r kyor kyo rt rte
ro
Y Y e
teb
jye
.U b Si
sm jy ete ksi.
ptosm.
U jyeteb s pto . U Ubrid .
co uí
m ikocos U jy eteb iko ptosm snd r id ksi esq
Ylkr kis ptosm. Ubrid ksi. Si m os bd su Ub . Jst
m ik ocos r kis oc d
Ylkr kis dyubwebd susnd id ksi. Ylk m ik ubwe susn dolsn . solo
Ubr d iicv
nsucn d susnd esquí r kis n dy eb lsn
yubweb dolsn. Jst Ylk nsuc yubw ;ld9o iicv dopoj. bs
nsucn d ydbs ;ld9oiicv bs j. yd
dios b
solo n d b yd 9o
;ld iwj ois po .B
v dolsn. uc ksi
b ydbs
;ld9oiic ns dios ydbs opi iwj ois rid . B
Si dios a Idiopi iwj oispoj. s b na Idi opi d Ub ksi i iwj
sn
kiuebn
a Idiopi
iwj oispoj. B ydbs
dio
Si kiueb na
Idi su Ubr id Idiop
sksi kiuebn Ubrid ksi. eb we
bd
sn
d na
ebd susnd id ksi. B i kiu ub su si kib na
Dyubw Ubr sks Dy bd st kib
iwj si r kis
ebd susnd we . Zu st
Dyubw Zust si kibna Idiopi ub
Dy ldoicv icv. Zu j. Ylk
ldoicv. kibna po r
Zust si ldo j. i iwj ylk
ydbs ldoicv. bs poj. po op poj.
yd ois ois Idi
eb oispoj. i iwj ebna opi iwj
jyet oispoj. Ylkr kis op
.U b Idiopi iwj iwj poj. Idi i kiu Idi
sm Si
pto jy ete ksi. kiuebn
a Idiopi ylkr sks ebna smo.
cos sm. U rid Tst sksi iwj poj. Tst i kiu pto o.
m iko s pto Ub a Idiopi s sm
nd ksi. kiuebn sks co
r kis sus r id esquí Tst sksi . Tst m iko os
pto
Ylk m ik oco ebd nd Ub Jst ptosmo
bw sus dolsn. solo
m ikocos . ik oc
r kis dyu bd v ocos ptosmo m
Ylk
nsu
cn we oiic sn. kis m ik kis
d yub s ;ld9 oiicv dol .
cn b ydb poj s
nsu
dio
s s ;ld9 iwj ois poj. B ydb
ydb pi ois ksi.
s b a Idio pi iwj rid
Si dio ebn Ub B
kiu a Idio susnd ksi. iwj
ebn r id pi
i kiu ebd nd Ub Idio
bw
sks Dyu sus kibna
ebd t si na
bw Zus kib
Dyu icv. t si
ldo Zus r kis
icv. . Ylk
s ldo poj
ydb poj. poj
.
pi iwj . ylkr
ois ois Idio poj
pi iwj ebna pi iwj
Idio i kiu a Idio
sks
Tst ebn
i kiu o. o. U jyeteb
sks U jyeteb ptosm.
m ik
m ik ocos ksi. Si dyubwebd ksi.
Ylkr kis susnd
Ubrid nsucn Ubr id
susnd
kis nsucn
dyubwebd
Ubr id
ksi. nsucn
d yubwebd dolsn.
Jst esquí
solo
susnd ydbs ;ld9oiicv
d yubwebd Jst esquí dios b dolsn.
nsucn dolsn. ;ld9oiicv
b ydbs
ydbs ;ld9oiicv Si dios iwj oispoj.
dios b solo Idiopi
dolsn. kiuebna iwj oispoj.
;ld9oiicv Idiopi ydbs
b ydbs ksi. B
Si dios iwj oispoj. sksi kiuebna Ubrid
Idiopi susnd
kiuebna Dyubwebd ksi. B
Ubr id
iwj oispoj. susnd iwj
Idiopi Idiopi
sksi kiuebna ksi. B ydbs Dyubwebd
Zust si
kibna
Ubrid ldoicv.
susnd kibna
Dyubwebd Zust si
ksi. B ydbs ldoicv.
Ubr id
susnd iwj
Dyubwebd oispoj.
kibna Idiopi
Zust si iwj oispoj. Ylkr kis
ldoicv. Idiopi iwj poj.
kibna Idiopi
Zust si kiuebna ylkr
ydbs ldoicv. Tst sksi iwj poj.
Idiopi
kiuebna
oispoj. Tst sksi ptosmo.
m ikocos
iwj oispoj. ptosmo.
Idiopi Ylkr kis ik ocos
iwj poj. kis m
Idiopi
kiuebna
Tst sksi ylkr
iwj poj.
Idiopi
kiuebna
Tst sksi
ptosmo.
m ikocos
ocos ptosmo.
kis m ik
Figura 29-4En la integración por fases, integra tantos componentes a la vez que es difícil saber
dónde está el error. Puede estar en alguno de los componentes o en alguna de sus conexiones.
En la integración incremental, el error suele estar en el nuevo componente o en la conexión
entre el nuevo componente y el sistema.
El sistema tiene éxito al principio del proyecto.Cuando el código está integrado y en ejecución,
incluso si el sistema no es utilizable, es evidente que pronto lo será. Con la integración incremental,
los programadores ven los primeros resultados de su trabajo, por lo que su moral es mejor que
cuando sospechan que su proyecto nunca dará su primer aliento.
Obtiene una mejor supervisión del progresoCuando integra con frecuencia, las características que están
presentes y las que no están presentes son obvias. La gerencia tendrá una mejor idea del progreso al ver
que el 50 por ciento de la capacidad de un sistema funciona que al escuchar que la codificación está
"completa en un 99 por ciento".
694 Capítulo 29: Integración
Mejorarás las relaciones con los clientesSi la integración frecuente tiene un efecto en la moral del desarrollador,
también tiene un efecto en la moral del cliente. A los clientes les gustan las señales de progreso y las compilaciones
incrementales proporcionan señales de progreso con frecuencia.
Las unidades del sistema se prueban más a fondoLa integración comienza temprano en el
proyecto. Integras cada clase a medida que se desarrolla, en lugar de esperar un magnífico atracón
de integración al final. Las clases son probadas por el desarrollador en ambos casos, pero cada clase
se ejercita como parte del sistema general más a menudo con integración incremental que con
integración por fases.
Con la integración incremental, debe planificar con más cuidado. La mayoría de los sistemas
requerirán la integración de algunos componentes antes de la integración de otros. La planificación
para la integración afecta así a la planificación para la construcción; el orden en que se construyen
los componentes tiene que soportar el orden en que se integrarán.
En la integración de arriba hacia abajo, la clase en la parte superior de la jerarquía se escribe e integra
primero. La parte superior es la ventana principal, el bucle de control de aplicaciones, el objeto que contiene
principal()en Java,WinPrincipal()para la programación de Microsoft Windows, o similar. Los talones tienen
que estar escritos para ejercitar la clase superior. Luego, a medida que las clases se integran de arriba hacia
abajo, las clases auxiliares se reemplazan por las reales. Este tipo de integración procede como se ilustra en
la figura 29-5.
29.3 Estrategias de Integración Incremental 695
Comienzo
Finalizar
Figura 29-5En la integración de arriba hacia abajo, agrega clases en la parte superior primero, en la parte inferior al final.
Un aspecto importante de la integración de arriba hacia abajo es que las interfaces entre clases deben
especificarse cuidadosamente. Los errores más problemáticos para depurar no son los que afectan a clases
individuales, sino los que surgen de interacciones sutiles entre clases. La especificación cuidadosa de la
interfaz puede reducir el problema. La especificación de la interfaz no es una actividad de integración, pero
asegurarse de que las interfaces se hayan especificado correctamente sí lo es.
Además de las ventajas que obtiene de cualquier tipo de integración incremental, una ventaja de la
integración de arriba hacia abajo es que la lógica de control del sistema se prueba relativamente temprano.
Todas las clases en la parte superior de la jerarquía se ejercitan mucho para que los grandes problemas
conceptuales de diseño se expongan rápidamente.
Otra ventaja de la integración de arriba hacia abajo es que, si la planifica cuidadosamente, puede completar un
sistema que funciona parcialmente al principio del proyecto. Si las partes de la interfaz de usuario están en la parte
superior, puede obtener una interfaz básica que funcione rápidamente y desarrollar los detalles más adelante. La
moral tanto de los usuarios como de los programadores se beneficia al hacer que algo visible funcione desde el
principio.
La integración incremental de arriba hacia abajo también le permite comenzar a codificar antes de
que se completen los detalles de diseño de bajo nivel. Una vez que el diseño se ha reducido a un
nivel de detalle bastante bajo en todas las áreas, puede comenzar a implementar e integrar las clases
en los niveles más altos sin esperar a puntear cada "i" y cruzar cada "t".
A pesar de estas ventajas, la integración de arriba hacia abajo pura generalmente implica desventajas que
son más problemáticas de lo que querrá soportar. La integración de arriba hacia abajo pura deja el ejercicio
de las complicadas interfaces del sistema para el final. Si las interfaces del sistema tienen errores o un
problema de rendimiento, por lo general le gustaría llegar a ellas mucho antes del final del proyecto. No es
raro que un problema de bajo nivel se abra paso hasta la parte superior del sistema, provocando cambios de
alto nivel y reduciendo el beneficio del trabajo de integración anterior. Minimice el problema burbujeante a
través de pruebas de desarrollador tempranas y cuidadosas y análisis de rendimiento de las clases que
ejercitan las interfaces del sistema.
696 Capítulo 29: Integración
Otro problema con la integración de arriba hacia abajo pura es que necesita un camión volquete
lleno de stubs para integrar de arriba hacia abajo. Muchas clases de bajo nivel no se han integrado,
lo que implica que se necesitará una gran cantidad de stubs durante los pasos intermedios de la
integración. Los stubs son problemáticos porque, como código de prueba, es más probable que
contengan errores que el código de producción diseñado con más cuidado. Los errores en los
nuevos stubs que admiten una nueva clase anulan el propósito de la integración incremental, que es
restringir la fuente de errores a una nueva clase.
Referencia cruzadaLa integración de arriba hacia abajo La integración de arriba hacia abajo también es casi imposible de implementar puramente. En la
integración de arriba hacia abajo realizada por el libro, comienza en la parte superior, llámelo Nivel 1,
está relacionada con el diseño de arriba hacia abajo solo
Finalmente, no puede usar la integración de arriba hacia abajo si la colección de clases no tiene una parte superior.
En muchos sistemas interactivos, la ubicación de la "parte superior" es subjetiva. En muchos sistemas, la interfaz de
usuario es la parte superior. En otros sistemas,principal()es la parte superior
Una buena alternativa a la integración de arriba hacia abajo pura es el enfoque de corte vertical que se
muestra en la figura 29-6. En este enfoque, el sistema se implementa de arriba hacia abajo en secciones, tal
vez desarrollando áreas de funcionalidad una por una y luego moviéndose a la siguiente área.
Comienzo
Figura 29-6 Como alternativa a proceder estrictamente de arriba hacia abajo, puede integrar desde
arriba hacia abajo en cortes verticales.
Aunque la integración de arriba hacia abajo pura no es viable, pensar en ello le ayudará a decidir sobre un
enfoque general. Algunos de los beneficios y riesgos que se aplican a un enfoque de arriba hacia abajo puro
se aplican, de manera menos obvia, a enfoques de arriba hacia abajo más flexibles como la integración de
corte vertical, así que téngalos en cuenta.
29.3 Estrategias de Integración Incremental 697
Integración ascendente
En la integración ascendente, primero escribe e integra las clases en la parte inferior de la jerarquía. Agregar
las clases de bajo nivel una a la vez en lugar de todas a la vez es lo que hace que la integración ascendente
sea una estrategia de integración incremental. Usted escribe controladores de prueba para ejercitar las
clases de bajo nivel inicialmente y agrega clases al andamiaje del controlador de prueba a medida que se
desarrollan. A medida que agrega clases de nivel superior, reemplaza las clases de controlador con las
reales. La figura 29-7 muestra el orden en que se integran las clases en el enfoque ascendente.
Finalizar
Comienzo
Figura 29-7 En la integración de abajo hacia arriba, integra las clases en la parte inferior primero, en la parte superior
ultimo.
La integración ascendente proporciona un conjunto limitado de ventajas de integración incremental. Restringe las
posibles fuentes de error a la única clase que se está integrando, por lo que los errores son fáciles de localizar. La
integración puede comenzar temprano en el proyecto. La integración de abajo hacia arriba también ejercita las
interfaces del sistema potencialmente problemáticas desde el principio. Dado que las limitaciones del sistema a
menudo determinan si puede cumplir con los objetivos del sistema, vale la pena asegurarse de que el sistema haya
realizado un conjunto completo de ejercicios de calistenia.
El principal problema con la integración de abajo hacia arriba es que deja la integración de las principales
interfaces del sistema de alto nivel para el final. Si el sistema tiene problemas de diseño conceptual en los
niveles superiores, la construcción no los encontrará hasta que se haya realizado todo el trabajo detallado. Si
el diseño debe cambiarse significativamente, es posible que se deba descartar parte del trabajo de bajo
nivel.
La integración ascendente requiere que complete el diseño de todo el sistema antes de comenzar la
integración. Si no lo hace, las suposiciones que no tenían por qué haber controlado el diseño podrían
terminar profundamente incrustadas en el código de bajo nivel, dando lugar a la situación incómoda en la
que diseña clases de alto nivel para resolver problemas en los de bajo nivel. Permitir que los detalles de bajo
nivel impulsen el diseño de clases de nivel superior contradice los principios de ocultación de información y
diseño orientado a objetos. Los problemas de integrar clases de alto nivel son solo una lágrima en una
tormenta en comparación con los problemas que tendrá si no completa el diseño de clases de alto nivel
antes de comenzar la codificación de bajo nivel.
698 Capítulo 29: Integración
Al igual que con la integración de arriba hacia abajo, la integración pura de abajo hacia arriba es rara y, en su lugar, puede
utilizar un enfoque híbrido, incluida la integración en segmentos, como se muestra en la figura 29-8.
Finalizar
Figura 29-8 Como alternativa a proceder puramente de abajo hacia arriba, puede integrar desde
de abajo hacia arriba en secciones. Esto desdibuja la línea entre la integración ascendente y la integración
orientada a funciones, que se describe más adelante en este capítulo.
Integración de sándwich
Los problemas con la integración puramente de arriba hacia abajo y de abajo hacia arriba han
llevado a algunos expertos a recomendar un enfoque sándwich (Myers 1976). Primero integre las
clases de objetos comerciales de alto nivel en la parte superior de la jerarquía. Luego integra las
clases de interfaz de dispositivo y las clases de utilidad ampliamente utilizadas en la parte inferior.
Estas clases de alto y bajo nivel son el pan del sándwich.
Dejas las clases de nivel medio para más tarde. Estos forman la carne, el queso y los tomates
del sándwich. Si usted es vegetariano, podrían hacer el tofu y los brotes de soja del sándwich,
pero el autor de la integración del sándwich no dice nada sobre este punto, tal vez tenía la boca
llena. La figura 29-9 ofrece una ilustración del enfoque sándwich.
Comienzo
Finalizar
Figura 29-9En la integración sándwich, primero integra las clases de nivel superior y las de nivel inferior
ampliamente utilizadas y deja las clases de nivel medio para el final.
29.3 Estrategias de Integración Incremental 699
Este enfoque evita la rigidez de la integración pura de abajo hacia arriba o de arriba hacia abajo.
Primero integra las clases a menudo problemáticas y tiene el potencial de minimizar la cantidad de
andamiaje que necesitará. Es un enfoque realista y práctico. El siguiente enfoque es similar pero
tiene un énfasis diferente.
La integración orientada al riesgo también se denomina "primera integración de la parte difícil". Es como la
integración sándwich en el sentido de que busca evitar los problemas inherentes a la integración
puramente de arriba hacia abajo o de abajo hacia arriba. Coincidentemente, también tiende a integrar
primero las clases de arriba y de abajo, dejando las clases de nivel medio para el final. La motivación, sin
embargo, es diferente.
En la integración orientada al riesgo, identifica el nivel de riesgo asociado con cada clase. Usted decide
cuáles serán las partes más difíciles de implementar y las implementa primero. La experiencia indica que las
interfaces de nivel superior son riesgosas, por lo que a menudo se encuentran en la parte superior de la
lista de riesgos. Las interfaces del sistema, generalmente en el nivel inferior de la jerarquía, también son
riesgosas, por lo que también se encuentran en la parte superior de la lista de riesgos. Además, es posible
que sepa de clases intermedias que serán desafiantes. Tal vez una clase implemente un algoritmo poco
conocido o tenga objetivos de rendimiento ambiciosos. Tales clases también pueden identificarse como de
alto riesgo e integrarse relativamente temprano.
El resto del código, las cosas fáciles, pueden esperar hasta más tarde. Parte de esto probablemente
resulte más difícil de lo que pensabas, pero eso es inevitable. La figura 29-10 presenta una
ilustración de la integración orientada al riesgo.
Figura 29-10 En la integración orientada al riesgo, integra las clases que espera que sean más
problemático primero; implementas clases más fáciles más tarde.
700 Capítulo 29: Integración
Cuando la característica a integrar es más grande que una sola clase, el "incremento" en la
integración incremental es más grande que una sola clase. Esto disminuye un poco el beneficio del
incrementalismo, ya que reduce la certeza sobre el origen de los nuevos errores, pero si ha probado
exhaustivamente las clases que implementan la nueva función antes de integrarlas, eso es solo una
pequeña desventaja. Puede utilizar las estrategias de integración incremental de forma recursiva
integrando piezas pequeñas para formar características y luego integrando características de forma
incremental para formar un sistema.
Por lo general, querrá comenzar con un esqueleto que haya elegido por su capacidad para admitir
otras funciones. En un sistema interactivo, la primera característica podría ser el sistema de menú
interactivo. Puede colgar el resto de las funciones en la función que integre primero. La figura 29-11
muestra cómo se ve gráficamente.
Característica 1 esqueleto
Figura 29-11En la integración orientada a funciones, las clases se integran en grupos que forman
funciones identificables, por lo general, pero no siempre, varias clases a la vez.
Los componentes se agregan en "árboles de características", colecciones jerárquicas de clases que componen una
característica. La integración es más fácil si cada característica es relativamente independiente, tal vez llamando al
mismo código de biblioteca de bajo nivel que las clases para otras características pero sin tener llamadas al código
de nivel medio en común con otras características. (Las clases de biblioteca compartidas de bajo nivel no se muestran
en la Figura 29-11).
La integración orientada a características ofrece tres ventajas principales. Primero, elimina el andamiaje para
prácticamente todo, excepto las clases de biblioteca de bajo nivel. El esqueleto podría necesitar un poco de
andamiaje, o algunas partes del esqueleto podrían simplemente no estar operativas hasta que se hayan
agregado características particulares. Cuando cada característica ha sido colgada en la estructura
29.3 Estrategias de Integración Incremental 701
tura, sin embargo, no se necesita andamiaje adicional. Dado que cada función es independiente, cada
función contiene todo el código de soporte que necesita.
La segunda ventaja principal es que cada función recién integrada genera una adición
incremental en la funcionalidad. Esto proporciona evidencia de que el proyecto avanza de
manera constante. También crea software funcional que puede proporcionar a sus clientes
para su evaluación o que puede lanzar antes y con menos funciones de las previstas
originalmente.
Una tercera ventaja es que la integración orientada a funciones funciona bien con el diseño orientado a objetos. Los objetos
tienden a asignarse bien a las características, lo que hace que la integración orientada a características sea una opción natural
La integración puramente orientada a funciones es tan difícil de lograr como la integración puramente descendente
o ascendente. Por lo general, parte del código de bajo nivel debe integrarse antes de que se puedan integrar ciertas
características significativas.
Integración en forma de T
Un enfoque final que a menudo aborda los problemas asociados con la integración de arriba hacia
abajo y de abajo hacia arriba se denomina "integración en forma de T". En este enfoque, se
selecciona un segmento vertical específico para el desarrollo y la integración tempranos. Esa porción
debe ejercitar el sistema de extremo a extremo y debe ser capaz de eliminar cualquier problema
importante en las suposiciones de diseño del sistema. Una vez que se ha implementado ese
segmento vertical y se han corregido los problemas asociados, se puede desarrollar la amplitud
general del sistema (como el sistema de menús en una aplicación de escritorio). Este enfoque,
ilustrado en la figura 29-12, a menudo se combina con una integración orientada al riesgo oa las
funciones.
Comienzo
Figura 29-12En la integración en forma de T, construye e integra una porción profunda del sistema
para verificar los supuestos arquitectónicos y luego construye e integra la amplitud del sistema para
proporcionar un marco para desarrollar la funcionalidad restante.
702 Capítulo 29: Integración
Mejora la moral. Ver el trabajo de un producto proporciona un increíble impulso a la moral. Casi no importa
lo que haga el producto. ¡Los desarrolladores pueden estar emocionados solo de ver que muestra un
rectángulo! Con compilaciones diarias, un poco más del producto funciona todos los días y eso mantiene la
moral alta.
Un efecto secundario de la integración frecuente es que emerge trabajo que de otro modo podría
acumularse sin verse hasta que aparece inesperadamente al final del proyecto. Esa acumulación de trabajo
sin superficie puede convertirse en un pozo de alquitrán al final del proyecto que lleva semanas o meses
salir. Los equipos que no han usado el proceso de compilación diaria a veces sienten que las compilaciones
diarias ralentizan su progreso a paso de tortuga. Lo que realmente sucede es que las compilaciones diarias
amortizan el trabajo de manera más constante a lo largo del proyecto, y el equipo del proyecto solo obtiene
una imagen más precisa de qué tan rápido ha estado trabajando todo el tiempo.
construir diariamenteLa parte más fundamental de la compilación diaria es la parte "diaria". Como dice
Jim McCarthy, trate la construcción diaria como el latido del corazón del proyecto (McCarthy 1995).
29.4 Prueba diaria de construcción y humo 703
Si no hay latido, el proyecto está muerto. Un poco menos metafóricamente, Michael Cusumano y
Richard W. Selby describen la construcción diaria como el pulso sincronizado de un proyecto
(Cusumano y Selby 1995). Se permite que el código de diferentes desarrolladores se desincronice un
poco entre estos pulsos, pero cada vez que hay un pulso de sincronización, el código tiene que volver
a alinearse. Cuando insiste en mantener los pulsos juntos, evita que los desarrolladores se
desincronicen por completo.
Algunas organizaciones construyen cada semana, en lugar de todos los días. El problema con esto es que si
la compilación se rompe una semana, puede pasar varias semanas antes de la siguiente compilación buena.
Cuando eso sucede, pierde prácticamente todo el beneficio de las compilaciones frecuentes.
Compruebe si hay compilaciones rotasPara que el proceso de creación diaria funcione, el software que se crea
tiene que funcionar. Si el software no se puede usar, se considera que la compilación está rota y repararla se
convierte en la máxima prioridad.
Cada proyecto establece su propio estándar de lo que constituye "romper la construcción". El estándar debe establecer un
nivel de calidad que sea lo suficientemente estricto como para mantener fuera los defectos sobresalientes, pero lo
suficientemente indulgente como para ignorar los defectos triviales, que pueden paralizar el progreso si se les presta una
atención indebida.
- No contener ningún error sensacional que impida que el programa se inicie o que haga
que su funcionamiento sea peligroso; en otras palabras, una buena construcción debería
pasar la prueba del humo.
Prueba de humo diariaLa prueba de humo debe ejercitar todo el sistema de punta a punta. No
tiene que ser exhaustivo, pero debe ser capaz de exponer problemas importantes. La prueba de
humo debe ser lo suficientemente exhaustiva como para que, si la construcción pasa, pueda asumir
que es lo suficientemente estable como para probarla más a fondo.
La construcción diaria tiene poco valor sin la prueba de humo. La prueba de humo es el centinela que
protege contra el deterioro de la calidad del producto y los crecientes problemas de integración. Sin él, la
compilación diaria se convierte en solo un ejercicio de pérdida de tiempo para garantizar que tenga una
compilación limpia todos los días.
Mantenga la prueba de humo actualizadaLa prueba de humo debe evolucionar a medida que evoluciona
el sistema. Al principio, la prueba de humo probablemente probará algo simple, como si el sistema puede
decir "Hola, mundo". A medida que se desarrolle el sistema, la prueba de humo será más exhaustiva. La
primera prueba puede tardar unos segundos en ejecutarse; a medida que crece el sistema, la prueba de
humo puede aumentar a 10 minutos, una hora o más. Si la prueba de humo no se mantiene actualizada, la
creación diaria puede convertirse en un ejercicio de autoengaño, en el que un conjunto fraccionario de casos
de prueba crea una falsa sensación de confianza en la calidad del producto.
704 Capítulo 29: Integración
Establecer un grupo de compilaciónEn la mayoría de los proyectos, cuidar la construcción diaria y mantener
actualizada la prueba de humo se convierte en una tarea lo suficientemente grande como para ser una parte
explícita del trabajo de alguien. En proyectos grandes, puede convertirse en un trabajo de tiempo completo para
más de una persona. En la primera versión de Microsoft Windows NT, por ejemplo, había cuatro personas a tiempo
Solicite a los desarrolladores que prueben su código antes de agregarlo al sistemaLos desarrolladores deben
probar su propio código antes de agregarlo a la compilación. Un desarrollador puede hacer esto creando una
compilación privada del sistema en una máquina personal, que luego el desarrollador prueba individualmente. O el
desarrollador puede lanzar una compilación privada a un "compañero de prueba", un probador que se enfoca en el
código de ese desarrollador. El objetivo en cualquier caso es asegurarse de que el nuevo código pase la prueba de
humo antes de que se le permita influir en otras partes del sistema.
Cree un área de espera para el código que se agregará a la compilaciónParte del éxito del
proceso de compilación diario depende de saber qué compilaciones son buenas y cuáles no. Al
probar su propio código, los desarrolladores deben poder confiar en un buen sistema conocido.
La mayoría de los grupos resuelven este problema creando un área de espera para el código que los
desarrolladores creen que está listo para agregarse a la compilación. El nuevo código entra en el área de
espera, se construye la nueva compilación y, si la compilación es aceptable, el nuevo código se migra a las
fuentes maestras.
En proyectos pequeños y medianos, un sistema de control de versiones puede cumplir esta función. Los
desarrolladores verifican el nuevo código en el sistema de control de versiones. Desarrolladores que quieren
29.4 Prueba diaria de construcción y humo 705
use una compilación buena conocida, simplemente configure un indicador de fecha en su archivo de opciones de control de
versiones que le indica al sistema que recupere archivos según la fecha de la última compilación buena conocida.
En proyectos grandes o proyectos que utilizan un software de control de versiones no sofisticado, la función del área
de espera debe manejarse manualmente. El autor de un conjunto de código nuevo envía un correo electrónico al
grupo de compilación para indicarles dónde encontrar los archivos nuevos para registrar. O el grupo establece un
área de "registro" en un servidor de archivos donde los desarrolladores colocan las nuevas versiones. de sus
archivos fuente. Luego, el grupo de compilación asume la responsabilidad de verificar el nuevo código en el control
de versiones después de haber verificado que el nuevo código no rompe la compilación.
Crear una penalización por romper la construcciónLa mayoría de los grupos que usan compilaciones diarias
crean una penalización por romper la compilación. Deje en claro desde el principio que mantener la construcción
saludable es una de las principales prioridades del proyecto. Una construcción rota debería ser la excepción, no la
regla. Insista en que los desarrolladores que han roto la compilación detengan todo el resto del trabajo hasta que lo
hayan arreglado. Si la construcción se rompe con demasiada frecuencia, es difícil tomar en serio el trabajo de no
romper la construcción.
Una penalización alegre puede ayudar a enfatizar esta prioridad. Algunos grupos reparten piruletas a cada “imbécil”
que rompe la construcción. Este desarrollador luego tiene que pegar con cinta adhesiva el tonto a la puerta de su
oficina hasta que solucione el problema. Otros grupos hacen que los desarrolladores culpables usen cuernos de
cabra o contribuyan $5 a un fondo de moral.
Algunos proyectos establecen una penalización con más mordida. Los desarrolladores de Microsoft en
proyectos de alto perfil como Windows 2000 y Microsoft Office se han acostumbrado a usar beepers en las
últimas etapas de sus proyectos. Si rompen la compilación, se les llama para que la arreglen incluso si su
defecto se descubre a las 3 a.m.
Publicar compilaciones por la mañanaAlgunos grupos han descubierto que prefieren construir durante la noche,
hacer pruebas de humo temprano en la mañana y lanzar nuevas construcciones por la mañana en lugar de por la
tarde. Las pruebas de humo y la liberación de compilaciones por la mañana tienen varias ventajas.
Primero, si publica una compilación por la mañana, los evaluadores pueden probar con una compilación nueva ese
día. Si generalmente lanza compilaciones por la tarde, los evaluadores se sienten obligados a lanzar sus pruebas
automatizadas antes de irse por el día. Cuando la compilación se retrasa, lo que sucede a menudo, los probadores
tienen que quedarse hasta tarde para iniciar sus pruebas. Debido a que no es su culpa que tengan que quedarse
hasta tarde, el proceso de construcción se vuelve desmoralizador.
Cuando completa la compilación por la mañana, tiene un acceso más confiable a los desarrolladores cuando
hay problemas con la compilación. Durante el día, los desarrolladores están al final del pasillo. Durante la
noche, los desarrolladores pueden estar en cualquier lugar. Incluso cuando los desarrolladores reciben
buscapersonas, no siempre son fáciles de localizar.
Puede ser más macho empezar a hacer pruebas de humo al final del día y llamar a la gente en medio
de la noche cuando encuentras problemas, pero es más difícil para el equipo, es una pérdida de
tiempo y al final pierdes más de lo que ganas. .
706 Capítulo 29: Integración
Prueba de construcción y humo incluso bajo presiónCuando la presión del cronograma se vuelve
intensa, el trabajo requerido para mantener la construcción diaria puede parecer una sobrecarga
extravagante. El opuesto es verdad. Bajo estrés, los desarrolladores pierden parte de su disciplina.
Sienten la presión de tomar atajos en la construcción que no tomarían en circunstancias menos
estresantes. Revisan y prueban su propio código con menos cuidado de lo habitual. El código tiende
hacia un estado de entropía más rápidamente que durante tiempos menos estresantes.
En este contexto, las compilaciones diarias imponen disciplina y mantienen en marcha los proyectos de olla
a presión. El código todavía tiende hacia un estado de entropía, pero el proceso de construcción hace que
esa tendencia se resienta todos los días.
Algunos desarrolladores protestan porque no es práctico construir todos los días porque sus
proyectos son demasiado grandes. Pero el que quizás fue el proyecto de software más complejo de
la historia reciente usó compilaciones diarias con éxito. En el momento de su lanzamiento, Microsoft
Windows 2000 constaba de unos 50 millones de líneas de código distribuidas en decenas de miles de
archivos fuente. Una compilación completa tomó hasta 19 horas en varias máquinas, pero el equipo
de desarrollo de Windows 2000 se las arregló para compilar todos los días. Lejos de ser una molestia,
el equipo de Windows 2000 atribuyó gran parte de su éxito en ese gran proyecto a sus compilaciones
diarias. Cuanto más grande es el proyecto, más importante se vuelve la integración incremental.
3 Una revisión de 104 proyectos en los EE. UU., India, Japón y Europa encontró que solo entre el 20 y el 25 por
2
1
ciento de los proyectos usaban compilaciones diarias al principio o a la mitad de sus proyectos (Cusumano et
DATOS DUROS
al. 2003), por lo que esto representa una oportunidad significativa para mejorar.
Integración continua
Algunos desarrolladores de software han tomado compilaciones diarias como punto de partida y
recomiendan integrarcontinuamente(Beck 2000). La mayoría de las referencias publicadas a la integración
continua usan la palabra “continuo” para significar “al menos diariamente” (Beck 2000), lo cual creo que es
razonable. Pero ocasionalmente me encuentro con personas que toman la palabra "continuo" literalmente.
Su objetivo es integrar cada cambio con la última versión cada dos horas. Para la mayoría de los proyectos,
creo que la integración continua literal es demasiado buena.
En mi tiempo libre, dirijo un grupo de debate formado por los principales ejecutivos técnicos de
empresas como Amazon.com, Boeing, Expedia, Microsoft, Nordstrom y otras empresas del área de
3 Seattle. En una encuesta de estos altos ejecutivos técnicos,ningunade ellos pensaban que la
2
1
integración continua era superior a la integración diaria. En proyectos medianos y grandes, vale la
DATOS DUROS
pena dejar que el código se desincronice por períodos cortos. Los desarrolladores con frecuencia
pierden la sincronización cuando realizan cambios a gran escala. Luego pueden resincronizarse
después de un corto tiempo. Las compilaciones diarias permiten que el equipo del proyecto tenga
puntos de encuentro con suficiente frecuencia. Siempre que el equipo se sincronice todos los días, no
es necesario que se reúnan continuamente.
Recursos adicionales 707
Estrategia de integración
- ¿Identifica la estrategia el orden óptimo en el que deben integrarse los
subsistemas, las clases y las rutinas?
- ¿El orden de integración está coordinado con el orden de construcción para que las clases
estén listas para la integración en el momento adecuado?
- ¿Se han especificado bien las interfaces entre los componentes? (Especificar
interfaces no es una tarea de integración, pero verificar que se hayan especificado
bien sí lo es).
- ¿El proyecto se desarrolla con frecuencia (idealmente, diariamente) para respaldar la integración
incremental?
- ¿Se ejecuta una prueba de humo con cada compilación para saber si la compilación
funciona?
- ¿La prueba de humo se mantiene actualizada con el código, ampliándose a medida que el código se
expande?
Recursos adicionales
cc2e.com/2999 Los siguientes son recursos adicionales relacionados con los temas de este capítulo:
Integración
Lakos, Juan.Diseño de software C++ a gran escala. Boston, MA: Addison-Wesley, 1996. Lakos
argumenta que el “diseño físico” de un sistema (su jerarquía de archivos, directorios y bibliotecas)
afecta significativamente la capacidad de un equipo de desarrollo para crear software. Si no presta
atención al diseño físico, los tiempos de construcción serán lo suficientemente largos como para
socavar la integración frecuente. La discusión de Lakos se centra en C++, pero las ideas relacionadas
con el "diseño físico" se aplican tanto a proyectos en otros lenguajes.
708 Capítulo 29: Integración
MyersGlenford J.El arte de las pruebas de software. Nueva York, NY: John Wiley & Sons, 1979. Este
libro clásico de pruebas analiza la integración como una actividad de prueba.
incrementalismo
McConnell, Steve.Desarrollo rápido. Redmond, WA: Microsoft Press, 1996. El Capítulo 7, “Planificación
del ciclo de vida”, entra en muchos detalles sobre las compensaciones involucradas con modelos de
ciclo de vida más flexibles y menos flexibles. Los capítulos 20, 21, 35 y 36 analizan modelos de ciclo de
vida específicos que admiten varios grados de incrementalismo. El Capítulo 19 describe el "diseño
para el cambio", una actividad clave necesaria para respaldar los modelos de desarrollo iterativos e
incrementales.
Puntos clave
- La secuencia de construcción y el enfoque de integración afectan el orden en que se
diseñan, codifican y prueban las clases.
- La integración incremental viene en varias variedades y, a menos que el proyecto sea trivial,
cualquiera de ellas es mejor que la integración por fases.
- El mejor enfoque de integración para cualquier proyecto específico suele ser una combinación de enfoques
de integración de arriba hacia abajo, de abajo hacia arriba, orientados al riesgo y otros. La integración en
forma de T y la integración de cortes verticales son dos enfoques que a menudo funcionan bien.
- Las compilaciones diarias pueden reducir los problemas de integración, mejorar la moral de los desarrolladores y
Herramientas de programación
cc2e.com/3084 Contenido
Temas relacionados
Las herramientas de programación modernas reducen la cantidad de tiempo necesario para la construcción. El uso
de un conjunto de herramientas de vanguardia, y la familiaridad con las herramientas utilizadas, puede aumentar la
productividad en un 50 por ciento o más (Jones 2000; Boehm et al. 2000). Las herramientas de programación
también pueden reducir la cantidad de tedioso trabajo detallado que requiere la programación.
3 Un perro puede ser el mejor amigo del hombre, pero algunas buenas herramientas son los mejores amigos de un
2
1
programador. Como Barry Boehm descubrió hace mucho tiempo, el 20 por ciento de las herramientas tiende a
representar el 80 por ciento del uso de herramientas (1987b). Si te estás perdiendo una de las herramientas más
DATOS DUROS
Este capítulo está enfocado de dos maneras. En primer lugar, cubre únicamente las herramientas de
construcción. Las herramientas de especificación de requisitos, gestión y desarrollo de extremo a extremo
están fuera del alcance de este libro. Consulte la sección "Recursos adicionales" al final del capítulo para
obtener más información sobre herramientas para esos aspectos del desarrollo de software. En segundo
lugar, este capítulo cubre tipos de herramientas en lugar de marcas específicas. Algunas herramientas son
tan comunes que se mencionan por su nombre, pero las versiones, los productos y las compañías
específicos cambian tan rápido que la información sobre la mayoría de ellos estaría desactualizada antes de
que se secara la tinta en estas páginas.
Un programador puede trabajar durante muchos años sin descubrir algunas de las herramientas
más valiosas disponibles. La misión de este capítulo es examinar las herramientas disponibles y
ayudarlo a determinar si ha pasado por alto alguna herramienta que podría ser útil. si eres un
709
710 Capítulo 30: Herramientas de programación
experto en herramientas, no encontrará mucha información nueva en este capítulo. Puede hojear las
partes anteriores del capítulo, leer la Sección 30.6 sobre "Tool Fantasyland" y luego pasar al siguiente
capítulo.
Referencia cruzadaPara obtener detalles Las herramientas de diseño actuales consisten principalmente en herramientas gráficas que crean diagramas de
sobre el diseño, consulte los Capítulos 5 a
diseño. Las herramientas de diseño a veces están integradas en una herramienta de ingeniería de software asistida
9.
por computadora (CASE) con funciones más amplias; algunos proveedores anuncian herramientas de diseño
independientes como herramientas CASE. Las herramientas de diseño gráfico generalmente le permiten expresar un
diseño en notaciones gráficas comunes: UML, diagramas de bloques de arquitectura, diagramas de jerarquía,
diagramas de relación de entidad o diagramas de clase. Algunas herramientas de diseño gráfico solo admiten una
notación. Otros admiten una variedad.
En cierto sentido, estas herramientas de diseño son solo paquetes de dibujo sofisticados. Usando un
paquete de gráficos simple o lápiz y papel, puede dibujar todo lo que la herramienta puede dibujar. Pero las
herramientas ofrecen capacidades valiosas que un paquete de gráficos simple no puede ofrecer. Si dibujó
un gráfico de burbujas y elimina una burbuja, una herramienta de diseño gráfico reorganizará
automáticamente las otras burbujas, incluidas las flechas de conexión y las burbujas de nivel inferior
conectadas a la burbuja. La herramienta también se encarga de la limpieza cuando agrega una burbuja. Una
herramienta de diseño puede permitirle moverse entre niveles de abstracción más altos y más bajos. Una
herramienta de diseño comprobará la coherencia de su diseño y algunas herramientas pueden crear código
directamente a partir de su diseño.
Las herramientas disponibles para trabajar con código fuente son más ricas y maduras que las
herramientas disponibles para trabajar con diseños.
Edición
Este grupo de herramientas se relaciona con la edición del código fuente.
DATOS DUROS
el mejor IDE posible es una buena inversión.
Además de las funciones básicas de procesamiento de texto, los buenos IDE ofrecen estas características:
- Saltar a todos los lugares donde se usa una clase, rutina o variable
- Plantillas para construcciones de lenguaje común (el editor completa la estructura de unpor
bucle después de que el programador escribapor, por ejemplo)
- Listado de cadenas de búsqueda para que no sea necesario volver a escribir las cadenas de uso común
- Deshacer multinivel
Teniendo en cuenta algunos de los editores primitivos que todavía están en uso, es posible que se sorprenda al
Si su editor no admite buscar y reemplazar en varios archivos, aún puede encontrar herramientas
complementarias para hacer ese trabajo. Estas herramientas son útiles para buscar todas las apariciones de
un nombre de clase o nombre de rutina. Cuando encuentre un error en su código, puede usar dichas
herramientas para buscar errores similares en otros archivos.
Puede buscar cadenas exactas, cadenas similares (ignorando las diferencias en mayúsculas) o
expresiones regulares. Las expresiones regulares son particularmente poderosas porque le permiten
buscar patrones de cadenas complejos. Si quisiera encontrar todas las referencias de matrices que
contienen números mágicos (dígitos del "0" al "9"), podría buscar "[", seguido de cero o más espacios,
seguido de uno o más dígitos, seguido de cero o más espacios, seguidos de “]”. Una herramienta de
búsqueda ampliamente disponible se llama "grep". Una consulta grep para números mágicos se
vería así:
Puede hacer que los criterios de búsqueda sean más sofisticados para afinar la búsqueda.
A menudo es útil poder cambiar cadenas en varios archivos. Por ejemplo, si desea dar un mejor
nombre a una rutina, constante o variable global, es posible que deba cambiar el nombre en varios
archivos. Las utilidades que permiten cambios de cadenas en varios archivos facilitan esta tarea, lo
cual es bueno porque debe tener la menor cantidad posible de obstrucciones para crear excelentes
nombres de clases, nombres de rutinas y nombres de constantes. Las herramientas comunes para
manejar cambios de cadenas de múltiples archivos incluyen Perl, AWK y sed.
Herramientas de diferencias
Los programadores a menudo necesitan comparar dos archivos. Si realiza varios intentos
para corregir un error y necesita eliminar los intentos fallidos, un comparador de archivos
comparará los archivos originales y modificados y enumerará las líneas que ha cambiado.
Si está trabajando en un programa con otras personas y desea ver los cambios que han
realizado desde la última vez que trabajó en el código, una herramienta de comparación
como Diff comparará la versión actual con la última versión del código. código en el que
trabajó y muestre las diferencias. Si descubre un nuevo defecto que no recuerda haber
encontrado en una versión anterior de un programa, en lugar de ver a un neurólogo por
amnesia, puede usar un comparador para comparar las versiones actuales y antiguas del
código fuente, determinar exactamente qué cambió, y encontrar el origen del problema.
Combinar herramientas
Un estilo de control de revisión bloquea los archivos de origen para que solo una persona pueda modificar un
archivo a la vez. Otro estilo permite que varias personas trabajen en archivos simultáneamente y maneja los
cambios combinados en el momento del registro. En este modo de trabajo, las herramientas que fusionan los
cambios son críticas. Estas herramientas suelen realizar fusiones simples automáticamente y consultan al usuario si
hay fusiones que entren en conflicto con otras fusiones o que estén más involucradas.
Referencia cruzadaPara obtener detalles Los embellecedores de código fuente mejoran su código fuente para que se vea consistente. Resaltan
sobre el diseño del programa, consulte el
nombres de clases y rutinas, estandarizan su estilo de sangría, dan formato a los comentarios de manera
Capítulo 31, "Diseño y estilo".
consistente y realizan otras funciones similares. Algunos embellecedores pueden poner cada rutina en una
página web separada o en una página impresa o realizar un formato aún más dramático. Muchos
embellecedores le permiten personalizar la forma en que se embellece el código.
Hay al menos dos clases de embellecedores de código fuente. Una clase toma el código fuente como
entrada y produce resultados mucho mejores sin cambiar el código fuente original. Otro tipo de
herramienta cambia el código fuente mismo: estandariza la sangría, el formato de la lista de
parámetros, etc. Esta capacidad es útil cuando se trabaja con grandes cantidades de código
heredado. La herramienta puede realizar gran parte del tedioso trabajo de formateo necesario para
que el código heredado se ajuste a las convenciones de estilo de codificación.
30.2 Herramientas de código fuente 713
Algunas herramientas extraen documentación detallada de la interfaz del programador de los archivos de
código fuente. El código dentro del archivo fuente usa pistas como@etiquetacampos para identificar el texto
que se debe extraer. La herramienta de documentación de la interfaz luego extrae ese texto etiquetado y lo
presenta con un formato agradable. Javadoc es un ejemplo destacado de este tipo de herramienta.
Plantillas
Las plantillas lo ayudan a explotar la simple idea de simplificar las tareas de mecanografía que realiza
con frecuencia y desea realizar de manera constante. Suponga que desea un prólogo de comentario
estándar al comienzo de sus rutinas. Puede crear un prólogo de esqueleto con la sintaxis y los
lugares correctos para todos los elementos que desee en el prólogo estándar. Este esqueleto sería
una "plantilla" que almacenaría en un archivo o en una macro de teclado. Cuando creó una nueva
rutina, podría insertar fácilmente la plantilla en su archivo fuente. Puede usar la técnica de plantilla
para configurar entidades más grandes, como clases y archivos, o entidades más pequeñas, como
bucles.
Si está trabajando en un proyecto de grupo, las plantillas son una manera fácil de fomentar estilos de
codificación y documentación coherentes. Haga que las plantillas estén disponibles para todo el
equipo al comienzo del proyecto, y el equipo las usará porque facilitan su trabajo: obtiene la
consistencia como un beneficio adicional.
Una herramienta de referencias cruzadas enumera variables y rutinas y todos los lugares en los que se
utilizan, normalmente en páginas web.
Un generador de jerarquía de clases produce información sobre árboles de herencia. Esto a veces es
útil en la depuración, pero se usa más a menudo para analizar la estructura de un programa o
modularizar un programa en paquetes o subsistemas. Esta funcionalidad también está disponible en
algunos IDE.
Las herramientas de esta categoría examinan el código fuente estático para evaluar su calidad.
para verificar errores más sutiles, cosas que no están mal desde el punto de vista del compilador
pero que probablemente no tenía la intención de escribir. Por ejemplo, en C++, la instrucción
es una declaración perfectamente legal, pero por lo general está destinado a ser
Reporteros de métricas
Referencia cruzadaPara obtener Algunas herramientas analizan su código e informan sobre su calidad. Por ejemplo, puede obtener
más información sobre las métricas,
herramientas que informan sobre la complejidad de cada rutina para que pueda enfocarse en las rutinas
consulte la Sección 28.4, “Medición”.
más complicadas para una revisión, prueba o rediseño adicional. Algunas herramientas cuentan líneas de
código, declaraciones de datos, comentarios y líneas en blanco en programas completos o rutinas
individuales. Realizan un seguimiento de los defectos y los asocian con los programadores que los
realizaron, los cambios que los corrigen y los programadores que realizan las correcciones. Cuentan las
modificaciones al software y anotan las rutinas que se modifican con mayor frecuencia. Se ha encontrado
que las herramientas de análisis de complejidad tienen un impacto positivo de alrededor del 20 por ciento
en la productividad del mantenimiento (Jones 2000).
Refactorizadores
Referencia cruzadaPara obtener más Un programa de refactorización admite refactorizaciones de código comunes, ya sea de forma independiente o
información sobre la refactorización, consulte
integrada en un IDE. Los navegadores de refactorización le permiten cambiar fácilmente el nombre de una clase en
el Capítulo 24, “Refactorización”.
una base de código completa. Le permiten extraer una rutina simplemente resaltando el código que le gustaría
convertir en una nueva rutina, ingresando el nombre de la nueva rutina y ordenando los parámetros en una lista de
parámetros. Los refactorizadores hacen que los cambios de código sean más rápidos y menos propensos a errores.
Están disponibles para Java y Smalltalk y están disponibles para otros idiomas. Para obtener más información sobre
Fowler 1999).
30.2 Herramientas de código fuente 715
Reestructuradores
Un reestructurador convertirá un plato de código de espagueti conirs a un plato principal más nutritivo de
código mejor estructurado sinirs. Capers Jones informa que en entornos de mantenimiento, las
herramientas de reestructuración de código pueden tener un impacto positivo del 25 al 30 por ciento en la
productividad del mantenimiento (Jones 2000). Un reestructurador tiene que hacer muchas suposiciones
cuando convierte el código, y si la lógica es terrible en el original, seguirá siendo terrible en la versión
convertida. Sin embargo, si está realizando una conversión manualmente, puede usar un reestructurador
para el caso general y ajustar manualmente los casos difíciles. Alternativamente, puede ejecutar el código a
través del reestructurador y usarlo como inspiración para la conversión manual.
Traductores de código
Algunas herramientas traducen código de un idioma a otro. Un traductor es útil cuando tiene
una gran base de código que está trasladando a otro entorno. El peligro de usar un traductor
de idiomas es que si comienza con un código incorrecto, el traductor simplemente traduce el
código incorrecto a un idioma desconocido.
Control de versiones
Referencia cruzadaEstas Puede hacer frente a la proliferación de versiones de software mediante el uso de herramientas de control de versiones para
herramientas y sus beneficios son
- Control de dependencia como el que ofrece la utilidad make asociada con UNIX
- Relacionar los artefactos del proyecto, como los requisitos, el código y los casos de prueba, de modo que
cuando cambie un requisito, pueda encontrar el código y las pruebas que se ven afectados.
Diccionarios de datos
Un diccionario de datos es una base de datos que describe todos los datos importantes de un proyecto. En
muchos casos, el diccionario de datos se centra principalmente en esquemas de bases de datos. En
proyectos grandes, un diccionario de datos también es útil para realizar un seguimiento de los cientos o
miles de definiciones de clase. En proyectos de equipos grandes, es útil para evitar conflictos de nombres.
Un choque puede ser un choque sintáctico directo, en el que el mismo nombre se usa dos veces, o puede ser
un choque (o brecha) más sutil en el que diferentes nombres se usan para significar lo mismo o el mismo
nombre se usa para significar cosas sutilmente diferentes. Para cada elemento de datos (tabla de base de
datos o clase), el diccionario de datos contiene el nombre y la descripción del elemento. El diccionario
también puede contener notas sobre cómo se usa el elemento.
716 Capítulo 30: Herramientas de programación
Las herramientas para trabajar con código ejecutable son tan ricas como las herramientas para trabajar con
código fuente.
Creación de código
Compiladores y Enlazadores
Los compiladores convierten el código fuente en código ejecutable. La mayoría de los programas están escritos
Un enlazador estándar vincula uno o más archivos de objetos, que el compilador ha generado a partir
de sus archivos fuente, con el código estándar necesario para crear un programa ejecutable. Los
enlazadores generalmente pueden vincular archivos de varios idiomas, lo que le permite elegir el
idioma más apropiado para cada parte de su programa sin tener que manejar los detalles de
integración usted mismo.
Un enlazador superpuesto lo ayuda a poner 10 libras en un saco de cinco libras mediante el desarrollo de
programas que se ejecutan en menos memoria que la cantidad total de espacio que consumen. Un enlazador de
superposición crea un archivo ejecutable que carga solo una parte de sí mismo en la memoria en un momento
dado, dejando el resto en un disco hasta que se necesite.
Herramientas de construcción
El propósito de una herramienta de compilación es minimizar el tiempo necesario para compilar un programa utilizando las
versiones actuales de los archivos fuente del programa. Para cada archivo de destino en su proyecto, especifique los archivos
de origen de los que depende el archivo de destino y cómo hacerlo. Las herramientas de compilación también eliminan los
errores relacionados con las fuentes en estados inconsistentes; la herramienta de compilación garantiza que todos se lleven a
un estado coherente. Las herramientas de compilación comunes incluyen la utilidad make que está asociada con UNIX y la
Supongamos que tiene un archivo de destino llamadocarausuario.obj. En el archivo make, indicas que para
hacercarausuario.obj, tienes que compilar el archivocarausuario.cpp. También indicas que carausuario.cpp
depende decarausuario.h,stdlib.h, yproyecto.h. El concepto de “depende de” simplemente significa que si
carausuario.h,stdlib.h, oproyecto.hcambios,carausuario.cppnecesita ser recompilado.
Cuando crea su programa, la herramienta make verifica todas las dependencias que ha descrito y
determina los archivos que deben volver a compilarse. Si cinco de sus 250 archivos fuente dependen
de definiciones de datos encarausuario.hy cambia, make vuelve a compilar automáticamente los
cinco archivos que dependen de él. No vuelve a compilar los 245 archivos que no dependen de
carausuario.h. Usar make o ant supera las alternativas de volver a compilar todos
30.3 Herramientas de código ejecutable 717
250 o recompilar cada archivo manualmente, olvidar uno y obtener extraños errores de desincronización. En
general, las herramientas de compilación como make o ant mejoran sustancialmente el tiempo y la confiabilidad del
ciclo promedio de compilación-enlace-ejecución.
Bibliotecas de códigos
Una buena manera de escribir código de alta calidad en un corto período de tiempo no es escribirlo todo,
sino encontrar una versión de código abierto o comprarla. Puede encontrar bibliotecas de alta calidad en al
menos estas áreas:
- Clases de contenedores
- herramientas de imagen
- Administradores de licencias
- Operaciones matemáticas
Si no puede encontrar el código que desea, ¿qué le parece pedirle a otra persona que lo escriba? No es necesario
que se ponga la chaqueta amarilla de cuadros escoceses y adopte el parloteo de un vendedor de autos para
engañar a otra persona para que escriba su código. Puede encontrar herramientas que escriban código para usted,
y dichas herramientas a menudo se integran en IDE.
Las herramientas de generación de código tienden a centrarse en aplicaciones de bases de datos, pero eso
incluye muchas aplicaciones. Los generadores de código comúnmente disponibles escriben código para
bases de datos, interfaces de usuario y compiladores. El código que generan rara vez es tan bueno como el
código generado por un programador humano, pero muchas aplicaciones no requieren código hecho a
mano. Para algunos usuarios, vale más tener 10 aplicaciones que funcionen que tener una que funcione
excepcionalmente bien.
Los generadores de código también son útiles para hacer prototipos de código de producción. Con
un generador de código, es posible que pueda piratear un prototipo en unas pocas horas que
demuestre los aspectos clave de una interfaz de usuario o que pueda experimentar con varios
enfoques de diseño. Puede llevarle varias semanas codificar a mano tanta funcionalidad. Si solo estás
experimentando, ¿por qué no hacerlo de la forma más económica posible?
El inconveniente común de los generadores de código es que tienden a generar código que es casi
ilegible. Si alguna vez tiene que mantener dicho código, puede arrepentirse de no haberlo escrito a
mano en primer lugar.
Configuración e instalación
preprocesadores
Referencia cruzadaPara obtener detalles Los preprocesadores y las funciones de macro del preprocesador son útiles para la depuración
sobre cómo mover las ayudas para la
porque facilitan el cambio entre el código de desarrollo y el código de producción. Durante el
depuración dentro y fuera del código,
Si usa un lenguaje con construcciones de control primitivas, como ensamblador, puede escribir un
preprocesador de flujo de control para emular las construcciones estructuradas desi-entonces-otro
ytiempobucles en su idioma.
cc2e.com/3091 Si su idioma no tiene un preprocesador, puede usar un preprocesador independiente como parte de
su proceso de compilación. Un preprocesador fácilmente disponible es M4, disponible en
www.gnu.org/software/m4/.
depuración
Referencia cruzadaEstas Estas herramientas ayudan en la depuración:
herramientas y sus beneficios son
- Perfiladores de ejecución
- Seguimiento de monitores
Las herramientas de prueba, que se analizan a continuación, están relacionadas con las herramientas de depuración.
Pruebas
Referencia cruzadaEstas Estas características y herramientas pueden ayudarlo a realizar pruebas efectivas:
herramientas y sus beneficios son
- Depuradores simbólicos
- Perturbadores del sistema (rellenos de memoria, sacudidores de memoria, fallos selectivos de memoria,
- Herramientas de comparación (para comparar archivos de datos, resultados capturados e imágenes de pantalla)
- Andamio
- Herramientas de inyección de defectos
Ajuste de código
Perfiladores de ejecución
Un generador de perfiles de ejecución observa su código mientras se ejecuta y le dice cuántas veces
se ejecuta cada declaración o cuánto tiempo pasa el programa en cada declaración o ruta de
ejecución. Crear un perfil de su código mientras se ejecuta es como que un médico presione un
estetoscopio contra su pecho y le diga que tosa. Le da una idea de cómo funciona su programa,
dónde están los puntos críticos y dónde debe centrar sus esfuerzos de ajuste de código.
Algún día querrá ver el código ensamblador generado por su lenguaje de alto nivel. Algunos
compiladores de lenguaje de alto nivel generan listados de ensamblador. Otros no lo hacen, y debe
usar un desensamblador para volver a crear el ensamblador a partir del código de máquina que
genera el compilador. Mirar el código ensamblador generado por su compilador le muestra cuán
eficientemente su compilador traduce código de lenguaje de alto nivel en código de máquina. Puede
decirle por qué el código de alto nivel que parece rápido se ejecuta lentamente. En el Capítulo 26,
"Técnicas de ajuste de código", varios de los resultados de referencia son contradictorios. Mientras
comparaba ese código, con frecuencia me refería a las listas del ensamblador para comprender
mejor los resultados que no tenían sentido en el lenguaje de alto nivel.
El entorno UNIX es famoso por su colección de pequeñas herramientas con nombres graciosos
que funcionan bien juntas: grep, diff, sort, make, crypt, tar, lint, ctags, sed, awk, vi y otras. Los
lenguajes C y C++, estrechamente acoplados con UNIX, encarnan la misma filosofía; la
biblioteca estándar de C++ se compone de pequeñas funciones que se pueden combinar
fácilmente en funciones más grandes porque funcionan muy bien juntas.
30.5 Creación de sus propias herramientas de programación 721
cc2e.com/3026 Algunos programadores trabajan tan productivamente en UNIX que se lo llevan consigo.
Utilizan herramientas similares a UNIX para respaldar sus hábitos UNIX en Windows y otros
entornos. Un tributo al éxito del paradigma UNIX es la disponibilidad de herramientas que
ponen un disfraz de UNIX en otras máquinas. Por ejemplo, cygwin proporciona herramientas
equivalentes a UNIX que funcionan en Windows (www.cygwin.com).
Suponga que le dan cinco horas para hacer el trabajo y tiene una opción:
- Dedique cuatro horas y 45 minutos a construir febrilmente una herramienta para hacer el trabajo y luego
La mayoría de los buenos programadores elegirían la primera opción una vez entre un millón y la
segunda opción en todos los demás casos. Construir herramientas es parte de la urdimbre y la trama
de la programación. Casi todas las grandes organizaciones (organizaciones con más de 1000
programadores) tienen herramientas internas y grupos de apoyo. Muchos tienen requisitos propios y
herramientas de diseño que son superiores a las del mercado (Jones 2000).
Puede escribir muchas de las herramientas descritas en este capítulo. Hacerlo puede no
ser rentable, pero no existen enormes barreras técnicas para hacerlo.
La mayoría de los proyectos medianos y grandes necesitan herramientas especiales exclusivas para el proyecto. Por
ejemplo, es posible que necesite herramientas para generar tipos especiales de datos de prueba, verificar la calidad
de los archivos de datos o emular hardware que aún no está disponible. Estos son algunos ejemplos de soporte de
- Microsoft planeó incluir una nueva tecnología de fuentes en una versión de su entorno gráfico de
Windows. Dado que tanto los archivos de datos de fuentes como el software para mostrar las
fuentes eran nuevos, los errores podrían haber surgido de los datos o del software. Los
desarrolladores de Microsoft escribieron varias herramientas personalizadas para verificar errores
en los archivos de datos, lo que mejoró su capacidad para discriminar entre errores de datos de
fuentes y errores de software.
722 Capítulo 30: Herramientas de programación
- Una compañía de seguros desarrolló un sistema ambicioso para calcular sus aumentos de
tarifas. Debido a que el sistema era complicado y la precisión era esencial, era necesario
verificar cuidadosamente cientos de tasas calculadas, aunque el cálculo manual de una sola
tasa tomaba varios minutos. La compañía escribió una herramienta de software separada para
calcular las tarifas de una en una. Con la herramienta, la empresa podía calcular una sola tarifa
en unos pocos segundos y verificar las tarifas del programa principal en una pequeña fracción
del tiempo que hubiera tomado verificar manualmente las tarifas del programa principal.
Parte de la planificación de un proyecto debe ser pensar en las herramientas que podrían ser
necesarias y asignar tiempo para construirlas.
Guiones
Un script es una herramienta que automatiza una tarea repetitiva. En algunos sistemas, los
scripts se denominan archivos por lotes o macros. Los scripts pueden ser simples o complejos,
y algunos de los más útiles son los más fáciles de escribir. Por ejemplo, llevo un diario y, para
proteger mi privacidad, lo cifro excepto cuando escribo en él. Para asegurarme de cifrarlo y
descifrarlo siempre correctamente, tengo un script que descifra mi diario, ejecuta el
procesador de textos y luego cifra el diario. El guión se ve así:
los%1es el campo de mi contraseña que, por razones obvias, no está incluido en el script.
El script me ahorra el trabajo de escribir (y escribir mal) todos los parámetros y garantiza
que siempre realice todas las operaciones y las realice en el orden correcto.
Si se encuentra escribiendo algo de más de cinco caracteres más de unas pocas veces al día, es un
buen candidato para un script o un archivo por lotes. Los ejemplos incluyen secuencias de
compilación/enlace, comandos de copia de seguridad y cualquier comando con muchos parámetros.
Fortran logró hacer posible que científicos e ingenieros escribieran programas, pero desde
nuestro punto de vista actual, Fortran parece ser un lenguaje de programación de nivel
relativamente bajo. Difícilmente eliminó la necesidad de programadores, y lo que la industria
experimentó con Fortran es indicativo del progreso en la industria del software en su
conjunto.
30.6 Herramienta Fantasyland 723
La industria del software desarrolla constantemente nuevas herramientas que reducen o eliminan algunos
de los aspectos más tediosos de la programación: detalles de diseño de declaraciones fuente; pasos
necesarios para editar, compilar, vincular y ejecutar un programa; trabajo necesario para encontrar llaves
que no coincidan; el número de pasos necesarios para crear cuadros de mensaje estándar; y así. A medida
que cada una de estas nuevas herramientas comienza a demostrar ganancias incrementales en la
productividad, los expertos extrapolan esas ganancias al infinito, asumiendo que las ganancias
eventualmente "eliminarán la necesidad de programación". Pero lo que sucede en realidad es que cada
nueva innovación de programación llega con algunas imperfecciones. A medida que pasa el tiempo, se
eliminan las imperfecciones y se realiza todo el potencial de la innovación. Sin embargo, una vez que se
comprende el concepto fundamental de la herramienta, se logran más ganancias eliminando las dificultades
accidentales que se crearon como efectos secundarios de la creación de la nueva herramienta. La
eliminación de estas dificultades accidentales no aumenta la productividad per se; simplemente elimina el
"un paso atrás" de la ecuación típica de "dos pasos adelante, un paso atrás".
Durante las últimas décadas, los programadores han visto numerosas herramientas que se suponía
que eliminarían la programación. Primero fueron los lenguajes de tercera generación. Luego fueron
los lenguajes de cuarta generación. Entonces fue la programación automática. Luego fueron las
herramientas CASE. Entonces fue la programación visual. Cada uno de estos avances generó valiosas
mejoras incrementales en la programación de computadoras y, en conjunto, han hecho que la
programación sea irreconocible para cualquiera que haya aprendido a programar antes de estos
avances. Pero ninguna de estas innovaciones logró eliminar la programación.
Referencia cruzadaLas La razón de esta dinámica es que, en su esencia, la programación es fundamentalmente difícil—
razones de la dificultad de la
incluso con un buen soporte de herramientas. No importa qué herramientas estén disponibles, los
programación se describen en
“Dificultades Accidentales y
programadores tendrán que lidiar con el desordenado mundo real; tendremos que pensar
Esenciales” en la Sección 5.2. rigurosamente en secuencias, dependencias y excepciones; y tendremos que tratar con usuarios
finales que no pueden decidirse. Siempre tendremos que lidiar con interfaces mal definidas para otro
software y hardware, y tendremos que dar cuenta de las regulaciones, las reglas comerciales y otras
fuentes de complejidad que surgen fuera del mundo de la programación informática.
Siempre necesitaremos personas que puedan cerrar la brecha entre el problema del mundo
real a resolver y la computadora que se supone que debe resolver el problema. Estas personas
se llamarán programadores sin importar si estamos manipulando registros de máquina en
ensamblador o cuadros de diálogo en Microsoft Visual Basic. Mientras tengamos
computadoras, necesitaremos personas que les digan qué hacer, y esa actividad se llamará
programación.
Cuando escuche que un proveedor de herramientas dice: "Esta nueva herramienta eliminará la programación de
Recursos adicionales
cc2e.com/3098 Eche un vistazo a estos recursos adicionales para obtener más información sobre las herramientas de programación:
cc2e.com/3005 www.sdmagazine.com/jolts.Revista de desarrollo de softwareEl sitio web del premio anual Jolt
Productivity es una buena fuente de información sobre las mejores herramientas actuales.
Hunt, Andrew y David Thomas.El programador pragmático. Boston, MA: Addison-Wesley, 2000. La
Sección 3 de este libro proporciona una discusión detallada de las herramientas de programación,
incluidos editores, generadores de código, depuradores, control de código fuente y herramientas
relacionadas.
cc2e.com/3012 Vaughn-Nichols, Steven. “Construyendo un mejor software con mejores herramientas,”Computadora IEEE,
septiembre de 2003, págs. 12–14. Este artículo examina las iniciativas de herramientas lideradas por IBM,
Microsoft Research y Sun Research.
vidrio, roberto l.Conflicto de software: ensayos sobre el arte y la ciencia de la ingeniería de software.
Englewood Cliffs, NJ: Yourdon Press, 1991. El capítulo titulado "Recomendado: un conjunto de herramientas
de software estándar mínimo" proporciona un contrapunto reflexivo a la visión de más herramientas es
mejor. Glass aboga por la identificación de un conjunto mínimo de herramientas que debería estar
disponible para todos los desarrolladores y propone un kit de inicio.
Boehm, Barry, et al.Estimación de costos de software con Cocomo II. Reading, MA: Addison-Wesley,
2000. Tanto el libro de Jones como el de Boehm dedican secciones al impacto del uso de
herramientas en la productividad.
- ¿Utiliza el control de versiones para administrar el código fuente, el contenido, los requisitos, los
- ¿Ha considerado las bibliotecas de código como alternativas a la escritura de código personalizado, cuando
estén disponibles?
Puntos clave 725
- ¿Ha creado herramientas personalizadas que ayudarían a satisfacer las necesidades de su proyecto
Puntos clave
- Los programadores a veces pasan por alto algunas de las herramientas más poderosas durante
años antes de descubrirlas.
- Las herramientas están fácilmente disponibles para editar, analizar la calidad del código,
refactorizar, controlar versiones, depurar, probar y ajustar el código.
- Las buenas herramientas pueden reducir los aspectos más tediosos del desarrollo de
software, pero no pueden eliminar la necesidad de programar, aunque seguirán
remodelando lo que entendemos por "programación".
Parte VII
Artesanía de software
En esta parte:
Diseño y estilo
cc2e.com/3187 Contenido
Temas relacionados
Las técnicas de este capítulo no afectan la velocidad de ejecución, el uso de memoria u otros
aspectos de un programa que son visibles desde fuera del programa. Afectan lo fácil que es
entender el código, revisarlo y revisarlo meses después de escribirlo. También afectan lo fácil
que es para otros leer, comprender y modificar una vez que estás fuera de escena.
Este capítulo está repleto de detalles quisquillosos a los que se refiere la gente cuando habla de “atención al
detalle”. A lo largo de la vida de un proyecto, la atención a esos detalles marca la diferencia en la calidad
inicial y la capacidad de mantenimiento final del código que escribe. Dichos detalles son demasiado
integrales para el proceso de codificación como para cambiarlos de manera efectiva más adelante. Si se van
a hacer, deben hacerse durante la construcción inicial. Si está trabajando en un proyecto de equipo, pídale a
su equipo que lea este capítulo y acuerde un estilo de equipo antes de comenzar a codificar.
Es posible que no esté de acuerdo con todo lo que lea aquí, pero mi punto es menos ganar su
acuerdo que convencerlo de que considere los problemas relacionados con el estilo de
formato. Si tiene presión arterial alta, pase al siguiente capítulo, es menos controvertido.
729
730 Capítulo 31: Diseño y estilo
Extremos de diseño
/* Utilice la técnica de clasificación por inserción para clasificar la matriz de "datos" en orden ascendente. Esta
rutina asume que data[ firstElement ] no es el primer elemento en data y que se puede acceder a
data[ firstElement-1 ]. */ public void InsertionSort( int[] data, int firstElement, int lastElement ) { /* Reemplazar
CODIFICACIÓN el elemento en el límite inferior con un elemento garantizado para ser el primero en una lista ordenada. */ int
HORROR
límiteinferior = datos[primerElemento-1]; datos[primerElemento-1] = SORT_MIN; /* Los elementos en las
posiciones firstElement hasta sortBoundary-1 siempre están ordenados. En cada paso por el ciclo,
sortBoundary aumenta, y el elemento en la posición del nuevo sortBoundary probablemente no esté en su
lugar ordenado en la matriz, por lo que se inserta en el lugar adecuado en algún lugar entre firstElement y
sortBoundary.
/* Utilice la técnica de clasificación por inserción para clasificar la matriz de "datos" en orden ascendente. Esta
rutina asume que data[ firstElement ] no es el primer elemento en data y que se puede acceder a
data[ firstElement-1 ]. */ public void InsertionSort( int[] data, int firstElement, int lastElement ) { /* Reemplazar
CODIFICACIÓN el elemento en el límite inferior con un elemento garantizado para ser el primero en una lista ordenada. */
HORROR
por (
int sortBoundary = firstElement+1;
sortBoundary <= lastElement;
31.1 Fundamentos del diseño 731
sortBoundary++
){
int insertVal = data[ sortBoundary ]; int insertPos =
sortBoundary;
while (insertarVal < datos[ insertarPos-1 ] )
{ datos[ insertarPos ] = datos[ insertarPos-1 ]; insertarPos =
insertarPos-1;
}
datos[ insertarPos ] = insertarVal; }
Este código es el mismo que el del Listado 31-1. Aunque la mayoría de la gente estaría de acuerdo en
que el diseño del código es mucho mejor que el del primer ejemplo, el código aún no es muy legible.
El diseño todavía está abarrotado y no ofrece ninguna pista sobre la organización lógica de la rutina.
Está aproximadamente en 0 en la recta numérica del diseño de malo a bueno. El primer ejemplo fue
inventado, pero el segundo no es nada raro. He visto programas de varios miles de líneas con un
diseño al menos tan malo como este. Sin documentación y con nombres de variables incorrectos, la
legibilidad general era peor que en este ejemplo. Este código está formateado para la computadora;
no hay evidencia de que el autor esperara que los humanos leyeran el código. El listado 31-3 es una
mejora.
/* Utilice la técnica de clasificación por inserción para clasificar la matriz de "datos" en orden
ascendente. Esta rutina asume que data[ firstElement ] no es el primer elemento en data y que se
puede acceder a data[ firstElement-1 ].
*/
*/
for ( int sortBoundary = firstElement + 1; sortBoundary <= lastElement;
sortBoundary++ ) {
int insertVal = data[ sortBoundary ]; int insertPos =
sortBoundary;
while (insertarVal <datos[insertarPos - 1]) {
datos[ insertarPos ] = datos[ insertarPos - 1 ]; insertarPos
= insertarPos - 1;
}
732 Capítulo 31: Diseño y estilo
Este diseño de la rutina es un fuerte positivo en la recta numérica del diseño de malo a bueno. La
rutina ahora se presenta de acuerdo con los principios que se explican a lo largo de este capítulo. La
rutina se ha vuelto mucho más legible, y ahora es evidente el esfuerzo que se ha puesto en la
documentación y los buenos nombres de variables. Los nombres de las variables eran igual de
buenos en los ejemplos anteriores, pero el diseño era tan pobre que no eran útiles.
La única diferencia entre este ejemplo y los dos primeros es el uso de espacios en blanco, el código y los
comentarios son exactamente iguales. El espacio en blanco solo es útil para los lectores humanos: su
computadora podría interpretar cualquiera de los tres fragmentos con la misma facilidad. ¡No se sienta mal
si no puede hacerlo tan bien como su computadora!
Hacer que el código se vea bonito vale algo, pero vale menos que mostrar la estructura del
código. Si una técnica muestra mejor la estructura y otra se ve mejor, use la que muestra mejor
la estructura. Este capítulo presenta numerosos ejemplos de estilos de formato que se ven bien
PUNTO CLAVE
pero que tergiversan la organización lógica del código. En la práctica, priorizar la
representación lógica generalmente no crea un código feo, a menos que la lógica del código
sea fea. Las técnicas que hacen que el buen código luzca bien y el mal código luzca mal son
más útiles que las técnicas que hacen que todo el código luzca bien.
Listado 31-4Ejemplo de Java de diseño que cuenta diferentes historias para humanos y computadoras.
Listado 31-5Otro ejemplo de Java de diseño que cuenta diferentes historias para humanos y
computadoras.
x = 3+4 * 2+7;
Un lector humano de este código se inclinaría a interpretar la declaración en el sentido de que Xse le
asigna el valor(3+4) * (2+7), o63. La computadora ignorará el espacio en blanco y obedecerá las
reglas de precedencia, interpretando la expresión como3 + (4*2) + 7, o18. El punto es que un buen
esquema de diseño haría que la estructura visual de un programa coincidiera con la estructura
lógica, o contara la misma historia al ser humano que le cuenta a la computadora.
Referencia cruzadaUn buen diseño es En el diseño, quizás más que en cualquier otro aspecto de la programación, entra en juego la
una clave para la legibilidad. Para obtener
diferencia entre comunicarse con la computadora y comunicarse con lectores humanos. La parte
detalles sobre el valor de la legibilidad,
Unos años más tarde, Ben Shneiderman duplicó los resultados de Chase y Simon en el campo de la
programación de computadoras y reportó sus resultados en un artículo llamado “Exploratory
Experiments in Programmer Behavior” (1976). Shneiderman descubrió que cuando las declaraciones
del programa se organizaban en un orden sensato, los expertos podían recordarlas mejor que los
novatos. Cuando se barajan las declaraciones, se reduce la superioridad de los expertos. Los
resultados de Shneiderman han sido confirmados en otros estudios (McKeithen et al. 1981, Soloway y
Ehrlich 1984). El concepto básico también ha sido confirmado en los juegos Go y bridge y en
electrónica, música y física (McKeithen et al. 1981).
Después de que publiqué la primera edición de este libro, Hank, uno de los programadores
que revisó el manuscrito, dijo: “Me sorprendió que no argumentaras con más fuerza a favor de
un estilo de llaves que se ve así:
por ( ...)
{
}
“Me sorprendió que incluso incluyeras el estilo de corsé que se veía así:
por ( ...) {
}
“Pensé que, con Tony y yo discutiendo por el primer estilo, preferirías ese”.
Respondí: “Quieres decir que estabas defendiendo el primer estilo, y Tony estaba defendiendo
el segundo estilo, ¿no es así? Tony defendió el segundo estilo, no el primero”.
Hank respondió: “Es gracioso. En el último proyecto en el que Tony y yo trabajamos juntos, yo
prefería el estilo n.º 2 y Tony prefería el estilo n.º 1. Pasamos todo el proyecto discutiendo qué
estilo era el mejor. ¡Supongo que nos convencimos para preferir los estilos de los demás!”.
Esta experiencia, así como los estudios citados anteriormente, sugieren que la estructura ayuda a los
expertos a percibir, comprender y recordar características importantes de los programas. Los
programadores expertos a menudo se aferran tenazmente a sus propios estilos, incluso cuando son muy
diferentes de otros estilos utilizados por otros programadores expertos. La conclusión es que los detalles de
un método específico para estructurar un programa son mucho menos importantes que el hecho de que el
programa esté estructurado de manera consistente.
PUNTO CLAVE
31.1 Fundamentos del diseño 735
Referencia cruzadaSi está A un nivel general, está claro que algunas formas de diseño son mejores que otras. Los diseños
mezclando software y religión,
sucesivamente mejores del mismo código al comienzo de este capítulo lo hicieron evidente. Este libro no se
puede leer la Sección 34.9,
“Thou Shalt Shalt Software and mantendrá alejado de los puntos más finos del diseño solo porque son controvertidos. Los buenos
Religion Separed” antes de leer programadores deben tener la mente abierta acerca de sus prácticas de diseño y aceptar las prácticas que
el resto de este capítulo.
han demostrado ser mejores que aquellas a las que están acostumbrados, incluso si adaptarse a un nuevo
método resulta en cierta incomodidad inicial.
avanzados tienenfuerte
menos subjetivos si especifica explícitamente los criterios de sus preferencias. Explícitamente, entonces, un
expectativas sobre cómo deberían buen esquema de diseño debería hacer lo siguiente:
ser los programas y cuándo se
violan esas expectativas de forma Representar con precisión la estructura lógica del código.Ese es el teorema fundamental del
aparentemente inocua
formato nuevamente: el propósito principal de un buen diseño es mostrar la estructura lógica del
maneras—su desempeño
cae drásticamente.
código. Por lo general, los programadores usan sangría y otros espacios en blanco para mostrar la
—Elliot Soloway y estructura lógica.
Kate Ehrlich
Representar consistentemente la estructura lógica del código.Algunos estilos de diseño tienen reglas
con tantas excepciones que es difícil seguir las reglas de manera consistente. Un buen estilo se aplica a la
mayoría de los casos.
Mejorar la legibilidadUna estrategia de sangría que es lógica pero que hace que el código sea más
difícil de leer es inútil. Un esquema de diseño que requiere espacios solo donde el compilador los
requiere es lógico pero no legible. Un buen esquema de diseño hace que el código sea más fácil de
leer.
736 Capítulo 31: Diseño y estilo
Soportar modificacionesLos mejores esquemas de diseño resisten bien la modificación del código. La
modificación de una línea de código no debería requerir la modificación de varias otras.
Puede usar los criterios para un buen esquema de diseño para fundamentar una discusión sobre el
diseño de modo que las razones subjetivas para preferir un estilo sobre otro queden expuestas.
PUNTO CLAVE
Ponderar los criterios de diferentes maneras podría llevar a conclusiones diferentes. Por
ejemplo, si cree firmemente que es importante minimizar el número de líneas utilizadas en la
pantalla, tal vez porque tiene una pantalla de computadora pequeña, puede criticar un estilo
porque usa dos líneas más para una lista de parámetros de rutina que otro.
espacio en blanco
Utilice espacios en blanco para mejorar la legibilidad. Los espacios en blanco, incluidos espacios, tabulaciones, saltos
de línea y líneas en blanco, son la principal herramienta disponible para mostrar la estructura de un programa.
Referencia cruzadaAlguno No pensarías en escribir un libro sin espacios entre palabras, sin saltos de párrafo y sin
Los investigadores han explorado la
divisiones en capítulos. Tal libro podría ser legible de cabo a rabo, pero sería virtualmente
similitud entre la estructura de un
Sección 32.5.
importante para la organización lógica del tema.
AgrupamientoDesde el otro lado del espejo, el espacio en blanco se agrupa, asegurándose de que
las declaraciones relacionadas estén agrupadas.
Al escribir, los pensamientos se agrupan en párrafos. Un párrafo bien escrito contiene solo
oraciones que se relacionan con un pensamiento en particular. No debe contener oraciones
extrañas. De manera similar, un párrafo de código debe contener declaraciones que realicen
una sola tarea y que estén relacionadas entre sí.
El uso de líneas en blanco es una forma de indicar cómo está organizado un programa. Puede
usarlos para dividir grupos de declaraciones relacionadas en párrafos, para separar rutinas
entre sí y para resaltar comentarios.
3 Aunque esta estadística en particular puede ser difícil de poner en práctica, un estudio realizado por Gorla, Benander
2
1
y Benander encontró que la cantidad óptima de líneas en blanco en un programa es de aproximadamente 8 a 16 por
ciento. Por encima del 16 por ciento, el tiempo de depuración aumenta drásticamente (1990).
DATOS DUROS
DATOS DUROS
estudios encontraron correlaciones entre la sangría y una mejor comprensión (Miaria et al.
1983). Los sujetos puntuaron entre un 20 y un 30 por ciento más en una prueba de
comprensión cuando los programas tenían un esquema de sangría de dos a cuatro espacios
que cuando los programas no tenían ninguna sangría.
3 El mismo estudio encontró que era importante no subestimar ni exagerar la estructura lógica
2
1
de un programa. Los puntajes más bajos de comprensión se lograron en programas que no
DATOS DUROS
tenían ninguna sangría. Los segundos más bajos se lograron en programas que usaban
sangría de seis espacios. El estudio concluyó que la sangría de dos a cuatro espacios era
óptima. Curiosamente, muchos sujetos del experimento sintieron que la sangría de seis
espacios era más fácil de usar que las sangrías más pequeñas, a pesar de que sus puntajes
eran más bajos. Probablemente se deba a que la sangría de seis espacios parece agradable.
Pero independientemente de lo bonito que se vea, la sangría de seis espacios resulta ser
menos legible. Este es un ejemplo de una colisión entre el atractivo estético y la legibilidad.
738 Capítulo 31: Diseño y estilo
paréntesis
Usa más paréntesis de los que crees que necesitas. Use paréntesis para aclarar
expresiones que involucran más de dos términos. Puede que no sean necesarios, pero
añaden claridad y no le cuestan nada. Por ejemplo, ¿cómo se evalúan las siguientes
expresiones?
Versión C++:12 + 4 % 3 * 7 / 8
La pregunta clave es, ¿tuviste que pensar en cómo se evalúan las expresiones? ¿Puede
confiar en su respuesta sin verificar algunas referencias? Incluso los programadores
experimentados no responden con confianza, y es por eso que debe usar paréntesis cada
vez que tenga alguna duda sobre cómo se evalúa una expresión.
- Bloques puros
Bloques puros
Gran parte de la controversia sobre el diseño se deriva de la incomodidad inherente de los lenguajes
de programación más populares. Un lenguaje bien diseñado tiene estructuras de bloques claras que
se prestan a un estilo de sangría natural. En Visual Basic, por ejemplo, cada construcción de control
tiene su propio terminador y no puede usar una construcción de control sin usar el terminador. El
código se bloquea naturalmente. Algunos ejemplos en Visual Basic se muestran en el Listado 31-6, el
Listado 31-7 y el Listado 31-8:
31.3 Estilos de diseño 739
Una construcción de control en Visual Basic siempre tiene una declaración inicial:si-entonces,Tiempo,
ySeleccione el casoen los ejemplos—y siempre tiene un correspondienteFinaldeclaración. Aplicar
sangría al interior de la estructura no es una práctica controvertida, y las opciones para alinear las
otras palabras clave son algo limitadas. El Listado 31-9 es una representación abstracta de cómo
funciona este tipo de formato:
A XXXXXXXXXXXXXXXXXXX
B XXXXXXXXXXX
C XXXXXXXXXXXXXX
D XXXX
740 Capítulo 31: Diseño y estilo
La controversia sobre las estructuras de control de formato surge en parte del hecho de que
algunos lenguajes norequerirestructuras de bloques. puedes tener unsi-entoncesseguido de
una sola declaración y no tiene un bloque formal. Tienes que agregar unprincipio-finemparejar
o abrir y cerrar llaves para crear un bloque en lugar de obtener uno automáticamente con
cada construcción de control. desacoplamientoempezaryfinalde la estructura de control, como
lo hacen lenguajes como C++ y Java con{y}—lleva a preguntas sobre dónde poner el empezary
final. En consecuencia, muchos problemas de sangría son problemas solo porque tiene que
compensar estructuras de lenguaje mal diseñadas. En las siguientes secciones se describen
varias formas de compensar.
A XXXXXXXXXXXXXXXXXXX
B XXXXXXXXXXXX
C XXXXXXXXXXXXXX
D XXXX
A XXXXXXXXXXXXX{X
B XXXXXXXXXXXXX
C XXXXXXXXXXXXXXXX
D }X
31.3 Estilos de diseño 741
Algunos ejemplos de cómo se ve el estilo en C++ se muestran en el Listado 31-12, el Listado 31-13 y el
Listado 31-14:
caso Color_Verde:
declaración1;
declaración2;
...
descanso;
defecto:
declaración1;
declaración2;
...
descanso;
Este estilo de alineación funciona bastante bien. Se ve bien, se puede aplicar de manera constante y
se puede mantener. Es compatible con el Teorema fundamental de formato en el sentido de que
ayuda a mostrar la estructura lógica del código. Es una elección de estilo razonable. Este estilo es
estándar en Java y común en C++.
742 Capítulo 31: Diseño y estilo
A XXXXXXXXXXXXXXXXXX
B XXXXXXXXXXX
C XXXXXXXXXXXXX
D XXXX
Pero en este estilo, para tratar elempezary elfinalcomo partes de la estructura del
bloque en lugar de la declaración de control, debe poner elempezaral comienzo del
bloque (en lugar de al final de la declaración de control) y elfinalal final del bloque (en
lugar de terminar la declaración de control). En resumen, tendrá que hacer algo como lo
que se hace en el Listado 31-16:
A XXXXXXXXXXXXXXXXXXX
{XXXXXXXXXXXXXXXX
B XXXXXXXXXXXXXXXXX
C XXXXXXXXXXXXXXXX
}X
caso Color_Verde:
declaración1;
declaración2;
...
descanso;
defecto:
declaración1;
declaración2;
...
descanso;
Este estilo de alineación funciona bien; admite el Teorema fundamental de formato (una vez más, al
exponer la estructura lógica subyacente del código). Su única limitación es que no se puede aplicar
literalmente eninterruptor/casodeclaraciones en C++ y Java, como se muestra en el Listado 31-19.
(LosdescansoLa palabra clave es un sustituto de la llave de cierre, pero no hay equivalente a la llave
de apertura).
Otra estrategia de diseño es el "diseño de línea final", que se refiere a un gran grupo de estrategias
de diseño en las que el código se sangra en la mitad o al final de la línea. La sangría de línea final se
utiliza para alinear un bloque con la palabra clave que lo inició, para alinear los parámetros
subsiguientes de una rutina bajo su primer parámetro, para alinear casos en uncaso declaración, y
para otros fines similares. El listado 31-20 es un ejemplo abstracto:
A XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX X
B XXXXXXXXXXXXX
C XXXXXXXXXXXXXX
D XX
El estilo de diseño de línea final funciona aceptablemente en algunos casos. El Listado 31-22 es un ejemplo
en el que funciona:
Listado 31-22Un ejemplo raro de Visual Basic en el que el diseño final parece atractivo.
En este caso, elDespués,Más, yTerminara silas palabras clave están alineadas y el código que las
sigue también está alineado. El efecto visual es una estructura lógica clara.
Listado 31-23Un ejemplo más típico de Visual Basic, en el que el diseño de la línea final se descompone.
Más
reducción = 0.025
Terminara si
Más
descuento = 0.0
Terminara si
V413HAV
31.4 Diseño de estructuras de control 745
¿Cuál es la razón del extraño formato de laMáscláusulas al final del ejemplo? Están sangrados
constantemente bajo las palabras clave correspondientes, pero es difícil argumentar que sus
sangrados aclaran la estructura lógica. Y si el código se modificara de modo que cambiara la
longitud de la primera línea, el estilo de línea final requeriría que se cambiara la sangría de las
declaraciones correspondientes. Esto plantea un problema de mantenimiento que el bloque
puro, la emulación de bloque puro y el usoprincipio-finpara designar los límites de los bloques
no lo hacen.
Podrías pensar que estos ejemplos son inventados solo para resaltar un punto, pero este estilo
ha sido persistente a pesar de sus inconvenientes. Numerosos libros de texto y referencias de
programación han recomendado este estilo. El primer libro que vi que recomendaba este estilo
se publicó a mediados de la década de 1970, y el más reciente se publicó en 2003.
En general, el diseño de la línea final es inexacto, difícil de aplicar de manera consistente y difícil de
mantener. Verá otros problemas con el diseño de línea final a lo largo del capítulo.
En C++, simplemente puede elegir el estilo que le guste o el que prefiera la mayoría de las
personas de su equipo. Ya sea emulación de bloques puros oprincipio-finlos límites de bloque
funcionan igualmente bien. El único estudio que comparó los dos estilos no encontró
diferencias estadísticamente significativas entre los dos en lo que se refiere a la
comprensibilidad (Hansen y Yim 1987).
Ninguno de los estilos es infalible, y cada uno requiere un compromiso "razonable y obvio" ocasional.
Es posible que prefieras uno u otro por motivos estéticos. Este libro utiliza el estilo de bloque puro en
sus ejemplos de código, por lo que puede ver muchas más ilustraciones de cómo funciona ese estilo
con solo hojear sus ejemplos. Una vez que haya elegido un estilo, obtendrá los mayores beneficios de
un buen diseño cuando lo aplique de manera consistente.
Trabajar con bloques de estructura de control requiere atención a algunos detalles finos. Aquí hay
algunas pautas:
A XXXXXXXXXXXXXXXXXXX
B XXXXXXX
C XXXXXXXX
D XXXXXXXXXXXXX
mi XXXX
En este ejemplo, ¿el enunciado B está subordinado al enunciado A? No parece parte del
enunciado A, y tampoco parece estar subordinado a él. Si ha utilizado este enfoque,
cambie a uno de los dos estilos de diseño descritos anteriormente y su formato será más
consistente.
HORROR
{
Línea de lectura (yo );
Las declaraciones debajo de la ProcesarLínea( i );
empezarestán sangrados como si }
estuvieran subordinados a él.
31.4 Diseño de estructuras de control 747
Este es otro ejemplo de un estilo que se ve bien pero viola el Teorema fundamental del
formato. Un estudio no mostró diferencias en la comprensión entre programas con
sangría simple y programas con sangría doble (Miaria et al. 1983), pero este estilo no
muestra con precisión la estructura lógica del programa. LeerLínea()yLínea de proceso ()
se muestran como si estuvieran lógicamente subordinados a lacomienzofinpar, y no lo
son.
XXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXX
XXXXXXXXXXX
XXXXX
XXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXXX
XXXXXXXXXXXXX
XXXXX
Otras Consideraciones
Aunque la sangría de los bloques es el principal problema al dar formato a las estructuras de control, se
encontrará con algunos otros tipos de problemas, así que aquí hay algunas pautas más:
cursor.start = comenzandoScanLine;
cursor.fin = finalizandoScanLine;
ventana.título = editarVentana.título;
dimensiones.de.la.ventana = editarVentana.dimensiones;
ventana.foregroundColor = preferencias de usuario.color de primer
cursor.blinkRate = plano; editMode.blinkRate;
ventana.backgroundColor = preferencias de usuario.color de fondo;
GuardarCursor( cursor );
EstablecerCursor( cursor );
Referencia cruzadaSi utiliza el Este código se ve bien, pero las líneas en blanco lo mejorarían de dos maneras. Primero,
proceso de programación de
cuando tiene un grupo de declaraciones que no tienen que ejecutarse en ningún orden en
pseudocódigo, sus bloques de código
El código reorganizado muestra que están sucediendo dos cosas. En el primer ejemplo, la falta de
organización de las declaraciones y las líneas en blanco, y el viejo truco de los signos iguales alineados,
hacen que las declaraciones parezcan más relacionadas de lo que son.
La segunda forma en que el uso de líneas en blanco tiende a mejorar el código es que abre espacios
naturales para los comentarios. En el Listado 31-30, un comentario sobre cada bloque
complementaría muy bien el diseño mejorado.
Dar formato a bloques de una sola instrucción de forma coherenteUn bloque de sentencia única es una
sentencia única que sigue una estructura de control, como una sentencia que sigue a unasiprueba. En cuyo
caso,empezaryfinalno son necesarios para la compilación correcta y tiene las tres opciones de estilo que se
muestran en el Listado 31-31:
31.4 Diseño de estructuras de control 749
Listado 31-31Ejemplo de Java de opciones de estilo para bloques de una sola instrucción.
Estilo 1 si (expresión)
una declaración;
Estilo 2a si ( expresión ) {
una declaración;
}
Estilo 2b si (expresión)
{
una declaración;
}
Hay argumentos a favor de cada uno de estos enfoques. El estilo 1 sigue el esquema de
sangría que se usa con los bloques, por lo que es consistente con otros enfoques. El estilo
2 (ya sea 2a o 2b) también es consistente, y elprincipio-finpar reduce la posibilidad de que
agregue declaraciones después delsiprueba y olvida agregarempezaryfinal. Este sería un
error particularmente sutil porque la sangría le diría que todo está bien, pero el
compilador no interpretaría la sangría de la misma manera. La principal ventaja del estilo
3 sobre el estilo 2 es que es más fácil de escribir. Su ventaja sobre el Estilo 1 es que si se
copia en otro lugar del programa, es más probable que se copie correctamente. Su
desventaja es que en un depurador orientado a líneas, el depurador trata la línea como
una sola línea y no muestra si ejecuta la declaración después de lasiprueba.
if ((('0' <= inChar) && (inChar <= '9')) || (('a' <= inChar) &&
(inChar <= 'z')) || (('A' <= inChar) && (inChar <= 'Z'))) . . .
Este es un ejemplo de formateo para la computadora en lugar de para lectores humanos. Al dividir la
expresión en varias líneas, como en el Listado 31-33, puede mejorar la legibilidad.
750 Capítulo 31: Diseño y estilo
Referencia cruzadaPara obtener EvitarirsLa razón original para evitarirs era que hacían difícil probar que un programa era
detalles sobre el uso deirs, consulte la
correcto. Ese es un buen argumento para todas las personas que quieren probar que sus
Sección 17.3, “ir.”
programas son correctos, que es prácticamente nadie. El problema más apremiante para la
mayoría de los programadores es queirs hacen que el código sea difícil de formatear. ¿Se
sangra todo el código entre eliry la etiqueta a la que va? ¿Qué pasa si tienes varios?irs a la
misma etiqueta? ¿Se sangra cada uno nuevo debajo del anterior? He aquí algunos consejos
para formatearirs:
Irlas etiquetas deben estar alineadas a - Evitarirs. Esto evita el problema de formato por completo.
la izquierda en mayúsculas y deben
incluir el nombre del programador, el - Use un nombre en mayúsculas para la etiqueta a la que va el código. Esto hace que la etiqueta sea obvia.
número de teléfono de su casa y el
- Coloque la declaración que contiene eliren una línea por sí mismo. Esto hace que elir
—Abdul Nizar obvio.
- Ponle la etiqueta alirva a en una línea por sí mismo. Rodéalo con líneas en blanco. Esto hace que la
etiqueta sea obvia. Elimine la sangría de la línea que contiene la etiqueta al margen izquierdo para
que la etiqueta sea lo más obvia posible.
Referencia cruzadaPara conocer Listado 31-34Ejemplo en C++ de sacar lo mejor de una mala situación (usandoir).
otros métodos para abordar este
int numFilesToPurge = 0;
MakePurgeFileList(fileList, numFilesToPurge);
if ( !OpenFile(fileToPurge) ) {
código de error = FileError_NotOpen; ir a
Aquí está unir. END_PROC;
}
if (! OverwriteFile (fileToPurge)) {
código de error = FileError_CantOverwrite; ir a
Aquí está unir. END_PROC;
}
Referencia cruzadaPara obtener detalles El ejemplo de C++ en el Listado 31-34 es relativamente largo para que pueda ver un caso en el que
sobre el usocasodeclaraciones, consulte
un programador experto podría decidir conscientemente que unires la mejor opción de diseño. En
la Sección 15.2, “caso Declaraciones."
tal caso, el formato que se muestra es lo mejor que puede hacer.
Sin excepción de línea final paracasodeclaracionesUno de los peligros del diseño de la línea final
surge en el formato decasodeclaraciones. Un estilo popular de formatocasos es sangrarlas a la
derecha de la descripción de cada caso, como se muestra en el Listado 31-35. El gran problema con
este estilo es que es un dolor de cabeza de mantenimiento.
}
752 Capítulo 31: Diseño y estilo
Si agrega un caso con un nombre más largo que cualquiera de los nombres existentes, debe
quitar todos los casos y el código que los acompaña. La gran sangría inicial hace que sea difícil
acomodar más lógica, como se muestra en laBlanco y azulcaso. La solución es cambiar a su
incremento de sangría estándar. Si sangra declaraciones en un bucle de tres espacios, sangre
los casos en uncasoinstrucción el mismo número de espacios, como en el Listado 31-36:
caso BallColor_Orange:
GirarDedo();
descanso;
caso BallColor_FluorescentGreen:
Pico();
descanso;
caso BallColor_Blanco:
QuitarCubrir();
descanso;
caso BallColor_WhiteAndBlue:
if (colorprincipal == ColorDeBola_Blanco) {
QuitarCubrir();
}
de lo contrario si (color principal == BallColor_Blue) {
Desenrollar();
}
descanso;
defecto:
FatalError("Tipo de bola no reconocido"); descanso;
Este es un caso en el que muchas personas podrían preferir el aspecto del primer ejemplo. Sin
embargo, por la capacidad de adaptarse a líneas más largas, la consistencia y la facilidad de
mantenimiento, el segundo enfoque gana sin dudas.
Si tienes uncasodeclaración en la que todos los casos son exactamente paralelos y todas las acciones
son cortas, podría considerar poner el caso y la acción en la misma línea. En la mayoría de los casos,
sin embargo, vivirá para arrepentirse. El formato es un dolor al principio y se rompe con las
modificaciones, y es difícil mantener la estructura de todos los casos en paralelo, ya que algunas de
las acciones cortas se vuelven más largas.
31.5 Diseño de declaraciones individuales 753
Longitud de la declaración
Referencia cruzadaPara obtener detalles Una regla común y algo obsoleta es limitar la longitud de la línea de declaración a 80
sobre cómo documentar estados de
caracteres. Aquí están las razones:
cuenta individuales, consulte
- Las líneas de más de 80 caracteres a menudo no caben en papel de 8,5” x 11”, especialmente
cuando el código se imprime “2 por cara” (2 páginas de código en cada página impresa física).
Con pantallas más grandes, tipos de letra estrechos y modo horizontal, el límite de 80
caracteres parece cada vez más arbitrario. Una sola línea de 90 caracteres suele ser más
legible que una que se ha partido en dos solo para evitar que se extienda a la columna 80. Con
la tecnología moderna, probablemente esté bien exceder las 80 columnas de vez en cuando.
Use espacios para hacer que las expresiones lógicas sean legiblesLa expresion
while(rutaNombre[rutaInicio+posición]<>';') y
((startPath+posición)<longitud(rutaNombre)) hacer
Como regla general, debe separar los identificadores de otros identificadores con espacios. Si
utiliza esta regla, eltiempoexpresión se ve así:
Algunos artistas de software pueden recomendar mejorar esta expresión en particular con
espacios adicionales para enfatizar su estructura lógica, de esta manera:
Esto está bien, aunque el primer uso de espacios fue suficiente para garantizar la legibilidad. Sin embargo,
los espacios extra casi nunca duelen, así que sé generoso con ellos.
754 Capítulo 31: Diseño y estilo
Use espacios para hacer que las referencias de matrices sean legiblesLa expresion
tasa bruta[census[groupId].género,census[groupId].ageGroup]
Use espacios para hacer que los argumentos de rutina sean legibles¿Cuál es el cuarto argumento de la
siguiente rutina?
ReadEmployeeData(maxEmps,empData,inputFile,empCount,inputError);
¿Cuál fue más fácil de encontrar? Esta es una pregunta realista que vale la pena porque las
posiciones de los argumentos son significativas en todos los principales lenguajes procesales.
Es común tener una especificación de rutina en la mitad de la pantalla y la llamada a la rutina
en la otra mitad, y comparar cada parámetro formal con cada parámetro real.
Aquí hay un enfoque sensato y consistente que es particularmente útil en Java, C, C ++, Visual
Basic y otros lenguajes que fomentan nombres largos de variables:
Haga obvia la incompletud de un enunciado.A veces, una declaración debe dividirse en líneas, ya
sea porque es más larga de lo que permiten los estándares de programación o porque es demasiado
larga para ponerla en una sola línea. Haz que sea obvio que la parte de la declaración en la primera
línea es solo una parte de una declaración. La forma más sencilla de hacerlo es dividir la declaración
de modo que la parte de la primera línea sea manifiestamente incorrecta sintácticamente si está sola.
Algunos ejemplos se muestran en el Listado 31-37:
Además de decirle al lector que la declaración no está completa en la primera línea, el salto
ayuda a evitar modificaciones incorrectas. Si se eliminara la continuación de la declaración, la
primera línea no se vería como si simplemente hubiera olvidado un paréntesis o un punto y
coma; claramente necesitaría algo más.
Un enfoque alternativo que también funciona bien es colocar el carácter de continuación al comienzo
de la línea de continuación, como se muestra en el Listado 31-38.
Si bien este estilo no inducirá un error de sintaxis con un colgante&&o+, hace que sea más
fácil buscar operadores en el borde izquierdo de la columna, donde el texto está alineado, que
en el borde derecho, donde está irregular. Tiene la ventaja adicional de iluminar la estructura
de las operaciones, como se ilustra en el Listado 31-39.
facturatotal = facturatotal
+ compras del cliente [ ID del cliente ]
+ CitySalesTax (compras del cliente [ID del cliente])
+ StateSalesTax(customerPurchases[customerID])
+ Impuesto sobre el estadio de fútbol()
- Exención de impuestos sobre las ventas (compras del cliente [ID del cliente]);
Mantener juntos los elementos estrechamente relacionadosCuando rompa una línea, mantenga juntas
las cosas que pertenecen juntas: referencias a arreglos, argumentos a una rutina, etc. El ejemplo que se
muestra en el Listado 31-40 es de mala forma:
CODIFICACIÓN
HORROR
Es cierto que este salto de línea sigue la directriz de hacer obvia la incompletitud de la declaración,
pero lo hace de una manera que hace que la declaración sea innecesariamente difícil de leer. Es
posible que encuentre un caso en el que el descanso sea necesario, pero en este caso no lo es. Es
mejor mantener todas las referencias de la matriz en una sola línea. El Listado 31-41 muestra un
mejor formato:
756 Capítulo 31: Diseño y estilo
facturacliente = SaldoAnterior(historialpago[idcliente]) +
LateCharge(pagoHistorial[IDcliente]);
Aplicar sangría a las líneas de continuación de llamada de rutina el importe estándarSi normalmente
sangra tres espacios para declaraciones en un ciclo o un condicional, sangre las líneas de continuación
para una rutina por tres espacios. Algunos ejemplos se muestran en el Listado 31-42:
Una alternativa a este enfoque es alinear las líneas de continuación bajo el primer
argumento de la rutina, como se muestra en el Listado 31-43:
Listado 31-43Ejemplos de Java de sangría de una línea de continuación de llamada de rutina para enfatizar
nombres de rutina.
Desde un punto de vista estético, parece un poco irregular en comparación con el primer enfoque. También es difícil
de mantener a medida que cambian los nombres de las rutinas, cambian los nombres de los argumentos, etc. La
mayoría de los programadores tienden a gravitar hacia el primer estilo con el tiempo.
Facilite la búsqueda del final de una línea de continuaciónUn problema con el enfoque que
se muestra arriba es que no puede encontrar fácilmente el final de cada línea. Otra alternativa
es poner cada argumento en una línea propia e indicar el final del grupo con un paréntesis de
cierre. El listado 31-44 muestra cómo se ve.
Listado 31-44Ejemplos de Java de formateo de líneas de continuación de llamada de rutina, un argumento por línea.
Dibujar linea(
ventana.norte,
ventana.sur,
ventana.este,
ventana.oeste,
ancho actual,
31.5 Diseño de declaraciones individuales 757
atributo actual
);
Obviamente, este enfoque requiere mucho espacio. Sin embargo, si los argumentos de una rutina
son referencias de campo de objeto largas o nombres de puntero, como lo son los dos últimos, usar
un argumento por línea mejora sustancialmente la legibilidad. los);al final del bloque deja claro el
final de la llamada. Tampoco tiene que volver a formatear cuando agrega un parámetro;
simplemente agrega una nueva línea.
En la práctica, normalmente solo es necesario dividir unas pocas rutinas en varias líneas. Puede manejar a
otros en una línea. Cualquiera de las tres opciones para dar formato a las llamadas de rutina de varias líneas
funciona bien si se usa de manera constante.
Aplicar sangría a las líneas de continuación del extracto de control del importe estándarSi te quedas
sin espacio para unporbucle, untiempobucle, o unsiinstrucción, sangre la línea de continuación con la
misma cantidad de espacio que sangra las declaraciones en un bucle o después de unasideclaración. En el
Listado 31-45 se muestran dos ejemplos:
Referencia cruzadaAlguno- Esto cumple con los criterios establecidos anteriormente en el capítulo. La parte de continuación de la
veces, la mejor solución para una
declaración se hace lógicamente: siempre se sangra debajo de la declaración que continúa. La sangría se
prueba complicada es ponerla en
No alinear los lados derechos de las instrucciones de asignaciónEn la primera edición de este libro,
recomendé alinear los lados derechos de las declaraciones que contienen asignaciones como se muestra
en el Listado 31-46:
Listado 31-46Ejemplo de Java de diseño de línea final utilizado para la continuación de declaración de asignación:
mala práctica.
Con el beneficio de la retrospectiva de 10 años, descubrí que, si bien este estilo de sangría puede parecer atractivo,
se convierte en un dolor de cabeza para mantener la alineación de los signos iguales a medida que cambian los
nombres de las variables y el código se ejecuta a través de herramientas que sustituyen las tabulaciones por espacios
y espacios para tabulaciones. También es difícil de mantener ya que las líneas se mueven entre diferentes partes del
Para mantener la coherencia con las otras pautas de sangría, así como la facilidad de mantenimiento, trate
los grupos de declaraciones que contienen operaciones de asignación como trataría otras declaraciones,
como muestra el Listado 31-47:
Aplicar sangría a las líneas de continuación del extracto de asignación del importe estándarEn
el Listado 31-47, la línea de continuación para la tercera declaración de cesión tiene la sangría del
monto estándar. Esto se hace por las mismas razones por las que las declaraciones de asignación en
general no tienen un formato especial: legibilidad general y mantenibilidad.
yo = 0; j = 0; k = 0; DestroyBadLoopNames( i, j, k );
31.5 Diseño de declaraciones individuales 759
Un argumento a favor de poner varias declaraciones en una línea es que requiere menos líneas de
espacio en la pantalla o papel de impresora, lo que permite ver más código a la vez. También es
una forma de agrupar declaraciones relacionadas, y algunos programadores creen que
proporciona pistas de optimización al compilador.
Estas son buenas razones, pero las razones para limitarse a una declaración por línea son más
convincentes:
- Poner cada declaración en una línea propia proporciona una visión precisa de la complejidad de un
programa. No oculta la complejidad haciendo que las declaraciones complejas parezcan triviales. Las
declaraciones que son complejas parecen complejas. Las declaraciones que son fáciles parecen
fáciles.
Referencia cruzadaLas - Poner varias declaraciones en una línea no proporciona pistas de optimización para los
optimizaciones de rendimiento a
compiladores modernos. Los compiladores de optimización de hoy en día no dependen de las
nivel de código se analizan en el
- Con declaraciones en sus propias líneas, es fácil encontrar errores de sintaxis cuando su
compilador proporciona solo los números de línea de los errores. Si tiene varias declaraciones
en una línea, el número de línea no le indica qué declaración tiene un error.
- Con una instrucción por línea, es fácil recorrer paso a paso el código con depuradores
orientados a líneas. Si tiene varias declaraciones en una línea, el depurador las ejecuta
todas a la vez y debe cambiar a ensamblador para recorrer las declaraciones
individuales.
- Con uno a una línea, es fácil editar declaraciones individuales: eliminar una línea o
convertir temporalmente una línea en un comentario. Si tiene varias declaraciones en
una línea, debe editarlas entre otras declaraciones.
En C++, evite usar múltiples operaciones por línea (efectos secundarios)Los efectos secundarios son
consecuencias de una declaración distinta de su consecuencia principal. En C++, el++operador en una línea
que contiene otras operaciones es un efecto secundario. Asimismo, asignar un valor a una variable y usar el
lado izquierdo de la asignación en un condicional es un efecto secundario.
Los efectos secundarios tienden a dificultar la lectura del código. Por ejemplo, sinortees igual4, ¿cuál es la impresión
del estado de cuenta que se muestra en el Listado 31-48?
ImprimirMensaje( ++n, n + 2 );
760 Capítulo 31: Diseño y estilo
+ + n;
ImprimirMensaje( n, n + 2 );
Si todavía no está convencido de que debe poner los efectos secundarios en las líneas por sí mismas, trate
de averiguar qué hace la rutina que se muestra en el Listado 31-50:
El segundo ejemplo parece más elaborado que el primero, aunque las operaciones
realizadas en el segundo ejemplo son idénticas. La razón por la que parece más
elaborado es que no oculta la complejidad de las operaciones.
31.5 Diseño de declaraciones individuales 761
Referencia cruzadaPara obtener detalles El rendimiento mejorado tampoco justifica poner varias operaciones en la misma línea. porque
los dosstrcpy()las rutinas son lógicamente equivalentes, esperaría que el compilador generara
sobre el ajuste de código, consulte el
En este caso, la versión "inteligente" conlleva una penalización de velocidad del 11 por ciento, lo que hace
que parezca mucho menos inteligente. Los resultados varían de un compilador a otro, pero en general
sugieren que hasta que haya medido las ganancias de rendimiento, es mejor que se esfuerce primero por
la claridad y la corrección, y luego por el rendimiento.
Incluso si lee declaraciones con efectos secundarios con facilidad, tenga piedad de otras personas que leerán su
código. La mayoría de los buenos programadores necesitan pensar dos veces para comprender las expresiones con
efectos secundarios. Permítales usar sus células cerebrales para comprender las preguntas más importantes de
cómo funciona su código en lugar de los detalles sintácticos de una expresión específica.
Rápidamente, en la declaración de datos del Listado 31-52, ¿qué tipo de variable esfondo
actual?
Listado 31-52Ejemplo de C++ de agrupar más de una declaración de variable en una línea.
int índice_fila, idx_columna; Color color anterior, color actual, color siguiente; Punto anteriorSuperior,
anteriorInferior, actualSuperior, actualInferior, siguienteSuperior, siguienteInferior; Fuente tipo de letra
anterior, tipo de letra actual, tipo de letra siguiente; Opciones de color[ NUM_COLORS ];
CODIFICACIÓN
HORROR
Este es un ejemplo extremo, pero no está muy alejado de un estilo mucho más común que se
muestra en el Listado 31-53:
Listado 31-53Ejemplo de C++ de agrupar más de una declaración de variable en una línea.
Este no es un estilo poco común de declarar variables, y la variable aún es difícil de encontrar porque
todas las declaraciones están atascadas. El tipo de variable también es difícil de encontrar. Ahora,
¿qué essiguienteColortipo en el Listado 31-54?
Listado 31-54Ejemplo de C++ de legibilidad lograda al poner solo una declaración de variable
en cada línea.
En t índicefila;
En t columnaIdx;
Color color anterior;
Color color actual;
Color siguienteColor;
Punto anteriorSuperior;
Punto anteriorInferior;
Punto actualTop;
Punto fondo actual;
Punto siguienteTop;
Punto siguienteInferior;
Fuente tipo de letra anterior;
Fuente tipo de letra actual;
Fuente siguienteTipo de letra;
Es cierto que este estilo ocupa mucho espacio en la pantalla: 20 líneas en lugar de las tres del
primer ejemplo, aunque esas tres líneas eran bastante feas. No puedo señalar ningún estudio
que muestre que este estilo genera menos errores o una mayor comprensión. Sin embargo, si
Sally Programmer, Jr. me pidiera que revisara su código y sus declaraciones de datos se
parecieran al primer ejemplo, diría "De ninguna manera, demasiado difícil de leer". Si se
pareciera al segundo ejemplo, diría "Uh... tal vez me comunique contigo". Si se vieran como el
ejemplo final, diría: "Ciertamente, es un placer".
Declarar variables cerca de donde se usaron por primera vezUn estilo que es preferible a declarar todas las
variables en un bloque grande es declarar cada variable cerca de donde se usa por primera vez. Esto reduce el
"intervalo" y el "tiempo de vida" y facilita la refactorización del código en rutinas más pequeñas cuando sea
necesario. Para obtener más detalles, consulte "Mantener las variables 'vivas' durante el menor tiempo posible" en la
Sección 10.4.
Ordene las declaraciones con sensatezEn el Listado 31-54, las declaraciones se agrupan por
tipos. La agrupación por tipos suele ser sensata ya que las variables del mismo tipo tienden a
usarse en operaciones relacionadas. En otros casos, puede optar por ordenarlos
alfabéticamente por nombre de variable. Aunque el orden alfabético tiene muchos defensores,
creo que es demasiado trabajo para lo que vale. Si su lista de variables es tan larga que alfa-
31.6 Diseño de comentarios 763
el orden bético ayuda, tu rutina probablemente sea demasiado grande. Divídalo para que tenga
rutinas más pequeñas con menos variables.
ListaEmpleados* empleados;
Archivo* archivo de entrada;
El problema de colocar el asterisco al lado del nombre del tipo en lugar del nombre de la
variable es que, cuando coloca más de una declaración en una línea, el asterisco se aplicará
solo a la primera variable aunque el formato visual sugiera que se aplica a todas las variables.
en la línea. Puede evitar este problema colocando el asterisco al lado del nombre de la variable
en lugar del nombre del tipo, como en el Listado 31-56:
ListaEmpleados *empleados;
Archivo *archivo de entrada;
Este enfoque tiene la debilidad de sugerir que el asterisco es parte del nombre de la
variable, lo cual no es. La variable se puede utilizar con o sin el asterisco.
empleados de EmployeeListPointer;
FilePointer archivo de entrada;
El problema particular abordado por este enfoque se puede resolver requiriendo que todos los
punteros se declaren usando tipos de puntero, como se muestra en el Listado 31-57, o requiriendo no
más de una declaración de variable por línea. ¡Asegúrese de elegir al menos una de estas soluciones!
autodocumentación”.
dificultan la legibilidad.
764 Capítulo 31: Diseño y estilo
Sangrar un comentario con su código correspondienteLa sangría visual es una ayuda valiosa para
comprender la estructura lógica de un programa y los buenos comentarios no interfieren con la
sangría visual. Por ejemplo, ¿cuál es la estructura lógica de la rutina que se muestra en el Listado
31-58?
Más
Si tipo de transacción = Transaction_CustomerReturn Entonces
' intente obtener la aprobación del gerente y luego acepte o rechace la devolución ' en
función de si se otorga la aprobación
GetMgrApproval( isTransactionApproved If )
( isTransactionApproved ) Entonces
AceptarRetornoDeCliente( cantidad de transacción )
Más
RechazarDevoluciónDeCliente( cantidad de transacción )
Terminara si
Más
Terminara si
Terminara si
próximo
En este ejemplo, no obtiene mucha idea de la estructura lógica porque los comentarios
oscurecen por completo la sangría visual del código. Puede que le resulte difícil creer que
alguien alguna vez tome la decisión consciente de usar ese estilo de sangría, pero lo he
visto en programas profesionales y conozco al menos un libro de texto que lo
recomienda.
El código que se muestra en el Listado 31-59 es exactamente el mismo que el del Listado 31-58,
excepto por la sangría de los comentarios.
31.6 Diseño de comentarios 765
Más
Si tipo de transacción = Transaction_CustomerReturn Entonces
' intente obtener la aprobación del gerente y luego acepte o rechace la devolución ' en
función de si se otorga la aprobación
GetMgrApproval( isTransactionApproved If )
( isTransactionApproved ) Entonces
AceptarRetornoDeCliente( cantidad de transacción )
Más
RechazarDevoluciónDeCliente( cantidad de transacción )
Terminara si
Más
' no se requiere la aprobación del gerente, así que acepte la
devolución AcceptCustomerReturn (transactionAmount)
Terminara si
Final Si
Final Si
próximo
Marca cada comentario con al menos una línea en blancoSi alguien está tratando de obtener una
descripción general de su programa, la forma más efectiva de hacerlo es leer los comentarios sin leer
el código. Establecer comentarios con líneas en blanco ayuda al lector a escanear el código. Un
ejemplo se muestra en el Listado 31-60:
// comentario cero
CódigoDeclaraciónCero;
CódigoDeclaraciónUno;
// comenta uno
CódigoDeclaraciónDos;
CódigoDeclaraciónTres;
766 Capítulo 31: Diseño y estilo
Algunas personas usan una línea en blanco antes y después del comentario. Dos espacios en blanco usan
más espacio de visualización, pero algunas personas piensan que el código se ve mejor que con uno solo.
Un ejemplo se muestra en el Listado 31-61:
// comentario cero
CódigoDeclaraciónCero;
CódigoDeclaraciónUno;
// comenta uno
CódigoDeclaraciónDos;
CódigoDeclaraciónTres;
A menos que su espacio de exhibición sea escaso, este es un juicio puramente estético y puede
hacerlo en consecuencia. En esto, como en muchas otras áreas, el hecho de que exista una
convención es más importante que los detalles específicos de la convención.
escritura de una rutina, consulte la Use líneas en blanco para separar partes de una rutinaUse líneas en blanco entre el encabezado de la
Sección 9.3, “Construcción de rutinas rutina, sus declaraciones de datos y constantes nombradas (si las hay) y su cuerpo.
mediante el uso del PPP”. Para una
Estos encabezados de rutina son puramente utilitarios. La computadora puede leerlos tan bien como puede
leer encabezados en cualquier otro formato, pero causan problemas a los humanos. Sin un esfuerzo
consciente para hacer que los encabezados sean difíciles de leer, ¿cómo podrían ser peores?
31.7 Diseño de rutinas 767
El segundo enfoque en el diseño de encabezado de rutina es el diseño de línea final, que generalmente
funciona bien. El Listado 31-63 muestra los mismos encabezados de rutina reformateados:
Listado 31-63Ejemplo de C++ de encabezados de rutina con un diseño de línea final mediocre.
En t * número de empleados,
...
vacío Tipo de inserción( OrdenarArray datos,
En t primer elemento,
En t último elemento)
Referencia cruzadaPara obtener más El enfoque final es limpio y estéticamente atractivo. El principal problema es que se necesita mucho
detalles sobre el uso de parámetros de
trabajo para mantener, y los estilos que son difíciles de mantener no se mantienen. Supongamos que
rutina, consulte la Sección 7.5, “Cómo usar
Los ejemplos que se muestran en el Listado 31-64, formateados con sangría estándar, son igual de
atractivos estéticamente pero requieren menos trabajo de mantenimiento.
En t * número de empleados,
bool * es un error de entrada
)
...
Este estilo se sostiene mejor bajo modificación. Si el nombre de la rutina cambia, el cambio no
tiene efecto en ninguno de los parámetros. Si se agregan o eliminan parámetros, solo se debe
modificar una línea, más o menos una coma. Las señales visuales son similares a las del
esquema de sangría para un bucle o unsideclaración. Su ojo no tiene que escanear diferentes
partes de la página para cada rutina individual para encontrar información significativa; sabe
dónde está la información en todo momento.
768 Capítulo 31: Diseño y estilo
Este estilo se traduce a Visual Basic de manera sencilla, aunque requiere el uso de caracteres
de continuación de línea, como se muestra en el Listado 31-65:
Listado 31-65Ejemplo de Visual Basic de encabezados de rutina con sangría estándar legible
y mantenible.
trabajadoras”.
2.Constructores y destructores
3.Rutinas públicas
4.rutinas protegidas
5.Rutinas privadas y datos de miembros
1.Comentario de encabezado que describe el contenido del archivo en el que se encuentra la clase
2.datos de clase
3.Rutinas públicas
4.rutinas protegidas
5.rutinas privadas
31.8 Disposición de Clases 769
Si tiene más de una clase en un archivo, identifique cada clase claramenteLas rutinas que están
relacionadas deben agruparse en clases. Un lector que escanee su código debería poder decir
fácilmente qué clase es cuál. Identifique cada clase claramente utilizando varias líneas en blanco
entre ella y las clases contiguas. Una clase es como un capítulo de un libro. En un libro, comienza
cada capítulo en una nueva página y usa letra grande para el título del capítulo. Enfatice el comienzo
de cada clase de manera similar. Un ejemplo de separación de clases se muestra en el Listado 31-66:
Esta es la última rutina de una // crea una cadena idéntica a sourceString excepto que los // espacios en blanco se
clase. reemplazan con guiones bajos.
void EditString::ConvertBlanks(
carbonizarse * cadena fuente,
carbonizarse * cadena objetivo
){
Assert( strlen( sourceString ) <= MAX_STRING_LENGTH ); Afirmar
(cadenafuente! = NULL);
Afirmar (cadenaobjetivo! = NULL); int
charIndex = 0;
hacer {
if (CadenaFuente[ÍndiceCaracteres] == " " ) {
targetString[charIndex] = '_';
}
más {
targetString[charIndex] = sourceString[charIndex];
}
índicechar++;
} while sourceString[ charIndex ] != '\0';
}
Esta es la primera rutina en una // encuentra el máximo aritmético de arg1 y arg2 int
nueva clase. Math::Max( int arg1, int arg2 ) {
si ( arg1 > arg2 ) {
devolver arg1;
}
más {
devolver arg2;
}
}
Esta rutina está separada de // encuentra el mínimo aritmético de arg1 y arg2 int
la rutina anterior solo por Math::Min( int arg1, int arg2 ) {
líneas en blanco. si (arg1 <arg2) {
devolver arg1;
}
más {
devolver arg2;
}
}
770 Capítulo 31: Diseño y estilo
Evite enfatizar demasiado los comentarios dentro de las clases. Si marca cada rutina y comenta con
una fila de asteriscos en lugar de líneas en blanco, tendrá dificultades para encontrar un dispositivo
que enfatice efectivamente el comienzo de una nueva clase. Un ejemplo se muestra en el Listado
31-67:
//**************************************************** ******************** //
**************************** ******************************************* //
FUNCIONES MATEMÁTICAS
//
// Esta clase contiene las funciones matemáticas del programa. //
**************************************************** ******************** //
**************************** *******************************************
//**************************************************** ********************** //
encontrar el máximo aritmético de arg1 y arg2
//**************************************************** ********************** int
Matemáticas::Max( int arg1, int arg2 ) {
//**************************************************** *********************
si ( arg1 > arg2 ) {
devolver arg1;
}
más {
devolver arg2;
}
}
//**************************************************** ********************** //
encontrar el mínimo aritmético de arg1 y arg2
//**************************************************** ********************** int
Matemáticas::Min( int arg1, int arg2 ) {
//**************************************************** *********************
si (arg1 <arg2) {
devolver arg1;
}
más {
devolver arg2;
}
}
En este ejemplo, se destacan tantas cosas con asteriscos que nada se enfatiza
realmente. El programa se convierte en un denso bosque de asteriscos. Aunque es
más un juicio estético que técnico, en formato, menos es más.
Si debe separar partes de un programa con largas líneas de caracteres especiales, desarrolle una
jerarquía de caracteres (del más denso al más ligero) en lugar de confiar exclusivamente en
asteriscos. Por ejemplo, use asteriscos para divisiones de clases, guiones para divisiones de rutina y
líneas en blanco para comentarios importantes. Abstenerse de juntar dos filas de asteriscos o
guiones. Un ejemplo se muestra en el Listado 31-68:
31.8 Disposición de Clases 771
//**************************************************** ********************** //
FUNCIONES MATEMÁTICAS
//
// Esta clase contiene las funciones matemáticas del programa. //
**************************************************** *********************
La ligereza de esta línea en //------------------------------------------------ ---------------------- // encontrar el máximo aritmético de arg1 y arg2
comparación con la línea de
asteriscos refuerza visualmente //------------------------------------------------ ------------- int Matemáticas::Max( int arg1, int arg2 ) {
el hecho de que la rutina está
Este consejo sobre cómo identificar múltiples clases dentro de un solo archivo se aplica solo cuando
su idioma restringe la cantidad de archivos que puede usar en un programa. Si está utilizando C++,
Java, Visual Basic u otros lenguajes que admitan varios archivos de origen, coloque solo una clase en
cada archivo a menos que tenga una razón convincente para hacerlo de otra manera (como incluir
algunas clases pequeñas que forman un único archivo). patrón). Sin embargo, dentro de una sola
clase, aún puede tener subgrupos de rutinas y puede agruparlos utilizando técnicas como las que se
muestran aquí.
Poner una clase en un archivoUn archivo no es solo un cubo que contiene un código. Si su idioma
lo permite, un archivo debe contener una colección de rutinas que admita un solo propósito. Un
archivo refuerza la idea de que una colección de rutinas están en la misma clase.
772 Capítulo 31: Diseño y estilo
Referencia cruzadaPara obtener Todas las rutinas dentro de un archivo forman la clase. La clase podría ser una que el programa
detalles sobre las diferencias
realmente reconozca como tal, o podría ser simplemente una entidad lógica que haya creado como
entre clases y rutinas y cómo
convertir una colección de rutinas
parte de su diseño.
en una clase, consulte el Capítulo
6, “Funcionamiento”. Las clases son un concepto de lenguaje semántico. Los archivos son un concepto de sistema
Clases. operativo físico. La correspondencia entre clases y archivos es una coincidencia y continúa
debilitándose con el tiempo a medida que más entornos admiten la colocación de código en bases de
datos o, de lo contrario, oscurecen la relación entre rutinas, clases y archivos.
Asigne al archivo un nombre relacionado con el nombre de la clase.La mayoría de los proyectos tienen una
correspondencia uno a uno entre los nombres de las clases y los nombres de los archivos. Una clase llamadaCuenta
Separe las rutinas dentro de un archivo claramenteSepare cada rutina de otras rutinas con al menos dos
líneas en blanco. Las líneas en blanco son tan efectivas como las grandes filas de asteriscos o guiones, y son
mucho más fáciles de escribir y mantener. Use dos o tres para producir una diferencia visual entre las líneas
en blanco que forman parte de una rutina y las líneas en blanco que separan las rutinas. Un ejemplo se
muestra en el Listado 31-69:
Listado 31-69Ejemplo de Visual Basic del uso de líneas en blanco entre rutinas.
función final
Al menos dos líneas en blanco
separar las dos rutinas.
función final
Las líneas en blanco son más fáciles de escribir que cualquier otro tipo de separador y se ven al
menos igual de bien. En este ejemplo se utilizan tres líneas en blanco para que la separación entre
rutinas sea más notoria que las líneas en blanco dentro de cada rutina.
En C++, ordene el archivo fuente con cuidadoEste es un orden típico del contenido del archivo fuente en
C++:
2.#incluirarchivos
3.Definiciones constantes que se aplican a más de una clase (si hay más de una clase en el
archivo)
4.Enumeraciones que se aplican a más de una clase (si hay más de una clase en el archivo)
6.Escriba definiciones que se apliquen a más de una clase (si hay más de una clase en el archivo)
General
- ¿El formateo se realiza principalmente para iluminar la estructura lógica del
código?
Estructuras de Control
- ¿El código evita la doble sangría?principio-fino{}pares?
- ¿Los bloques secuenciales están separados entre sí con líneas en blanco?
- Tenerir¿Ha sido formateado de una manera que hace que su uso sea obvio?
Traducido del inglés al español - www.onlinedoctranslator.com
- ¿Se utilizan espacios en blanco para hacer legibles las expresiones lógicas, las referencias de matriz y
- ¿Las declaraciones incompletas terminan la línea de una manera que es obviamente incorrecta?
Comentarios
- ¿Los comentarios tienen la misma sangría de espacios que el código que
comentan?
Rutinas
- ¿Los argumentos de cada rutina están formateados de modo que cada argumento sea
fácil de leer, modificar y comentar?
- ¿Las rutinas dentro de un archivo están claramente separadas con líneas en blanco?
- En lugar de un principio de organización más fuerte, ¿todas las rutinas están en secuencia
alfabética?
Recursos adicionales
cc2e.com/3101 La mayoría de los libros de texto de programación dicen algunas palabras sobre el diseño y el estilo, pero las
discusiones exhaustivas sobre el estilo de programación son raras; las discusiones sobre el diseño son aún más
raras. Los siguientes libros hablan sobre diseño y estilo de programación:
Vermeulen, Allan, et al.Los elementos del estilo Java. Prensa de la Universidad de Cambridge, 2000.
Misfeldt, Trevor, Greg Bumgardner y Andrew Gray.Los elementos del estilo C++. Prensa de la
Universidad de Cambridge, 2004.
Kernighan, Brian W. y PJ Plauger.Los elementos del estilo de programación, 2ª ed. New York,
NY: McGraw-Hill, 1978. Este es el libro clásico sobre estilo de programación, el primero en el
género de libros de estilo de programación.
Knuth, Donald E.Programación alfabetizada. Cambridge University Press, 2001. Esta es una colección de
artículos que describen el enfoque de "programación alfabetizada" de combinar un lenguaje de
programación y un lenguaje de documentación. Knuth ha estado escribiendo sobre las virtudes de la
programación alfabetizada durante aproximadamente 20 años y, a pesar de su fuerte reclamo por el título
de Mejor programador del planeta, la programación alfabetizada no se está poniendo de moda. Lea parte
de su código para formar sus propias conclusiones sobre la razón.
Puntos clave
- La primera prioridad del diseño visual es iluminar la organización lógica del código.
Los criterios utilizados para evaluar si se logra esa prioridad incluyen precisión,
consistencia, legibilidad y mantenibilidad.
- Verse bien es secundario a los otros criterios, un distante segundo lugar. Sin embargo,
si se cumplen los otros criterios y el código subyacente es bueno, el diseño se verá
bien.
- Visual Basic tiene bloques puros y la práctica convencional en Java es usar el estilo de bloques
puros, por lo que puede usar un diseño de bloques puros si programa en esos lenguajes. En
C++, ya sea emulación de bloques puros oprincipio-finlos límites de bloque funcionan bien.
- La estructuración del código es importante por sí misma. La convención específica que sigues
es menos importante que el hecho de que sigas alguna convención consistentemente.
Una convención de diseño que se sigue de manera inconsistente puede dañar la legibilidad.
- Muchos aspectos del diseño son cuestiones religiosas. Trate de separar las preferencias objetivas de
las subjetivas. Use criterios explícitos para ayudar a fundamentar sus discusiones sobre las
preferencias de estilo.
capitulo 32
Código de autodocumentación
cc2e.com/3245 Contenido
Temas relacionados
- Diseño: Capítulo 31
Codifique como si quien La mayoría de los programadores disfrutan escribiendo documentación si los estándares de
mantiene su programa fuera
documentación no son irrazonables. Al igual que el diseño, una buena documentación es una señal del
un psicópata violento que
supiera dónde vive. orgullo profesional que un programador pone en un programa. La documentación del software puede
—Anónimo tomar muchas formas y, después de describir el panorama de la documentación, este capítulo cultiva el
parche específico de documentación conocido como "comentarios".
"Estándares IEEE".
unidades. En proyectos grandes y formales, la mayor parte de la documentación está fuera del código
fuente (Jones 1998). La documentación de construcción externa tiende a estar en un nivel alto en
comparación con el código, en un nivel bajo en comparación con la documentación de la definición del
problema, los requisitos y las actividades de la arquitectura.
777
778 Capítulo 32: Código de autodocumentación
Otras lecturasPara obtener una Carpetas de desarrollo de unidadesUna carpeta de desarrollo de unidades (UDF) o carpeta de desarrollo de
descripción detallada, consulte
software (SDF) es un documento informal que contiene notas utilizadas por un desarrollador durante la
"El desarrollo de la unidad".
Perspectiva de Diez relevantes, las partes del diseño de nivel superior que implementa la unidad, una copia de los estándares de
Años” (Ingrassia 1987). desarrollo, una lista de códigos actual y notas de diseño del desarrollador de la unidad. A veces, el cliente
requiere que un desarrollador de software entregue los UDF del proyecto; a menudo son solo para uso
interno.
El principal contribuyente a la documentación a nivel de código no son los comentarios, sino un buen estilo
de programación. El estilo incluye una buena estructura del programa, el uso de enfoques sencillos y
fácilmente comprensibles, buenos nombres de variables, buenos nombres de rutinas, uso de constantes
con nombre en lugar de literales, diseño claro y minimización de la complejidad del flujo de control y la
estructura de datos.
HORROR
for ( i = 2; i <= num / 2; i++ ) { j = i + i;
¿Qué crees que hace esta rutina? Es innecesariamente críptico. Está mal documentado no porque
carezca de comentarios, sino porque carece de un buen estilo de programación. Los nombres de las
variables no son informativos y el diseño es tosco. Aquí está el mismo código mejorado: solo mejorar
el estilo de programación hace que su significado sea mucho más claro:
Referencia cruzadaEn este código, la for ( int factor = 2; factor < ( num / 2 ); factor++ ) {
variablefactorableNumberse añade int factorableNumber = factor + factor; while
únicamente con el fin de aclarar la (factorableNumber <= num) {
esPrimo[Númerofactorable] = false; factorableNumber =
Descargar desde Guau! Libro electrónico <www.wowebook.com>
A diferencia del primer fragmento de código, este te permite saber a primera vista que tiene algo que
ver con los números primos. Una segunda mirada revela que encuentra los números primos entre1y
número. Con el primer fragmento de código, se necesitan más de dos miradas para descubrir dónde
terminan los bucles.
La diferencia entre los dos fragmentos de código no tiene nada que ver con los comentarios,
ninguno de los fragmentos tiene ninguno. Sin embargo, el segundo es mucho más legible y se
acerca al Santo Grial de la legibilidad: el código autodocumentado. Dicho código se basa en un
buen estilo de programación para llevar la mayor parte de la carga de documentación. En un
código bien escrito, los comentarios son la guinda del pastel de legibilidad.
780 Capítulo 32: Código de autodocumentación
Clases
- ¿La interfaz de la clase presenta una abstracción consistente?
Rutinas
- ¿El nombre de cada rutina describe exactamente lo que hace la rutina?
Nombres de datos
- ¿Son los nombres de tipos lo suficientemente descriptivos para ayudar a documentar las declaraciones de datos?
- ¿Se les da a los contadores de bucles nombres más informativos quei,j, yk?
- ¿Se utilizan tipos enumerados bien nombrados en lugar de banderas improvisadas o variables
booleanas?
- ¿Se usan constantes con nombre en lugar de números mágicos o cadenas mágicas?
Organización de datos
- ¿Se utilizan variables adicionales para mayor claridad cuando es necesario?
- ¿Se accede a datos complicados a través de rutinas de acceso abstractas (tipos de datos
abstractos)?
Control
- ¿Está clara la ruta nominal a través del código?
- ¿Cada bucle realiza una y sólo una función, como lo haría una rutina bien
definida?
- ¿Se han simplificado las expresiones booleanas mediante el uso de variables booleanas
adicionales, funciones booleanas y tablas de decisión?
Diseño
- ¿El diseño del programa muestra su estructura lógica?
Diseño
- ¿Es el código sencillo y evita la astucia?
- ¿Los detalles de implementación están ocultos tanto como sea posible?
- ¿El programa está escrito en términos del dominio del problema tanto como sea posible
en lugar de en términos de estructuras informáticas o de lenguaje de programación?
El comentario
Caracteres:
ISMENE Un programador senior cansado de grandes promesas, solo busca algunas prácticas que
funcionen
Ajuste:
"¿Alguien tiene algún otro problema antes de que volvamos al trabajo?" preguntó Sócrates.
“Debes estar más fresco en la universidad de lo que pensaba”, respondió Callicles. “Los comentarios
son una panacea académica, pero cualquiera que haya hecho algo de programación real sabe que
los comentarios hacen que el código sea más difícil de leer, no más fácil. El inglés es menos preciso
que Java o Visual Basic y genera un exceso de palabrería. Las declaraciones del lenguaje de
programación son cortas y van al grano. Si no puede aclarar el código, ¿cómo puede aclarar los
comentarios? Además, los comentarios quedan obsoletos a medida que cambia el código. Si crees un
comentario desactualizado, estás hundido”.
“Estoy de acuerdo con eso”, se unió Glaucon. “El código muy comentado es más difícil de leer porque
significa más para leer. Ya tengo que leer el código; ¿Por qué debería tener que leer muchos
comentarios también?”
“Espera un minuto”, dijo Ismene, dejando su taza de café para poner el valor de sus dos
dracmas. “Sé que se puede abusar de comentar, pero los buenos comentarios valen su
peso en oro. Tuve que mantener el código que tenía comentarios y el código que no, y
prefiero mantener el código con comentarios. No creo que debamos tener un estándar
que diga usar un comentario para cadaXlíneas de código, pero deberíamos alentar a
todos a comentar”.
“Si los comentarios son una pérdida de tiempo, ¿por qué alguien los usa, Calicles?” preguntó
Sócrates.
“Ya sea porque están obligados a hacerlo o porque leyeron en alguna parte que son
útiles. Nadie que haya pensado en ello podría decidir que son útiles”.
Ismene cree que son útiles. Lleva aquí tres años, manteniendo tu código sin
comentarios y otro código con comentarios, y prefiere el código con
comentarios. ¿Qué piensas de eso?
“Los comentarios son inútiles porque solo repiten el código de una forma más detallada…”
"Correcto", dijo Ismene. “Exploro los comentarios para encontrar la sección que hace lo que necesito
cambiar o corregir. Tienes razón en que los comentarios que repiten el código no ayudan en nada
32.3 Comentar o no comentar 783
porque el código ya lo dice todo. Cuando leo comentarios, quiero que sea como leer los
encabezados de un libro o una tabla de contenido. Los comentarios me ayudan a encontrar la
sección correcta y luego empiezo a leer el código. Es mucho más rápido leer una oración en
inglés que analizar 20 líneas de código en un lenguaje de programación”. Ismene se sirvió otra
taza de café.
“Creo que las personas que se niegan a escribir comentarios (1) piensan que su código es más claro de lo
que podría ser, (2) piensan que otros programadores están mucho más interesados en su código de lo que
realmente están, (3) piensan que otros programadores están más inteligentes de lo que realmente son, (4)
son perezosos o (5) tienen miedo de que alguien más pueda descubrir cómo funciona su código.
"Las revisiones de código serían de gran ayuda aquí, Sócrates", continuó Ismene. “Si alguien afirma
que no necesita escribir comentarios y lo bombardean con preguntas durante una revisión, cuando
varios compañeros comienzan a decir: '¿Qué diablos está tratando de hacer en este fragmento de
código?', Entonces comenzará a poner en comentarios. Si no lo hacen solos, al menos su gerente
tendrá la munición para obligarlos a hacerlo.
“No te estoy acusando de ser perezoso o temeroso de que la gente descubra tu código,
Callicles. He trabajado en tu código y eres uno de los mejores programadores de la
empresa. Pero ten corazón, ¿eh? Me sería más fácil trabajar en su código si usara
comentarios”.
“Pero son un desperdicio de recursos”, respondió Callicles. “El código de un buen programador
debe ser autodocumentado; todo lo que necesitas saber debe estar en el código.”
"¡De ninguna manera!" Trasímaco se había levantado de su silla. “¡Todo lo que el compilador necesita saber
está en el código! ¡También podría argumentar que todo lo que necesita saber está en el archivo ejecutable
binario! ¡Si fueras lo suficientemente inteligente como para leerlo! Que esquiso decirsuceder no está en el
código”.
Trasímaco se dio cuenta de que estaba de pie y se sentó. “Sócrates, esto es ridículo. ¿Por qué
tenemos que discutir sobre si los comentarios son valiosos? Todo lo que he leído dice que son
valiosos y deben usarse generosamente. Estamos perdiendo el tiempo.
Claramente, en algún nivel los Cálmate, Trasímaco. Pregúntale a Callicles cuánto tiempo ha estado programando.
comentariostenerser útil.
Creer lo contrario sería creer "¿Cuánto tiempo, Calicles?"
que la comprensibilidad de un
programa es independiente
“Bueno, comencé con la Acrópolis IV hace unos 15 años. Supongo que he visto alrededor de una
de la cantidad de información
que el lector
docena de sistemas importantes desde que nacieron hasta que les dimos una taza de cicuta. Y he
ya podría tener al respecto. trabajado en partes importantes de una docena más. Dos de esos sistemas tenían más de medio
—BA Sheil millón de líneas de código, así que sé de lo que hablo. Los comentarios son bastante inútiles”.
Sócrates miró al programador más joven. “Como dice Callicles, los comentarios tienen
muchos problemas legítimos, y no te darás cuenta de eso sin más experiencia. Si no se
hacen bien, son peores que inútiles”.
784 Capítulo 32: Código de autodocumentación
“Incluso cuando se hacen bien, son inútiles”, dijo Callicles. “Los comentarios son menos
precisos que un lenguaje de programación. Preferiría no tenerlos en absoluto”.
“Espera un minuto”, dijo Sócrates. “Ismene está de acuerdo en que los comentarios son menos precisos. Su
punto es que los comentarios te dan un mayor nivel de abstracción, y todos sabemos que los niveles de
abstracción son una de las herramientas más poderosas de un programador”.
“No estoy de acuerdo con eso”, respondió Glaucon. “En lugar de centrarse en comentar, debe
centrarse en hacer que el código sea más legible. La refactorización elimina la mayoría de mis
comentarios. Una vez que he refactorizado, mi código puede tener 20 o 30 llamadas de rutina
sin necesidad de comentarios. Un buen programador puede leer la intención del código
mismo, y ¿de qué sirve leer sobre la intención de alguien cuando sabes que el código tiene un
error? Glaucon estaba complacido con su contribución. Calicles asintió.
“Parece que ustedes nunca han tenido que modificar el código de otra persona”, dijo Ismene.
Calicles de repente pareció muy interesado en las marcas de lápiz en las tejas del techo. “¿Por
qué no intentas leer tu propio código seis meses o un año después de escribirlo? Puede
mejorar su capacidad de lectura de códigos y sus comentarios. No tienes que elegir uno u
otro. Si está leyendo una novela, es posible que no desee encabezados de sección. Pero si está
leyendo un libro técnico, le gustaría poder encontrar lo que busca rápidamente. No debería
tener que cambiar al modo de ultraconcentración y leer cientos de líneas de código solo para
encontrar las dos líneas que quiero cambiar”.
"Está bien, puedo ver que sería útil poder escanear el código", dijo Glaucon. Había visto algunos de
los programas de Ismene y había quedado impresionado. Pero, ¿qué pasa con el otro punto de
Callicles, que los comentarios quedan obsoletos a medida que cambia el código? Solo he estado
programando durante un par de años, pero incluso yo sé que nadie actualiza sus comentarios”.
“Bueno, sí y no”, dijo Ismene. “Si tomas el comentario como sagrado y el código como
sospechoso, estás en serios problemas. En realidad, encontrar un desacuerdo entre el
comentario y el código tiende a significar que ambos están equivocados. El hecho de que
algunos comentarios sean malos no significa que comentar sea malo. Voy al comedor a buscar
otra taza de café. Ismene salió de la habitación.
“Mi principal objeción a los comentarios”, dijo Callicles, “es que son un desperdicio de recursos”.
"¿Alguien puede pensar en formas de minimizar el tiempo que lleva escribir los comentarios?" preguntó
Sócrates.
“Está bien, eso funcionaría siempre y cuando los comentarios no repitan el código”, dijo Callicles.
32.4 Claves para comentarios efectivos 785
“Escribir un comentario te hace pensar más sobre lo que está haciendo tu código”, dijo
Ismene, al regresar del comedor. “Si es difícil comentar, es un código incorrecto o no lo
entiendes lo suficientemente bien. De cualquier manera, debe dedicar más tiempo al código,
por lo que el tiempo que dedicó a comentar no se desperdició porque le señaló el trabajo
requerido”.
“Está bien”, dijo Sócrates. “No puedo pensar en más preguntas, y creo que Ismene obtuvo lo
mejor de ustedes hoy. Animaremos a comentar, pero no seremos ingenuos al respecto.
Tendremos revisiones de código para que todos tengan una buena idea del tipo de
comentarios que realmente ayudan. Si tiene problemas para entender el código de otra
persona, hágales saber cómo pueden mejorarlo”.
// bucle de 2 a "num"
para (int i = 2; i <= número; i++) {
// multiplicamos "base" por "producto"
producto = producto * base;
}
System.out.println( "Producto = " + producto);
786 Capítulo 32: Código de autodocumentación
Esta rutina calcula la raíz cuadrada denúmero. El código no es muy bueno, pero el comentario es
preciso.
¿Qué rutina te resultó más fácil descifrar correctamente? Ninguna de las rutinas está especialmente
bien escrita; los nombres de las variables son especialmente deficientes. Sin embargo, en pocas
palabras, estas rutinas ilustran las fortalezas y debilidades de los comentarios internos. La rutina uno
tiene un comentario incorrecto. Los comentarios de la Rutina Dos simplemente repiten el código y,
por lo tanto, son inútiles. Solo los comentarios de la Rutina Tres ganan su renta. Los malos
comentarios son peores que ningún comentario. Las rutinas uno y dos serían mejores sin
comentarios que con los comentarios pobres que tienen.
Las siguientes subsecciones describen las claves para escribir comentarios efectivos.
Tipos de comentarios
Los comentarios se pueden clasificar en seis categorías:
Marcador en el Código
Un comentario de marcador es uno que no está destinado a permanecer en el código. Es una nota para el
desarrollador que el trabajo aún no ha terminado. Algunos desarrolladores escriben un marcador que es
sintácticamente incorrecto (******, por ejemplo) para que el compilador lo marque y les recuerde que
tienen más trabajo por hacer. Otros desarrolladores colocan un conjunto específico de caracteres en los
comentarios que no interfieren con la compilación para que puedan buscarlos.
Pocas sensaciones son peores que hacer que un cliente informe un problema en el código, depurar
el problema y rastrearlo hasta una sección del código donde encuentre algo como esto:
Liberar un código defectuoso a los clientes ya es bastante malo; código de liberación que ustedsupo
estaba defectuoso es aún peor.
Descubrí que es útil estandarizar el estilo de los comentarios de los marcadores. Si no estandariza, algunos
programadores usarán*******, algunos usarán!!!!!!, algunos usarán Por determinar, y algunos usarán varias
otras convenciones. El uso de una variedad de notaciones hace que la búsqueda mecánica de código
incompleto sea propensa a errores o imposible. La estandarización en un estilo de marcador específico le
permite realizar una búsqueda mecánica de secciones de código incompletas como uno de los pasos en una
lista de verificación de publicación, lo que evita laARREGLAR ANTES DE LIBERAR!!!problema. Algunos editores
admiten etiquetas de "cosas por hacer" y le permiten navegar hasta ellas fácilmente.
3 es un comentario resumido en términos de la solución. Un estudio de seis meses realizado por IBM
2
1 encontró que los programadores de mantenimiento “dijeron con mayor frecuencia que comprender
la intención del programador original era el problema más difícil” (Fjelstad y Hamlen 1979). La
DATOS DUROS
distinción entre comentarios de intención y de resumen no siempre es clara y, por lo general, no es
importante. A lo largo de este capítulo se dan ejemplos de comentarios de intenciones.
Parte de la información no se puede expresar en código, pero aún debe estar en el código fuente.
Esta categoría de comentarios incluye avisos de derechos de autor, avisos de confidencialidad,
números de versión y otros detalles de limpieza; notas sobre el diseño del código; referencias a
requisitos relacionados o documentación de arquitectura; punteros a referencias en línea; notas de
optimización; comentarios requeridos por herramientas de edición como Javadoc y Doxygen; y así.
Los tres tipos de comentarios que son aceptables para el código completo son la información que no se
puede expresar en el código, los comentarios de intención y los comentarios de resumen.
Comentando eficientemente
Los comentarios efectivos no consumen tanto tiempo. Demasiados comentarios son tan malos
como muy pocos, y puedes lograr un término medio económicamente.
Los comentarios pueden tomar mucho tiempo para escribir por dos razones comunes. En primer
lugar, el estilo de comentario puede llevar mucho tiempo o ser tedioso. Si es así, busque un nuevo
estilo. Un estilo de comentario que requiere mucho trabajo es un dolor de cabeza de
mantenimiento. Si los comentarios son difíciles de cambiar, no se cambiarán y se volverán inexactos
y engañosos, lo que es peor que no tener ningún comentario.
En segundo lugar, comentar puede ser difícil porque las palabras para describir lo que está haciendo
el programa no son fáciles de encontrar. Eso suele ser una señal de que no entiende lo que hace el
programa. El tiempo que dedica a "comentar" es realmente tiempo dedicado a comprender mejor el
programa, que es tiempo que debe dedicarse independientemente de si comenta.
Use estilos que no desmoronen o desalienten la modificaciónCualquier estilo que sea demasiado
elegante es molesto de mantener. Por ejemplo, seleccione la parte del comentario a continuación
que no se mantendrá:
Si dijiste que los puntos guía (.....) serán difíciles de mantener, ¡tienes razón! Se ven bien,
pero la lista está bien sin ellos. Agregan mucho trabajo al trabajo de modificar los
comentarios, y preferiría tener comentarios precisos que agradables, si esa es la opción,
y generalmente lo es.
Este es un comentario de bloque atractivo. Está claro que todo el bloque va unido, y el
principio y el final del bloque son obvios. Lo que no está claro de este bloque es lo fácil que es
cambiarlo. Si tiene que agregar el nombre de un archivo al final del comentario, es muy
probable que tenga que preocuparse por la bonita columna de asteriscos a la derecha. Si
necesita cambiar los comentarios del párrafo, tendrá que preocuparse por los asteriscos tanto
a la izquierda como a la derecha. En la práctica, esto significa que el bloque no se mantendrá
porque será demasiado trabajo. Si puede presionar una tecla y obtener columnas ordenadas
de asteriscos, eso es genial. úsalo El problema no son los asteriscos sino que son difíciles de
mantener. El siguiente comentario se ve casi tan bien y es muy fácil de mantener:
Rutinas para controlar la herramienta de evaluación de código del siglo XXI. El punto
de entrada a estas rutinas es la rutina EvaluateCode() en la parte inferior de este
archivo.
*********************************************************************/
790 Capítulo 32: Código de autodocumentación
Es difícil saber qué valor agrega al comentario el signo más al principio y al final de cada línea
discontinua, pero es fácil adivinar que cada vez que cambia un comentario, se debe ajustar el
subrayado para que el signo más final quede exactamente el lugar correcto. ¿Y qué haces
cuando un comentario se extiende a dos líneas? ¿Cómo se alinean los signos más? ¿Eliminar
palabras del comentario para que ocupe solo una línea? ¿Hacer que ambas líneas tengan la
misma longitud? Los problemas con este enfoque se multiplican cuando intenta aplicarlo de
manera consistente.
Una pauta común para Java y C++ que surge de una motivación similar es usar la sintaxis // para
comentarios de una sola línea y la sintaxis /* ... */ para comentarios más largos, como se muestra
aquí:
Ejemplo de Java del uso de diferentes sintaxis de comentarios para diferentes propósitos
// Este es un breve comentario. . .
/* Este es un comentario mucho más largo. Hace cuarenta y siete años nuestros padres crearon en este
continente una nueva nación, concebida en libertad y dedicada a la proposición de que todos los hombres
son creados iguales. Ahora estamos enfrascados en una gran guerra civil, probando si esa nación o cualquier
otra nación así concebida y dedicada puede perdurar por mucho tiempo. Nos encontramos en un gran
campo de batalla de esa guerra. Hemos venido a dedicar una parte de ese campo como lugar de descanso
final para aquellos que aquí dieron su vida para que esa nación pudiera vivir. Es del todo apropiado y
apropiado que hagamos esto.
*/
El primer comentario es fácil de mantener siempre que sea breve. Para comentarios más
largos, la tarea de crear columnas largas de barras dobles, dividir manualmente líneas de
texto entre filas y actividades similares no es muy gratificante, por lo que la sintaxis /* ... */ es
más apropiada para comentarios de varias líneas.
El punto es que debes prestar atención a cómo pasas tu tiempo. Si pasa mucho tiempo
ingresando y eliminando guiones para alinear los signos más, no está programando;
estas perdiendo el tiempo Encuentra un estilo más eficiente. En el caso de los
subrayados con signos más, puede optar por tener solo los comentarios sin ningún
PUNTO CLAVE
subrayado. Si necesita usar subrayados para enfatizar, busque otra forma que no sea
32.4 Claves para comentarios efectivos 791
subraya con signos más para enfatizar esos comentarios. Una forma sería tener un subrayado
estándar que siempre tenga la misma longitud, independientemente de la longitud del comentario.
Dicha línea no requiere mantenimiento, y puede usar una macro de editor de texto para ingresarla
en primer lugar.
Referencia cruzadaPara obtener detalles Use el proceso de programación de pseudocódigo para reducir el tiempo de comentariosSi describe el
sobre el proceso de programación de
código en los comentarios antes de escribirlo, gana de varias maneras. Cuando terminas el código, los
pseudocódigo, consulte
Si su diseño es difícil de codificar, simplifique el diseño antes de preocuparse por los comentarios o el
código. Si usa pseudocódigo para aclarar sus pensamientos, la codificación es sencilla y los
comentarios son automáticos.
PUNTO CLAVE
El rendimiento no es una buena razón para evitar comentarUn atributo recurrente de la ola de
tecnología que se analiza en la Sección 4.3, "Su ubicación en la ola de tecnología", son los entornos
interpretados en los que los comentarios imponen una penalización de rendimiento medible. En la
década de 1980, los comentarios en los programas básicos sobre la PC IBM original ralentizaron los
programas. En la década de 1990,.áspidLas páginas hicieron lo mismo. En la década de 2000, el
código JavaScript y otros códigos que deben enviarse a través de conexiones de red presentan un
problema similar.
En cada uno de estos casos, la solución final no ha sido evitar comentar; ha sido crear una
versión de lanzamiento del código que sea diferente de la versión de desarrollo. Esto
generalmente se logra ejecutando el código a través de una herramienta que elimina los
comentarios como parte del proceso de compilación.
792 Capítulo 32: Código de autodocumentación
Se puede abusar de este tipo de investigación, y los proyectos a veces adoptan un estándar como “los
programas deben tener un comentario al menos cada cinco líneas”. Este estándar aborda el síntoma
de que los programadores no escriben un código claro, pero no aborda la causa.
- La línea única una vez tuvo un error y desea un registro del error.
Terminara si
Terminara si
próximo
Aunque son útiles en algunas circunstancias, los comentarios finales plantean varios
problemas. Los comentarios tienen que estar alineados a la derecha del código para que no
interfieran con la estructura visual del código. Si no los alinea correctamente, harán que su
anuncio parezca haber pasado por la lavadora. Los comentarios finales tienden a ser difíciles
de formatear. Si usa muchos de ellos, lleva tiempo alinearlos. Ese tiempo no se dedica a
aprender más sobre el código; se dedica únicamente a la tediosa tarea de presionar la barra
espaciadora o la tecla Tabulador.
Los comentarios finales también son difíciles de mantener. Si el código en cualquier línea que contiene un
comentario de línea final crece, empuja el comentario más lejos y todos los demás comentarios de línea final
tendrán que eliminarse para que coincidan. Los estilos que son difíciles de mantener no se mantienen, y los
comentarios se deterioran con las modificaciones en lugar de mejorar.
Los comentarios finales también tienden a ser crípticos. El lado derecho de la línea por lo general no
ofrece mucho espacio, y el deseo de mantener el comentario en una sola línea significa que el comentario
debe ser breve. Luego, el trabajo se centra en hacer que la línea sea lo más corta posible en lugar de lo
más clara posible.
Evite los comentarios finales en líneas individualesAdemás de sus problemas prácticos, los
comentarios finales plantean varios problemas conceptuales. Este es un ejemplo de un conjunto de
comentarios finales:
Un problema sistémico con los comentarios finales es que es difícil escribir un comentario significativo
para una línea de código. La mayoría de los comentarios finales simplemente repiten la línea de código, lo
que perjudica más de lo que ayuda.
Evite los comentarios finales para varias líneas de códigoSi se pretende que un comentario
final se aplique a más de una línea de código, el formato no muestra a qué líneas se aplica el
comentario:
Referencia cruzadaOtro Use comentarios finales para anotar declaraciones de datosLos comentarios finales son útiles
Los aspectos de los comentarios
para anotar declaraciones de datos porque no tienen los mismos problemas sistémicos que los
finales sobre las declaraciones de
Evite el uso de comentarios finales para las notas de mantenimientoLos comentarios finales a veces se
usan para registrar modificaciones al código después de su desarrollo inicial. Este tipo de comentario
generalmente consta de una fecha y las iniciales del programador, o posiblemente un número de informe
de error. Aquí hay un ejemplo:
Agregar un comentario de este tipo puede ser gratificante después de una sesión de depuración nocturna en el
software que está en producción, pero tales comentarios realmente no tienen cabida en el código de producción.
Dichos comentarios se manejan mejor con el software de control de versiones. Los comentarios deben explicar por
qué funciona el código.ahora, no por qué el código no funcionó en algún momento en el pasado.
32.5 Técnicas de comentarios 795
Referencia cruzadaUso de Use comentarios de línea final para marcar los extremos de los bloquesUn comentario de línea final es
Los comentarios finales para marcar los
útil para marcar el final de un bloque largo de código: el final de untiempobucle o unsideclaración, por
extremos de los bloques se describen
esta sección. Aparte de un par de casos especiales, los comentarios finales tienen problemas conceptuales y
tienden a usarse para código que es demasiado complicado. También son difíciles de formatear y
mantener. En general, es mejor evitarlos.
El comentario no repite el código, describe la intención del código. Dichos comentarios son
relativamente fáciles de mantener. Incluso si encuentra un error en la forma en que se intercambian
las raíces, por ejemplo, no será necesario cambiar el comentario. Los comentarios que no están
escritos al nivel de la intención son más difíciles de mantener.
Puedes darte cuenta de que el ciclo busca unpsleyendo el código, y es algo útil
tenerlo resumido en el comentario. El problema con este comentario es
796 Capítulo 32: Código de autodocumentación
que simplemente repite el código y no le da ninguna idea de lo que se supone que debe
hacer el código. Este comentario sería un poco mejor:
Este comentario es mejor porque indica que el objetivo del ciclo es encontrar unps. Pero
todavía no le da mucha idea de por qué el ciclo necesitaría encontrar unps—en otras palabras,
en la intención más profunda del bucle. Aquí hay un comentario que es mejor aún:
Este comentario en realidad contiene información que la lista de códigos no contiene, a saber, que el
pstermina una palabra de comando. De ninguna manera podría deducir ese hecho simplemente
leyendo el fragmento de código, por lo que el comentario es realmente útil.
Otra forma de pensar en comentar en el nivel de intención es pensar en el nombre que le daría a una
rutina que hiciera lo mismo que el código que desea comentar. Si está escribiendo párrafos de código
que tienen un propósito cada uno, no es difícil. El comentario en el ejemplo de código anterior es un
buen ejemplo.FindCommandWordTerminator() sería un nombre de rutina decente. Las otras
opciones,Buscar$EnCadenaDeEntrada()yVerificar cada carácter en la cadena de entrada hasta que un
signo de dólar se encuentre o todos los caracteres se hayan verificado (), son nombres pobres (o
inválidos) por razones obvias. Escriba la descripción sin acortarla ni abreviarla, como lo haría con el
nombre de una rutina. Esa descripción es su comentario, y probablemente esté en el nivel de
intención.
Si el código es lo suficientemente bueno, comienza a leerse cerca del nivel de intención, invadiendo la
explicación del comentario sobre la intención del código. En ese momento, el comentario y el código
pueden volverse algo redundantes, pero ese es un problema que tienen pocos programas.
Referencia cruzadaPara obtener más Otro buen paso para este código sería crear una rutina llamada algo como
información sobre cómo mover una
FindCommandWordTerminator()y mueva el código de la muestra a esa rutina. Un
sección de código a su propia rutina,
Centrar los comentarios de los párrafos en el por qué en lugar del cómo.Los comentarios
que explican cómo se hace algo generalmente operan a nivel de lenguaje de programación
más que a nivel de problema. Es casi imposible que un comentario que se centre en cómo se
realiza una operación explique la intención de la operación, y los comentarios que dicen cómo
suelen ser redundantes. ¿Qué te dice el siguiente comentario que el código no dice?
HORROR
El comentario no le dice nada más que el propio código. ¿Qué pasa con este
comentario?
Este comentario es mucho mejor porque te dice algo que no podrías inferir del código en sí. El
código en sí aún podría mejorarse mediante el uso de un nombre de tipo enumerado
significativo en lugar deOy un mejor nombre de variable. Aquí está la mejor versión de este
comentario y código:
Ejemplo de Java del uso de buen estilo además de un comentario de "por qué"
// si se establece una nueva cuenta
if (TipoDeCuenta == TipoDeCuenta.NuevaCuenta) ...
Cuando el código alcanza este nivel de legibilidad, es apropiado cuestionar el valor del
comentario. En este caso, el código mejorado ha hecho redundante el comentario y
probablemente debería eliminarse. Alternativamente, el propósito del comentario podría
cambiarse sutilmente, así:
Si este comentario documenta todo el bloque de código que sigue alsiprueba, sirve como un comentario de
nivel de resumen y es apropiado conservarlo como un encabezado de sección para el párrafo del código al
que hace referencia.
Use comentarios para preparar al lector para lo que sigueLos buenos comentarios le dicen a la
persona que lee el código qué esperar. Un lector debería poder escanear solo los comentarios y tener
una buena idea de lo que hace el código y dónde buscar una actividad específica. Un corolario de esta
regla es que un comentario siempre debe preceder al código que describe. Esta idea no siempre se
enseña en las clases de programación, pero es una convención bien establecida en la práctica
comercial.
Haz que cada comentario cuenteNo hay ninguna virtud en comentar en exceso: demasiados
comentarios oscurecen el código que pretenden aclarar. En lugar de escribir más comentarios, haga
un esfuerzo adicional para hacer que el código sea más legible.
La selección del cambio a la derecha en este ejemplo es intencional. Entre los programadores experimentados, es de
conocimiento común que para números enteros, el desplazamiento a la derecha es funcionalmente equivalente a
dividir por dos.
Evite las abreviaturasLos comentarios deben ser inequívocos, legibles sin el trabajo de
descifrar abreviaturas. Evite todas las abreviaturas excepto las más comunes en los
comentarios. A menos que esté utilizando comentarios finales, el uso de abreviaturas no suele
ser una tentación. Si lo eres y lo es, date cuenta de que las abreviaturas son otro golpe contra
una técnica que se ponchó hace varios lanzamientos.
Diferenciar entre comentarios mayores y menoresEn algunos casos, es posible que desee
diferenciar entre diferentes niveles de comentarios, lo que indica que un comentario detallado es
parte de un comentario anterior más amplio. Puedes manejar esto de un par de maneras. Puede
intentar subrayar el comentario principal y no subrayar el comentario secundario:
. . . o aquí.
La debilidad de este enfoque es que te obliga a subrayar más comentarios de los que
realmente te gustaría. Si subraya un comentario, se supone que todos los comentarios no
subrayados que le siguen están subordinados a él. En consecuencia, cuando escribe el primer
comentario que no está subordinado al comentario subrayado, también debe subrayarse y el
ciclo comienza de nuevo. El resultado es demasiado subrayado o subrayado inconsistente en
algunos lugares y ningún subrayado en otros.
Este tema tiene varias variaciones que tienen el mismo problema. Si escribe el comentario principal en
mayúsculas y los comentarios secundarios en minúsculas, sustituye el problema de demasiados
comentarios en mayúsculas por demasiados comentarios subrayados. Algunos programadores usan un
límite inicial en las declaraciones principales y ningún límite inicial en las menores, pero esa es una señal
visual sutil que se pasa por alto con demasiada facilidad.
Ejemplo en C++ de diferenciación entre comentarios mayores y menores con puntos suspensivos
El comentario principal // copiar la porción de cadena de la tabla, omitiendo en el camino // cadenas que se van
tiene el formato normal. a eliminar
// ... determinar el número de cadenas en la tabla. . .
Un comentario menor que es parte
. . . y aquí.
800 Capítulo 32: Código de autodocumentación
Otro enfoque que a menudo es mejor es poner la operación de comentarios principales en su propia
rutina. Las rutinas deben ser lógicamente "planas", con todas sus actividades en aproximadamente el
mismo nivel lógico. Si su código diferencia entre actividades principales y secundarias dentro de una
rutina, la rutina no es plana. Poner el complicado grupo de actividades en su propia rutina genera dos
rutinas lógicamente planas en lugar de una lógicamente irregular.
Comente cualquier cosa que evite un error o una característica no documentada en un idioma
o entorno.Si es un error, probablemente no esté documentado. Incluso si está documentado en
alguna parte, no está de más volver a documentarlo en su código. Si es una característica no
documentada, por definición no está documentada en ningún otro lugar y debería estar
documentada en su código.
*/
if (tamaño del bloque == DATOS ESCRITOS_TAMAÑO_BROTE) {
blockSize = WRITEDATA_WORKAROUND_SIZE;
}
WriteData (archivo, datos, blockSize);
32.5 Técnicas de comentarios 801
Justificar las violaciones del buen estilo de programación.Si ha tenido que violar el buen
estilo de programación, explique por qué. Eso evitará que un programador bien intencionado
cambie el código a un mejor estilo, posiblemente rompiendo su código. La explicación dejará
en claro que sabías lo que estabas haciendo y no solo eras descuidado. ¡Date crédito a ti mismo
donde se debe!
Este es un buen ejemplo de una de las partes más frecuentes y peligrosas del folclore de
programación: los comentarios deben usarse para documentar secciones de código
especialmente "difíciles" o "sensibles". El razonamiento es que las personas deben saber que
deben tener cuidado cuando trabajan en ciertas áreas.
3 Un estudio encontró que las áreas del código fuente con una gran cantidad de comentarios también tendían
2
1
a tener la mayoría de los defectos y consumir la mayor parte del esfuerzo de desarrollo (Lind y Vairavan
DATOS DUROS
1989). Los autores plantearon la hipótesis de que los programadores tendían a comentar mucho el código
difícil.
Cuando alguien dice: "Esto es realmentedifícilcódigo", los escucho decir, "Esto es realmentemalo
código." Si algo te parece engañoso, será incomprensible para otra persona. Incluso algo que no
parece tan complicado para ti puede parecer increíblemente complicado para otra persona que no
PUNTO CLAVE
ha visto el truco antes. Si tienes que preguntarte "¿Es esto complicado?" es. Siempre puede encontrar
una reescritura que no sea complicada, así que reescriba el código. Haga que su código sea tan
bueno que no necesite comentarios y luego coméntelo para hacerlo aún mejor.
Este consejo se aplica principalmente al código que está escribiendo por primera vez. Si está manteniendo
un programa y no tiene la libertad para reescribir código incorrecto, comentar las partes difíciles es una
buena práctica.
802 Capítulo 32: Código de autodocumentación
De manera alternativa, en muchos casos, debe incrustar las unidades en los nombres de las variables en
lugar de en los comentarios. Una expresión comodistanciaALaSuperficie = marsLanderAltitudeparece que es
probablemente correcto, perodistanciaALaSuperficieEnMetros = marsLanderAltitudeInFeet expone un error
evidente.
Referencia cruzadaUna técnica Comente el rango de valores numéricos permitidosSi una variable tiene un rango esperado
más sólida para documentar los
de valores, documente el rango esperado. Una de las características poderosas del lenguaje de
rangos permitidos de variables es
Comentar significados codificadosSi su idioma admite tipos enumerados, como lo hacen C++ y
Visual Basic, utilícelos para expresar significados codificados. Si no es así, use comentarios para
indicar qué representa cada valor y use una constante con nombre en lugar de un literal para cada
uno de los valores. Si una variable representa tipos de corriente eléctrica, comente el hecho de que1
representa la corriente alterna,2representa corriente continua, y3representa indefinido.
Aquí hay un ejemplo de cómo documentar declaraciones de variables que ilustra las tres
recomendaciones anteriores: toda la información del rango se proporciona en los comentarios:
32.5 Técnicas de comentarios 803
Dim antennaLength As Long Dim ' longitud de la antena en metros; el rango es >= 2 ' fuerza de la
signalStrength As Integer señal en kilovatios; el rango es >= 1
Dim characterCode As Integer ' código de carácter ASCII; oscila entre 0..255
Dim characterAttribute As Integer ' 0=Normal; 1=cursiva; 2=Negrita; 3=Negrita Cursiva Tenue carácter
Tamaño como entero ' tamaño del carácter en puntos; oscila entre 4..127
Indicadores de documentos al nivel de bitSi se utiliza una variable como campo de bits, documente el significado
de cada bit:
Referencia cruzadaPara obtener detalles Ejemplo de Visual Basic de la documentación de indicadores a nivel de bits
sobre cómo nombrar variables de marca,
' Los significados de los bits en statusFlags son los siguientes, desde el ' bit más significativo
consulte "Nombrar variables de estado"
hasta el bit menos significativo:
en la Sección 11.2.
MSB 0 error detectado: 1=sí, 0=no
' 1-2 tipo de error: 0=sintaxis, 1=advertencia, 2=grave, 3=fatal reservado
' 3 (debería ser 0)
' 4 estado de la impresora: 1=lista, 0=no lista
' ...
' 14 no utilizado (debe ser 0)
LSB 15-32 no se usa (debería ser 0)
Dim statusFlags As Integer
Si el ejemplo estuviera escrito en C++, requeriría una sintaxis de campo de bits para que los significados de los
campos de bits se documentaran por sí mismos.
Sello de comentarios relacionados con una variable con el nombre de la variableSi tiene comentarios
que hacen referencia a una variable específica, asegúrese de que el comentario se actualice siempre que se
actualice la variable. Una forma de mejorar las probabilidades de una modificación consistente es estampar
el comentario con el nombre de la variable. De esa forma, las búsquedas de cadenas por el nombre de la
variable encontrarán el comentario así como la variable.
Referencia cruzadaPara obtener detalles Documentar datos globalesSi se utilizan datos globales, anote bien cada pieza en el punto en el que
sobre el uso de datos globales, consulte la
se declara. La anotación debe indicar el propósito de los datos y por qué debe ser global. En cada
Sección 13.3, "Datos globales".
punto en el que se utilicen los datos, aclare que los datos son globales. Una convención de
nomenclatura es la primera opción para resaltar el estado global de una variable. Si no se utiliza una
convención de nomenclatura, los comentarios pueden llenar el vacío.
Traducido del inglés al español - www.onlinedoctranslator.com
Pon un comentario antes de cadasi,caso, bucle o bloque de sentenciasTal lugar es un lugar natural para
un comentario, y estas construcciones a menudo necesitan una explicación. Utilice un comentario para
aclarar el propósito de la estructura de control.
Comente el final de cada estructura de controlUse un comentario para mostrar lo que terminó; por
ejemplo,
Un comentario es especialmente útil al final de los bucles largos y para aclarar el anidamiento de bucles. Aquí hay
un ejemplo de Java del uso de comentarios para aclarar los extremos de las estructuras de bucle:
Esta técnica de comentarios complementa las pistas visuales sobre la estructura lógica
proporcionada por la sangría del código. No necesitas usar la técnica para abreviar.
32.5 Técnicas de comentarios 805
bucles que no están anidados. Sin embargo, cuando el anidamiento es profundo o los bucles son largos, la
técnica vale la pena.
Tratar los comentarios de fin de ciclo como una advertencia que indica un código complicadoSi un ciclo es lo
suficientemente complicado como para necesitar un comentario al final del ciclo, trate el comentario como una
señal de advertencia: es posible que se deba simplificar el ciclo. La misma regla se aplica a complicadossi pruebas y
casodeclaraciones.
Los comentarios de fin de ciclo brindan pistas útiles sobre la estructura lógica, pero escribirlos inicialmente y
luego mantenerlos puede volverse tedioso. La mejor manera de evitar un trabajo tan tedioso es, a menudo,
reescribir cualquier código que sea lo suficientemente complicado como para requerir una documentación
tediosa.
Rutinas de comentarios
Referencia cruzadaPara obtener detalles Los comentarios de nivel de rutina son el tema de algunos de los peores consejos en los libros de texto típicos de
sobre las rutinas de formateo, consulte la
ciencias de la computación. Muchos libros de texto lo instan a acumular una pila de información en la parte superior
Sección 31.7. Para obtener detalles sobre
Otro problema con los encabezados de rutina pesados es que desalientan la buena factorización del
código: la sobrecarga para crear una nueva rutina es tan alta que los programadores tenderán a
equivocarse y crear menos rutinas, no más. Las convenciones de codificación deberían fomentar las buenas
prácticas; los encabezados de rutina pesados hacen lo contrario.
Mantenga los comentarios cerca del código que describenUna de las razones por las que el
prólogo de una rutina no debería contener documentación voluminosa es que tal práctica aleja los
comentarios de las partes de la rutina que describen. Durante el mantenimiento, los comentarios
que están lejos del código tienden a no mantenerse con el código. Los comentarios y el código
comienzan a estar en desacuerdo y, de repente, los comentarios no tienen ningún valor. En su lugar,
siga el Principio de proximidad y coloque los comentarios lo más cerca posible del código que
describen. Es más probable que se mantengan y seguirán valiendo la pena.
Varios componentes de los prólogos de rutina se describen a continuación y deben incluirse según
sea necesario. Para su comodidad, cree un prólogo de documentación repetitivo. Simplemente no se
sienta obligado a incluir toda la información en todos los casos. Complete las partes que importan y
elimine el resto.
Referencia cruzadaLos buenos nombres Describa cada rutina en una o dos oraciones en la parte superior de la rutinaSi no puede describir la
de rutinas son clave para la
rutina en una o dos oraciones cortas, probablemente deba pensar más sobre lo que se supone que debe
documentación de rutinas. Para obtener
Parámetros del documento donde se declaranLa forma más fácil de documentar las variables de
entrada y salida es colocar comentarios junto a las declaraciones de parámetros:
32.5 Técnicas de comentarios 807
Referencia cruzadaLos Esta práctica es una buena excepción a la regla de no utilizar comentarios finales; son
comentarios finales se analizan
excepcionalmente útiles para documentar parámetros de entrada y salida. Esta ocasión para
con más detalle en "Comentarios
Con una herramienta como Javadoc, el beneficio de configurar el código para extraer documentación
supera los riesgos asociados con separar la descripción del parámetro de la declaración del
parámetro. Si no está trabajando en un entorno que admita la extracción de documentos, como
Javadoc, generalmente es mejor mantener los comentarios más cerca de los nombres de los
parámetros para evitar ediciones inconsistentes y la duplicación de los nombres mismos.
808 Capítulo 32: Código de autodocumentación
Diferenciar entre datos de entrada y salida.Es útil saber qué datos se utilizan como entrada y
cuáles como salida. Visual Basic lo hace relativamente fácil de saber porque los datos de salida están
precedidos por elPorRefLa palabra clave y los datos de entrada están precedidos por elPorValpalabra
clave. Si su idioma no admite dicha diferenciación automáticamente, inclúyalo en los comentarios.
He aquí un ejemplo en C++:
Referencia cruzadaEl orden de estos Ejemplo de C++ de diferenciación entre datos de entrada y salida
parámetros sigue el orden estándar
copia de cadena vacía (
de las rutinas de C++ pero entra en
char *objetivo, // out: cadena a copiar // in: cadena
conflicto con prácticas más
const char *fuente a copiar
generales. Para más detalles,
)
consulte "Colocar parámetros en
...
entrada-modificar-salida
detalles sobre el uso de una convención Las declaraciones de rutinas en lenguaje C++ son un poco complicadas porque algunas veces el
de nomenclatura para diferenciar entre
asterisco (*) indica que el argumento es un argumento de salida y muchas veces simplemente
datos de entrada y salida, consulte la
Sección 11.4.
significa que la variable es más fácil de manejar como un puntero que como un no puntero. escribe.
Por lo general, es mejor identificar explícitamente los argumentos de entrada y salida.
Si sus rutinas son lo suficientemente cortas y mantiene una distinción clara entre los datos de
entrada y salida, probablemente no sea necesario documentar el estado de entrada o salida
de los datos. Sin embargo, si la rutina es más larga, es un servicio útil para cualquiera que lea
la rutina.
Referencia cruzadaPara obtener Supuestos de la interfaz del documentoLa documentación de las suposiciones de la interfaz
detalles sobre otras consideraciones
podría verse como un subconjunto de las otras recomendaciones de comentarios. Si ha hecho
para las interfaces de rutina, consulte
Mientras escribe la rutina y se da cuenta de que está haciendo una suposición de interfaz,
anótela inmediatamente.
Documentar los efectos globales de la rutina.Si la rutina modifica los datos globales, describa
exactamente lo que hace con los datos globales. Como se mencionó en la Sección 13.3, “Datos globales”,
modificar datos globales es al menos un orden de magnitud más peligroso que simplemente leerlos, por lo
que las modificaciones deben realizarse con cuidado, siendo parte del cuidado una documentación clara.
Como de costumbre, si la documentación se vuelve demasiado onerosa, vuelva a escribir el código para
reducir los datos globales.
Use comentarios para marcar partes de su programaAlgunos programadores usan comentarios para
marcar partes de su programa para que puedan encontrarlos fácilmente. Una de esas técnicas en C++ y Java
es marcar la parte superior de cada rutina con un comentario que comience con estos caracteres:
/**
Esto le permite saltar de una rutina a otra haciendo una búsqueda de cadenas para/**o
usar su editor para saltar automáticamente si lo admite.
Una técnica similar consiste en marcar diferentes tipos de comentarios de manera diferente, según lo
que describan. Por ejemplo, en C++ podrías usar@palabra clave, dóndepalabra clavees un código que
se utiliza para indicar el tipo de comentario. El comentario@parámetropodría indicar que el
comentario describe un parámetro para una rutina,@versiónpodría indicar información sobre la
versión del archivo,@lanzapodría documentar las excepciones lanzadas por una rutina, y así
sucesivamente. Esta técnica le permite usar herramientas para extraer diferentes tipos de
información de sus archivos fuente. Por ejemplo, podría buscar@lanzapara recuperar documentación
sobre todas las excepciones lanzadas por todas las rutinas en un programa.
cc2e.com/3259 Esta convención de C++ se basa en la convención Javadoc, que es una convención de
documentación de interfaz bien establecida para programas Java (java.sun.com/j2se/javadoc/).
Puede definir sus propias convenciones en otros idiomas.
Para cada clase, use un comentario de bloque para describir los atributos generales de la clase:
Describir limitaciones, supuestos de uso, etc.De manera similar a las rutinas, asegúrese de describir
cualquier limitación impuesta por el diseño de la clase. Describa también las suposiciones sobre los datos de
entrada y salida, las responsabilidades de manejo de errores, los efectos globales, las fuentes de los
algoritmos, etc.
Comentar la interfaz de clase¿Puede otro programador entender cómo usar una clase sin
mirar la implementación de la clase? Si no, la encapsulación de clases está seriamente en
riesgo. La interfaz de la clase debe contener toda la información que cualquier persona
necesita para usar la clase. La convención de Javadoc exige, como mínimo, documentación
para cada parámetro y cada valor de retorno (Sun Microsystems 2000). Esto debe hacerse para
todas las rutinas expuestas de cada clase (Bloch 2001).
En la parte superior de un archivo, use un comentario de bloque para describir el contenido del archivo:
Describa el propósito y el contenido de cada archivo.El comentario del encabezado del archivo
debe describir las clases o rutinas contenidas en un archivo. Si todas las rutinas de un programa
están en un archivo, el propósito del archivo es bastante obvio: es el archivo que contiene todo el
programa. Si el propósito del archivo es contener una clase específica, el propósito también es obvio:
es el archivo que contiene la clase con un nombre similar.
Si el archivo contiene más de una clase, explique por qué las clases deben combinarse en un
solo archivo.
Si la división en múltiples archivos fuente se hace por alguna razón distinta a la modularidad,
una buena descripción del propósito del archivo será aún más útil para un programador que
esté modificando el programa. Si alguien está buscando una rutina que
32.5 Técnicas de comentarios 811
lo haceX, ¿ayuda el comentario del encabezado del archivo a esa persona a determinar si este
archivo contiene tal rutina?
Pon tu nombre, dirección de correo electrónico y número de teléfono en el comentario del bloqueLa autoría y
la responsabilidad principal de áreas específicas del código fuente se vuelven importantes en proyectos grandes. Los
proyectos pequeños (menos de 10 personas) pueden utilizar enfoques de desarrollo colaborativo, como la propiedad
de código compartida en la que todos los miembros del equipo son igualmente responsables de todas las secciones
del código. Los sistemas más grandes requieren que los programadores se especialicen en diferentes áreas de
código, lo que hace que la propiedad de código compartido de todo el equipo sea poco práctica.
En ese caso, la autoría es información importante para tener en una lista. Da a otros
programadores que trabajan en el código una pista sobre el estilo de programación, y les
da a alguien a quien contactar si necesitan ayuda. Dependiendo de si trabaja en rutinas,
clases o programas individuales, debe incluir información del autor a nivel de rutina, clase
o programa.
Incluir una etiqueta de control de versionesMuchas herramientas de control de versiones insertarán información
de la versión en un archivo. En CVS, por ejemplo, los personajes
// $Identificación$
se expandirá automáticamente a
Esto le permite mantener la información de versión actual dentro de un archivo sin requerir
ningún esfuerzo del desarrollador más que insertar el originalpsidentificación$comentario.
Incluir avisos legales en el comentario del bloqueA muchas empresas les gusta incluir
declaraciones de derechos de autor, avisos de confidencialidad y otros avisos legales en sus
programas. Si el suyo es uno de ellos, incluya una línea similar a la siguiente. Consulte con el asesor
legal de su empresa para determinar qué información, si corresponde, incluir en sus archivos.
Dale al archivo un nombre relacionado con su contenido.Normalmente, el nombre del archivo debe estar
estrechamente relacionado con el nombre de la clase pública contenida en el archivo. Por ejemplo, si la
clase se llamaEmpleado, el archivo debe llamarseEmpleado.cpp. Algunos lenguajes, especialmente Java,
requieren que el nombre del archivo coincida con el nombre de la clase.
812 Capítulo 32: Código de autodocumentación
Otras lecturasEsta discusión Los programadores más experimentados están de acuerdo en que las técnicas de
está adaptada de “The Book
documentación descritas en la sección anterior son valiosas. La evidencia científica sólida del
Paradigm for Improved
Maintenance” (Omán y valor de cualquiera de las técnicas es aún débil. Sin embargo, cuando se combinan las técnicas,
Cook 1990a) y “El estilo la evidencia de su eficacia es sólida.
tipográfico es más que
cosmético” (Oman y Cook En 1990, Paul Oman y Curtis Cook publicaron un par de estudios sobre el “Paradigma del libro”
1990b). Un análisis similar
para la documentación (1990a, 1990b). Buscaron un estilo de codificación que admitiera varios
se presenta en detalle en
Factores humanos y estilos diferentes de lectura de código. Un objetivo era admitir búsquedas de arriba hacia
tipografía para programas abajo, de abajo hacia arriba y enfocadas. Otra era dividir el código en fragmentos que los
más legibles(Baecker y
programadores pudieran recordar más fácilmente que una larga lista de código homogéneo.
Marcus 1990).
Oman y Cook querían que el estilo proporcionara pistas de alto y bajo nivel sobre la
organización del código.
La "tabla de contenido" muestra los archivos, clases y rutinas (capítulos) de nivel superior. Pueden mostrarse
en una lista, como lo son los capítulos de un libro tradicional, o gráficamente en un diagrama de estructura.
Las "secciones" son las divisiones dentro de las rutinas: declaraciones de rutina, declaraciones de
datos y declaraciones ejecutables, por ejemplo.
Las "referencias cruzadas" son mapas de referencias cruzadas del código, incluidos los números de línea.
Las técnicas de bajo nivel que utilizan Oman y Cook para aprovechar las similitudes
entre un libro y una lista de códigos son similares a las técnicas descritas en el
Capítulo 31, "Diseño y estilo", y en este capítulo.
3 El resultado de usar sus técnicas para organizar el código fue que cuando Oman y Cook asignaron una tarea
2
1
de mantenimiento a un grupo de programadores profesionales experimentados, el tiempo promedio para
DATOS DUROS
realizar una tarea de mantenimiento en un programa de 1000 líneas fue solo alrededor de las tres cuartas
partes del tiempo. tiempo que les tomó a los programadores hacer la misma tarea en una lista de fuente
tradicional (1990b). Además, los puntajes de mantenimiento de los programadores en el código
documentado con el Paradigma del libro promediaron un 20 por ciento más que en el código documentado
tradicionalmente. Oman y Cook llegaron a la conclusión de que si se presta atención a los principios
tipográficos del diseño de libros, se puede obtener una mejora del 10 al 20 por ciento en
32.6 Normas IEEE 813
Los nombres de las normas se componen del número de las normas, el año en que se adoptó
la norma y el nombre de la norma. Asi que,IEEE/EIA Std 12207-1997, Tecnología de la
información—Procesos del ciclo de vida del software, se refiere al estándar número 12207.2,
que fue adoptado en 1997 por IEEE y EIA.
Estos son algunos de los estándares nacionales e internacionales más aplicables a los proyectos de
software:
cc2e.com/3266 El estándar de nivel superior esISO/IEC Std 12207, Tecnología de la información—Procesos del ciclo
de vida del software, que es el estándar internacional que define un marco de ciclo de vida para
desarrollar y gestionar proyectos de software. Esta norma fue adoptada en los Estados Unidos como
IEEE/EIA Std 12207, Tecnología de la información—Procesos del ciclo de vida del software.
IEEE Std 1233-1998, Guía para desarrollar especificaciones de requisitos del sistema
IEEE Std 1061-1998, Estándar para una Metodología de Métricas de Calidad de Software
Estándares de Gestión
cc2e.com/3287 Estos son algunos estándares de gestión de software:
IEEE Std 1074-1997, estándar para desarrollar procesos de ciclo de vida de software
IEEE Std 1540-2001, Estándar para Procesos del Ciclo de Vida del Software - Gestión de Riesgos
IEEE Std 1490-1998, Guía - Adopción del estándar PMI - Una guía para el conocimiento de la
gestión de proyectos
cc2e.com/3294 Aquí hay fuentes que proporcionan una descripción general de los estándares:
cc2e.com/3201 Colección de estándares de ingeniería de software IEEE, edición de 2003. Nueva York, NY: Instituto de
Ingenieros Eléctricos y Electrónicos (IEEE). Este volumen integral contiene 40 de los estándares ANSI/
IEEE más recientes para el desarrollo de software a partir de 2003. Cada estándar incluye un esquema
del documento, una descripción de cada componente del esquema y una justificación para ese
componente. El documento incluye estándares para planes de garantía de calidad, planes de gestión
de configuración, documentos de prueba, especificaciones de requisitos, planes de verificación y
validación, descripciones de diseño, planes de gestión de proyectos y documentación del usuario. El
libro es una destilación de la experiencia de cientos de personas en la cima de sus campos y sería una
ganga prácticamente a cualquier precio. Algunos de los estándares también están disponibles
individualmente. Todos están disponibles en la IEEE Computer Society en Los Alamitos, California y en
www.computer.org/cspress.
Moore, James W.Estándares de ingeniería de software: hoja de ruta de un usuario. Los Alamitos, CA: IEEE
Computer Society Press, 1997. Moore brinda una descripción general de los estándares de ingeniería de
software IEEE.
Recursos adicionales 815
Recursos adicionales
cc2e.com/3208 Además de los estándares IEEE, muchos otros recursos están disponibles en la
documentación del programa.
Spinellis, Diomidis.Lectura de código: la perspectiva del código abierto. Boston, MA: Addison-Wesley,
2003. Este libro es una exploración pragmática de técnicas para leer código, incluido dónde encontrar
código para leer, consejos para leer bases de código grandes, herramientas que respaldan la lectura
de código y muchas otras sugerencias útiles.
cc2e.com/3215 SourceForge.net. Durante décadas, un problema perenne en la enseñanza del desarrollo de software ha sido
encontrar ejemplos de tamaño real de código de producción para compartir con los estudiantes. Mucha
Me pregunto cuántos
grandes novelistas nunca gente aprende más rápido estudiando ejemplos de la vida real, pero la mayoría de las bases de código de
han leído la obra de otro, tamaño real son tratadas como información patentada por las empresas que las crearon. Esta situación ha
cuántos grandes pintores
mejorado drásticamente gracias a la combinación de Internet y el software de código abierto. El sitio web de
nunca han estudiado las
Source Forge contiene código para miles de programas en C, C++, Java, Visual Basic, PHP, Perl, Python y
pinceladas de otro, cuántos
hábiles cirujanos nunca muchos otros lenguajes, todos los cuales puede descargar de forma gratuita. Los programadores pueden
aprendido mirando por encima del
beneficiarse de navegar a través del código en el sitio web para ver ejemplos mucho más grandes del mundo
hombro de un colega... Y, sin embargo,
real queCódigo completo, segunda edición, es capaz de mostrar en sus ejemplos de código corto. Los
eso es lo que esperamos que hagan los
programadores. programadores jóvenes que no hayan visto previamente ejemplos extensos de código de producción
—david thomas encontrarán este sitio web especialmente valioso como fuente de buenas y malas prácticas de codificación.
cc2e.com/3222 Microsistemas Sun. “Cómo escribir comentarios de documentos para la herramienta Javadoc”, 2000.
Disponible enhttp://java.sun.com/j2se/javadoc/writingdoccomments/. Este artículo describe cómo
usar Javadoc para documentar programas Java. Incluye consejos detallados sobre cómo etiquetar
comentarios usando un@etiquetanotación de estilo. También incluye muchos detalles específicos
sobre cómo redactar los comentarios en sí. Las convenciones de Javadoc son probablemente los
estándares de documentación a nivel de código más desarrollados actualmente disponibles.
Aquí hay fuentes de información sobre otros temas en la documentación del software:
McConnell, Steve.Guía de supervivencia de proyectos de software. Redmond, WA: Microsoft Press, 1998.
Este libro describe la documentación requerida por un proyecto crítico empresarial de tamaño mediano. Un
sitio web relacionado proporciona numerosas plantillas de documentos relacionados.
cc2e.com/3229 www.construx.es. Este sitio web (el sitio web de mi empresa) contiene numerosas plantillas de
documentos, convenciones de codificación y otros recursos relacionados con todos los aspectos del
desarrollo de software, incluida la documentación del software.
cc2e.com/3236 Publicar, Ed. "Los verdaderos programadores no usan Pascal"Datamación, julio de 1983, págs. 263–265. Este
artículo irónico aboga por un regreso a los "buenos viejos tiempos" de la programación en Fortran, cuando
los programadores no tenían que preocuparse por problemas molestos como la legibilidad.
816 Capítulo 32: Código de autodocumentación
- ¿Los comentarios explican la intención del código o resumen lo que hace el código, en
lugar de simplemente repetir el código?
comentarios?
Declaraciones y Párrafos
- ¿El código evita los comentarios finales?
- ¿Todos los comentarios cuentan? ¿Se han eliminado o mejorado los comentarios
redundantes, superfluos y autoindulgentes?
Declaraciones de datos
- ¿Se reemplazan los números mágicos con constantes o variables nombradas en lugar de
simplemente documentarse?
Puntos clave 817
Estructuras de Control
- ¿Se comenta cada instrucción de control?
- ¿Se comentan los extremos de las estructuras de control largas o complejas o, cuando
es posible, se simplifican para que no necesiten comentarios?
Rutinas
- ¿Se comenta el propósito de cada rutina?
- ¿Se dan otros datos sobre cada rutina en los comentarios, cuando sea relevante,
incluidos datos de entrada y salida, suposiciones de interfaz, limitaciones, correcciones
de errores, efectos globales y fuentes de algoritmos?
- ¿Están el nombre del autor, la dirección de correo electrónico y el número de teléfono en la lista?
Puntos clave
- La cuestión de si comentar es legítima. Comentar mal hecho es una
pérdida de tiempo y, a veces, dañino. Bien hecho, comentar vale la pena.
- Los comentarios deben decir cosas sobre el código que el código no puede decir sobre sí
mismo, a nivel de resumen o de intención.
- Algunos estilos de comentarios requieren mucho trabajo administrativo tedioso. Desarrolle un estilo que
sea fácil de mantener.
V413HAV
capitulo 33
Carácter personal
cc2e.com/3313 Contenido
- 33.1 ¿No está el carácter personal fuera del tema?: página 820
Temas relacionados
El carácter personal ha recibido un raro grado de atención en el desarrollo de software. Desde el histórico
artículo de Edsger Dijkstra de 1965, “La programación considerada como una actividad humana”, el carácter
del programador ha sido considerado como un área de investigación legítima y fructífera. títulos comoLa
psicología de la construcción de puentesy “Experimentos exploratorios en el comportamiento de los
abogados” puede parecer absurdo, pero en el campo de la informáticaLa psicología de la programación
informática, "Experimentos exploratorios en el comportamiento del programador" y títulos similares son
clásicos.
Los ingenieros de todas las disciplinas conocen los límites de las herramientas y los materiales con los
que trabajan. Si eres ingeniero eléctrico, conoces la conductividad de varios metales y cientos de
formas de usar un voltímetro. Si es ingeniero estructural, conoce las propiedades de carga de la
madera, el hormigón y el acero.
819
820 Capítulo 33: Carácter personal
En consecuencia, cuando los ingenieros de software estudian las propiedades esenciales de sus
herramientas y materias primas, descubren que están estudiando personas: intelecto, carácter y otros
atributos que son menos tangibles que la madera, el hormigón y el acero.
Si está buscando consejos de programación concretos, este capítulo puede parecer demasiado
abstracto para ser útil. Sin embargo, una vez que haya asimilado los consejos específicos del
resto del libro, este capítulo explica en detalle lo que debe hacer para seguir mejorando. Lea la
siguiente sección y luego decida si desea omitir el capítulo.
3
Una vez que decidas convertirte en un programador superior, el potencial de mejora es
2
1 enorme. Estudio tras estudio ha encontrado diferencias del orden de 10 a 1 en el tiempo
requerido para crear un programa. También encontraron diferencias del orden de 10 a 1 en el
DATOS DUROS
tiempo requerido para depurar un programa y de 10 a 1 en el tamaño, la velocidad, la tasa de
error y la cantidad de errores detectados resultantes (Sackman, Erikson y Grant 1968; Curtis
1981). , Mills 1983, DeMarco y Lister 1985, Curtis y otros 1986, Card 1987, Valett y McGarry
1989).
No puedes hacer nada con tu inteligencia, dice la sabiduría clásica, pero puedes
hacer algo con tu carácter. Y resulta que el carácter es el factor más decisivo en la
composición de un programador superior.
33.2 Inteligencia y Humildad 821
despertarse alguna mañana para Comprender completamente un programa promedio requiere una capacidad casi ilimitada para absorber
encontrarse como uno de los detalles y una capacidad igual para comprenderlos todos al mismo tiempo. La forma en que enfocas tu
competentes de su generación.
inteligencia es más importante que la cantidad de inteligencia que tienes.
—Guillermo James
El propósito de muchas buenas prácticas de programación es reducir la carga de las celdas grises.
Aquí están algunos ejemplos:
- Escribir programas en términos del dominio del problema en lugar de en términos de detalles
de implementación de bajo nivel reduce su carga de trabajo mental.
Podrías pensar que el mejor camino sería desarrollar mejores habilidades mentales para que no
necesites estas muletas de programación. Podrías pensar que un programador que usa muletas
mentales está tomando el camino bajo. Empíricamente, sin embargo, se ha demostrado que los
programadores humildes que compensan sus falibilidades escriben código que es más fácil de
entender para ellos y para otros y que tiene menos errores. El verdadero camino bajo es el camino
de los errores y los retrasos en los horarios.
822 Capítulo 33: Carácter personal
33.3 Curiosidad
Una vez que admite que su cerebro es demasiado pequeño para comprender la mayoría de los programas y
se da cuenta de que la programación efectiva es una búsqueda de formas de compensar ese hecho,
comienza una búsqueda de formas de compensar durante toda su carrera. En el desarrollo de un
programador superior, la curiosidad por temas técnicos debe ser una prioridad. La información técnica
relevante cambia continuamente. Muchos programadores Web nunca han tenido que programar en
Microsoft Windows, y muchos programadores de Windows nunca han tenido que lidiar con DOS, UNIX o
tarjetas perforadas. Las características específicas del entorno técnico cambian cada 5 a 10 años. Si no tiene
la curiosidad suficiente para mantenerse al día con los cambios, es posible que se encuentre jugando a las
cartas en casa de los antiguos programadores con T-Bone Rex y las hermanas Brontosaurus.
Los programadores están tan ocupados trabajando que a menudo no tienen tiempo para sentir curiosidad
acerca de cómo podrían hacer mejor su trabajo. Si esto es cierto para ti, no estás solo. Las siguientes
subsecciones describen algunas acciones específicas que puede tomar para ejercitar su curiosidad y hacer
del aprendizaje una prioridad.
Referencia cruzadaPara una Construya su conciencia sobre el proceso de desarrollo.Cuanto más consciente esté del
discusión más completa sobre la
proceso de desarrollo, ya sea por la lectura o por sus propias observaciones sobre el
importancia del proceso en el
Si su carga de trabajo consiste enteramente en asignaciones a corto plazo que no desarrollan sus
habilidades, siéntase insatisfecho. Si está trabajando en un mercado de software competitivo, la mitad de lo
que ahora necesita saber para hacer su trabajo estará desactualizado en tres años. Si no estás aprendiendo,
te estás convirtiendo en un dinosaurio.
3 Tiene demasiada demanda para pasar tiempo trabajando para una gerencia que no tiene en cuenta sus
2
1
intereses. A pesar de algunos altibajos y algunos trabajos que se mudan al extranjero, se espera que la
DATOS DUROS
cantidad promedio de trabajos de software disponibles en los EE. UU. aumente dramáticamente entre 2002 y
2012. Se espera que los trabajos para analistas de sistemas aumenten alrededor del 60 por ciento y para
ingenieros de software alrededor del 50 por ciento. por ciento. Para todas las categorías de trabajos de
computación combinadas, se crearán alrededor de 1 millón de nuevos trabajos más allá de los 3 millones
que existen actualmente (Hecker 2001, BLS 2004). Si no puede aprender en su trabajo, busque uno nuevo.
Referencia cruzadaVarios aspectos ExperimentoUna forma efectiva de aprender sobre programación es experimentar con la
clave de la programación.
programación y el proceso de desarrollo. Si no sabe cómo funciona una característica de su
giran en torno a la idea de
¿Qué pasa si el programa corto muestra que la función no funciona de la manera que desea? Eso es
lo que querías averiguar. Es mejor averiguarlo en un programa pequeño que en uno grande. Una
clave para una programación efectiva es aprender a cometer errores rápidamente, aprendiendo de
ellos cada vez. Cometer un error no es pecado. No aprender de un error lo es.
Otras lecturasUn gran libro Lea sobre la resolución de problemasLa resolución de problemas es la actividad central en la construcción
que enseña a resolver
de software de computadora. Herbert Simon informó sobre una serie de experimentos sobre la resolución
problemas es el de James
Adams. Superación de problemas humanos. Descubrieron que los seres humanos no siempre descubren por sí mismos
conceptual (2001). estrategias inteligentes para resolver problemas, aunque las mismas estrategias podrían enseñarse
fácilmente a las mismas personas (Simon 1996). La implicación es que incluso si desea reinventar la rueda,
no puede contar con el éxito. Podrías reinventar el cuadrado en su lugar.
Analiza y planifica antes de actuarDescubrirá que existe una tensión entre el análisis y la acción. En algún
momento tienes que dejar de recopilar datos y actuar. Sin embargo, el problema para la mayoría de los
programadores no es un exceso de análisis. El péndulo se encuentra actualmente tan lejos en el lado de
"actuación" del arco que puede esperar hasta que esté al menos parcialmente en el medio antes de
preocuparse por quedarse atascado en el lado de "análisis-parálisis".
cc2e.com/3320 Conoce los proyectos exitososUna forma especialmente buena de aprender sobre programación es
estudiar el trabajo de los grandes programadores. Jon Bentley piensa que deberías poder sentarte
con una copa de brandy y un buen cigarro y leer un programa como lo harías con una buena novela.
Eso podría no ser tan descabellado como parece. La mayoría de la gente no querría utilizar su tiempo
de ocio para escudriñar una lista de fuentes de 500 páginas, pero muchas personas disfrutarían
estudiando un diseño de alto nivel y profundizando en listas de fuentes más detalladas para áreas
seleccionadas.
Thomas Kuhn señala que una parte de cualquier ciencia madura es un conjunto de problemas
resueltos que comúnmente se reconocen como ejemplos de buen trabajo en el campo y que sirven
como ejemplos para el trabajo futuro (Kuhn 1996). La ingeniería de software recién está comenzando
a madurar a este nivel. En 1990, la Junta de Tecnología y Ciencias de la Computación concluyó que
había pocos estudios de casos documentados de éxitos o fracasos en el campo del software (CSTB
1990).
porque esto es significativo. Que una de las columnas informáticas más populares, "Programming
Pearls", se haya construido en torno a estudios de casos de problemas de programación también es
sugerente. Y uno de los libros más populares en ingeniería de software esEl Hombre-Mes Mítico, un
estudio de caso en la gestión de la programación del proyecto IBM OS/360.
Con o sin un libro de estudios de casos de programación, encuentre el código escrito por
programadores superiores y léalo. Pide ver el código de los programadores que respetas. Pida
mirar el código de los programadores que no. Compare su código y compare su código con el
suyo. ¿Cuáles son las diferencias? ¿Por qué son diferentes? ¿Qué camino es mejor? ¿Por qué?
Además de leer el código de otras personas, desarrolle el deseo de saber qué piensan los
programadores expertos sobre su código. Encuentre programadores de clase mundial que le
brinden sus críticas. Mientras escucha las críticas, filtre los puntos que tengan que ver con sus
idiosincrasias personales y concéntrese en los puntos que importan. Entonces cambia tu
programación para que sea mejor.
Referencia cruzadaPara libros que Leer otros libros y publicaciones periódicas.Date una palmadita en la espalda por leer este libro.
puede usar en un programa de lectura
Ya estás aprendiendo más que la mayoría de las personas en la industria del software porque un
personal, consulte la Sección 35.4,
Afíliate con otros profesionalesEncuentre otras personas que se preocupen por mejorar sus habilidades de
desarrollo de software. Asista a una conferencia, únase a un grupo de usuarios local o participe en un grupo
de discusión en línea.
33.3 Curiosidad 825
Otras lecturasPara otras Comprometerse con el desarrollo profesionalLos buenos programadores buscan
discusiones sobre los niveles de
constantemente formas de mejorar. Considere la siguiente escalera de desarrollo
programador, consulte "Programa
de desarrollo profesional de
profesional utilizada en mi empresa y varias otras:
Construx" (Capítulo 16) en
Desarrollo de software - Nivel 1: PrincipioUn principiante es un programador capaz de usar las capacidades
profesional(McConnell 2004). básicas de un lenguaje. Tal persona puede escribir clases, rutinas, bucles y
condicionales y usar muchas de las características de un lenguaje.
Un gran codificador que no hace hincapié en la legibilidad probablemente esté atascado en el Nivel 3, pero
incluso ese no suele ser el caso. En mi experiencia, la razón principal por la que las personas escriben código
ilegible es porque su código es malo. No se dicen a sí mismos: "Mi código es malo, así que haré que sea
difícil de leer". Simplemente no entienden su código lo suficientemente bien como para hacerlo legible, lo
que los encierra en uno de los niveles inferiores.
El peor código que he visto fue escrito por alguien que no dejaba que nadie se acercara a sus
programas. Finalmente, su gerente amenazó con despedirla si no cooperaba. Su código no
estaba comentado y lleno de variables comox, xx, xxx, xx1, yxx2, todos los cuales eran globales.
El jefe de su gerente pensó que era una gran programadora porque solucionaba los errores
rápidamente. La calidad de su código le dio abundantes oportunidades para demostrar su
habilidad para corregir errores.
Los dos primeros elementos de esta lista, admitir que no sabes algo o que cometiste
un error, hacen eco del tema de la humildad intelectual discutido anteriormente.
¿Cómo puedes aprender algo nuevo si finges que ya lo sabes todo? Será mejor que
finjas que no sabes nada. Escuche las explicaciones de las personas, aprenda algo
nuevo de ellas y evalúe siellossaber queelloshablemos acerca.
Esté preparado para cuantificar su grado de certeza sobre cualquier tema. Si suele ser del 100 por ciento, es una
señal de advertencia.
Cualquier tonto puede defender Negarse a admitir errores es un hábito particularmente molesto. Si Sally se niega a
sus errores, y la mayoría
admitir un error, aparentemente cree que no admitirlo engañará a los demás para que
los tontos lo hacen.
—Dale Carnegie crean que ella no lo cometió. El opuesto es verdad. Todos sabrán que ella cometió un
error. Los errores se aceptan como parte del flujo y reflujo de actividades intelectuales
complejas, y mientras no haya sido negligente, nadie le reprochará los errores.
Si se niega a admitir un error, la única persona a la que engañará es a sí misma. Todos los demás
aprenderán que están trabajando con un programador orgulloso que no es completamente honesto.
Esa es una falta más condenatoria que cometer un simple error. Si comete un error, admítalo rápida y
enfáticamente.
Pretender entender los mensajes del compilador cuando no es así es otro punto ciego
común. Si no entiende una advertencia del compilador o si cree que sabe lo que significa
pero tiene demasiado tiempo para comprobarlo, ¿adivine qué es realmente una pérdida
de tiempo? Probablemente terminará tratando de resolver el problema desde cero
mientras el compilador le muestra la solución en la cara. Varias personas me han pedido
ayuda para depurar programas. Preguntaré si tienen una compilación limpia y dirán que
sí. Luego comenzarán a explicar los síntomas del problema y les diré: “Hmmmm. Parece
que sería un puntero no inicializado, pero el compilador debería haberte advertido sobre
eso”. Entonces dirán: “Oh, sí, advirtió sobre eso. Nosotros
33.4 Honestidad Intelectual 827
Pensé que significaba otra cosa. Es difícil engañar a otras personas acerca de tus errores. Es aún
más difícil engañar a la computadora, así que no pierdas el tiempo intentándolo.
El primer 90 por ciento del La información sobre el estado es un área de duplicidad escandalosa. Los programadores son
código representa el primer 90
conocidos por decir que un programa está "completo en un 90 por ciento" durante el último 50 por
por ciento del tiempo de
desarrollo. El 10 por ciento ciento del proyecto. Si su problema es que tiene un mal sentido de su propio progreso, puede
restante del código representa resolverlo aprendiendo más sobre cómo trabaja. Pero si su problema es que no dice lo que piensa
el otro 90 por ciento del
porque quiere dar la respuesta que su gerente quiere escuchar, esa es una historia diferente. Un
tiempo de desarrollo.
—tom cargill gerente generalmente aprecia las observaciones honestas sobre el estado de un proyecto, incluso si
no son las opiniones que el gerente quiere escuchar. Si sus observaciones están bien pensadas,
hágalas tan desapasionadamente como pueda y en privado. La gerencia necesita tener información
precisa para coordinar las actividades de desarrollo, y la cooperación total es esencial.
cc2e.com/3341 Un problema relacionado con los informes de estado inexactos es la estimación inexacta. El
escenario típico es así: la gerencia le pide a Bert una estimación de cuánto tiempo llevaría
desarrollar un nuevo producto de base de datos. Bert habla con algunos programadores,
analiza algunos números y regresa con una estimación de ocho programadores y seis meses.
Su gerente dice: “Eso no es realmente lo que estamos buscando. ¿Puedes hacerlo en menos
tiempo, con menos programadores?” Bert se va y lo piensa y decide que por un período corto
podría reducir el tiempo de capacitación y vacaciones y hacer que todos trabajen un poco de
tiempo extra. Vuelve con una estimación de seis programadores y cuatro meses. Su gerente
dice: “Eso es genial. Este es un proyecto de prioridad relativamente baja, así que trate de
mantenerlo a tiempo sin horas extra porque el presupuesto no lo permitirá”.
El error que cometió Bert fue no darse cuenta de que las estimaciones no son negociables.
Puede revisar una estimación para que sea más precisa, pero negociar con su jefe no cambiará
el tiempo que lleva desarrollar un proyecto de software. Bill Weimer de IBM dice:
“Descubrimos que los técnicos, en general, eran muy buenos para estimar los requisitos y los
cronogramas de los proyectos. El problema que tenían era defender sus decisiones;
necesitaban aprender a mantenerse firmes” (Weimer en Metzger y Boddie 1996). Bert no va a
hacer más feliz a su gerente con la promesa de entregar un proyecto en
828 Capítulo 33: Carácter personal
Si la gerencia aplica presión para cambiar su estimación, tenga en cuenta que, en última instancia, la
decisión de hacer un proyecto recae en la gerencia: “Mire. Esto es lo que va a costar. No puedo decir
si vale la pena este precio para la empresa, ese es su trabajo. Pero puedo decirle cuánto tiempo lleva
desarrollar una pieza de software: ese es mi trabajo. No puedo 'negociar' cuánto tiempo tomará; eso
es como negociar cuántos pies hay en una milla. No se puede negociar las leyes de la naturaleza. Sin
embargo, podemos negociar otros aspectos del proyecto que afecten el cronograma y luego
reestimar el cronograma. Podemos eliminar funciones, reducir el rendimiento, desarrollar el
proyecto en incrementos o usar menos personas y un cronograma más largo o más personas y un
cronograma más corto”.
Uno de los intercambios más aterradores que he escuchado fue en una conferencia sobre la
gestión de proyectos de software. El orador era el autor de un libro de gestión de proyectos de
software de gran éxito de ventas. Un miembro de la audiencia preguntó: "¿Qué haces si la
gerencia pide un presupuesto y sabes que si les das un presupuesto exacto dirán que es
demasiado alto y decidirán no hacer el proyecto?" El orador respondió que esa era una de
esas áreas difíciles en las que tenías que conseguir que la gerencia aceptara el proyecto
subestimándolo. Dijo que una vez que hubieran invertido en la primera parte del proyecto, lo
verían hasta el final.
Algunos programadores creativos ven la disciplina de los estándares y convenciones como sofocante
para su creatividad. El opuesto es verdad. ¿Puedes imaginar un sitio web en el que cada página use
diferentes fuentes, colores, alineación de texto, estilos de gráficos y pistas de navegación? El efecto
sería caótico, no creativo. Sin estándares y convenciones sobre grandes proyectos, la finalización del
proyecto en sí es imposible. La creatividad ni siquiera es imaginable. No desperdicies tu creatividad
en cosas que no importan. Establezca convenciones en áreas no críticas para que pueda enfocar sus
energías creativas en los lugares que cuentan.
Una obra maestra de programación requiere tanta disciplina. Si no trata de analizar los requisitos y
el diseño antes de comenzar a codificar, gran parte de su aprendizaje sobre el proyecto ocurrirá
durante la codificación y el resultado de su trabajo se parecerá más a la pintura con los dedos de un
niño de tres años que a una obra de arte. .
830 Capítulo 33: Carácter personal
33.7 Pereza
Pereza: La cualidad que te hace La pereza se manifiesta de varias maneras:
hacer un gran esfuerzo para
ahorran trabajo y que otras - Hacer una tarea desagradable rápidamente para quitarla del camino
personas encontrarán útiles, y
documenta lo que escribiste para - Escribir una herramienta para hacer la tarea desagradable para que nunca tenga que volver a hacer la
que no tengas que responder tarea
tantas preguntas al respecto.
Algunas de estas manifestaciones de pereza son mejores que otras. La primera casi nunca es
—Larry Muro
beneficiosa. Probablemente haya tenido la experiencia de pasar varias horas lidiando con trabajos
que realmente no necesitaban hacerse para no tener que enfrentarse a un trabajo relativamente
menor que no podía evitar. Detesto la entrada de datos, y muchos programas requieren una
pequeña cantidad de entrada de datos. He sido conocido por retrasar el trabajo en un programa
durante días solo para retrasar la inevitable tarea de ingresar varias páginas de números a mano.
Este hábito es “verdadera pereza”. Se manifiesta nuevamente en el hábito de compilar una clase para
ver si funciona, de modo que pueda evitar el ejercicio de verificar la clase con su mente.
Las pequeñas tareas nunca son tan malas como parecen. Si desarrolla el hábito de
hacerlo de inmediato, puede evitar el tipo de pereza que procrastina. Este hábito es
“pereza ilustrada”, el segundo tipo de pereza. Todavía eres perezoso, pero estás
solucionando el problema al pasar la menor cantidad de tiempo posible en algo que es
desagradable.
La tercera opción es escribir una herramienta para hacer la tarea desagradable. Esto es
“pereza a largo plazo”. Sin duda, es el tipo de pereza más productiva (siempre que al final
ahorres tiempo al haber escrito la herramienta). En estos contextos, una cierta cantidad de
pereza es beneficiosa.
Cuando atraviesas el espejo, ves el otro lado de la imagen de la pereza. “Hustle” o “hacer un
esfuerzo” no tiene el brillo rosado que tiene en la clase de educación física de la escuela
secundaria. El ajetreo es un esfuerzo adicional e innecesario. Muestra que estás ansioso, pero
no que estás haciendo tu trabajo. Es fácil confundir movimiento con progreso, ajetreo con ser
productivo. El trabajo más importante en la programación efectiva es pensar, y las personas
tienden a no parecer ocupadas cuando están pensando. Si trabajara con un programador que
pareciera estar ocupado todo el tiempo, asumiría que no es un buen programador porque no
está usando su herramienta más valiosa, su cerebro.
El ajetreo no es la única característica que podrías admirar en otros aspectos de tu vida, pero
eso no funciona muy bien en el desarrollo de software.
33.8 Características que no importan tanto como crees 831
Persistencia
Dependiendo de la situación, la persistencia puede ser un activo o un pasivo. Como la mayoría
de los conceptos cargados de valor, se identifica con diferentes palabras dependiendo de si
cree que es una buena o mala calidad. Si quieres identificar la persistencia como una mala
cualidad, dices que es "terquedad" o "terquedad". Si quieres que sea de buena calidad, llámalo
“tenacidad” o “perseverancia”.
La mayoría de las veces, la persistencia en el desarrollo de software es obstinación, tiene poco valor. La
persistencia cuando estás atascado en una pieza de código nuevo casi nunca es una virtud. Intente rediseñar
la clase, pruebe con un enfoque de codificación alternativo o intente volver a él más tarde. Cuando un
enfoque no funciona, es un buen momento para probar una alternativa (Pirsig 1974).
Referencia cruzadaPara una En la depuración, puede ser muy satisfactorio rastrear el error que lo ha estado molestando durante
discusión más detallada de la
cuatro horas, pero a menudo es mejor darse por vencido después de una cierta cantidad de tiempo
persistencia en la depuración,
Es difícil saber cuándo rendirse, pero es esencial que preguntes. Cuando notes que estás frustrado,
es un buen momento para hacer la pregunta. Preguntar no significa necesariamente que sea hora de
darse por vencido, pero probablemente signifique que es hora de establecer algunos parámetros en
la actividad: “Si no resuelvo el problema usando este enfoque dentro de los próximos 30 minutos, lo
tomaré”. 10 minutos para hacer una lluvia de ideas sobre diferentes enfoques y probar el mejor para
la próxima hora”.
Experiencia
El valor de la experiencia práctica en comparación con el aprendizaje de libros es menor en el
desarrollo de software que en muchos otros campos por varias razones. En muchos otros campos, el
conocimiento básico cambia tan lentamente que alguien que se graduó de la universidad 10 años
después que tú probablemente aprendió el mismo material básico que tú. En el desarrollo de
software, incluso el conocimiento básico cambia rápidamente. La persona que se graduó de la
universidad 10 años después que tú probablemente aprendió el doble sobre técnicas de
programación efectivas. Los programadores mayores tienden a ser vistos con recelo, no solo porque
pueden estar fuera de contacto con una tecnología específica, sino porque es posible que nunca
hayan estado expuestos a los conceptos básicos de programación que se hicieron muy conocidos
después de dejar la escuela.
En otros campos, es probable que lo que aprenda hoy sobre su trabajo lo ayude en su trabajo
mañana. En software, si no puedes deshacerte de los hábitos de pensamiento que desarrollaste
mientras usabas tu antiguo lenguaje de programación o las técnicas de ajuste de código que
funcionaban en tu vieja máquina, tu experiencia será peor que ninguna. mucho software
832 Capítulo 33: Carácter personal
la gente pasa su tiempo preparándose para pelear la última guerra en lugar de la siguiente. Si no puedes
cambiar con los tiempos, la experiencia es más una desventaja que una ayuda.
Aparte de los rápidos cambios en el desarrollo de software, las personas a menudo sacan
conclusiones equivocadas de sus experiencias. Es difícil ver tu propia vida objetivamente.
Puede pasar por alto elementos clave de su experiencia que le harían sacar conclusiones
diferentes si los reconociera. Leer estudios de otros programadores es útil porque los estudios
revelan la experiencia de otras personas, lo suficientemente filtrada como para que pueda
examinarla objetivamente.
La gente también pone un énfasis absurdo en laMontode experiencia que tienen los programadores.
“Queremos un programador con cinco años de experiencia en programación C” es una declaración
tonta. Si un programador no ha aprendido C después de un año o dos, los próximos tres años no
harán mucha diferencia. Este tipo de “experiencia” tiene poca relación con el desempeño.
Programación gonzo
Si no ha pasado al menos un mes trabajando en el mismo programa, trabajando 16
horas al día, soñando con él durante las 8 horas restantes de sueño inquieto,
trabajando varias noches seguidas tratando de eliminar ese "último error" del
programa, entonces realmente no ha escrito un programa de computadora
complicado. Y es posible que no tenga la sensación de que hay algo emocionante en la
programación.
—Edward Yourdon
Este lujurioso tributo al machismo de la programación es pura tontería y una receta casi
segura para el fracaso. Esos períodos de programación de toda la noche te hacen sentir como
el mejor programador del mundo, pero luego tienes que pasar varias semanas corrigiendo los
defectos que instalaste durante tu gloria. Por todos los medios, entusiasmarse con la
programación. Pero la emoción no sustituye a la competencia. Recuerda cuál es más
importante.
33.9 Hábitos 833
33.9 Hábitos
Las virtudes morales, entonces, no son engendradas en nosotros por la
naturaleza ni son contrarias a ella... su pleno desarrollo en nosotros se debe
al hábito... Todo lo que tenemos que aprender a hacer, lo aprendemos
haciéndolo. ..Los hombres se convertirán en buenos constructores como
resultado de construir bien y en malos como resultado de construir mal.
diferencia, o más bien toda la diferencia del mundo.
— Aristóteles
Los buenos hábitos son importantes porque la mayor parte de lo que haces como programador lo
haces sin pensarlo conscientemente. Por ejemplo, en algún momento, podría haber pensado en
cómo quería dar formato a los bucles sangrados, pero ahora no vuelve a pensar en ello cada vez que
escribe un nuevo bucle. Lo haces como lo haces por costumbre. Esto es cierto para prácticamente
todos los aspectos del formato del programa. ¿Cuándo fue la última vez que cuestionaste seriamente
tu estilo de formato? Es muy probable que si ha estado programando durante cinco años, la última
vez que lo cuestionó fue hace cuatro años y medio. El resto del tiempo has confiado en la costumbre.
Referencia cruzadaPara obtener detalles Tienes hábitos en muchas áreas. Por ejemplo, los programadores tienden a verificar los índices de
sobre los errores en las declaraciones de
los bucles con cuidado y no a las declaraciones de asignación, lo que hace que los errores en las
asignación, consulte "Errores por
Bill Gates dice que cualquier programador que alguna vez sea bueno es bueno en los primeros años.
Después de eso, si un programador es bueno o no es algo que se concreta (Lammers 1986). Después de
haber estado programando durante mucho tiempo, es difícil empezar a decir de repente: "¿Cómo puedo
hacer que este ciclo sea más rápido?" o "¿Cómo puedo hacer que este código sea más legible?" Estos son
hábitos que los buenos programadores desarrollan temprano.
Cuando aprenda algo por primera vez, aprenda de la manera correcta. Cuando lo haces por primera
vez, estás pensando activamente en ello y todavía tienes una elección fácil entre hacerlo bien o mal.
Después de haberlo hecho unas cuantas veces, presta menos atención a lo que está haciendo y la
“fuerza de la costumbre” se hace cargo. Asegúrate de que los hábitos que toman el control son los
que quieres tener.
Traducido del inglés al español - www.onlinedoctranslator.com
¿Qué sucede si aún no tiene los hábitos más efectivos? ¿Cómo se cambia un mal hábito? Si tuviera la
respuesta definitiva a eso, podría vender cintas de autoayuda en la televisión nocturna. Pero aquí hay al
menos parte de una respuesta. No se puede reemplazar un mal hábito con ningún hábito en absoluto. Es
por eso que las personas que de repente dejan de fumar, maldecir o comer en exceso tienen tantas
dificultades a menos que lo sustituyan por algo más, como goma de mascar. Es más fácil reemplazar un viejo
hábito por uno nuevo que eliminarlo por completo. En la programación, trata de desarrollar nuevos hábitos
que funcionen. Desarrolle los hábitos de escribir una clase en pseudocódigo antes de codificarla y leer
cuidadosamente el código antes de compilarlo, por ejemplo. No tendrás que preocuparte por perder los
malos hábitos; naturalmente, se quedarán en el camino a medida que los nuevos hábitos tomen su lugar.
Recursos adicionales
cc2e.com/3327 Los siguientes son recursos adicionales sobre los aspectos humanos del desarrollo de software:
cc2e.com/3334 Dijkstra, Edsger. “El humilde programador”. Conferencia Premio Turing.Comunicaciones de la ACM15,
núm. 10 (octubre de 1972): 859–66. Este artículo clásico ayudó a comenzar la investigación sobre
cuánto depende la programación de computadoras de las habilidades mentales del programador.
Dijkstra ha enfatizado persistentemente el mensaje de que la tarea esencial de la programación es
dominar la enorme complejidad de la informática. Argumenta que la programación es la única
actividad en la que los humanos tienen que dominar nueve órdenes de magnitud de diferencia entre
el nivel de detalle más bajo y el más alto. Este documento sería una lectura interesante únicamente
por su valor histórico, pero muchos de sus temas suenan frescos décadas después. También
transmite una buena idea de cómo era ser un programador en los primeros días de la informática.
Pirsig, Robert M.Zen y el arte del mantenimiento de motocicletas: una investigación sobre los valores.
William Morrow, 1974. Pirsig brinda una discusión extensa sobre la "calidad", aparentemente en
relación con el mantenimiento de motocicletas. Pirsig trabajaba como redactor técnico de software
cuando escribióZAMM, y sus perspicaces comentarios se aplican tanto a la psicología de los proyectos
de software como al mantenimiento de motocicletas.
Curtis, Bill, ed.Tutorial: Factores humanos en el desarrollo de software. Los Ángeles, CA: IEEE
Computer Society Press, 1985. Esta es una excelente colección de artículos que abordan los aspectos
humanos de la creación de programas de computadora. Los 45 artículos se dividen en secciones
sobre modelos mentales de conocimientos de programación, aprender a programar, resolución de
problemas y diseño, efectos de las representaciones de diseño, características del lenguaje,
diagnóstico de errores y metodología. Si la programación es una de las cosas más
Puntos clave 835
desafíos intelectuales difíciles que la humanidad ha enfrentado alguna vez, aprender más sobre las
capacidades mentales humanas es fundamental para el éxito de la empresa. Estos documentos
sobre factores psicológicos también lo ayudan a volver su mente hacia adentro y aprender cómo
puede programar individualmente de manera más efectiva.
Puntos clave
- Su carácter personal afecta directamente su capacidad para escribir programas de computadora.
- Las características de un programador superior no tienen casi nada que ver con
el talento y todo que ver con un compromiso con el desarrollo personal.
- El buen carácter es principalmente una cuestión de tener los hábitos correctos. Para ser un
gran programador, desarrolla los hábitos correctos y el resto vendrá naturalmente.
capitulo 34
Temas en Software
Artesanía
cc2e.com/3444 Contenido
- 34.3 Escribir programas para personas primero, computadoras segundo: página 841
Temas relacionados
- todo el libro
Este libro trata principalmente sobre los detalles de la construcción del software: clases de alta calidad,
nombres de variables, bucles, disposición del código fuente, integración de sistemas, etc. Este libro ha
quitado énfasis a los temas abstractos para enfatizar temas que son más concretos.
Una vez que las partes anteriores del libro han puesto los temas concretos sobre la mesa, todo
lo que tiene que hacer para apreciar los conceptos abstractos es tomar los temas de los
distintos capítulos y ver cómo se relacionan. Este capítulo hace explícitos los temas abstractos:
complejidad, abstracción, proceso, legibilidad, iteración, etc. Estos temas explican en gran
parte la diferencia entre la piratería y la artesanía del software.
837
838 Capítulo 34: Temas en la artesanía del software
- Definir cuidadosamente las interfaces de clase para que pueda ignorar el funcionamiento interno de
la clase.
- Evitar los datos globales, porque los datos globales aumentan enormemente el porcentaje del código que
necesita hacer malabarismos en su cerebro en un momento dado.
- Evitar el anidamiento profundo de bucles y condicionales porque pueden ser reemplazados por
estructuras de control más simples que queman menos celdas grises.
- No permitir que las clases se conviertan en clases de monstruos que equivalgan a programas
completos en sí mismos.
- Usar nombres de variables claros y que se expliquen por sí mismos para que su
cerebro no tenga que desperdiciar ciclos recordando detalles como "irepresenta el
índice de la cuenta, yj representa el índice de clientes, ¿o era al revés?
- Minimizar el número de parámetros que se pasan a una rutina o, lo que es más importante, pasar
solo los parámetros necesarios para conservar la abstracción de la interfaz de la rutina.
- En general, atacar lo que el Capítulo 5 describe como “dificultades accidentales” siempre que
sea posible.
Cuando coloca una prueba complicada en una función booleana y abstrae el propósito de la
prueba, hace que el código sea menos complejo. Cuando sustituye una búsqueda de tabla por
una cadena lógica complicada, hace lo mismo. Cuando creas una bien definida,
34.2 Elija su proceso 839
La abstracción en sus diversas formas es una herramienta particularmente poderosa para gestionar
la complejidad. La programación ha avanzado en gran medida mediante el aumento de la abstracción
de los componentes del programa. Fred Brooks argumenta que la mayor ganancia jamás lograda en
ciencias de la computación fue el salto del lenguaje de máquina a lenguajes de nivel superior: liberó a
los programadores de preocuparse por las peculiaridades detalladas de piezas individuales de
hardware y les permitió concentrarse en la programación (Brooks 1995). La idea de las rutinas fue
otro gran paso, seguida de clases y paquetes.
Nombrar variables funcionalmente, para el "qué" del problema en lugar del "cómo" de la
solución a nivel de implementación, aumenta el nivel de abstracción. Si dice: "Está bien, voy a
abrir la pila y eso significa que obtendré al empleado más reciente", la abstracción puede
ahorrarle el paso mental "Voy a abrir la pila". Simplemente diga: "Obtendré al empleado más
reciente". Esta es una pequeña ganancia, pero cuando intenta reducir un rango de
complejidad de 1 a 109, cada paso cuenta. El uso de constantes con nombre en lugar de
literales también aumenta el nivel de abstracción. La programación orientada a objetos
proporciona un nivel de abstracción que se aplica a algoritmos y datos al mismo tiempo, un
tipo de abstracción que la descomposición funcional por sí sola no proporciona.
Referencia cruzadaPara obtener detalles sobre Un ejemplo de la importancia del proceso es la consecuencia de no establecer requisitos
estables antes de comenzar a diseñar y codificar. Si no sabe lo que está construyendo, no
cómo hacer que los requisitos sean estables,
Mi mensaje para el programador El mismo principio de atención consciente al proceso se aplica al diseño. Tienes que sentar una
serio es: dedique una parte de su
base sólida antes de poder empezar a construir sobre ella. Si se apresura a codificar antes de
jornada laboral a examinar y
pasado, la abstracción
construir una casa sobre ella.
metodológica es una sabia
Referencia cruzadaPara obtener detalles Los procesos de bajo nivel también son importantes. Si sigue el proceso de escribir pseudocódigo y
sobre la iteración, consulte la Sección
luego completa el código alrededor del pseudocódigo, obtendrá los beneficios de diseñar de arriba
34.8, "Iterar, repetidamente, una y otra
Observar procesos grandes y procesos pequeños significa hacer una pausa para prestar atención a
cómo crea software. Es tiempo bien empleado. Decir que “el código es lo que importa; tienes que
centrarte en lo bueno que es el código, no en un proceso abstracto” es miope e ignora montañas de
pruebas experimentales y prácticas de lo contrario. El desarrollo de software es un ejercicio creativo.
Si no comprende el proceso creativo, no está aprovechando al máximo la herramienta principal que
utiliza para crear software: su cerebro. Un mal proceso desperdicia tus ciclos cerebrales. Un buen
proceso los aprovecha al máximo.
34.3 Escribir programas para las personas primero, las computadoras después
mi programanorte. Una joya de precisión algorística, que ofrece el equilibrio más sublime
entre codificación compacta y eficiente por un lado y legibilidad completamente comentada
para la posteridad por el otro. Compara TU PROGRAMA.
— Stan Kelly-Bootle
Otro tema que se repite a lo largo de este libro es el énfasis en la legibilidad del código. La
comunicación con otras personas es la motivación detrás de la búsqueda del Santo Grial del
código autodocumentado.
- comprensibilidad
- Revisabilidad
- Tasa de error
- depuración
- modificabilidad
En los primeros años de la El código legible no tarda más en escribirse que el código confuso, al menos no a largo plazo. Es más
programación, un programa se
fácil estar seguro de que su código funciona si puede leer fácilmente lo que escribió. Esa debería ser
consideraba propiedad privada del
programas se llenaron de
“¿Qué pasa si solo estoy escribiendo código para mí mismo? ¿Por qué debería hacerlo legible?”
nombres cariñosos y taquigrafías
fuera de la asociación.
peligroso precedente. Tu madre solía decir: "¿Qué pasa si tu rostro se congela en esa expresión?" Y
tu papá solía decir: “Juegas como practicas”. Los hábitos afectan todo tu trabajo; no puede
encenderlos y apagarlos a voluntad, así que asegúrese de que lo que está haciendo es algo que
—Michael Marcotty
desea que se convierta en un hábito. Un programador profesional escribe código legible, punto.
También es bueno reconocer que es discutible que una pieza de código te pertenezca
exclusivamente a ti. Douglas Comer propuso una distinción útil entre programas públicos y
privados (Comer 1981): Los “programas privados” son programas para el uso propio del
programador. No son utilizados por otros. No son modificados por otros. Otros ni siquiera
saben que existen los programas. Suelen ser triviales, y son la rara excepción. Los “programas
públicos” son programas usados o modificados por alguien que no es el autor.
Los estándares para programas públicos y privados pueden ser diferentes. Los programas privados
pueden estar mal escritos y llenos de limitaciones sin afectar a nadie más que al autor. Los
programas públicos deben redactarse con más cuidado: sus limitaciones deben estar documentadas,
deben ser confiables y deben ser modificables. Tenga cuidado con que un programa privado se haga
público, como sucede a menudo con los programas privados. Debe convertir el programa en un
programa público antes de que entre en circulación general. Parte de hacer público un programa
privado es hacerlo legible.
3 Incluso si cree que es el único que leerá su código, en el mundo real es muy probable que
2
1
alguien más necesite modificar su código. Un estudio encontró que 10 generaciones de
DATOS DUROS
programadores de mantenimiento trabajan en un programa promedio antes de que sea
reescrito (Thomas 1984). Los programadores de mantenimiento pasan del 50 al 60 por ciento
de su tiempo tratando de entender el código que tienen que mantener, y aprecian el tiempo
que usted dedica a documentarlo (Parikh y Zvegintzov 1983).
34.4 Programa en tu idioma, no en él 843
Los capítulos anteriores de este libro examinaron las técnicas que lo ayudan a lograr la legibilidad:
buenos nombres de clases, rutinas y variables, formato cuidadoso, rutinas pequeñas, ocultar pruebas
booleanas complejas en funciones booleanas, asignar resultados intermedios a variables para mayor
claridad en cálculos complicados, etc. en. Ninguna aplicación individual de una técnica puede marcar
la diferencia entre un programa legible y uno ilegible, pero la acumulación de muchas pequeñas
mejoras en la legibilidad será significativa.
Si cree que no necesita hacer que su código sea legible porque nadie más lo
mira, asegúrese de no confundir la causa y el efecto.
¿Debería usar una rutina de miembro de clase que sea inconsistente con la abstracción de la
clase solo porque es más conveniente que usar una que proporcione más consistencia? Debe
escribir código de manera que conserve la abstracción representada por la interfaz de la clase
tanto como sea posible. No necesita usar datos globales oirs solo porque su idioma los admite.
Puede optar por no utilizar esas capacidades de programación peligrosas y, en su lugar,
utilizar las convenciones de programación para compensar las debilidades del lenguaje.
Programar usando la ruta más obvia equivale a programarenun lenguaje en lugar de
programacióndentroun idioma; es el equivalente del programador a "Si Freddie saltara de un
puente, ¿tú también saltarías de un puente?" Piense en sus objetivos técnicos y luego decida la
mejor manera de lograr esos objetivos mediante la programación.dentrotu lenguaje.
¿Valor?" y “Objetivos del Buen Muchos de los detalles de la programación son algo arbitrarios. ¿Cuántos espacios sangras en un
Diseño” en la Sección 31.1. bucle? ¿Cómo se formatea un comentario? ¿Cómo se deben ordenar las rutinas de clase? La mayoría
de las preguntas como estas tienen varias respuestas correctas. La forma específica en que se
responda tal pregunta es menos importante que el hecho de que se responda de manera consistente
cada vez. Las convenciones ahorran a los programadores la molestia de responder las mismas
preguntas, tomando las mismas decisiones arbitrarias, una y otra vez. En proyectos con muchos
programadores, el uso de convenciones evita la confusión que resulta cuando diferentes
programadores toman decisiones arbitrarias de manera diferente.
Una convención transmite información importante de manera concisa. En las convenciones de nomenclatura, un solo
carácter puede diferenciar entre variables locales, de clase y globales; las mayúsculas pueden diferenciar de forma
concisa entre tipos, constantes nombradas y variables. Las convenciones de sangría pueden mostrar de manera
concisa la estructura lógica de un programa. Las convenciones de alineación pueden indicar de manera concisa que
las declaraciones están relacionadas.
Las convenciones protegen contra peligros conocidos. Puede establecer convenciones para
eliminar el uso de prácticas peligrosas, para restringir dichas prácticas a los casos en que sean
necesarias o para compensar sus peligros conocidos. Podría eliminar una práctica peligrosa,
por ejemplo, al prohibir variables globales o varias declaraciones en una línea. Podría
compensar una práctica peligrosa al requerir paréntesis alrededor de expresiones
complicadas o requerir que los punteros se establezcan en NULL inmediatamente después de
que se eliminen para ayudar a evitar los punteros colgantes.
Las convenciones agregan previsibilidad a las tareas de bajo nivel. Tener formas
convencionales de manejar solicitudes de memoria, procesamiento de errores, entrada/salida
e interfaces de clase agrega una estructura significativa a su código y facilita que otro
programador lo descubra, siempre que el programador conozca sus convenciones. Como se
mencionó en un capítulo anterior, uno de los mayores beneficios de eliminar datos globales es
que elimina interacciones potenciales entre diferentes clases y subsistemas. Un lector sabe
aproximadamente qué esperar de los datos locales y de clase. Pero es difícil saber cuándo
cambiar los datos globales romperá un poco de código a cuatro subsistemas de distancia. Los
datos globales aumentan la incertidumbre del lector. Con buenas convenciones, usted y sus
lectores pueden dar más por sentado. Se reducirá la cantidad de detalle que tiene que ser
asimilado,
Las convenciones pueden compensar las debilidades del lenguaje. En lenguajes que no admiten
constantes con nombre (como Python, Perl, secuencias de comandos de shell de UNIX, etc.), una
convención puede diferenciar entre variables destinadas a ser tanto de lectura como de escritura y
34.6 Programa en términos del dominio del problema 845
aquellos que están destinados a emular constantes de solo lectura. Las convenciones para el uso
disciplinado de punteros y datos globales son otros ejemplos de cómo compensar las debilidades
del lenguaje con convenciones.
Los programadores en proyectos grandes a veces se pasan de la raya con las convenciones.
Establecen tantos estándares y pautas que recordarlos se convierte en un trabajo de tiempo
completo. Pero los programadores en proyectos pequeños tienden a "ir por la borda", sin darse
cuenta de todos los beneficios de las convenciones concebidas inteligentemente. Debe comprender
su valor real y aprovecharlos; debe usarlos para proporcionar estructura en áreas en las que se
necesita estructura.
El código de nivel superior no debe estar lleno de detalles sobre archivos, pilas, colas, matrices y
personajes cuyos padres no pudieron pensar en mejores nombres para ellos quei,j, yk. El código de
nivel superior debe describir el problema que se está resolviendo. Debe estar repleto de nombres de
clase descriptivos y llamadas de rutina que indiquen exactamente lo que está haciendo el programa,
no abarrotado de detalles sobre cómo abrir un archivo como "solo lectura". El código de nivel
superior no debe contener grupos de comentarios que digan "ies una variable que representa el
índice del registro del archivo del empleado aquí, y luego un poco más tarde se usa para indexar el
archivo de la cuenta del cliente allí”.
Esa es una práctica de programación torpe. En el nivel superior del programa, no necesita saber que
los datos de los empleados vienen como registros o que se almacenan como un archivo. La
información a ese nivel de detalle debe ocultarse. En el nivel más alto, no debería tener idea de cómo
se almacenan los datos. Tampoco necesitas leer un comentario que explique quéisignifica y que se
utiliza para dos propósitos. En su lugar, debería ver diferentes nombres de variables para los dos
propósitos, y también deberían tener nombres distintivos comoíndice de empleadosyíndice de cliente
.
4
Términos de dominio de problemas de alto nivel
3
Términos de dominio de problemas de bajo nivel
2
Estructuras de implementación de bajo nivel
1
Estructuras y herramientas del lenguaje de programación.
0
Operaciones del sistema operativo e instrucciones de máquina
Figura 34-1Los programas se pueden dividir en niveles de abstracción. Un buen diseño le permitirá pasar
gran parte de su tiempo enfocándose solo en las capas superiores e ignorando las capas inferiores.
Si está trabajando en un idioma de alto nivel, no tiene que preocuparse por el nivel más
bajo: su idioma se encarga de eso automáticamente. Si está trabajando en un lenguaje de
bajo nivel, debe intentar crear capas más altas para trabajar, aunque muchos
programadores no lo hacen.
Las estructuras del lenguaje de programación son los tipos de datos primitivos del lenguaje, las estructuras
de control, etc. La mayoría de los lenguajes comunes también brindan bibliotecas adicionales, acceso a
llamadas del sistema operativo, etc. El uso de estas estructuras y herramientas es algo natural, ya que no se
puede programar sin ellas. Muchos programadores nunca trabajan por encima de este nivel de abstracción,
lo que hace que sus vidas sean mucho más difíciles de lo que deberían ser.
Las estructuras de implementación de bajo nivel son estructuras de nivel ligeramente más alto que las
proporcionadas por el propio lenguaje. Tienden a ser las operaciones y los tipos de datos que aprende en
los cursos universitarios sobre algoritmos y tipos de datos: pilas, colas, listas vinculadas, árboles, archivos
indexados, archivos secuenciales, algoritmos de clasificación, algoritmos de búsqueda, etc. Si su programa
consta completamente de código escrito en este nivel, estará inundado de demasiados detalles para
ganar la batalla contra la complejidad.
En este nivel, tiene las primitivas que necesita para trabajar en términos del dominio del problema. Es
una capa adhesiva entre las estructuras informáticas de abajo y el código de dominio de problemas
de alto nivel de arriba. Para escribir código en este nivel, debe averiguar
34.6 Programa en términos del dominio del problema 847
el vocabulario del área del problema y crear bloques de construcción que puede usar para trabajar con el
problema que resuelve el programa. En muchas aplicaciones, esta será la capa de objetos comerciales o una
capa de servicios. Las clases de este nivel proporcionan el vocabulario y los componentes básicos. Las clases
pueden ser demasiado primitivas para usarse para resolver el problema directamente en este nivel, pero
brindan un marco que las clases de nivel superior pueden usar para construir una solución al problema.
Este nivel proporciona el poder de abstracción para trabajar con un problema en sus propios
términos. Su código en este nivel debería ser algo legible por alguien que no sea un genio de la
informática, tal vez incluso por su cliente no técnico. El código en este nivel no dependerá mucho de
las características específicas de su lenguaje de programación porque habrá creado su propio
conjunto de herramientas para trabajar con el problema. En consecuencia, en este nivel su código
depende más de las herramientas que ha creado para sí mismo en el Nivel 3 que de las capacidades
del lenguaje que está utilizando.
Los detalles de implementación deben ocultarse dos capas debajo de esta, en una capa de
estructuras informáticas, para que los cambios en el hardware o el sistema operativo no afecten en
absoluto a esta capa. Incorpore la visión del mundo del usuario en el programa en este nivel porque
cuando el programa cambia, cambiará en términos de la visión del usuario. Los cambios en el
dominio del problema deberían afectar mucho a esta capa, pero deberían ser fáciles de acomodar
mediante la programación en los bloques de construcción del dominio del problema de la capa
inferior.
Además de estas capas conceptuales, a muchos programadores les resulta útil dividir un programa
en otras "capas" que atraviesan las capas descritas aquí. Por ejemplo, la arquitectura típica de tres
niveles atraviesa los niveles descritos aquí y proporciona herramientas adicionales para hacer que el
diseño y el código sean intelectualmente manejables.
- Use clases para implementar estructuras que sean significativas en términos del dominio del
problema.
- Oculte información sobre los tipos de datos de bajo nivel y sus detalles de
implementación.
- Use constantes con nombre para documentar los significados de las cadenas y de los literales numéricos.
- Asigne variables intermedias para documentar los resultados de los cálculos intermedios.
Cuando usted u otra persona dice "Este es un código realmente complicado", es una señal de advertencia,
generalmente de un código deficiente. "Código engañoso" es una frase clave para "código incorrecto". Si cree que el
El hecho de que una clase tenga más errores que el promedio es una señal de advertencia. Algunas clases
propensas a errores tienden a ser la parte más costosa de un programa. Si tiene una clase que ha tenido
más errores que el promedio, probablemente seguirá teniendo más errores que el promedio. Piensa en
reescribirlo.
Si la programación fuera una ciencia, cada señal de advertencia implicaría una acción correctiva específica y bien
definida. Sin embargo, debido a que la programación sigue siendo un oficio, una señal de advertencia simplemente
señala un problema que debe considerar. No necesariamente puede reescribir código complicado o mejorar una
clase propensa a errores.
Así como un número anormal de defectos en una clase le advierte que la clase es de baja calidad, un
número anormal de defectos en un programa implica que su proceso es defectuoso. Un buen
proceso no permitiría desarrollar código propenso a errores. Incluiría los controles y equilibrios de
arquitectura seguidos de revisiones de arquitectura, diseño seguido de revisiones de diseño y código
seguido de revisiones de código. Para cuando el código estuviera listo para la prueba, la mayoría de
los errores se habrían eliminado. El rendimiento excepcional requiere un trabajo inteligente además
de trabajar duro. Mucha depuración en un proyecto es una señal de advertencia que implica que las
personas no están trabajando de manera inteligente. Escribir mucho código en un día y luego pasar
dos semanas depurándolo no funciona bien.
Puede utilizar las métricas de diseño como otro tipo de señal de advertencia. La mayoría de las métricas de
diseño son heurísticas que dan una indicación de la calidad de un diseño. El hecho de que una clase
contenga más de siete miembros no significa necesariamente que esté mal diseñada, pero es una
advertencia de que la clase es complicada. De manera similar, más de 10 puntos de decisión en una rutina,
más de tres niveles de anidamiento lógico, un número inusual de variables, un alto acoplamiento con otras
clases o una baja cohesión de clases o rutinas deberían generar una bandera de advertencia. Ninguno de
estos signos significa necesariamente que una clase está mal diseñada, pero la presencia de cualquiera de
ellos debería hacer que mires la clase con escepticismo.
34.7 Esté atento a las rocas que caen 849
Cualquier señal de advertencia debería hacerle dudar de la calidad de su programa. Como dice
Charles Saunders Peirce, “La duda es un estado de inquietud e insatisfacción del que luchamos
por liberarnos y pasar al estado de creencia”. Trate una señal de advertencia como una
"irritación de duda" que lo impulsa a buscar el estado de creencia más satisfactorio.
Cuando esté inmerso en un programa, preste atención a las señales de advertencia que indican que
parte del diseño del programa no está lo suficientemente bien definido para codificar. Las
dificultades para escribir comentarios, nombrar variables y descomponer el problema en clases
cohesivas con interfaces claras indican que debe pensar más en el diseño antes de codificar. Los
nombres insípidos y la dificultad para describir secciones de código en comentarios concisos son
otros signos de problemas. Cuando el diseño está claro en su mente, los detalles de bajo nivel
aparecen con facilidad.
Sea sensible a las indicaciones de que su programa es difícil de entender. Cualquier molestia
es una pista. Si es difícil para ti, lo será aún más para los próximos programadores. Apreciarán
el esfuerzo extra que hagas para mejorarlo. Si está descifrando el código en lugar de leerlo, es
demasiado complicado. Si es difícil, está mal. Hazlo más simple.
3 Si desea aprovechar al máximo las señales de advertencia, programe de tal manera que cree sus
2
1
propias advertencias. Esto es útil porque incluso después de saber cuáles son los signos, es
DATOS DUROS
sorprendentemente fácil pasarlos por alto. Glenford Myers realizó un estudio de corrección de
defectos en el que descubrió que la causa más común de no encontrar errores era simplemente
pasarlos por alto. Los errores eran visibles en la salida de la prueba pero no se notaban (Myers
1978b).
Haga que sea difícil pasar por alto los problemas en su programa. Un ejemplo es establecer punteros en
nulo después de liberarlos para que causen problemas feos si usa uno por error. Un puntero liberado
podría apuntar a una ubicación de memoria válida incluso después de que se haya liberado. Establecerlo en
nulo garantiza que apunte a una ubicación no válida, lo que hace que el error sea más difícil de pasar por
alto.
Las advertencias del compilador son señales de advertencia literales que a menudo se pasan por alto. Si
su programa genera advertencias o errores, corríjalo para que no lo haga. No tiene muchas posibilidades
de notar señales de advertencia sutiles cuando ignora las que tienen "ADVERTENCIA" impresa
directamente en ellas.
850 Capítulo 34: Temas en la artesanía del software
¿Por qué es especialmente importante prestar atención a las señales de advertencia intelectuales en el
desarrollo de software? La calidad del pensamiento que se incluye en un programa determina en gran
medida la calidad del programa, por lo que prestar atención a las advertencias sobre la calidad del
pensamiento afecta directamente al producto final.
Como se señala en el Capítulo 28, “Administración de la construcción”, las estimaciones del cronograma
durante la planificación inicial del proyecto pueden variar mucho según la técnica de estimación que utilice.
El uso de un enfoque iterativo para la estimación produce una estimación más precisa que confiar en una
sola técnica.
El diseño de software es un proceso heurístico y, como todos los procesos heurísticos, está sujeto a
revisiones y mejoras iterativas. El software tiende a validarse en lugar de probarse, lo que significa
que se prueba y desarrolla de forma iterativa hasta que responde correctamente a las preguntas. Se
deben repetir los intentos de diseño de alto y bajo nivel. Un primer intento puede producir una
solución que funcione, pero es poco probable que produzca la mejor solución. Tomar varios enfoques
repetidos y diferentes produce una comprensión del problema que es poco probable con un solo
enfoque.
La idea de iteración vuelve a aparecer en el ajuste de código. Una vez que el software esté operativo, puede
reescribir pequeñas partes del mismo para mejorar en gran medida el rendimiento general del sistema.
Muchos de los intentos de optimización, sin embargo, dañan el código más de lo que lo ayudan. No es un
proceso intuitivo, y algunas técnicas que parecen hacer que un sistema sea más pequeño y rápido en
realidad lo hacen más grande y más lento. La incertidumbre sobre el efecto de cualquier técnica de
optimización crea la necesidad de ajustar, medir y ajustar de nuevo. Si un cuello de botella es fundamental
para el rendimiento del sistema, puede ajustar el código varias veces y varios de sus intentos posteriores
pueden tener más éxito que el primero.
Una definición de ingeniería es hacer por un centavo lo que cualquiera puede hacer por un dólar.
Iterar en las últimas etapas es hacer por dos dólares lo que cualquiera puede hacer por un dólar. Fred
Brooks sugirió que “construyas uno para tirar; lo harás, de todos modos” (Brooks 1995). El truco de la
ingeniería de software es construir las partes desechables de la manera más rápida y económica
posible, que es el objetivo de la iteración en las primeras etapas.
Oráculos de software
Referencia cruzadaPara obtener detalles Desafortunadamente, la actitud celosa es decretada desde lo alto por algunas de las personas
sobre el manejo de la religión de
más prominentes en la profesión. Es importante dar a conocer las innovaciones para que los
programación como administrador,
Eclecticismo
Referencia cruzadaPara obtener más La fe ciega en un método excluye la selectividad que necesita para encontrar las soluciones más
información sobre la diferencia entre
efectivas a los problemas de programación. Si el desarrollo de software fuera un proceso algorítmico
los enfoques algorítmico y heurístico,
Sección 5.4.
de arriba hacia abajo funciona bien. A veces, un enfoque orientado a objetos, una composición de
abajo hacia arriba o un enfoque de estructura de datos funciona mejor. Tienes que estar dispuesto a
probar varios enfoques, sabiendo que algunos fallarán y otros tendrán éxito, pero sin saber cuáles
funcionarán hasta después de probarlos. Tienes que ser ecléctico.
852 Capítulo 34: Temas en la artesanía del software
Referencia cruzadaPara una descripción El eclecticismo es una actitud útil para aplicar a las técnicas presentadas en este libro tanto como a
más detallada de la metáfora de la caja de
las técnicas descritas en otras fuentes. Las discusiones de varios temas presentados aquí tienen
herramientas, ver
Una postura dogmática entra en conflicto con el enfoque ecléctico de caja de herramientas para la construcción de
software. Es incompatible con la actitud necesaria para construir software de alta calidad.
Experimentación
El eclecticismo tiene un pariente cercano en la experimentación. Necesitas experimentar a lo largo
del proceso de desarrollo, pero la inflexibilidad celosa frena el impulso. Para experimentar con
eficacia, debe estar dispuesto a cambiar sus creencias en función de los resultados del experimento.
Si no está dispuesto, la experimentación es una pérdida de tiempo gratuita.
Muchos de los enfoques inflexibles del desarrollo de software se basan en el miedo a cometer
errores. Un intento general de evitar errores es el error más grande de todos. El diseño es un
proceso de planificación cuidadosa de pequeños errores para evitar cometer errores grandes. La
experimentación en el desarrollo de software es un proceso de establecer pruebas para que
Puntos clave 853
El punto es que debe mantener una mente abierta sobre todos los aspectos del desarrollo de
software. Tienes que ser técnico sobre tu proceso, así como sobre tu producto. La experimentación
de mente abierta y la adhesión religiosa a un enfoque predefinido no se mezclan.
Puntos clave
- Uno de los objetivos principales de la programación es gestionar la complejidad.
- Cuando se abusa de ella, una convención de programación puede ser una cura peor que
la enfermedad. Usada cuidadosamente, una convención agrega una estructura valiosa al
entorno de desarrollo y ayuda a administrar la complejidad y la comunicación.
- Las metodologías dogmáticas y el desarrollo de software de alta calidad no se mezclan. Llene su caja
de herramientas intelectuales con alternativas de programación y mejore su habilidad para elegir la
herramienta adecuada para el trabajo.
capitulo 35
Temas relacionados
- Recursos web:www.cc2e.com
Si ha leído hasta aquí, ya sabe que se ha escrito mucho sobre las prácticas efectivas de
desarrollo de software. Hay mucha más información disponible de lo que la mayoría de la
gente cree. La gente ya ha cometido todos los errores que tú estás cometiendo ahora y, a
menos que seas un glotón del castigo, preferirás leer sus libros y evitar sus errores que
inventar nuevas versiones de viejos problemas.
Debido a que este libro describe cientos de otros libros y artículos que contienen información sobre el
desarrollo de software, es difícil saber qué leer primero. Una biblioteca de desarrollo de software se
compone de varios tipos de información. Un núcleo de libros de programación explica los conceptos
fundamentales de la programación efectiva. Los libros relacionados explican los contextos técnicos,
administrativos e intelectuales más amplios dentro de los cuales se desarrolla la programación. Y las
referencias detalladas sobre idiomas, sistemas operativos, entornos y hardware contienen
información útil para proyectos específicos.
cc2e.com/3581 Los libros de la última categoría generalmente tienen una vida útil de aproximadamente un proyecto; son
más o menos temporales y no se tratan aquí. De los otros tipos de libros, es útil tener un conjunto básico
que analice en profundidad cada una de las principales actividades de desarrollo de software: libros sobre
requisitos, diseño, construcción, administración, pruebas, etc. Las siguientes secciones describen los
recursos de construcción en profundidad y luego brindan una descripción general de los materiales
disponibles en otras áreas de conocimiento del software. La Sección 35.4 envuelve estos recursos en un
paquete ordenado definiendo un programa de lectura para desarrolladores de software.
855
856 Capítulo 35: Dónde encontrar más información
Jon BentleyPerlas de programación, 2ª ed. (Bentley 2000) analiza el arte y la ciencia del diseño de
software en los pequeños. El libro está organizado como un conjunto de ensayos que están muy bien
escritos y expresan una gran cantidad de conocimientos sobre técnicas de construcción efectivas, así
como un entusiasmo genuino por la construcción de software. Uso algo que aprendí de los ensayos
de Bentley casi todos los días que programo.
Referencia cruzadaPara obtener más Kent BeckExplicación de la programación extrema: aceptar el cambio(Beck 2000) define un enfoque
información sobre la economía de la
centrado en la construcción para el desarrollo de software. Como se explicó en la Sección 3.1 (“Importancia
programación extrema y la
Otro libro más especializado esLa práctica de la programación, por Brian Kernighan y Rob Pike
(Kernighan y Pike 1999). Este libro se centra en los aspectos prácticos y esenciales de la
programación, cerrando la brecha entre el conocimiento académico de las ciencias de la
computación y las lecciones prácticas. Incluye discusiones sobre estilo de programación,
diseño, depuración y pruebas. Asume familiaridad con C/C++.
Resumen Material
cc2e.com/3595 Los siguientes libros brindan una descripción general del desarrollo de software desde una variedad de
puntos de vista:
MíoDesarrollo de software profesional(2004) examina el campo del desarrollo de software tal como
se practica ahora y cómo podría ser si se practicara rutinariamente de la mejor manera.
Cada gerente de programadores debería tener su propia copia. Debería leerlo, tomarlo en
serio, actuar según los preceptos y dejar la copia en su escritorio para que sus
programadores la roben. Debe continuar reemplazando las copias robadas hasta que se
establezca el equilibrio (Weiss 1972).
Una excelente visión general final de los problemas en el desarrollo de software esCreatividad de software
(Vidrio 1995). Este libro debería haber sido un libro innovador sobre la creatividad del software de la misma
manera quegenteestaba en equipos de software. Glass analiza la creatividad frente a la disciplina, la teoría
frente a la práctica, la heurística frente a la metodología, el proceso frente al producto y muchas de las otras
dicotomías que definen el campo del software. Después de años de discutir este libro con los programadores
que trabajan para mí, he llegado a la conclusión de que el
858 Capítulo 35: Dónde encontrar más información
La dificultad del libro es que es una colección de ensayos editados por Glass pero no escritos en su
totalidad por él. Para algunos lectores, esto le da al libro una sensación inacabada. No obstante,
sigo exigiendo que todos los desarrolladores de mi empresa lo lean. El libro está agotado y es difícil
de encontrar, pero vale la pena el esfuerzo si puede encontrarlo.
Todo programador informático o ingeniero de software en ejercicio debe tener una referencia de alto
nivel en ingeniería de software. Dichos libros examinan el panorama metodológico en lugar de pintar
características específicas en detalle. Proporcionan una descripción general de las prácticas efectivas
de ingeniería de software y descripciones resumidas de técnicas específicas de ingeniería de
software. Las descripciones de las cápsulas no son lo suficientemente detalladas como para
entrenarlo en las técnicas, pero un solo libro tendría que tener varios miles de páginas para hacer
eso. Proporcionan suficiente información para que pueda aprender cómo encajan las técnicas y
puede elegir técnicas para una mayor investigación.
35.3 Periódicos
Revistas de programadores Lowbrow
Estas revistas suelen estar disponibles en los quioscos locales:
cc2e.com/3523 Diario del Dr. Dobb.www.ddj.com. Esta revista está orientada a programadores empedernidos.
Sus artículos tienden a tratar temas detallados e incluyen mucho código.
Publicaciones profesionales
cc2e.com/3551 La IEEE Computer Society publica revistas especializadas en ingeniería de software, seguridad y
privacidad, gráficos y animación por computadora, desarrollo de Internet, multimedia,
sistemas inteligentes, historia de la computación y otros temas. Verwww.computadora.orgpara
más detalles.
cc2e.com/3565 Todas estas revistas cubren lo que sus nombres sugieren que cubren.
diario de linux.www.linuxjournal.com.
Revisión de Unix.www.unixreview.com.
Nivel Introductorio
Para avanzar más allá del nivel "introductorio" en Construx, un desarrollador debe leer los
siguientes libros:
Adams, James L.Blockbusting conceptual: una guía para mejores ideas, 4ª ed. Cambridge, MA:
Perseo Publishing, 2001.
35.4 Plan de lectura de un desarrollador de software 861
McConnell, Steve.Guía de supervivencia de proyectos de software. Redmond, WA: Microsoft Press, 1998.
Nivel de practicante
Para lograr el estado "intermedio" en Construx, un programador debe leer los siguientes
materiales adicionales:
Fowler, Martín.UML destilado: una breve guía del objeto estándarModo CTLenguaje, 3d
ed. Boston, MA: Addison-Wesley, 2003.
Kaner, Cem, Jack Falk, Hung Q. Nguyen.Pruebas de software informático, 2ª ed. Nueva York,
NY: John Wiley & Sons, 1999.
cc2e.com/3514 "Manual del administrador para el desarrollo de software", Centro de vuelo espacial Goddard de la
NASA. Descargable desdesel.gsfc.nasa.gov/website/documents/online-doc.htm.
Nivel profesional
Un desarrollador de software debe leer los siguientes materiales para lograr una posición
profesional completa en Construx (nivel de "liderazgo"). Los requisitos adicionales se adaptan a
cada desarrollador individual; esta sección describe los requisitos genéricos.
Fowler, Martín.Refactorización: mejora del diseño del código existente. Reading, MA: Addison-
Wesley, 1999.
MEYER, Bertrand.Construcción de software orientado a objetosción, 2ª ed. Nueva York, NY: Prentice
Hall PTR, 1997.
cc2e.com/3521 “Guía de medición de software”, Centro de vuelo espacial Goddard de la NASA. Disponible de
sel.gsfc.nasa.gov/website/documents/online-doc.htm.
cc2e.com/3528 Para obtener más detalles sobre este programa de desarrollo profesional, así como para obtener listas de
lectura actualizadas, visite nuestro sitio web de desarrollo profesional enwww.construx.com/
professionaldev/.
Aho, Alfred V., John E. Hopcroft y Jeff- Baecker, Ronald M. y Aaron Marcus.
Frey D.Ullman. 1983.Estructuras de 1990.Factores humanos y tipografía para
datos y algoritmos. Reading, MA: programas más legibles. Reading, MA:
Addison-Wesley. Addison-Wesley.
Albrecht, Allan J. 1979. “Measuring Appli- Bairdain, EF 1964. “Estudios de investigación de
Productividad del desarrollo de las cationes”. Programadores y Programación.” Estudios
Actas del Simposio conjunto de desarrollo de no publicados informados en Boehm 1981.
aplicaciones SHARE/GUIDE/IBM, octubre de
1979: 83–92.
Baker, F. Terry y Harlan D. Mills. 1973.
Ambler, Scott. 2003.Tecnología de base de datos ágil "Equipos de programadores jefe".Datamación
niques. Nueva York, NY: John Wiley & 19, núm. 12 (diciembre): 58–61.
Sons. Barbour, Ian G. 1966.Cuestiones en Ciencias y
Anand, N. 1988. "¡Aclarar la función!"MCA Religión. Nueva York, NY: Harper & Row.
Avisos de plan de señales23, núm. 6 (junio): 69–79.
863
Traducido del inglés al español - www.onlinedoctranslator.com
864 Bibliografía
Basili, Victor R., Richard W. Selby y Dav- Bentley, Jon. mil novecientos ochenta y dos.Redacción Eficiente Pro-
1996. "Una validación de métricas de diseño Bentley, Jon. 1991. “Software Explorator-
orientadas a objetos como indicadores de um: escribir programas C eficientes”.Revisión
calidad", Transacciones IEEE sobre ingeniería de de Unix9, núm. 8 (agosto): 62–73.
software, octubre de 1996, 751–761.
Bentley, Jon. 2000.Perlas de programación, 2d
Bass, Len, Paul Clements y Rick Ka- edición Reading, MA: Addison-Wesley.
zman. 2003.Arquitectura de software en la
práctica, 2ª ed. Boston, MA: Addison-
Wesley.
Bibliografía 865
Boehm-Davis, Deborah, Sylvia Sheppard, Brooks, Ruven. 1977. “Hacia una teoría de la
y John Bailey. 1987. “Lenguajes de diseño los procesos cognitivos en la programación
de programas: ¿cuántos detalles deben de computadoras”.Revista internacional de
incluir?”Revista internacional de estudios estudios hombre-máquina9:737–51.
hombre-máquina27, núm. 4: 337–47. Brooks, W.Douglas. 1981. “Software Tech-
Böhm, C. y G. Jacopini. 1966. “Flow Di- Recompensa de la tecnología: alguna
agramas, máquinas de Turing y evidencia estadística”.El Diario de Sistemas
lenguajes con solo dos reglas de y Software2:3–9.
formación”. Comunicaciones de la ACM Brown, AR y WA Sampson. 1973.
9, núm. 5 (mayo): 366–71. Depuración de programas. Nueva York, NY:
Booch, Grady. 1987.Ingeniería de software American Elsevier.
con ada, 2ª ed. Menlo Park, CA: Buschman, Frank, et al. 1996.Patrón-Ori-
Benjamín/Cummings. Arquitectura de software presentada, volumen 1:
Booch, Grady. 1994.Análisis orientado a objetos un sistema de patrones. Nueva York, NY: John
sis y Diseño con Aplicaciones, 2ª ed. Wiley & Sons.
Boston, MA: Addison-Wesley. Bush, Marilyn y John Kelly. 1989. “La
Booth, Rick. 1997.Bucles internos: una fuente Experiencia del Laboratorio de Propulsión a
libro para el desarrollo rápido de software de 32 Chorro con Inspecciones Formales”.Actas del
bits. Boston, MA: Addison-Wesley. decimocuarto taller anual de ingeniería de
software, 29 de noviembre de 1989.
Boundy, David. 1991. “A Taxonomy of Pro-
Greenbelt, MD: Centro de Vuelo Espacial
gramáticos”.Notas de ingeniería de software
Goddard. Documento SEL-89-007.
ACM SIGSOFT16, núm. 4 (octubre): 23– 30.
Caine, SH y EK Gordon. 1975.
"PDL: una herramienta para el diseño de
Marca, Stewart. 1995.Cómo aprenden los edificios:
software". Actas de AFIPS de la Conferencia
Qué sucede después de que se construyen. Pingüino
Nacional de Computación de 1975 44. Montvale,
Estados Unidos.
Nueva Jersey: AFIPS Press, 271–76.
Branstad, Martha A., John C. Cherniavsky,
y W. Richards Adrion. 1980. Card, David N. 1987. “A Software Technol-
Programa de Evaluación de la logía”.Tecnología
"Validación, verificación y prueba para
de la información y el software29, núm. 6 (julio/
el programador individual". Computadora,
agosto): 291–300.
24–30 de diciembre.
Brockmann, R. John. 1990.escribir mejor Card, David N., Frank E. McGarry y Ger-
ald T. Página. 1987. "Evaluación de
Documentación de Usuario Informático: Del
tecnologías de ingeniería de software".
Papel al Hipertexto: Versión 2.0. Nueva York,
Transacciones IEEE sobre ingeniería de
NY: John Wiley & Sons.
softwareSE-13, núm. 7 (julio): 845–51.
Brooks, Frederick P., Jr. 1987. “No Silver
Balas: esencia y accidentes de la ingeniería
Card, David N., Victor E. Church y Will-
iam W. Agresti. 1986. "Un estudio empírico
de software”.Computadora, 10–19 de abril.
de las prácticas de diseño de software".
Transacciones IEEE sobre ingeniería de
Brooks, Frederick P. Jr. 1995.el mítico softwareSE-12, núm. 2 (febrero): 264–71.
Hombre-Mes: Ensayos sobre ingeniería de
software, Edición de aniversario(2ª ed.).
Card, David N., con Robert L. Glass. 1990.
Medición de la calidad del diseño del software.
Reading, MA: Addison-Wesley.
Englewood Cliffs, Nueva Jersey: Prentice Hall.
Bibliografía 867
Card, David, Gerald Page y Frank Mc- Constantino, Larry L. 1990b. "Objetos,
Gary. 1985. "Criterios para la funciones y extensibilidad del programa”.
modularización de software".Actas de la 8.ª Lenguaje de ordenador, enero, 34–56.
Conferencia Internacional sobre Ingeniería Conte, SD, HE Dunsmore y VY
de Software- nering. Washington, DC: IEEE Shen. 1986.Métricas y modelos de ingeniería
Computer Society Press, 372–77. de software. Menlo Park, CA: Benjamín/
Carnegie, Dale. 1981.Cómo ganar amigos Cummings.
e influir en las personas, Edición revisada.
Cooper, Doug y Michael Clancy. mil novecientos ochenta y dos.
Nueva York, NY: Libros de bolsillo.
¡Vaya! ¡Pascal!2d ed. Nueva York, NY:
Chase, William G. y Herbert A. Simon. Norton.
1973. "Percepción en el ajedrez".Psicología Cooper, Kenneth G. y Thomas W.
cognitiva4:55–81. Mullen. 1993. "Espadas y rejas de arado: los
Clark, R. Lawrence. 1973. “Una Lingüística ciclos de reelaboración de los proyectos de
Contribución de la programación sin desarrollo de software comercial y de
GOTO”,Datamación, diciembre de 1973. defensa".programador estadounidense,
mayo de 1993, 41–51.
Clemente, Paul, ed. 2003.Documentando Soft-
Arquitecturas de software: vistas y más allá. Corbató, Fernando J. 1991. “Sobre la construcción
Boston, MA: Addison-Wesley. Sistemas que fallarán”. 1991 Conferencia
Premio Turing.Comunicaciones de la ACM
Clements, Paul, Rick Kazman y Mark
Klein. 2002.Evaluación de arquitecturas de
34, núm. 9 (septiembre): 72–81.
software: métodos y estudios de casos. Cornell, Gary y Jonathan Morrison.
Boston, MA: Addison-Wesley. 2002.Programación VB .NET: una guía para
programadores experimentados, Berkeley, CA:
Coad, Peter y Edward Yourdon. 1991.
Apress.
Diseño orientado a objetos. Cliffs, Englewood
Nueva Jersey: Yourdon Press. Corwin, Al. 1991. Comunicación privada.
Cobb, Richard H. y Harlan D. Mills. CSTB 1990. “Scaling Up: A Research Agen-
1990. "Software de ingeniería bajo control da para Ingeniería de Software.” Extractos de un
estadístico de calidad".Software IEEE7, informe de la Junta de Ciencias y Tecnología de la
núm. 6 (noviembre): 45–54. Computación.Comunicaciones de la ACM33,
núm. 3 (marzo): 281–93.
Cockburn, Alistair. 2000.escritura efectiva
Casos de uso. Boston, MA: Addison-Wesley. Curtis, Bill, ed. 1985.Tutorial: Fac-
Cockburn, Alistair. 2002.Desarrollo ágil de software
tores en desarrollo de software. Los
desarrollo. Boston, MA: Addison-Wesley. Ángeles, CA: IEEE Computer Society Press.
Collofello, Jim y Scott Woodfield. 1989. Curtis, Bill, et al. 1986. “Software Psycholo-
"Evaluación de la efectividad de las técnicas
gy: La necesidad de un programa
de garantía de confiabilidad".Revista de
interdisciplinario”.Actas del IEEE74, núm.
Sistemas y Software9, núm. 3 (marzo).
8: 1092-1106.
Comer, Douglas. 1981. “Principios de Pro- Curtis, Bill, et al. 1989. “Experimentación de
Formatos de documentación de software”.
Diseño de programas inducidos a partir de la
Revista de Sistemas y Software9, núm. 2
experiencia con pequeños programas públicos”.
(febrero): 167–207.
Transacciones IEEE sobre ingeniería de software
SE-7, núm. 2 (marzo): 169–74. Curtis, Bill, H. Krasner y N. Iscoe. 1988.
"Un estudio de campo del proceso de diseño
Constantino, Larry L. 1990a. “Comentarios
de software para sistemas grandes".
en 'Sobre los criterios para las interfaces de los
Comunicaciones de la ACM31, núm. 11
módulos'”. Transacciones IEEE sobre ingeniería de
(noviembre): 1268–87.
softwareSE-16, núm. 12 (diciembre): 1440.
868 Bibliografía
Curtis, Bill. 1981. “Programa Justificante- DeMillo, Richard A., Richard J. Lipton y
más variabilidad.”Actas del IEEE Alan J. Perlis. 1979. "Procesos sociales y
69, núm. 7: 846. pruebas de teoremas y programas".
Cusumano, Michael y Richard W. Selby. Comunicaciones de la ACM22, núm. 5
1995.Secretos de Microsoft. Nueva York, (mayo): 271–80.
NY: Prensa libre. Dijkstra, Edsger. 1965. “Programación Con-
considerada como una Actividad Humana.”
Cusumano, Michael, et al. 2003. “Software
Desarrollo mundial: el estado de la Actas del Congreso IFIP de 1965.
Ámsterdam: Holanda Septentrional, 213–
práctica”,Software IEEE, noviembre/
17. Reimpreso en Yourdon 1982.
diciembre de 2003, 28–34.
Dahl, OJ, EW Dijkstra y CAR Dijkstra, Edsger. 1968. “Ir a declaración
Hoare. 1972.Programación estructurada.
Considerado Dañino.”Comunicaciones
Nueva York, NY: Prensa académica.
de la ACM11, núm. 3 (marzo): 147–48.
Gremillion, Lee L. 1984. “Determinantes de Hezel, Bill. 1988.La guía completa para
Requisitos de mantenimiento de reparación Pruebas de software, 2ª ed. Wellesley, MA:
del programa”.Comunicaciones de la ACM27, Sistemas de información QED.
núm. 8 (agosto): 826–32.
Highsmith, James A., III. 2000.Adaptado
Gries, David. 1981.La ciencia del programa- Desarrollo de software: un enfoque
ming. Nueva York, NY: Springer-Verlag. colaborativo para gestionar sistemas
complejos. Nueva York, NY: Dorset House.
Grove, Andrew S. 1983.Hombre de alto rendimiento
gestion. Nueva York, NY: Random House. Highsmith, Jim. 2002.Desarrollo ágil de software
Hatley, Derek J. e Imtiaz A. Pirbhai. 1988. Hughes, Charles E., Charles P. Pfleeger y
Lawrence L. Rose. 1978.Técnicas Avanzadas
Estrategias para la especificación de sistemas en
de Programación: Un Segundo Curso de
tiempo real. Nueva York, NY: Dorset House.
Programación Usando Fortran. Nueva York,
Hecht, Alan. 1990. “Lindo orientado a objetos NY: John Wiley & Sons.
Siglas consideradas TONTAS”.Notas de
ingeniería de software, enero, 48. Humphrey, Watts S. 1989.Manejando el
Proceso de Software. Reading, MA: Addison-
Heckel, Paul. 1994.Los elementos de la amistad Wesley.
Diseño de software. Alameda, CA: Sybex.
Humphrey, Watts S. 1995.Una disciplina para
Hecker, Daniel E. 2001. “Ocupational Em- Ingeniería de software. Reading, MA:
Proyecciones de empleo al 2010”.Revisión Addison-Wesley.
Laboral Mensual, noviembre de 2001.
Humphrey, Watts S., Terry R. Snyder y
Hecker, Daniel E. 2004. "Empleo ocupacional Ronald R. Willis. 1991. "Mejora del proceso
Proyecciones de empleo al 2012".Revisión de software en Hughes Aircraft". Software
Laboral Mensual, febrero de 2004, vol. 127, IEEE8, núm. 4 (julio): 11–23.
núm. 2, págs. 80-105.
Humprey, Watts. 1997.Introducción a la
Henry, Sallie y Dennis Kafura. 1984. Proceso de software personal. Reading, MA:
"La evaluación de la estructura de los Addison-Wesley.
sistemas de software utilizando métricas
cuantitativas de software".Software: práctica
y experiencia14, núm. 6 (junio): 561–73.
872 Bibliografía
Humprey, Watts. 2002.Ganar con Soft- IEEE Std 730-2002, estándar para software
ware: una estrategia ejecutiva. Boston, MA: Planes de Garantía de Calidad
Addison-Wesley.
IEEE Std 828-1998, estándar para software
Hunt, Andrew y David Thomas. 2000. Planes de gestión de la configuración
El programador pragmático. Boston, MA: IEEE Std 829-1998, estándar para software
Addison-Wesley. Documentación de prueba
Ichbiah, Jean D., et al. 1986.Justificación de IEEE Std 830-1998, práctica recomendada
Diseño del Lenguaje de Programación Ada.
para especificaciones de requisitos de software
Minneapolis, MN: Centro de Investigación y
Sistemas Honeywell. Norma IEEE 830-1998. Práctica recomendada por IEEE
Aviso para las especificaciones de los requisitos
Software IEEE7, núm. 3 (mayo de 1990).
del software. Los Alamitos, CA: IEEE Computer
IEEE Std 1008-1987 (R1993), Estándar para Society Press.
Pruebas unitarias de software
IEEE, 1991.Estándar de ingeniería de software IEEE
IEEE Std 1016-1998, práctica recomendada Colección de dardos, edición de primavera de
para descripciones de diseño de software 1991. Nueva York, NY: Instituto de Ingenieros
Eléctricos y Electrónicos.
IEEE Std 1028-1997, estándar para software
Reseñas IEEE, 1992. “Muere la contralmirante Grace Hopper
a los 85.”Computadora IEEE, febrero, 84.
IEEE Std 1045-1992, estándar para software
Métricas de productividad Ingrassia, Frank S. 1976. “The Unit Development
IEEE Std 1058-1998, estándar para software Opment Folder (UDF): una herramienta de
gestión eficaz para el desarrollo de
Planes de gestión de proyectos
software”. Informe técnico TRW TRW-
IEEE Std 1061-1998, estándar para un software SS-76-11. También reimpreso en Reifer
Metodología de métricas de calidad 1986, 366–79.
IEEE Std 1062-1998, práctica recomendada Ingrassia, Frank S. 1987. “The Unit Development
para Adquisición de Software Opment Folder (UDF): una perspectiva de
IEEE Std 1063-2001, estándar para software diez años”.Tutorial: Gestión de Proyectos
Documentación del usuario de Ingeniería de Software, ed. Richard H.
Thayer. Los Alamitos, CA: IEEE Computer
IEEE Std 1074-1997, Estándar para desarrollar
Society Press, 405–15.
Procesos del ciclo de vida del software
Jackson, Michael A. 1975.Principios de Pro-
IEEE Std 1219-1998, estándar para software
diseño de gramo. Nueva York, NY: Prensa
Mantenimiento
académica.
IEEE Std 1233-1998, Guía para desarrollar
Jacobson, Ivar, Grady Booch y James
Requisitos del sistema Especificaciones
Rumbaugh. 1999.El proceso de desarrollo
Norma IEEE 1233-1998. Guía IEEE para el desarrollo de software unificado. Reading, MA:
Especificaciones de los requisitos del sistema Addison-Wesley.
Norma IEEE 1471-2000. Práctica recomendada Johnson, Jim. 1999. “Convertir el caos en
para descripción arquitectónica de sistemas Éxito,"Revista de software, diciembre de
intensivos en software 1999, 30–39.
IEEE Std 1490-1998, Guía - Adopción de PMI Johnson, Marcos. 1994a. "Dr. Boris Beizer
Estándar: una guía para el cuerpo de conocimiento sobre pruebas de software: una entrevista, parte 1”,El
de la dirección de proyectos control de calidad de software trimestral, primavera de
Johnson, Marcos. 1994b. "Dr. Boris Beizer en Kaner, Cem, James Bach y Bret Pettichord.
Pruebas de software: una entrevista, parte 2”, El 2002.Lecciones aprendidas en pruebas de
control de calidad de software trimestral, verano de software. Nueva York, NY: John Wiley & Sons.
1994, 41–45.
Keller, Daniel. 1990. “Una guía para la naturaleza
Johnson, Walter L. 1987. “Algunos comentarios Nombrando.”Avisos de ACM Signplan25, núm. 5
sobre la práctica de codificación”.Notas de (mayo): 95–102.
ingeniería de software ACM SIGSOFT12, núm. 2
Kelly, John C. 1987. “Una comparación de cuatro
(abril): 32–35.
Métodos de diseño para sistemas en tiempo real”.
Jones, T. Alcaparras. 1977. “Calidad del Programa Actas de la Novena Conferencia Internacional sobre
y productividad del programador”.Informe Ingeniería de Software. 238–52.
técnico de IBM TR 02.764, enero, 42–78. Kelly-Bootle, Stan. 1981.El DP del diablo Dic-
También en Jones 1986b. cionario. Nueva York, NY: McGraw-Hill.
Jones, Alcaparras. 1986a.Producción de programación
Kernighan, Brian W. y Rob Pike. 1999.
actividad. Nueva York, NY: McGraw-Hill.
La práctica de la programación. Reading,
Jones, T. Alcaparras, ed. 1986b.Tutorial: Pro- MA: Addison-Wesley.
Gramática Productividad: Temas para los Kernighan, Brian W. y PJ Plauger.
años ochenta, 2ª ed. Los Ángeles, CA: IEEE 1976.Herramientas de software. Reading, MA:
Computer Society Press. Addison-Wesley.
Jones, Alcaparras. 1996. “Software Defecto-Re-
Kernighan, Brian W. y PJ Plauger.
Eficiencia móvil,”Computadora IEEE, abril de
1978.Los elementos del estilo de programación.
1996.
2d ed. Nueva York, NY: McGraw-Hill.
Jones, Alcaparras. 1997.Medición de software aplicado
Kernighan, Brian W. y PJ Plauger.
surement: asegurando la productividad y la
1981.Herramientas de software en Pascal.
calidad, 2ª ed. Nueva York, NY: McGraw-Hill.
Reading, MA: Addison-Wesley.
Jones, Alcaparras. 1998.Software de estimación
Kernighan, Brian W. y Dennis M. Ritch-
Costos. Nueva York, NY: McGraw-Hill.
es decir. 1988.El lenguaje de programación C, 2ª
Jones, Alcaparras. 2000.Evaluaciones de software, ed. Englewood Cliffs, Nueva Jersey: Prentice Hall.
Puntos de referencia y mejores prácticas.
Reading, MA: Addison-Wesley.
Killelea, Patrick. 2002.Rendimiento web
Jones, Alcaparras. 2003. “Variaciones en Soft- Afinación, 2ª ed. Sebastopol, CA: O'Reilly &
Prácticas de desarrollo de software”,Software Associates.
IEEE, noviembre/diciembre de 2003, 22–27.
Rey David. 1988.Creación de software eficaz
ware: diseño de programas informáticos utilizando
Jonson, Dan. 1989. “Next: The Elimina- la metodología Jackson. Nueva York, Nueva York:
de GoTo-Patches?Avisos de ACM Yourdon Press.
Signplan24, núm. 3 (marzo): 85–92. Knuth, Donald. 1971. “Un estudio empírico
Kaelbling, Michael. 1988. “Programación de los programas FORTRAN,”Software:
Los idiomas NO deben tener declaraciones de práctica y experiencia1:105–33.
comentarios”.Avisos de ACM Signplan23,
Knuth, Donald. 1974. “Pro-
núm. 10 (octubre): 59–60.
gramática con ir a estados de cuenta”. En
Kaner, Cem, Jack Falk y Hung Q. Nguy- Clásicos en Ingeniería de Software, editado por
es 1999.Pruebas de software informático, 2ª Edward Yourdon. Englewood Cliffs, Nueva Jersey:
ed. Nueva York, NY: John Wiley & Sons. Yourdon Press, 1979.
V413HAV
874 Bibliografía
Knuth, Donald. 1986.Computadoras y Tipo- Lampson, mayordomo. 1984. “Sugerencias para la computación”.
Knuth, Donald. 1998.El arte de la computadora Larman, Craig. 2004.Diseño ágil e iterativo
Programación, vol. 3,Ordenar y buscar, 2ª desarrollo: una guía para gerentes.
ed. Reading, MA: Addison-Wesley. Boston, MA: Addison-Wesley, 2004.
Knuth, Donald. 2001.Programa de alfabetización- Lauesen, Soren.Requisitos de Software:
ming. Prensa de la Universidad de Cambridge. Estilos y Técnicas. Boston, MA:
Korson, Timothy D. y Vijay K. Vaishnavi. Addison-Wesley, 2002.
1986. "Un estudio empírico de modularidad Laurel, Brenda, ed. 1990.El arte de lo humano-
en la modificabilidad del programa". En Diseño de interfaz de computadora. Reading,
Soloway e Iyengar 1986: 168–86. MA: Addison-Wesley.
Kouchakdjian, Ara, Scott Green y Victor Ledgard, Henry F., con John Tauer. 1987a.
Basilio. 1989. “Evaluación de la C con excelencia: proverbios de
Metodología de Sala Limpia en el programación. Indianápolis: Hayden Books.
Laboratorio de Ingeniería de Software”. Ledgard, Henry F., con John Tauer. 1987b.
Actas del decimocuarto taller anual de software profesional, vol. 2,Práctica de
ingeniería de software, 29 de noviembre programación. Indianápolis: Hayden Books.
de 1989. Greenbelt, MD: Centro de Vuelo
Espacial Goddard. Documento SEL-89-007. Ledgard, Henry y Michael Marcotty.
1986.El panorama del lenguaje de
Kovitz, Benjamín, L. 1998Software Práctico programación: sintaxis, semántica e
Requisitos: un manual de contenido y estilo,
implementación, 2ª ed. Chicago: Asociados de
Compañía de Publicaciones de Manning.
Investigación Científica.
Kreitzberg, CB y B. Shneiderman. Ledgard, Henry. 1985. “Programadores: El
1972.Los elementos del estilo Fortran. Nueva
Aficionado contra el profesional”.Ábaco2,
York, NY: Harcourt Brace Jovanovich.
núm. 4 (verano): 29–35.
Kruchten, Philippe B. “El modelo de vista 4+1 Leffingwell, Decano. 1997. “Cálculo del
de Arquitectura.”Software IEEE, páginas Retorno de la inversión de una gestión
42–50, noviembre de 1995. de requisitos más eficaz”,programador
Kruchten, Philippe. 2000.La unidad racional estadounidense, 10(4):13–16.
Proceso fied: una introducción, 2d Ed., Lewis, Daniel W. 1979. “A Review of Ap-
Reading, MA: Addison-Wesley. se acerca a la Enseñanza de Fortran.”
Kuhn, Thomas S. 1996.La estructura de la ciencia Transacciones IEEE en Educación, E-22, n.
Revoluciones entíficas, 3d ed. Chicago: Prensa de 1: 23–25.
la Universidad de Chicago.
Lewis, William E. 2000.Pruebas de software y
Lammers, Susan. 1986.programadores en Mejora continua de la calidad, 2ª ed.
Trabajar. Redmond, WA: Microsoft Press. Editorial Auerbach.
Bibliografía 875
McConnell, Steve. 2004.suave profesional Miaria, Richard J., et al. 1983. “Program In-
desarrollo de productos. Boston, MA: Addison- dentación y Comprensibilidad.”
Wesley. Comunicaciones de la ACM26, núm.
McCue, Gerald M. 1978. “IBM's Santa Ter- 11 (noviembre): 861–67.
sa Laboratorio: diseño arquitectónico para el Michalewicz, Zbigniew y David B. Fogel.
desarrollo de programas”.Diario de sistemas 2000.Cómo resolverlo: heurística moderna.
de IBM17, núm. 1:4–25. Berlín: Springer-Verlag.
McGarry, Frank y Rose Pajerski. 1990. Miller, GA 1956. “El Número Mágico
“Hacia la comprensión del software: 15 Siete, más o menos dos: algunos límites en
años en el SEL”.Actas del decimoquinto nuestra capacidad para procesar
taller anual de ingeniería de software, 28 y información”.La revisión psicológica63,
29 de noviembre de 1990. Greenbelt, MD: núm. 2 (marzo): 81–97.
Centro de Vuelo Espacial Goddard. Molinos, Harlan D. 1983.Productividad del software.
Documento SEL-90-006.
Boston, MA: Little, Brown.
McGarry, Frank, Sharon Waligora y Tim Mills, Harlan D. 1986. “Pro-
McDermott. 1989. “Experiencias en el
gramática: retrospectiva y prospectiva”.
Laboratorio de Ingeniería de Software Software IEEE, noviembre, 58–66.
(SEL) Aplicando Medición de Software”.
Actas del decimocuarto taller anual de Mills, Harlan D. y Richard C. Linger.
ingeniería de software, 29 de noviembre 1986. "Programación estructurada de
de 1989. Greenbelt, MD: Centro de Vuelo datos: diseño de programas sin matrices ni
Espacial Goddard. Documento SEL-89-007. punteros".Transacciones IEEE sobre
ingeniería de softwareSE-12, núm. 2
McGarry, John, et al. 2001.Práctico Soft- (febrero): 192–97.
Medición de productos: información objetiva
para los tomadores de decisiones. Boston, MA: Mills, Harlan D., Michael Dyer y Richard
Addison-Wesley. C. Permanecer. 1987. "Ingeniería de software de
sala limpia".Software IEEE, 19–25 de septiembre.
McKeithen, Katherine B., et al. 1981.
"Organización del conocimiento y diferencias de
habilidades en programadores de Misfeldt, Trevor, Greg Bumgardner y
computadoras". Psicología cognitiva13:307–25. Andrés Gray. 2004.Los elementos del estilo C++.
Prensa de la Universidad de Cambridge.
Metzger, Philip W. y John Boddie. 1996.
Gestión de un Proyecto de Programación: Mitchell, Jeffrey, Joseph Urban y Robert
Procesos y Personas, 3d ed. Englewood Cliffs, McDonald. 1987. "El efecto de los tipos de
Nueva Jersey: Prentice Hall, 1996. datos abstractos en el desarrollo de
programas". Computadora IEEE20, núm. 9
MEYER, Bertrand. 1997.Software orientado a objetos
(septiembre): 85–88.
construcción de mercancías, 2ª ed. Nueva York, NY:
Prentice Hall. Mody, RP 1991. “C en Educación y Soft-
ingeniería de productos.”Boletín SIGCSE23,
Meyers, Scott. 1996.C++ más eficaz: 35 núm. 3 (septiembre): 45–56.
Nuevas formas de mejorar sus programas y
diseños. Reading, MA: Addison-Wesley. Moore, Dave. 1992. Comunicación privada.
Parnas, David L. 1999. Perfil de ACM Fellow: Pietrasanta, Alfred M. 1991b. "Implementar-
David Lorge Parnás”,Notas de ingeniería de Ingeniería de Software en IBM”. Discurso
software de ACM, mayo de 1999, 10–14. principal.Novena Conferencia Anual de Calidad
de Software del Noroeste del Pacífico, 7 y 8 de
Parnas, David L. y Paul C. Clements.
octubre de 1991. Centro de Convenciones de
1986. "Un proceso de diseño racional:
Oregón, Portland, Oregón.
cómo y por qué falsificarlo".Transacciones
IEEE sobre ingeniería de softwareSE-12, Pigoski, Thomas M. 1997.Software Práctico
núm. 2 (febrero): 251–57. Mantenimiento. Nueva York, NY: John Wiley &
Sons.
Parnas, David L., Paul C. Clements y D.
M. Weiss. 1985. "La estructura modular de los Pirsig, Robert M. 1974.Zen y el arte de
sistemas complejos".Transacciones IEEE Mantenimiento de motocicletas: una indagación en
sobre ingeniería de softwareSE-11, núm. 3 valores. Guillermo Morrow.
(marzo): 259–66.
Plauger, PJ 1988. “A Designer's Bibliogra-
Perrot, Pamela. 2004. Comunidad privada físico.”Lenguaje de ordenador, 17–22 de julio.
catión. Plauger, PJ 1993.Programación a propósito:
Peters, LJ y LL Tripp. 1976. “Es Soft- Ensayos sobre diseño de software. Nueva York, NY:
diseño de artículos malvados”Datamación, vol. Prentice Hall.
22, núm. 5 (mayo de 1976), 127–136.
Ciruela, Tomás. 1984.Programación en C
Peters, Lawrence J. 1981.manual de soft- Pautas. Cardiff, Nueva Jersey: Plum Hall.
Diseño de artículos: Métodos y Técnicas. Nueva
Polia, G. 1957.Cómo resolvermiEso: un nuevo aspecto
York, Nueva York: Yourdon Press.
del Método Matemático, 2ª ed. Princeton,
Peters, Lawrence J. y Leonard L. Tripp. Nueva Jersey: Princeton University Press.
1977. "Comparación de metodologías de Publicar, Ed. 1983. “Los verdaderos programadores no
diseño de software".Datamación, Usa Pascal”,Datamación, julio de 1983,
noviembre, 89–94. 263–265.
Peters, Tom. 1987.Prosperando en el Caos: Mano-
Prechelt, Lutz. 2000. “Una comparación empírica
libro para una revolución de gestión. Nueva
hijo de los Siete Lenguajes de Programación,”
York, NY: Knopf.
Computadora IEEE, octubre de 2000, 23–29.
Petrosky, Henry. 1994.Paradigmas de diseño:
Pressman, Roger S. 1987.Ingeniero de software-
Historias de casos de error y juicio en ing: el enfoque de un profesional. Nueva
ingeniería. Cambridge, Reino Unido: York, NY: McGraw-Hill.
Cambridge University Press.
Pressman, Roger S. 1988.Hacer software
Pietrasanta, Alfred M. 1990. “Alfred M. Pi- Engineering Happen: una guía para instituir
etrasanta sobre la mejora del proceso de la tecnología. Englewood Cliffs, Nueva Jersey:
software”.Ingeniería de Software: Prentice Hall.
Herramientas, Técnicas, Prácticas1, no. 1
(mayo/junio): 29–34. Putnam, Lawrence H. 2000. “Reunión familiar
Gestión de recursos: interacción entre el
Pietrasanta, Alfred M. 1991a. “Una estrategia para
esfuerzo, el tiempo de desarrollo y los defectos”.
Mejora de Procesos de Software.”Novena Conferencia
Descargable desdewww.qsm.com.
Anual de Calidad de Software del Noroeste del Pacífico, 7
y 8 de octubre de 1991. Centro de Convenciones de Putnam, Lawrence H. y Ware Myers.
Oregón, Portland, Oregón
1992.Medidas para la excelencia: software confiable
a tiempo, dentro del presupuesto. Englewood Cliffs,
Nueva Jersey: Yourdon Press, 1992.
Bibliografía 879
Soloway, Elliot, Jeffrey Bonar y Kate Ehr- Suter, hierba. 2000.Excepcional C++: 47 En-
liche 1983. "Estrategias cognitivas y Rompecabezas de ingeniería, problemas de
construcciones en bucle: un estudio programación y soluciones. Boston, MA:
empírico".Comunicaciones de la ACM26, Addison-Wesley.
núm. 11 (noviembre): 853–60. Tackett, Buford D., III y Buddy Van
Sistemas de solución. 1987.Profesional de clase mundial Dorén. 1999. "Control de procesos para software
Técnicas de edición de gramers: entrevistas libre de errores: una historia de éxito de
con siete programadores. South Weymouth, software".Software IEEE, mayo de 1999.
MA: Sistemas de soluciones.
Tenner, Eduardo. 1997.Por qué muerden las cosas
Sommerville, Ian. 1989.Ingeniero de software- Atrás: La tecnología y la venganza de las
En g, 3d ed. Reading, MA: Addison-Wesley. consecuencias no deseadas. Libros antiguos.
Spier, Michael J. 1976. “Software Malprac- Teny, Ted. 1988. “Legibilidad del programa:
tice—Una Experiencia Desagradable.” Procedimientos versus Comentarios”.
Software: práctica y experiencia6:293–99. Transacciones IEEE sobre ingeniería de software
SE-14, núm. 9 (septiembre): 1271-1279.
Spinellis, Diomidis. 2003.Lectura de código:
La perspectiva del código abierto. Boston, Thayer, Richard H., editor. 1990.Tutorial: Suave-
MA: Addison-Wesley. Gestión de proyectos de ingeniería de
mercancías. Los Alamitos, CA: IEEE Computer
SPMN. 1998.Pequeño Libro de Configuración
Society Press.
administración. Arlington, VA; Red de
Administradores de Programas de Software. Thimbleby, Harold. 1988. “Delaying Com-
compromiso.”Software IEEE, mayo, 78–86.
Starr, Daniel. 2003. “Lo que apoya la
¿Techo?"Desarrollo de software. julio de 2003, Thomas, Dave y Andy Hunt. 2002.
38–41. "Objetos simulados",Software IEEE, mayo/
junio de 2002.
Stephens, Matt. 2003. “Diseño Emergente vs.
Primeros prototipos”, 26 de mayo de 2003, Thomas, Edward J. y Paul W. Omán.
www.softwarereality.com/design/ 1990. "Una bibliografía de estilo de
early_prototyping.jsp. programación".Avisos de ACM Signplan25,
núm. 2 (febrero): 7–16.
Stevens, Scott M. 1989. “Interacción inteligente
Video Simulación de una Inspección de Thomas, Richard A. 1984. “Using Com-
Código”.Comunicaciones de la ACM32, mentos para el Mantenimiento del Programa de
núm. 7 (julio): 832–43. Ayuda”. BYTE, mayo, 415–22.
Votta, Lawrence G., et al. 1991. “Investiga- los datos del centro de investigación de
885
886 justo oe
pt-ilceveexlpere
nstsryiones
tiempo permitido, 56 diseño condición posterior 190, variables, momento de, 252–254
de interfaz de usuario, 47 192–193 prueba de caja negra, 500
diseño de validación, 50 verificación de condiciones previas, líneas en blanco para formatear, 747–748,
expresiones aritméticas 192–193 765–766
ejemplo engañoso de precedencia, eliminando del código, 190 bloques
733 recursos para, 212 regla de escritura de llaves, 443
magnitudes, muy diferentes, 295 Ejemplos de Visual Basic, 192–194 comentarios sobre, 795–796
multiplicación, cambiando a declaraciones de asignación, 249, 758 rol condicionales, aclaración, 443
además, 623–624 de autor en inspecciones, 486 auto_ptrs, definido, 443
errores de redondeo, 297 333 estilo de diseño puro emulado,
arreglos pruebas automatizadas, 528–529 740–743
macro en lenguaje C para, 311 puro, estilo de diseño, 738–740
lista de control, 317 declaraciones individuales, 748–
contenedores como alternativa, 310
B 749 Book Paradigm, 812–813
planes de copia de seguridad, 669, 670
costos de operaciones, 602 expresiones booleanas
datos incorrectos, pruebas de, 514–515
diafonía, 311 0, comparaciones con, 441–442 0 y 1
barricadas
definido, 310 como valores, 432 división en pruebas
aserciones, relación con, 205
dimensiones, minimizando, parciales, 433 sintaxis de lenguajes C,
nivel de clase, 204
625–626 442–443 caracteres, comparaciones
conversiones de datos de entrada,
puntos finales, comprobación, con cero,
204 interfaces como límites, 203
310 bucles foreach con, 372 441
analogía de la sala de operaciones,
índices de, 310–311 lista de verificación para, 459
204 propósito de, 203
diseño de referencias, 754 constantes en las comparaciones,
bucles con, 387–388 clases base 442–443
rutinas anulables abstractas, 145
multidimensional, 310 tablas de decisión, pasando a, 435
aspecto de abstracción de, 89
convenciones de nomenclatura para, 280–281 Teoremas de DeMorgan, aplicando,
acoplamiento, demasiado apretado, 143
436–437
te-rle, vpeelre
clahsatrtaocp sonntrayl 887
caracter personalcontinuado pruebas diarias de construcción y humo, 707 asociaciones bidireccionales, 577
mensajes del compilador, tratamiento de, organización de datos, 780 llamadas a, refactorización, 575
826–827 tipos de datos, 316–318 declaraciones de caso vs. herencia,
licenciados en informática, 829 depuración, 559–561 147–148
habilidades de cooperación, 828 defectos, 489, 559–560 control centralizado con, 153
creatividad, 829, 857 programación defensiva, 211–212 cambios, efectos limitantes de, 153
curiosidad, 822–825 diseño, 122–123, 781 listas de verificación, 157–158, 774,
conciencia del proceso de desarrollo, documentación, 780–781, 780 rutinas de codificación de
822 816–817 pseudocódigo, 225–229
disciplina, 829 encapsulación, 158 la cohesión como indicador de refactorización,
estimaciones, 827–828 tipos enumerados, 317 566
experiencia, 831–832 arreglar defectos, 560 problemas de complejidad, 152–
experimentación, 822–823 inspecciones formales, 489, 491–492 153 valores constantes devueltos,
programación gonzo, 832 formato, 773–774 574 constructores, 151–152
hábitos, 833–834 declaraciones goto, 410 contención, 143–144
humildad, 821, 826, 834 si declaraciones, 365 consideraciones de acoplamiento,
importancia de, 819–820 herencia, 158 100–102, 142–143
honestidad intelectual, 826–828 inicialización, 257 sin datos, 155
inteligencia, 821 integración, 707 árboles de herencia profunda, 147
juicio, 848 interfaces, 579 definido, 125
puntos clave, 835 diseño, 773–774 delegación vs herencia,
pereza, 830 lista de, xxix–xxx refactorización, 576
errores, admitir, 826 bucles, 388–389 descendientes, indicador de refactorización
persistencia, 831 nombres, 288–289, 780 para, 567
prácticas compensatorias programación en pareja, 484 diseñar, 86, 216, 220–225, 233
debilidad, 821 parámetros, 185 prohibir funciones y
resolución de problemas, 823 ajuste de rendimiento, 607–608 operadores, 150
desarrollo profesional, punteros, 344 documentando, 780, 810
824–825 requisitos previos, 59 encapsulación, 139–143, 158
lectura, 824 pseudocodificación, 233–234 extensión, refactorización con, 576
religión en la programación, perjudicial herramientas de programación, 724– factorización, beneficio de, 154
efectos de, 851–853 725 control de calidad, 42–43, 70, 476 archivos que contienen, 771–772 rutinas
recursos en, 834–835 refactorización, 570, 577–579, 584 foráneas, refactorización con,
informes de estado, 827 requisitos, 40, 42–43 576
proyectos exitosos, aprendiendo de, rutinas, 185, 774, 780 velocidad, formalización de contratos de
823–824 ajuste para, 642–643 interfaces, 106
listas de control declaraciones, 774 formato, 768–771
abstracción, 157 código de línea recta, 353 amigo, violación de encapsulación
arquitectura, 54–55 cadenas, 316–317 preocupación, 141
problemas específicos del idioma, superclases para código común, ineficiencia, fuentes de, 598–601
156 diseño de, 768–771 575 inicializando en tiempo de compilación,
limitar la colaboración, 150 desarrollo de prueba primero, 233 632–633
Principio de sustitución de Liskov, pruebas con objetos stub, 523 rutinas en línea, 639–640
144–145 asociaciones unidireccionales, 577 entrada/salida, 598–599
variables miembro, nomenclatura, 273, visibilidad de, 93 enteros preferidos a flotantes, 625
279 señales de advertencia para, 848, 849 interpretados vs. compilados
métodos de.Verrutinas que minimizan la generadores de jerarquía de clases, 713 idiomas, 592, 600–601
regla de accesibilidad, 139 mixins, 149 pasos de limpieza, PPP, 232 iteración de, 608, 850 bucles de
desarrollo de salas limpias, 521 CMM interferencia, 617–618
modelado de objetos del mundo real, 152 (modelo de madurez de capacidad), puntos clave, 608, 645 especificidad del
múltiplos por archivo, diseño de, 491 idioma, 644 evaluación perezosa, 615–616
769–770 Cobol, 64 líneas de código, minimizando el número
nombrando, 277, 278 pruebas de cobertura de código,
número de miembros, 143 número bibliotecas de código 506, 222, 717 de, 593–594
de rutinas, 150 nombres de objetos, herramientas de análisis de calidad de código, 713– pautas de manipulación lógica,
diferenciando de, 714 método de lectura de código, 494 610–616
272–273 sintonización de código tablas de búsqueda para, 614–615,
objetos, contrastados con, 86 regla 80/20, 592 635 bucles, 616–624
sobreformato, 770 ventajas de, 591 lenguaje de bajo nivel, grabación en,
anular rutinas, 145–146, 156 identidades algebraicas, 640–642
paquetes, 155–157 apelación de 630, 591–592 medición para localizar puntos calientes,
modificaciones paralelas refactorización matrices, 593–594, 603–604, 603–604, 644
indicador, 566 625–627 operaciones de memoria vs archivo,
planificación para familias de programas, ensamblador, herramientas de listado, 720 598–599
154 ensamblador, grabación en, 640–642 identificación minimizando el trabajo dentro de los bucles,
datos privados vs. protegidos, 148 de cuello de botella, 594 datos de almacenamiento 620–621
privados, declarando miembros como, en caché, 628–629 multiplicación, cambiando a
150 listas de verificación, 607–608, 642–643 además, 623–624
procedimientos en.Verrutinas comparando estructuras lógicas, 614 orden de bucle anidado, 623 cuentos
datos protegidos, 148 dilema de objetivos en competencia, de viejas, 593–596 consideraciones del
pseudocódigo para diseñar, 595 sistema operativo,
232–234 consideraciones del compilador, 590, 590
miembros públicos, 139, 141, 576 regla 596–597 velocidades de operación, presunciones
de conveniencia de tiempo de lectura, conversión de tipos de datos, sobre, 594
141 razones para crear, 152–156 635 corrección, importancia de, operaciones, costos comunes,
refactorización, 155, 574–576, 595–596 601–603
578–579, 582 transformaciones de datos, 624–629 optimización sobre la marcha, 594–595
recursos, 159 opciones de tipo de datos, 635 descripción general de, 643–644
beneficio de reutilización de, 154 indexación de bases de datos, 601 operaciones de paginación, 599
paso de revisión y prueba, 217 paso defectos en código, 601 Principio de Pareto, 592
de construcción de rutinas, 217 definido, 591 resultados de precálculo, 635–638
rutinas en.Verrutinas rutinas, sin DES ejemplo, 605–606 requisitos del programa vista de,
usar, 146–147, 576 violaciones vista de diseño, 589–590 589
semánticas de desventajas de, 591 refactorización, en comparación con, 609
encapsulación, 141–142 desmontadores, 720 objetivos de recursos, 590
Set() rutinas, innecesarias, 576 sub herramientas de generador de perfiles de recursos en, 606–607, 644–645
y superclases similares, 576 ejecución, 720 expresiones, 630–639 desplazamiento a la derecha, 634
instancia única, 146 característica específica, 595 rutinas, 590, 639–640
propiedad singleton, hacer cumplir, 151 frecuencia, prueba en orden de, pruebas centinela para bucles, 621–
pasos en la creación, 216–217 612–613 623 evaluación de cortocircuito, 610
optimizar el paso de parámetros, puntos de código de uso frecuente, velocidad, importancia de, 595–596
153 592 consideraciones de hardware, 591 reducción de fuerza, 623–624,
subclases, 165, 575 mejoras posibles, 605 datos de 630–632
indexación, 627–628
890 fciorsdtet-ogpe-nlevraetlio entre
ryizards
ajuste de código,continuado propiedad colectiva, 482.Ver también regla del código anterior, 798
eliminación de subexpresiones, colaboración proporcionalidad de, 806
638–639 comentariosVer tambiéndocumentación pseudocódigo, derivado de, 220,
resumen del enfoque para, 606 llamadas /* frente a //, 790 784, 791
al sistema, 599–600, 633–634 abreviaturas en, 799 propósito de, 782
herramientas, 720 algoritmos, 809 código repetido con, 786
desenrollar bucles, 618–620 argumento en contra, 782 recursos activados, 815
desconectar bucles, 616–617 autoría, 811 rutinas con, 805–809, 817 código
variaciones en entornos para, código incorrecto, encendido, 568 líneas autocomentario, 796–797 Diálogo
594 en blanco alrededor, 765–766 Paradigma socrático sobre, 781–785
cuándo sintonizar, 596 asistentes de del libro para, 812–813 categorías de, estándares, IEEE, 813–814
generación de código, 718 codificación. 786–788 diferencias de estilo, manejo, 683
Ver tambiénconstrucción; listas de control, 774, 816– violaciones de estilo, 801
Convenciones generales de construcción 817 clases, 810 resúmenes de código, 787
de software.Verconvenciones, significados codificados, 802–803 sorpresas, 798
codificación estructuras de control, 804–805, 817 código complicado, 798, 801
checklist de prácticas, 69 secuenciales.Ver declaraciones con, 794, 802–803, características no documentadas, 800
construcción de software de código de 816 variables, 803
línea recta como, estilo 5.Verdiseño descripciones de la intención del código, control de versiones, 811
787 guía de distancia al código, 806 por qué frente a cómo, 797–798
interfaces, clase, 138 comentarios finales, 793–795 errores, estrategia de demora de compromiso, 52
rutinas, diseño con, 168–171 marcado de soluciones alternativas, habilidades de comunicación, importancia
reducción de fuerza, 623–624, 800 de, 828
630–632 explicativo, 786 cohesión comunicacional, 169
cohesión coincidente, 170 archivos, 810–811 comunicaciones, desarrollo
colaboración indicadores, nivel de bits, 803 variables equipo, 650
lectura de código, 494 globales, 803, 809 pautas de sangría, comparaciones
beneficios de propiedad colectiva, 764–765 líneas individuales con, 792– booleanoVerpruebas booleanas de
482 comparaciones de técnicas, tabla 795 datos de entrada, 803, 808 igualdad de punto flotante, 295–296 tipos
de, 495–496 de datos mixtos, 293
ventaja de costo, 480–481 integrando en el desarrollo, 791 compiladores
definido, 479, 480 interfaces, clase, 810 enlace durante la compilación,
fase de diseño, 115 interfaces, rutina, 808 252–253
beneficio del tiempo de desarrollo, 480 Javadoc, 807, 815 construcciones rotas, 703
espectáculos de perros y ponis, 495 que se puntos clave, 817 advertencias de tipo de datos, 293
extienden más allá de la construcción, directrices de diseño, 763–766 herramientas de depuración, como, 557,
483 avisos legales, 811 827 errores, búsqueda en rutinas,
método de programación extrema, longitud de las descripciones, 806 230–231
482 nivel de intención del código, 795–796 números de línea, depuración con,
inspecciones formales.Verformal bucles, 804–805 549
inspecciones mantenimiento de, 220, 788–791, mensajes, tratamiento de, 549,
Principio general del software 794 826–827
calidad, 481 mayor contra menor, 799–800 múltiples mensajes de error, 550
inspeccionesVerformal marcadores, 787 optimizaciones por, 596–597
inspecciones información esencial sin código, ajuste de rendimiento
puntos clave, 497 788 consideraciones, 590
aspecto de tutoría de, 482 datos numéricos, 802 estándares de todo el proyecto para, 557
programación en pareja.Verpar densidad óptima de, 792 velocidades de optimización, tabla
programación datos de salida, 808 de, 597
propósito de, 480 párrafos de código con, herramientas para, 716
integridad de los requisitos igualdad, bifurcación, 355 ejemplos propósito de, 307
lista de control, 43 de procesamiento de errores, refactorización, 571
tipos de datos complejos.Vercomplejidad de las 356–357 simulando en lenguas carentes,
estructuras frecuencia, prueba en orden de, 309
abstracción para el manejo, 839 clases 612–613 construcción.Ver tambiénsoftware
para reducir, 152 convenciones de si declaraciones.Versi declaraciones descripción general de la construcción
confiabilidad correlacionada con, 457 control de cambios, 667 consideraciones programación decisiones de construcción
rutinas para reducir, 164 burocráticas, 667 lista de verificación, 669– lista de verificación de la construcción principal
llamadas a funciones booleanas con, 359 666–667 elección de lenguaje de programación, 70 lista de
sentencias if-then-else encadenadas, palabra clave const 668, C++, 176, 177, 243, cronogramas de construcción, estimación
358–360 274, 333 se acerca a, lista de, 671
lista de control, 365 constantes alcanzando por detrás,
primera pauta de casos comunes, lista de control, 317 675–676
359–360 regla de consistencia, 309 controlar vs. estimar, 675
comparando el desempeño de, 614 declaraciones usando, 308 factores que influyen, 674–675
cubriendo todos los casos, 360 definido, 307 nivel de detalle para, 672
definido, 355 emulación por variables globales, 338 múltiples técnicas con
eliminando la redundancia de pruebas, inicialización, 243 comparaciones, 672
610–611 literales, evitar con, 308–309 objetivos, establecer, 671
cláusulas else, 358–360 nombrar, 270, 273, 277–279 optimismo, 675
892 rup-tlo
fciorsntsto entrada de evrsel
datos selectivos con, 254 personalización, construcción de metáfora punto flotante a números enteros, 625
datos secuenciales con, 254 para, 18 indexación de datos, 627–628
programación estructurada, propósito de, 624
454–455
último dto fe-cletsveinl ecnotdre
episodio y 893
inspecciones formales para la detección. concepto de, 347 tabla de buenas prácticas para, 31–32
Verinspecciones formales documentación, 350 heurística.Verjerarquías de diseño
intermitente, 542–543 comprobación de errores, 350 heurístico para, 105–106 objetivo de
diseños mal entendidos como fuentes escondido, 348 fan-in alto, 80
para, 519 orden de inicialización, 348 estándares IEEE, 122
oportunidades presentadas por, nombrando rutinas, 348–349 ocultación de información, 92–97,
537–538 no obvio, 348 120 herencia, 91–92
fuera del dominio de la construcción, organización de código, 348 práctica de iteración, 111–117
519 parámetros, efectivo, 349 puntos clave, 123
porcentaje de, medida, diseño meta de delgadez, 81
469–472 abstracciones, formando consistentes, nivel de detalle necesario, 115–117
problemas de rendimiento, 601 89–90 niveles de, 82–87
programadores culpables de, 519 problemas accidentales, 77–78 objetivo de acoplamiento flojo, 80 objetivo
mejoras de legibilidad, 538 BDUF, 119 de fan-out de bajo a medio, 81 objetivos de
refactorización después de corregir, 582 belleza, 80 mantenimiento, 80
alcance de, 519 enfoque de abajo hacia arriba para el diseño, limitaciones mentales de los humanos,
autoconocimiento de, 538 tamaño 112–113 79 métricas, señales de advertencia de,
de los proyectos, efectos sobre, subsistema de lógica empresarial, 85 848 naturaleza no determinista de, 76,
651–653 trabajo de captura, 117–118 puntos 87 orientado a objetos, recurso para,
fuentes de, tabla, 518 centrales de control, 107 cambio, 119 objetos, mundo real, hallazgo, 87–89
estabilizador, 542–543 identificación de áreas de, nivel de paquetes, 82–85
programación defensiva 97–99 patrones, comunes.Verajuste de
afirmaciones, 189–194 cambios, gestión de, rendimiento de patrones
suposiciones para verificar, lista de, 190 666–667 consideraciones, 589–590
barricadas, 203–205 características de alta calidad, objetivo de portabilidad, 81
lista de control, 211–212 80–81 practicar heurísticas.Verheurístico
ayudas de depuración, 205–209 listas de verificación, 122– diseño
definidas, 187 123, 781 clases, división en, prácticas, 110–118, 122
manejo de errores para, 194–197 86 colaboración, 115 priorización durante, 76 creación
excepciones, 198–203, 211 guía de comunicaciones entre de prototipos, 114–115
mensajes amistosos, 210 guía de subsistemas, 83–84 recursos para, 119–121
bloqueo elegante, 210 pautas para completar, determinar, naturaleza restrictiva de, 76
código de producción, 115–117 objetivo de reutilización, 80
209–210 gestión de la complejidad, 77–80 rutinas, de, 86–87
guía de errores de bloqueo duro, 209 actividad de construcción, como, 73– proceso descuidado naturaleza de, 75–76
guía de errores importantes, 209 74 contrato, por, 233 nivel de sistema de software, 82
puntos clave para, 213 consideraciones de acoplamiento, 100–102 objetivo de técnicas estándar, 81
directriz de tala, 210 subsistema de acceso a la base de datos, 85 estándares, IEEE, 122
problemas causados por, 210 técnicas definido, 74 meta de estratificación,
de mejora de la calidad, diagramas, dibujo, 107 81 cohesión fuerte, 105
otro, 188 discusión, resumen, 117 nivel de subsistema, 82–85
robustez frente a corrección, 197
laesntutm
ope-rlaetveedl teynpterys 895
subsistema de dependencias del sistema, diseño como, 117, 781 documentos de primera pauta de casos comunes,
85 diseño detallado, 778 externo, 777– 359–360
pruebas para implementación, 503 778 prueba de corrección, 358 predeterminado
herramientas para, 710 Javadoc, 807, 815 para cubrir todos los casos, 360 gotos con,
enfoque de arriba hacia abajo, 111–113 puntos clave, 817 406–407
compensaciones, 76 nombres como, 284–285, 778–779, nulo, 358
diagramas UML, 118 780 sistemas integrados críticos para la vida,
subsistema de interfaz de usuario, 85 organización de datos, 780 31–32
documentación visual de, 118 suposiciones de parámetros, 178 naturaleza emergente del proceso de diseño,
naturaleza del problema perverso de, pseudocódigo, derivado de, 220 76
74–75 Wikis, captura en, 117 recursos en, 815 emulado estilo de diseño de bloques puros,
destructores, excepciones con, 199 suposiciones de parámetros de rutina, 740–743
documentos de diseño detallado, 778 178 encapsulación
pruebas de desarrollo.Verpruebas de rutinas, 780 suposiciones sobre los usuarios, 141 lista
procesos de desarrollo.Ver SDF, 778 de control, 158
enfoques para el desarrollo código autodocumentado, 778–781 clases, papel para, 139–143 clases de
estándares de desarrollo, IEEE, 813 tamaño de los proyectos, efectos de, 657 acoplamiento demasiado fuerte,
diagramas código fuente como, 7 142–143
diseño heurístico uso de, 107 estándares, IEEE, 813–814 objetos abatidos, 574
UML, 118 diferencias de estilo, gestión, 683 preocupación de clase amiga, 141 diseño
Herramientas diferenciales, 556, UDF, 778 heurístico con, 90–91 minimizando la
712 tablas de acceso directo visual, de diseños, 118 por qué frente accesibilidad, 139 detalles privados en
ventajas de, 420 a cómo, 797–798 espectáculos de interfaces de clase,
Descargar desde Guau! Libro electrónico <www.wowebook.com>
validación con, 304–305 inexactitud, basada en caracteres, Función de salida, 391.Ver tambiéndevolver
Visual Basic, 303–306 827–828 declaraciones
igualdad, coma flotante, 295–296 múltiples técnicas con Declaraciones de salida.Verdescanso
partición de equivalencia, 512 códigos comparaciones, 672 declaraciones
de error, 195 objetivos, establecer, 671 Salida secundaria, 392–393.Ver tambiéndevolver
detección de errores, realización anticipada, 29–30 optimismo, 675 declaraciones
adivinación de errores, 513 resumen, 671 bucles de salida, 369–372, 377–381
manejo de errores.Ver tambiénexcepciones planear tiempo de estimación, 671 experiencia, personal, 831–832 prototipos
requisitos previos de arquitectura, 49–50 rehacer periódicamente, 672 experimentales, 114–115 experimentación
afirmaciones, en comparación con, 191 reducción de alcance, 676 como aprendizaje,
barricadas, 203–205 especificación de requisitos, 672 822–823, 852–853
desbordamientos de búfer comprometedores, recursos para, 677 expresiones exponenciales, 631–632
196 equipos, en expansión, 676 expresiones
valor legal más cercano, 195 controladores de eventos, 170 booleanoVerconstantes de expresiones
programación defensiva, evolución.Verevolución del booleanas, tipos de datos para, 635
técnicas para, 194–197 códigos de error, software Entrega evolutiva.Ver inicialización en tiempo de compilación,
devolución, 195 rutinas de procesamiento metáfora del desarrollo 632–633
de errores, llamadas, incremental pautas de diseño, 749–750 resultados de
196 excepcionesVer tambiénmanejo de errores cálculo previo, 635–638 desplazamiento a
implicación de diseño de alto nivel, problemas de abstracción, 199– la derecha, 634
197 manejo local, 196 200 alternativas a, 203 reducción de fuerza, eliminación
registro de mensajes de advertencia, 195 clases base para, proyecto específico, de subexpresiones 630–632,
mensajes, 49, 195–196, 210 próximos 203 638–639
datos válidos, devolución, 195 C++, 198–199 llamadas al sistema, rendimiento de,
respuestas anteriores, reutilización, 195 reporteros centralizados, 201–202 633–634
diseño de propagación, 49 constructores con, 199 objetivo de diseño de extensibilidad, 80
refactorización, 577 lista de verificación de programación defensiva, auditorías externas, 467
devolver valores neutrales, 211 documentación externa, 777–778
194 robustez, 51, 197 destructores con, 199 Programación extrema
rutinas, diseñando junto con, regla de bloques catch vacíos, 201 componente de colaboración de, 482
222 encapsulación, ruptura, 200 regla detección de defectos, 471–472
apagar, 196 de información completa, 200 Java, definido, 58
diseño de validación, 50 198–201 recursos en, 708, 856
error de mensajes idiomas, comparación de tablas,
códigos, retorno, 195 198–199
diseño, 49 regla de nivel de abstracción, 199–200
F
Patrón de fachada, 104
mostrando, 196 generación de código de biblioteca de, 201
guía de mensajes amistosos, 210 errores. regla de manejo local, 199
factoriales, 397–398
factorización, 154.Ver tambiénrefactorización de
Ver tambiéndefectos en el código; condiciones no excepcionales, 199
métodos de fábrica
excepciones propósito de, 198, 199
Patrón Factory Method, 103–104 Ejemplo de
clasificaciones de, codificación 518–520.Ver legibilidad del código usando, 199
refactorización de ifs anidados,
defectos en los campos de la etiqueta de refactorización, 577
identificación del código, excepciones 326– recursos para, 212–213 452–453
refactorizando a, 577
327.Vermanejo de excepciones.Vermanejo estandarizar el uso de, 202–203 Visual
de errores sentencias goto para Basic, 198–199, 202 generadores de
fan-in, 80
despliegue, 81
procesamiento, perfiles de ejecución, 557–558, 720
401–402 herramientas de código ejecutable
metáfora agrícola, 14–15
ADT, tratados como, 130 registros inspecciones formales Principio general del software
de autoría para, 811 C++ orden de papel autor, 486 Calidad
archivo fuente, 773 eliminación de resumen de beneficios, 491 efectos de colaboración, 481
varios ejemplos, juego de culpas, 490 costes, 522
401–402 lista de control, 491–492 depuración, 537
documentación, 810–811 MMC, 491 definido, 474–475
diseño interior, 771–773 Código Ejemplo completo, variables globales
nombrando, 772, 811 490–491 rutinas de acceso para.Veracceso
rutinas en, 772 en comparación con otras colaboraciones, rutinas
palabra clave final, Java, 243 495–496 problemas de alias con, 336–337
declaraciones finalmente, 404–405 definido, 485 alternativas a, 339–342
corrección de defectos egos en, 490 anotar, 343
comprobación de correcciones, 553 listas de verificación de errores, 489 cambios a, inadvertidos, 336 lista de
lista de control, 560 resultados esperados de, 485–486 verificación para, 343–344
confirmación de diagnóstico, 551 ajuste fino, 489 alternativas de variables de clase, 339
apuro, impacto de, 551 defectos de etapa de seguimiento, 489 problemas de reutilización de código, 337
inicialización, 553 problemas de reuniones de inspección, 488 comentarios, 803, 809
mantenimiento, 553 regla de un puntos clave, 497 emulación de tipos enumerados por,
cambio a la vez, 553 razonamiento función de gestión, 486–487 338
para cambios, 553 guardar código no función de moderador, 486 pauta de prefijo g_, 340 ocultando la
fijo, 552 defectos similares, buscando, etapa de resumen, 487 implementación en las clases,
554 casos especiales, 553 evaluaciones de desempeño desde, 487 153
etapa de planificación, 487 problemas de ocultación de información
síntomas, arreglar en lugar de etapa de preparación, 487–488 con, 95–96
problemas, 552–553 procedimiento para, 487–489 problemas de inicialización, 337
comprender la primera directriz, tasa de revisión de código, 488 resultados intermedios, evitar,
550–551 informes, 488–489 343
pruebas unitarias para, 554 recursos para, 496–497 puntos clave, 344
banderas rol revisor, 486 primera directriz local, 339
cambiar, identificando áreas de, revisiones, en comparación con, 485 bloqueo, 341
98–99 etapa de reelaboración, 489 modularidad dañada por, 337–338
comentarios para significados a nivel de bit, papeles en, 486–487 emulación de constantes nombradas por,
803 enfoque de escenarios, 488 rol 338
tipos enumerados para, 266–267 de escribano, 486 nombrando, 263, 273, 277, 278, 279,
gotos, reescritura con, 403–404 etapas de, 487–489 342
nombres para, 266–267 reunión de soluciones de tres horas, 489 objetos para, monstruo,
acoplamiento semántico con, 102 revisiones técnicas formales, 467 código de 343 resumen de, 335–336
flexibilidad formato.Verdiseño Fortran, 64 persistencia de, 251
criterios de acoplamiento para, 100–101 preservación de valores con, 338
definidos, 464 cohesión funcional, 168–169 problemas de código reentrante, 337
tipos de datos de punto flotante especificación funcional.Ver refactorización, 568
limitaciones de precisión, 295 requisitos estrategias de reducción de riesgos, 342–
BCD, 297 funcionesVer tambiénrutinas 343 rutinas que utilizan como parámetros,
lista de control, 316 cálculos convertidos a 336 acoplamiento semántico con, 102
costos de operaciones, 602 ejemplo, 166–167 racionalización del uso de datos con, 338
comparaciones de igualdad, 295–296 definido, 181 datos de trampa, eliminación con, 338 clases
magnitudes, muy diferentes, rechazo, 150 de dios, 155
operaciones con, 295 punto clave para, 186 programación gonzo, 832
errores de redondeo, 297 convenciones de nomenclatura para, buenos datos, pruebas,
Tipos de Visual Basic, 297 172, 181 privado, anular, 146 declaraciones 515–516 goto
bucles for valores de retorno, configuración, Ada, inclusión en, 399
ventajas de, 374 182 estado como valor de retorno, ventajas de, 399
formato, 732–733, 746–747 181 cuándo usarlo, 181–182 alternativas en comparación con, 405 lista de
índices, 377–378 Teorema fundamental de verificación, 410
propósito de, 372 Formateo, 732
898 fgirastpthoicpa-lledveesligenttroyols
ir a declaraciones,continuado cambiar, identificando áreas de, humildad, papel en el carácter, 821, 826,
desasignación con, 399 97–99 834
desventajas de, 398–399 código lista de verificación para, 122–123 Convención de nomenclatura húngara, 279
duplicado, eliminando con, colaboración, 115 acoplamiento híbrido de variables,
399 las comunicaciones se benefician de 256–257
cláusulas else con, 406–407 patrones, 104
procesamiento de errores con, 401– completar, determinar,
402 Uso de Fortran de, 399 115–117
yo
directriz de dirección de avance, consideraciones de acoplamiento, 100–
E/S (entrada/salida)
requisitos previos de arquitectura, 49
408 directriz, 407–408 102 diagramas, dibujo, 107
cambio, identificación de áreas de, 98
problema de sangría con, 398 técnica divide y vencerás,
consideraciones de rendimiento,
puntos clave, 410 111
pautas de diseño, 750–751 usos encapsulación, 90–91 598–599
legítimos de, 407–408 problema de reducción de errores con patrones, 103
IDE (Desarrollo Integrado
optimización con, 398 debate falso formalidad de, determinación,
Ambientes), 710–711
sobre, 400–401 problema de 115–117 IEEE (Instituto de Electricidad y
Ingenieros Eléctricos), 813
legibilidad, 398 formalización de contratos de clase, 106 lista de
instancias implícitas, 132 datos de clase confundidos con globales consecuencias de no hacerlo, 240
en palabra clave, creación, 175–176 datos, 95–96 const recomendado, 243
preparación incompleta, causas de, consideraciones de diseño de clase, 93 constantes, 243
25–27 detalles de implementación de clase, 153 contadores, 243
metáfora del desarrollo incremental, ejemplo, 93–94 declarar y definir cerca del primer uso
15–16 problema de distribución excesiva, regla, 242–243
integración incremental 95 último recomendado, 243
beneficios de, 693–694 importancia de, 92 directriz de primer uso, 241–242
estrategia ascendente, 697–698 interfaces, clase, 93 defectos de fijación, 553
clases, 694, 697 problemas de rendimiento, 96 variables globales, 337
beneficio de relaciones con los clientes, 694 derechos de privacidad de las clases, 92– importancia de, 240–241
definido, 692 93 recursos para, 120 Ejemplo de Java, 242–243
desventajas de arriba hacia abajo concepto de secretos, 92 punto clave, 258
estrategia, 695–696 creación de tipos para, bucles, variables utilizadas en, 249
errores, localización, 693 herencia 313–314 validez de parámetros, 244
integración orientada a características, privilegios de acceso desde, 148 problemas de puntero, 241, 244,
700–701 declaraciones de casos, 147–148 lista 325–326
especificación de interfaz, 695, 697 de verificación, 158 Principio de proximidad, 242
beneficio de monitoreo de progreso, contención en comparación con, 143 reinicialización, 243
693 recursos en, 708 decisiones involucradas en, 144 árboles cuerdas, 300
resultados, temprano, 693 profundos, 147 perturbadores del sistema, pruebas con,
integración orientada al riesgo, 699 definido, 144 527
estrategia sándwich, 698–699 regla de diseño para, 144 funciones, Ejemplos de Visual Basic, 241–242
beneficios de programación, 694 privado, primordial, 146 pautas, lista inicialización de la memoria de trabajo,
enfoque de cortes, 698 de, 149 244 rutinas en línea, 184–185
interviene, 692 diseño heurístico con, 91–92 parámetros de entrada, 274
estrategias para, resumen, identificar como un paso de diseño, de entrada y salida.VerE/S
694 stubs, 694, 696 88 es una relación, 144 inspeccionesVerinspecciones formales
resumen de enfoques, 702 pilotos puntos clave para, 160 Principio de herramientas de instalación, 718
de prueba, 697 sustitución de Liskov, instanciar objetos
estrategia de arriba hacia abajo para, 144–145 ADT, 132
694–696 integración en forma de T, objetivo principal de, método de fábrica, 103–104
701 enfoque de corte vertical, 696 136 mixins, 149 singleton, 104, 151
sangría, 737, 764–768 tablas de acceso múltiples, 148–149 tipos de datos enteros
indexadas, 425–426, anulable frente a no anulable lista de control, 316
735 estándar de desarrollo 31–32, 813 listas 623 bucles evaluados continuamente,
ejemplo mediocre, 731–732 vinculadas 367.Ver tambiénwhile bucles líneas
ejemplo de sangría engañosa, borrar punteros, 330 de continuación en, 757 declaraciones
732–733 inserción de nodos, 327–329 punteros, de continuación, 379, 380,
precedencia engañosa, 733 guía operaciones de aislamiento de, 381
de modificaciones, 736 325 bucles contados, 367.Ver tambiénpor
declaraciones múltiples por línea, enlazadores, 716 bucles
758–761 herramienta de pelusa, 557 diafonía, 383
ejemplos negativos, 730–731 Principio de sustitución de Liskov (LSP), definido, 367
objetivos de, 735–736 144–145 diseño, proceso para, 385–387
paréntesis para, 738 liza hacer bucles, 369–370
punteros, C++, 763 de listas de verificación, xxix– vacío, evitando, 375–376
estilo de bloques puros, 738–740 xxx de figuras, xxxiii bucles sin fin, 367, 374
objetivo de legibilidad, 735 de mesas, xxxi–xxxii consideraciones de punto final,
aspectos religiosos de, 735 datos literales, 297–298, 308–309 381–382
recursos en, 774–775 programas alfabetizados, 13 entrar, directrices para, 373–375,
argumentos de rutina, 754 tiempo de vida de las variables, 246–248, 459 388
continuaciones de llamadas de rutina, tiempo de carga, vinculación durante, 253 tipos enumerados para, 305
756 pautas de rutina, 766–768 código localización pautas de salida, 369–372,
autodocumentado, 778–781 bloques de requisitos previos de arquitectura, 48 377–381, 389
declaración única, 748–749 continuación tipos de datos de cadena, 298 para bucles, 372, 374–378,
de declaración, 754–758 longitud de bloqueo de datos globales, 732–733, 746–747
declaración, 753 341 logaritmos, 632–634 bucles foreach, 367, 372
estructuras, importancia de, Inicio sesión fusión de, 617–618
733–734 guía de programación defensiva, ir con, 371
resumen de estilos, 738 210 declaraciones de limpieza, 376
pares de inicio y fin sin sangría, 746 herramientas para pruebas, alteraciones del índice, 377
violaciones de, comentarios, 801 estilo 526 pruebas de cobertura lógica, lista de control del índice, 389
de bloqueo de Visual Basic, 738 506 cohesión lógica, 170 valores finales de índice, 377–378
espacios en blanco, 732, 736–737, expresiones lógicas.Ver tambiénbooleano nombres de variables de índice, 265
753–754 expresiones ámbito de índice, 383-384
pereza, 830 ajuste de código, 610–616 bucles infinitos, 367, 374
evaluación perezosa, 615–616 comparando el rendimiento de, 614
902 flor
irostsetocpo-ulepvlienlgentry
instrucciones de interrupción comentarios que requieren, 788–791 tabla de tipos útiles de, 678–679
etiquetadas, 381 específico del idioma, objetivo de diseño para, 80 memoria
tabla de, 368 longitud de, 385 rutinas propensas a errores, priorizando asignación, detección de errores para,
minimización del trabajo interno, 620–621 para, 518 206 corrupción por punteros, 325
instrucciones de ruptura múltiple, 380 arreglando defectos, problemas de, 553 rellenos, 244
variables de nomenclatura, 382–383 mantenibilidad definida, 464 beneficio inicialización de trabajo, rendimiento de
anidadas, 382–383, 385, 623 de legibilidad para, 842 operación de paginación 244
sentencias nulas, reescritura, 445 estructuras para reducir, 323 impacto, 599
errores uno por uno, 381–382 principales prácticas de construcción punteros, corrupción por, 325
directriz de una función, 376 orden lista de verificación, 69–70 herramientas para, 527
de anidamiento, 623 administrando la construccion tutoría, 482
consideraciones de rendimiento, 599 enfoques.Verse aproxima a combinar herramientas, 712
conmutación, 616 puntos clave, 688 centrados en la tierra vs. centrados en el sol,
análisis de límites, 513–514 fijación, formal, coincidencia con real, 180 defectos en el código,
enfoques para, 553 programación variables globales para, 336 ejemplo 601 DES, 605–606
ofensiva, 206 construcciones de control pautas de uso en rutinas, vista de diseño, 589–590
uno dentro, uno fuera, 174–180 característica específica, 595
454 en la creación de palabras clave, orden de consideraciones de hardware, 591
sistemas operativos, 590 entrada-modificación-salida 175-176, ineficiencia, fuentes de, 598–601
operaciones, costos comunes, 174–175 ocultación de información
601–603 Java, 176-177 consideraciones de, 96
opuestos para nombres de variables, tamaño de la lista como indicador de refactorización, entrada/salida, 598–599
264 optimización, prematura, 840.Ver 566 interpretado vs. compilado
ademásajuste de rendimiento 180 nombres, 178, 180, 274, 277, idiomas, 600–601
oráculos, software, 851 278, emparejando real con formal, puntos clave, 608
fuera de la creación de palabras clave, 175– 279 líneas de código, minimizando el número
176 ingeniería excesiva, 51 número de, limitando, 178 de, 593–594
desbordamientos, entero, 293–295 objetos, pasando, 179 medición de, 603–604 operaciones
enlazadores superpuestos, 716 pedido de, 174–176 de memoria vs. archivo,
rutinas superables, 145–146, 156 out creación de palabras clave, 175–176 598–599
metáfora del cultivo de ostras, 15–16 aprobación, tipos de, 333 cuentos de viejas, 593–596 consideraciones
refactorización, 571, 573 sobre el sistema operativo,
estado, 176 590
PAGS estructuras como, 322 operaciones, costos comunes,
paquetes, 156–157 usando todas las reglas, 176 601–603
operaciones de paginación, 599
variables, usando como, 176–177 descripción general de, 643–644
programación en pareja
Visual Basic, 180 operaciones de paginación, 599
beneficios de, 484
paréntesis optimización prematura, vista de los
lista de control, 484
técnica de equilibrio, 437–438 requisitos del programa 840 de,
Compatibilidad con estándares de codificación para,
diseño con, 738 589
483 en comparación con otras colaboraciones,
Principio de Pareto, 592 propósito de, 587
495–496
prerelaqsutistiotp
es-,leuvpeslterenatmry 905
calidad de código, impacto en, 588 ayudas de depuración, 208– suplentes a, 232–233
objetivos de recursos, 590 209 declaración, 325–326, 763 comprobación de errores, 230–231 lista
recursos, 606–607 eliminación, 330–331, 332 de verificación para, 233–234
diseño de rutinas, 165, 222–223, diagramación, 329 pasos de limpieza, 232
590 campos de etiquetas de identificación, 326–327 codificación debajo de los comentarios,
velocidad, importancia de, 595–596 escritura explícita de, 334 campos explícitamente 227–229
resumen del enfoque para, 606 redundantes, 327 variables adicionales para codificación de rutinas de, 225–229
llamadas al sistema, 599–600 mayor claridad, estructura de datos para rutinas, 224
problemas de tiempo, 604 327–329 declaraciones de, 226
vista del usuario de la codificación, 588 ocultar operaciones con rutinas, definido, 218
cuándo sintonizar, 596 165 diseño de rutinas, 220–225 consideraciones
publicaciones periódicas sobre programación, inicialización, 241, 244, 325–326 sobre el manejo de errores,
859-860 interpretación de la dirección 222
Perla, 65 contenido, 324–325 ejemplo para rutinas, 224
persistencia de variables, 251–252, operaciones de aislamiento de, 325 funcionalidad de bibliotecas, 222
831 puntos clave, 344 comentarios de cabecera para rutinas,
carácter personal.Verpersonaje, idiomas que no proporcionan, 323 listas 223
personal vinculadas, eliminación en, 330 ubicación comentarios de alto nivel de,
perturbadores.Verperturbadores del en la memoria, 323 corrupción de memoria 226–227
sistema integración en fases, 691–692 por, 325–327 paracaídas de memoria, 330 iterando, 225
abreviaturas fónicas de nombres, 283 PHP puntos clave para, 234
(procesador de hipertexto PHP), 65, nulo, establecer en después de eliminar, rutinas de nombres, 222
600 330 nulo, usar como advertencias, 849 consideraciones de rendimiento,
entorno físico para sobrescribir la memoria con basura, 222–223
programadores, 684–685 330 requisitos previos, 221
planificación partes de, 323 definición de problema, 221
argumento de analogía a favor, 27–28 pasando por referencia, 333 refactorización, 229
construcción de metáfora a favor, 18–19 referencias, C++, 332 eliminar errores, 231
argumento de datos a favor, 28–30 recursos para, 343 repetir pasos, 232
tabla de buenas prácticas para, 31–32 SAFE_ rutinas para, 331–332 revisar el pseudocódigo, 224–225
argumento lógico para, 27 simplificación complicada recorrer el código paso a paso, 231
punteros expresiones, 329 probar el código, 222, 231
* (símbolo de declaración de puntero), tamaño de(), 335 paso de escritura de pseudocódigo,
332, 334–335, 763 inteligente, 334 223–224
& (símbolo de referencia de puntero), 332 operaciones de cadena en C, 299 precedencia, engañoso, 733
– > (símbolo de puntero), 328 conversión de tipos, evitar, 334 variables condiciones previas
dirección de, 323, 326 referenciadas por, verificación, diseño de rutina con, 221
asignación de, 326, 330, 331 326 verificación, 192–193
alternativas a, 332 polimorfismo prefijos, estandarización de,
como valores de retorno de función, declaraciones de caso, reemplazando con, 279–281
182 regla de asterisco (*), 334–335 147–148 optimización prematura, preparación
auto_ptrs, 333 definido, 92 840.Verrequisitos previos,
herramientas de comprobación de reglas específicas del idioma, 156 ifs río arriba
límites, 527 lenguaje C, 334–335 anidados, conversión a, 452–453 preprocesadores
Ejemplos de C++, 325, 328–334 Pautas de expresiones polinómicas, 631–632 C++, 207–208
C++, 332–334 Comprobación antes de portabilidad ayudas de depuración, eliminación con,
usar, 326, 331 Lista de comprobación tipos de datos, definición para, 315– 207–208
para, 344 316 definido, 464 propósito de, 718–719
comparaciones con, 441 rutinas para, 165 escribir, 208
contenidos, interpretación de, poscondiciones requisitos previos, aguas arriba
324–325 diseño de rutina con, 221 argumento de analogía a favor, 27–28
cubrir rutinas para, 331–332 verificación, 192–193 arquitectónico.Verprueba de preparación
peligros de, 323, 325 PPP (Programación en pseudocódigo) del jefe de arquitectura, 30–31 lista de
tipos de datos señalados, 324–325 Proceso) verificación para, 59
desasignación de, 326, 330, 332 algoritmos, investigando, 223
906 evf ePlre
fPirisntctiopple-lo en
xitm
rigurosidad
requisitos previos, aguas arriba,continuado programadores, carácter de.Ver bibliotecas de código, 717
elegir entre iterativo y carácter, personal ajuste de código, 720
enfoques secuenciales, 35–36 error de programadores, tratamiento de.Ver también asistentes de generación de código, 718
codificación demasiado temprano, 25 equipos compiladores, 716
argumento convincente a favor, 27–31 resumen, 680 herramientas de referencia cruzada, 713
argumento de los datos, 28–30 entorno físico, 684–685 diccionarios de datos, 715
detección de errores, hacer temprano, privacidad de las oficinas, 684 herramientas de depuración, 526–527, 545,
29–30 cuestiones religiosas, 683–684 558–559, 719
gol de, 25 recursos sobre, 685–686 comprobadores de dependencia, 716
tabla de buenas prácticas para, 31– problemas de estilo, 683–684 herramientas de diseño, 710
para, 172 cuándo usar, 181–182 programación en lenguajes, entornos orientados a herramientas,
procesos, desarrollo.Ver 68–69, 843 720–721
enfoques para el desarrollo Pitón, 65 traductores, 715
productividad proporción de declaraciones en comparación con C herramientas de control de versiones,
efectos de una buena construccion código, tabla de, 62 715 tipos de proyectos, requisitos previos
clases, pasos para crear, 216–217 calidad del software comprensibilidad, 465
codificación debajo de los comentarios, exactitud, 464 usabilidad, 463
227–229 adaptabilidad, 464 cuando hacer aseguramiento de, 473
codificación desde, 225–229 procedimientos de control de cambios, 468 lista de
lista de control, 70
reutilización, 464
577
tabla de buenas prácticas para, 31–32 expresiones booleanas, 572
reseñas, 467
requisitos previos papel en, 24 declaraciones de casos, 573
robustez, 464
lista de verificación de requisitos, 42–43 listas de control para, 570, 577–579
estándares, IEEE, 477, 814
puertas de calidad, 467 puntos de control para, 580
pruebas, 465, 467, 500–502
908 el),
frierfsetrteonpc-elsev(& np+robar+y
eC
de datos públicos, 567 consultas, comunicar cambios en, 40–41 integración orientada al riesgo, 699
574 integridad, lista de verificación, 43 robustez
razones para no hacerlo, gestión de configuración de, prerrequisitos de la arquitectura, 51
571 registros, 572 664, 666–667 aserciones con manejo de errores,
rediseñar en lugar de, 582 definido, 38 193–194
objetos de referencia, 574 enfoques de desarrollo con, 41 corrección, equilibrado contra, 197
recursos en, 585 definido, 197, 464
- lfevaerliean
lassctotpoepo bletrys 909
errores de redondeo, 297 funciones, consideraciones especiales razones para crear, lista de, 167
rutinas para, 181–182 refactorización, 229, 573–575, 578,
abstracto anulable, 145 enfoque de piratería para, 233 582
beneficio de abstracción, comentarios de encabezado para, 223 confiabilidad de la cohesión, 168
164 abstracción con objeto alta calidad, contraejemplo, eliminación de errores, 231
parámetros, 179, 574 161–163 repetir pasos, 232
acceso.Verselección de algoritmos de comentarios de alto nivel de regresa de, múltiple, 391–393 revisión
rutinas de acceso para, 223, 573 pseudocódigo, 226–227 de pseudocódigo, 224–225 beneficio
alternativas a PPP, 232–233 prueba de importancia de, 163 de ocultamiento de secuencia, 165
caja negra de, 502 en la creación de palabras clave, cohesión secuencial, 168
líneas en blanco en, 766 beneficio de 175–176 sangría de, 766–768 código de configuración para, refactorización,
para bucles, 621–623 secuencias, código. descripción general de la construcción de software analizando la calidad, 713–714
Ver tambiénbloques actividades excluidas de, 6 embellecedores, 712
ocultando con rutinas, 165 actividades en, lista de, 3 generadores de jerarquía de clases, 713
orden de.Verdependencias, centralidad para el desarrollo comparadores, 556
ordenación de códigos proceso, 7 herramientas de referencia cruzada, 713
enfoque secuencial, 33–36 naturaleza hecha garantizada de, 7 herramientas de edición, 710–713
requisitos funcionales específicos tipos de datos de cadena nivel de diseño del subsistema, 82–
lista de control, 42 Lenguaje C, 299–301 85 sustracción, 295
requisitos no funcionales específicos conjuntos de caracteres, 298 intercambiar datos usando estructuras,
lista de control, 42 lista de verificación, 316–317 321–322
especificación.Verlista de verificación de estrategias de conversión, 299 cambiar declaraciones.Vercaso
mejora de velocidad de requisitos, índices, 298, 299–300, 627 declaraciones
642–643.Ver tambiénsintonización de código; la inicialización, 300 depuradores simbólicos, 526–527
optimización del rendimiento localización, 298 sintaxis, errores en, 549–550, 560,
SQL, 65 cadenas mágicas (literales), 297–298 713–714
errores de estabilización, 542–543 tablas de problemas de memoria, 298, 300 punteros arquitectura del sistema.Verllamadas al sistema de
acceso escalonado, 426–429 estándares, frente a matrices de caracteres, 299 arquitectura
descripción general de, 814 variables de Unicode, 298, 299 ajuste de código, 633–634
estado.Versentencias de variables de estado punteros de cadena, 299 problemas de rendimiento, 599–600
strncpy(), 301 dependencias del sistema, 85
lista de control, 774 fuerte cohesión, 105 perturbadores del sistema, 527
elementos estrechamente relacionados, estructurasVerpruebas de base prueba del sistema, 500
755–756 diseño de continuación, 754–758 estructurada de estructuras refactorización a nivel del sistema, 576–577,
extremos de continuaciones, 756–757 recomendado, 503 579
incompletos, 754–755 teoría de, 505–509
longitud de, 753 programación estructurada
refactorización, 572–573, 577–578 tesis central de, 456
T
métodos basados en tablas
secuencial.Verinforme de estado de iteración, 456
código de línea recta, 827 resumen, 454 ventajas de, 420
búsquedas binarias con, enfoque
variables de estado selecciones, 455
de declaración de caso 428,
significados a nivel de bit, cambio secuencias, 454
803, identificación de áreas de, estructuras
421–422
lista de control, 429
98–99 bloques de datos, operaciones sobre,
ajuste de código con, 614–615 creación
tipos enumerados para, 266–267 320–322
a partir de expresiones, 435 ejemplo de
gotos reescritos con, 403–404 lista de verificación para, 343
días en mes, 413–414 definido, 411
nombres para, 266–267 aclarar las relaciones de datos con,
acoplamiento semántico de, 320
método de diseño, 420
código de línea recta 102 clases actuando como, 319
acceso directo.Veracceso directo
lista de verificación, 353 definido, 319
aclarar dependencias, 348–350 puntos clave, 344
mesas
puntos finales de rangos, ejemplo de
concepto de dependencias, 347 reducción de mantenimiento con,
formato de mensaje flexible 428,
documentación, 350 323 exagerar, 322
comprobación de errores, 350 simplificación de parámetros con,
416–423
falsear claves para, 423–424 tablas de
agrupar declaraciones relacionadas, 322
acceso indexado, 425–426,
352–353 relaciones, claro ejemplo de,
dependencias ocultas, 348 orden de 320 428–429
ejemplo de tasas de seguro, 415–416
inicialización, 348 rutinas de llamadas de rutina con, 322 simplificando
cuestiones en, 412–413
nomenclatura, 348–349 dependencias las operaciones de datos con,
320–322 puntos clave, 430
no obvias, 348 organización para
intercambiar datos, 321–322 claves para, 423–424
mostrar
problema de búsqueda, 412
dependencias, 348 ejemplo de datos no estructurados, 320
parámetros, efectivo, 349 principio de ejemplos de Visual Basic, 320–322 objetos
ejemplos misceláneos, 429
proximidad, 351 orden específico, stub, pruebas con, 523 stubs como ayudas de
enfoque de objeto, 422–423
requerido, 347–350 legibilidad de arriba integración, 694, 696 stubs con ayudas de
cálculos de precomputación, 635
equiposVer tambiéngerente rutas de datos de uso definido, 510–512 resultados, usos para, 502
construcción problemas de diseño, 503 papel en el aseguramiento de la calidad del software,
variables temporales, 267–268 pruebas de integración, 499 JUnit pruebas de caja blanca, 500, 502
comprobabilidad para, 531 roscado, 337
definido, 465 puntos clave, 533 código desechable, 114
estrategias para, 467 limitaciones en las pruebas de desarrollador, desechar una metáfora, 13–14
generadores de datos de prueba, 524–525 504 asignaciones de tiempo, 55–56
desarrollo de prueba primero, 233 prueba herramientas de registro para, 526 pruebas control de versiones de herramientas, 668
pruebas de caja negra, 500 medición de, 520, 529 depuraciónVeredición de depuración.Ver
análisis de límites, 513–514 herramientas de memoria, 527 Programación de herramientas de edición.
herramientas de comprobación de configuraciones mínimas normales, Verprogramación
límites, 527 casos, creación, 506–508, 515 instrumentos
características de, problemático, errores de casos nominales, 515 datos enfoque de arriba hacia abajo para el diseño,
idiomas con, evaluación de, regla de descripción precisa, prueba de alfabetización de datos, 238–239
314–315 260–261 relación del tipo de datos con el control
beneficio de modificación, 314 malos nombres, ejemplos de, estructuras, 254–255
convenciones de nomenclatura, 315 259–260, 261 declarandoVerdeclaraciones
ejemplo de Pascal, 312–313 beneficio de variables booleanas, 268–269 globales.Vervariables globales
portabilidad, 315–316 tipos predefinidos, Lenguaje C, 275, 278 significados ocultos, evitando,
evitar, 315 propósito de, 311–312 C++, 263, 275–277 256–257
mayúsculas, 286 acoplamiento híbrido, 256–257
razones para, 314 caracteres, difícil de leer, 287 lista de declaraciones implícitas, 239–240
redefiniendo predefinido, 315 verificación, 288–289 inicialización, 240–244, 257 datos
beneficio de confiabilidad, 314 variables miembro de clase, 273 iterativos, 255
beneficio de validación, 314 calificadores de valor calculado, puntos clave, 258
definiciones de tipo, 278 263–264 tiempo en vivo, 246–248, 459
constantes, 270 localización de referencias a, 245
tipos enumerados, 269 bucle, 382–384
tu regla de descripción completa, 260–261 nombrarVerpersistencia de
UDF (carpetas de desarrollo de unidades),
global, calificadores para, 263 buenos nombres variables de, 251–252
778 nombres, ejemplos de, 260, Principio de proximidad, 242
UDT (tipo definido por el usuario)
261 miembros de clase pública, 576
abreviaturas, 279–280 homónimos, 286 refactorización, 571, 576
diagramas UML, 118, 120 convenciones de Java, 277 reutilización, 255–257
comprensibilidad, 465.Ver también puntos clave, 289 ámbito de aplicación de.Verámbito de
legibilidad tipos de información en, 277 variables datos selectivos, 254
Unicode, 288–299 longitud, óptima, 262 datos secuenciales, 254
carpetas de desarrollo de unidades (UDF),
índices de bucle, 265 lapso de, 245
778 palabras mal escritas, 286 tipos de.Vertipos de datos
pruebas unitarias, 499
múltiples lenguajes naturales, 287 utilizando todos los declarados,
entorno de programación UNIX, espacios de nombres, 263 control de versión 257
720 números en, 286 comentando, 811
bucles de desenrollado, 618–620
pares opuestos para, 264 eliminación de la ayuda de depuración, 207
bucles de desconexión, 616–617
abreviaturas fónicas, 283 regla de herramientas para, 668, 715
requisitos previos aguas arriba.Ver
orientación del problema, 261 visibilidad.Ver tambiénalcance de las variables
prerrequisitos, usabilidad
distancia psicológica, 556 propósito criterios de acoplamiento para,
upstream, 463
de, 240 100 clases, de, 93
estado de datos usados, 509–510
nombres reservados, 287 requisitos previos de la declaración de visión.Ver
tipo definido por el usuario (UDT)
nombres de rutinas, diferenciando definición del problema
abreviaturas, 279–280 de, 272 requisitos previos
interfaces de usuario
alcance, efectos de, 262–263 básico visual
requisitos previos de arquitectura, 47
similitud de nombres, demasiado, ejemplos de afirmación, 192–194
datos de refactorización de, 576
285 estilo de bloqueo, 738
diseño de subsistema, 85
regla de especificidad, 261 mayúsculas y minúsculas,
variables de estado, 266–267 273 descripción de, 65
contadores, 243
914 thpr-oleuvgehlsentry
fwirasltkt-o
romper declaraciones, 379 agrupando con, 737 escribir metáfora para codificar, 13–14
Si tiene algún comentario o pregunta sobre este libro, comuníquese con Steve al
stevemcc@construx.com o víawww.stevemcconnell.com.